Skip to content

Commit ece2ff4

Browse files
feat: add new options recoverErrors and singleLine support (#104)
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> Co-authored-by: JounQin <admin@1stg.me>
1 parent ccabf8a commit ece2ff4

File tree

7 files changed

+90
-15
lines changed

7 files changed

+90
-15
lines changed

.changeset/ninety-rings-guess.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"sh-syntax": patch
3+
---
4+
5+
feat: add new options `recoverErrors` and `singleLine` support

main.go

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,12 @@ func Print(originalText string, filepath string, syntaxOptions processor.SyntaxO
5050
return processor.Print(originalText, filepath, syntaxOptions)
5151
}
5252

53+
// `process` processes the input file path and text by performing either formatting (printing) or parsing based on the print flag.
54+
//
55+
// It converts the input byte slices to strings and configures parser options—including comment retention, language variant, stop marker, and error recovery settings. When printing is enabled, it applies printer options (indentation, binary next line, switch case indentation, space redirects, padding, minification, single-line formatting, and function next line) to format the text via the Print function. Otherwise, it parses the text with Parse and maps the resulting AST into a file representation.
56+
//
57+
// The function then encapsulates the file, the processed text, and any parsing error information into a result structure, marshals it to JSON, appends a null terminator, and returns a pointer to the first byte of the JSON output.
58+
//
5359
//export process
5460
func process(
5561
filepathBytes []byte,
@@ -59,8 +65,9 @@ func process(
5965

6066
// parser
6167
keepComments bool,
62-
stopAt []byte,
6368
variant int,
69+
stopAt []byte,
70+
recoverErrors int,
6471

6572
// printer
6673
indent int,
@@ -69,15 +76,17 @@ func process(
6976
spaceRedirects,
7077
keepPadding,
7178
minify,
79+
singleLine bool,
7280
functionNextLine bool,
7381
) *byte {
7482
filepath := string(filepathBytes)
7583
text := string(textBytes)
7684

7785
parserOptions := processor.ParserOptions{
78-
KeepComments: keepComments,
79-
StopAt: string(stopAt),
80-
Variant: syntax.LangVariant(variant),
86+
KeepComments: keepComments,
87+
Variant: syntax.LangVariant(variant),
88+
StopAt: string(stopAt),
89+
RecoverErrors: recoverErrors,
8190
}
8291

8392
var file processor.File
@@ -91,6 +100,7 @@ func process(
91100
SpaceRedirects: spaceRedirects,
92101
KeepPadding: keepPadding,
93102
Minify: minify,
103+
SingleLine: singleLine,
94104
FunctionNextLine: functionNextLine,
95105
}
96106

main.wasm

621 Bytes
Binary file not shown.

processor/main.go

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,10 @@ var (
1313
)
1414

1515
type ParserOptions struct {
16-
KeepComments bool
17-
StopAt string
18-
Variant syntax.LangVariant
16+
KeepComments bool
17+
Variant syntax.LangVariant
18+
StopAt string
19+
RecoverErrors int
1920
}
2021

2122
type PrinterOptions struct {
@@ -25,6 +26,7 @@ type PrinterOptions struct {
2526
SpaceRedirects bool
2627
KeepPadding bool
2728
Minify bool
29+
SingleLine bool
2830
FunctionNextLine bool
2931
}
3032

@@ -33,6 +35,11 @@ type SyntaxOptions struct {
3335
PrinterOptions
3436
}
3537

38+
// `Parse` converts shell script text into a structured syntax tree.
39+
// It assembles parser options based on the provided configuration—such as whether to keep comments,
40+
// the shell syntax variant to use, an optional stopping point, and the desired error recovery level.
41+
// The supplied file path is used for contextual error reporting.
42+
// It returns a syntax.File representing the parsed script, or an error if parsing fails.
3643
func Parse(text string, filepath string, parserOptions ParserOptions) (*syntax.File, error) {
3744
var options []syntax.ParserOption
3845

@@ -42,11 +49,20 @@ func Parse(text string, filepath string, parserOptions ParserOptions) (*syntax.F
4249
options = append(options, syntax.StopAt(parserOptions.StopAt))
4350
}
4451

52+
if parserOptions.RecoverErrors != 0 {
53+
options = append(options, syntax.RecoverErrors(parserOptions.RecoverErrors))
54+
}
55+
4556
parser = syntax.NewParser(options...)
4657

4758
return parser.Parse(bytes.NewReader([]byte(text)), filepath)
4859
}
4960

61+
// `Print` returns the formatted shell script defined in originalText.
62+
// It first parses the input using the parser options in syntaxOptions and then prints the resulting
63+
// syntax tree using printer options—including indentation, single-line formatting, and others.
64+
// The filepath parameter is used for context in error messages. On success, Print returns the formatted
65+
// script as a string, or an error if parsing or printing fails.
5066
func Print(originalText string, filepath string, syntaxOptions SyntaxOptions) (string, error) {
5167
file, err := Parse(originalText, filepath, syntaxOptions.ParserOptions)
5268

@@ -61,6 +77,7 @@ func Print(originalText string, filepath string, syntaxOptions SyntaxOptions) (s
6177
syntax.SpaceRedirects(syntaxOptions.SpaceRedirects),
6278
syntax.KeepPadding(syntaxOptions.KeepPadding),
6379
syntax.Minify(syntaxOptions.Minify),
80+
syntax.SingleLine(syntaxOptions.SingleLine),
6481
syntax.FunctionNextLine(syntaxOptions.FunctionNextLine),
6582
)
6683

processor/structs.go

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -115,10 +115,11 @@ func mapNode(node syntax.Node) *Node {
115115
}
116116
}
117117

118+
// `mapComments` transforms a slice of syntax.Comment into a slice of Comment by converting each comment's hash, text, start, and end positions using mapPos. It preserves the order of the comments and returns an empty slice if the input is nil or empty.
118119
func mapComments(comments []syntax.Comment) []Comment {
119120
commentsSize := len(comments)
120121
commentList := make([]Comment, commentsSize)
121-
for i := 0; i < commentsSize; i++ {
122+
for i := range commentsSize {
122123
curr := comments[i]
123124
commentList[i] = Comment{
124125
Hash: mapPos(curr.Hash),
@@ -130,6 +131,8 @@ func mapComments(comments []syntax.Comment) []Comment {
130131
return commentList
131132
}
132133

134+
// `mapWord` converts a *syntax.Word into a custom *Word structure. It maps each part of the syntax.Word using mapNode,
135+
// extracts the literal via Lit(), and maps the start and end positions using mapPos. If the input word is nil, it returns nil.
133136
func mapWord(word *syntax.Word) *Word {
134137
if word == nil {
135138
return nil
@@ -138,7 +141,7 @@ func mapWord(word *syntax.Word) *Word {
138141
size := len(word.Parts)
139142
parts := make([]Node, size)
140143

141-
for i := 0; i < size; i++ {
144+
for i := range size {
142145
parts[i] = *mapNode(word.Parts[i])
143146
}
144147

@@ -150,10 +153,13 @@ func mapWord(word *syntax.Word) *Word {
150153
}
151154
}
152155

156+
// `mapRedirects` converts a slice of syntax.Redirect pointers into a slice of custom Redirect structures.
157+
// It maps each redirect’s operator position, associated literal (if present), word, heredoc, and overall positional data using helper functions.
158+
// If the literal component (N) is non-nil, it is transformed into a Lit structure that encapsulates both its value and positional information.
153159
func mapRedirects(redirects []*syntax.Redirect) []Redirect {
154160
redirsSize := len(redirects)
155161
redirs := make([]Redirect, redirsSize)
156-
for i := 0; i < redirsSize; i++ {
162+
for i := range redirsSize {
157163
curr := redirects[i]
158164
var N *Lit
159165
if curr.N != nil {
@@ -180,10 +186,11 @@ func mapRedirects(redirects []*syntax.Redirect) []Redirect {
180186
return redirs
181187
}
182188

189+
// `mapStmts` converts a slice of *syntax.Stmt into a slice of Stmt by mapping each statement's components—including comments, command node, positional information, semicolon, redirections, and execution flags (negated, background, coprocess).
183190
func mapStmts(stmts []*syntax.Stmt) []Stmt {
184191
stmtsSize := len(stmts)
185192
stmtList := make([]Stmt, stmtsSize)
186-
for i := 0; i < stmtsSize; i++ {
193+
for i := range stmtsSize {
187194
curr := stmts[i]
188195
stmtList[i] = Stmt{
189196
Comments: mapComments(curr.Comments),

src/processor.ts

Lines changed: 37 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,34 @@ export const getProcessor = (
4545
},
4646
): Promise<string>
4747

48+
/**
49+
* Processes a shell script input using a WebAssembly module.
50+
*
51+
* This asynchronous function accepts shell script input either as a string or as an AST File, along with a set of options
52+
* that control formatting, error recovery, and output. It ensures that the WebAssembly module is loaded and instantiated,
53+
* allocates memory for the file path and text content, and then calls the module's processing function with the provided options.
54+
* Depending on the `print` flag, it returns either the processed text or a File representing the parsed AST.
55+
*
56+
* @param textOrAst - The shell script input as a string or as an AST File. When providing a non-string input and `print` is false,
57+
* the `originalText` option must be supplied.
58+
* @param options - An object containing processing options:
59+
* - filepath: The file path associated with the input, used primarily for error reporting.
60+
* - print: If true, the function returns the processed text; otherwise, it returns the processed AST as a File.
61+
* - originalText: The original text of the shell script, required when `textOrAst` is not a string.
62+
* - keepComments: Determines whether comments should be preserved in the output.
63+
* - variant: Specifies the shell scripting variant (e.g., {@link LangVariant.LangBash}).
64+
* - stopAt: A token indicating where to halt further processing.
65+
* - recoverErrors: Sets the level of error recovery during processing (default is 0).
66+
* - useTabs, tabWidth, indent: Options to control indentation formatting.
67+
* - binaryNextLine, switchCaseIndent, spaceRedirects, keepPadding, minify, singleLine, functionNextLine:
68+
* Additional flags that influence formatting details and output structure.
69+
*
70+
* @returns A promise that resolves to either the processed text (if `print` is true) or a File (if `print` is false).
71+
*
72+
* @throws {TypeError} If the original text is required but not provided.
73+
* @throws {ParseError} If the processed output is not valid JSON or indicates a parsing error.
74+
* @throws {SyntaxError} If a syntax error is detected without an associated parse error object.
75+
*/
4876
async function processor(
4977
textOrAst: File | string,
5078
{
@@ -53,8 +81,9 @@ export const getProcessor = (
5381
originalText,
5482

5583
keepComments = true,
56-
stopAt = '',
5784
variant = LangVariant.LangBash,
85+
stopAt = '',
86+
recoverErrors = 0,
5887

5988
useTabs = false,
6089
tabWidth = 2,
@@ -64,6 +93,7 @@ export const getProcessor = (
6493
spaceRedirects = true,
6594
keepPadding = false,
6695
minify = false,
96+
singleLine = false,
6797
functionNextLine = false,
6898
}: ShOptions & { print?: boolean; originalText?: string } = {},
6999
) {
@@ -110,17 +140,19 @@ export const getProcessor = (
110140
isAst: boolean,
111141

112142
keepComments: boolean,
143+
variant: LangVariant,
113144
stopAtPointer: number,
114145
stopAt0: number,
115146
stopAt1: number,
116-
variant: LangVariant,
147+
recoverErrors: number,
117148

118149
indent: number,
119150
binaryNextLine: boolean,
120151
switchCaseIndent: boolean,
121152
spaceRedirects: boolean,
122153
keepPadding: boolean,
123154
minify: boolean,
155+
singleLine: boolean,
124156
functionNextLine: boolean,
125157
) => number
126158
}
@@ -150,17 +182,19 @@ export const getProcessor = (
150182
print,
151183

152184
keepComments,
185+
variant,
153186
stopAtPointer,
154187
uStopAt.byteLength,
155188
uStopAt.byteLength,
156-
variant,
189+
recoverErrors,
157190

158191
indent,
159192
binaryNextLine,
160193
switchCaseIndent,
161194
spaceRedirects,
162195
keepPadding,
163196
minify,
197+
singleLine,
164198
functionNextLine,
165199
)
166200

src/types.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,9 @@ export type LangVariant = ValueOf<typeof LangVariant>
1111

1212
export interface ShParserOptions {
1313
keepComments?: boolean
14-
stopAt?: string
1514
variant?: LangVariant
15+
stopAt?: string
16+
recoverErrors?: number
1617
}
1718

1819
export interface ShPrinterOptions {
@@ -22,6 +23,7 @@ export interface ShPrinterOptions {
2223
spaceRedirects?: boolean
2324
keepPadding?: boolean
2425
minify?: boolean
26+
singleLine?: boolean
2527
functionNextLine?: boolean
2628
}
2729

0 commit comments

Comments
 (0)