diff --git a/.changeset/eighty-candles-own.md b/.changeset/eighty-candles-own.md new file mode 100644 index 00000000..5dcfaa84 --- /dev/null +++ b/.changeset/eighty-candles-own.md @@ -0,0 +1,5 @@ +--- +"sh-syntax": minor +--- + +feat: support parse as AST diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c85365e8..918b4913 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -10,7 +10,6 @@ jobs: strategy: matrix: node: - - 12 - 14 - 16 runs-on: ubuntu-latest diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index f826cb6a..e165ebf0 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -21,10 +21,10 @@ jobs: with: version: latest - - name: Setup Node.js 14.x + - name: Setup Node.js 16.x uses: actions/setup-node@v3 with: - node-version: 14.x + node-version: 16.x cache: pnpm - name: Install Dependencies diff --git a/README.md b/README.md index c894912a..5f6a4f2a 100644 --- a/README.md +++ b/README.md @@ -43,19 +43,32 @@ npm i sh-syntax ```js // node -import { print } from 'sh-syntax' - -await print("echo 'Hello World!'") +import { parse, print } from 'sh-syntax' + +const text = "echo 'Hello World!'" +const ast = await parse(text) +const newText = await print(ast, { + // `originalText` is required for now, hope we will find better solution later + originalText: text, +}) ``` ```js // browser -import { getPrinter } from 'sh-syntax' +import { getProcessor } from 'sh-syntax' -const print = getPrinter(() => +const processor = getProcessor(() => fetch('path/to/main.wasm').then(res => res.arrayBuffer()), ) -await print("echo 'Hello World!'") + +const parse = (text, options) => processor(text, options) + +const print = (ast, options) => processor(ast, options) + +// just like node again +const text = "echo 'Hello World!'" +const ast = await parse(text) +const newText = await print(ast, { originalText: text }) ``` ## Changelog diff --git a/go.sum b/go.sum index c1315376..95574bc2 100644 --- a/go.sum +++ b/go.sum @@ -1,26 +1,20 @@ github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/creack/pty v1.1.15/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= -github.com/frankban/quicktest v1.13.1 h1:xVm/f9seEhZFL9+n5kv5XLrGwy6elc4V9v/XFY2vmd8= github.com/frankban/quicktest v1.13.1/go.mod h1:NeW+ay9A/U67EYXNFA1nPE8e/tnQv/09mUdL/ijj8og= -github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/renameio v1.0.1/go.mod h1:t/HQoYBZSsWSNK35C6CO/TpPLDVWvxOHboWUAweKUpk= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= -github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= -github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= -github.com/rogpeppe/go-internal v1.8.1-0.20210923151022-86f73c517451 h1:d1PiN4RxzIFXCJTvRkvSkKqwtRAl5ZV4lATKtQI0B7I= github.com/rogpeppe/go-internal v1.8.1-0.20210923151022-86f73c517451/go.mod h1:JeRgkft04UBgHMgCIwADu4Pn6Mtm5d4nPKWu0nJ5d+o= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210925032602-92d5a993a665/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20210916214954-140adaaadfaf/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= diff --git a/main.go b/main.go index ae984749..dadd6249 100644 --- a/main.go +++ b/main.go @@ -7,6 +7,7 @@ import ( "bytes" "flag" "io" + "reflect" "syscall/js" "mvdan.cc/sh/v3/syntax" @@ -18,9 +19,11 @@ var ( ) var ( - uid = flag.String("uid", "", "unique ID") - text = flag.String("text", "", "processing text") + uid = flag.String("uid", "", "the unique ID") + text = flag.String("text", "", "the processing text") filepath = flag.String("filepath", "", "file path of the processing text") + ast = flag.String("ast", "", "AST of the processing text") + originalText = flag.String("originalText", "", "original processing text for AST") keepComments = flag.Bool("keepComments", false, "KeepComments makes the parser parse comments and attach them to nodes, as opposed to discarding them.") stopAt = flag.String("stopAt", "", `StopAt configures the lexer to stop at an arbitrary word, treating it as if it were the end of the input. It can contain any characters except whitespace, and cannot be over four bytes in size. This can be useful to embed shell code within another language, as one can use a special word to mark the delimiters between the two. @@ -37,9 +40,129 @@ Note that this feature is best-effort and will only keep the alignment stable, s functionNextLine = flag.Bool("functionNextLine", false, "FunctionNextLine will place a function's opening braces on the next line.") ) -func main() { - flag.Parse() +func mapParseError(err error) interface{} { + if err == nil { + return nil + } + + parseError, ok := err.(syntax.ParseError) + + if ok { + return map[string]interface{}{ + "Filename": parseError.Filename, + "Text": parseError.Text, + "Incomplete": parseError.Incomplete, + "Pos": posToMap(parseError.Pos), + } + } + + return err.Error() +} + +func Map(in interface{}, fn func(interface{}) interface{}) interface{} { + val := reflect.ValueOf(in) + out := make([]interface{}, val.Len()) + + for i := 0; i < val.Len(); i++ { + out[i] = fn(val.Index(i).Interface()) + } + + return out +} + +func posToMap(Pos syntax.Pos) map[string]interface{} { + return map[string]interface{}{ + "Offset": Pos.Offset(), + "Line": Pos.Line(), + "Col": Pos.Col(), + } +} + +func handleNode(node syntax.Node) interface{} { + if node == nil { + return nil + } + return map[string]interface{}{ + "Pos": posToMap(node.Pos()), + "End": posToMap(node.End()), + } +} + +func handleComments(comments []syntax.Comment) []interface{} { + return Map(comments, func(val interface{}) interface{} { + curr := val.(syntax.Comment) + return map[string]interface{}{ + "Hash": posToMap(curr.Hash), + "Text": curr.Text, + "Pos": posToMap(curr.Pos()), + "End": posToMap(curr.End()), + } + }).([]interface{}) +} + +func handleWord(word *syntax.Word) interface{} { + if word == nil { + return nil + } + return map[string]interface{}{ + "Parts": Map(word.Parts, func(val interface{}) interface{} { + curr := val.(syntax.WordPart) + return handleNode(curr) + }), + "Lit": word.Lit(), + "Pos": posToMap(word.Pos()), + "End": posToMap(word.End()), + } +} +func fileToMap(file syntax.File) map[string]interface{} { + return map[string]interface{}{ + "Name": file.Name, + "Stmt": Map(file.Stmts, func(val interface{}) interface{} { + curr := val.(*syntax.Stmt) + return map[string]interface{}{ + "Comments": handleComments(curr.Comments), + "Cmd": handleNode(curr.Cmd), + "Position": posToMap(curr.Position), + "Semicolon": posToMap(curr.Semicolon), + "Negated": curr.Negated, + "Background": curr.Background, + "Coprocess": curr.Coprocess, + "Redirs": Map(curr.Redirs, func(val interface{}) interface{} { + curr := val.(*syntax.Redirect) + var N interface{} + if curr.N != nil { + ValuePos := posToMap(curr.N.Pos()) + ValueEnd := posToMap(curr.N.End()) + N = map[string]interface{}{ + "ValuePos": ValuePos, + "ValueEnd": ValueEnd, + "Value": curr.N.Value, + "Pos": ValuePos, + "End": ValueEnd, + } + } + return map[string]interface{}{ + "OpPos": posToMap(curr.OpPos), + "Op": curr.Op.String(), + "N": N, + "Word": handleWord(curr.Word), + "Hdoc": handleWord(curr.Hdoc), + "Pos": posToMap(curr.Pos()), + "End": posToMap(curr.End()), + } + }), + "Pos": posToMap(curr.Pos()), + "End": posToMap(curr.End()), + } + }), + "Last": handleComments(file.Last), + "Pos": posToMap(file.Pos()), + "End": posToMap(file.End()), + } +} + +func parse(text string, filepath string) (*syntax.File, error) { var options []syntax.ParserOption options = append(options, syntax.KeepComments(*keepComments), syntax.Variant(syntax.LangVariant(*variant))) @@ -50,51 +173,62 @@ func main() { parser = syntax.NewParser(options...) + return parser.Parse(bytes.NewReader([]byte(text)), filepath) +} + +func print(originalText string, filepath string) (string, error) { + file, err := parse(originalText, filepath) + + if err != nil { + return "", err + } + printer = syntax.NewPrinter( syntax.Indent(*indent), syntax.BinaryNextLine(*binaryNextLine), syntax.SwitchCaseIndent(*switchCaseIndent), syntax.SpaceRedirects(*spaceRedirects), syntax.KeepPadding(*keepPadding), + syntax.Minify(*minify), syntax.FunctionNextLine(*functionNextLine), ) + var buf bytes.Buffer + writer := io.Writer(&buf) + + err = printer.Print(writer, file) + + if err != nil { + return "", err + } + + return buf.String(), err +} + +func main() { + flag.Parse() + Go := js.Global().Get("Go") if Go.Get("__shProcessing").IsUndefined() { Go.Set("__shProcessing", js.ValueOf(map[string]interface{}{})) } - result := map[string]interface{}{} + var Data interface{} + var Error interface{} - file, err := parser.Parse(bytes.NewReader([]byte(*text)), "path") - if err != nil { - error, ok := err.(syntax.ParseError) - if ok { - result["error"] = map[string]interface{}{ - "filename": error.Filename, - "incomplete": error.Incomplete, - "text": error.Text, - "pos": map[string]interface{}{ - "col": error.Col(), - "line": error.Line(), - "offset": error.Offset(), - }, - "message": error.Error(), - } - } else { - result["error"] = err.Error() - } + if *ast == "" { + file, err := parse(*text, *filepath) + Data = fileToMap(*file) + Error = mapParseError(err) } else { - var buf bytes.Buffer - writer := io.Writer(&buf) - err = printer.Print(writer, file) - if err != nil { - result["error"] = err.Error() - } else { - result["text"] = buf.String() - } + result, err := print(*originalText, *filepath) + Data = result + Error = mapParseError(err) } - Go.Get("__shProcessing").Set(*uid, js.ValueOf(result)) + Go.Get("__shProcessing").Set(*uid, js.ValueOf(map[string]interface{}{ + "Data": Data, + "Error": Error, + })) } diff --git a/main.wasm b/main.wasm index fac652d5..83c435b8 100755 Binary files a/main.wasm and b/main.wasm differ diff --git a/package.json b/package.json index 088d1a98..ca8d7840 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,7 @@ "author": "JounQin ", "license": "MIT", "engines": { - "node": ">=v12.20" + "node": ">=v14" }, "exports": { "import": "./lib/index.js", @@ -40,7 +40,7 @@ "tslib": "^2.3.1" }, "devDependencies": { - "@1stg/lib-config": "^5.3.0", + "@1stg/lib-config": "^5.4.0", "@changesets/changelog-github": "^0.4.4", "@changesets/cli": "^2.22.0", "@types/jest": "^27.4.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 5a984147..4e577add 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1,7 +1,7 @@ lockfileVersion: 5.3 specifiers: - '@1stg/lib-config': ^5.3.0 + '@1stg/lib-config': ^5.4.0 '@changesets/changelog-github': ^0.4.4 '@changesets/cli': ^2.22.0 '@types/jest': ^27.4.1 @@ -16,7 +16,7 @@ dependencies: tslib: 2.3.1 devDependencies: - '@1stg/lib-config': 5.3.0_typescript@4.6.3 + '@1stg/lib-config': 5.4.0_typescript@4.6.3 '@changesets/changelog-github': 0.4.4 '@changesets/cli': 2.22.0 '@types/jest': 27.4.1 @@ -27,10 +27,10 @@ devDependencies: typescript: 4.6.3 packages: - /@1stg/babel-preset/2.1.5_ae3b9447df613e6578bbb6d083fbf381: + /@1stg/babel-preset/2.2.0_ae3b9447df613e6578bbb6d083fbf381: resolution: { - integrity: sha512-2ldiD7YR2wgV71vGOu+vWcb869r6j1iPjDF2OZIgV0sBPbcy2VYDhajwyn1F+Ka6isdP9BI7lNqD5F29PoJcaw==, + integrity: sha512-gj8B+hzY91eSpmMl4TUKS6g7e3fNJA1pVRyDdMuPyp4rYDtH/QhM5AX7XDoy2z4CH8Iw5wduUC/yi1yKG3xG7g==, } engines: { node: '>=12' } peerDependencies: @@ -74,19 +74,19 @@ packages: - lerna dev: true - /@1stg/common-config/3.3.0_jest@27.5.1+typescript@4.6.3: + /@1stg/common-config/3.4.0_jest@27.5.1+typescript@4.6.3: resolution: { - integrity: sha512-Bx2a4j2rhwcWpcdxaLB2UMOzdRpAJjnodrUc58vuMAIBWAV4SEXJV8xTsrqCFESDcq+WnYK+IkBIU3zmMedTvw==, + integrity: sha512-Vj42UkUEYy6iokG0jVkir/4NgezBLXG6jRv4lWEcs/870B9RsLxAqjXFjZwccxp02rO+fkj5mK9KUxquucN2Og==, } engines: { node: '>=12' } dependencies: - '@1stg/babel-preset': 2.1.5_ae3b9447df613e6578bbb6d083fbf381 + '@1stg/babel-preset': 2.2.0_ae3b9447df613e6578bbb6d083fbf381 '@1stg/commitlint-config': 2.0.1 - '@1stg/eslint-config': 3.3.0_d2a0ea4891b98d09b861e9c88ab6a703 + '@1stg/eslint-config': 3.4.0_d2a0ea4891b98d09b861e9c88ab6a703 '@1stg/lint-staged': 1.7.5_7b0ec2462ff6fd63966398ab126709a2 '@1stg/markuplint-config': 1.0.2_eslint@8.12.0 - '@1stg/prettier-config': 2.0.0_prettier@2.6.1 + '@1stg/prettier-config': 2.1.0_prettier@2.6.1 '@1stg/remark-config': 2.0.0_prettier@2.6.1 '@1stg/simple-git-hooks': 0.1.2_ce3d4b297bb12fd52c7cc4c97d23889c '@1stg/tsconfig': 2.0.1_typescript@4.6.3 @@ -112,10 +112,10 @@ packages: - typescript dev: true - /@1stg/eslint-config/3.3.0_d2a0ea4891b98d09b861e9c88ab6a703: + /@1stg/eslint-config/3.4.0_d2a0ea4891b98d09b861e9c88ab6a703: resolution: { - integrity: sha512-9L3NEOS1r9eaJ6QYRLG6Z8liEd9gfBPvCTrLC86uddAFECCe3+aqI0eFxtHwc0iYHm5w40CjdP6oj0BtIGZNZA==, + integrity: sha512-wyPK0sgaXvLbFONVu3+eI6Xpq706NvERYmSpBAUGmRBdQhXK3AnUQd5wrro2qBhdR95f3rQzuARPor3T1kt1nA==, } engines: { node: '>=12' } peerDependencies: @@ -141,7 +141,7 @@ packages: eslint-plugin-eslint-comments: 3.2.0_eslint@8.12.0 eslint-plugin-import: 2.25.4_eslint@8.12.0 eslint-plugin-jest: 26.1.3_376793c692fafbde76e94b5cf79c8e11 - eslint-plugin-jsdoc: 38.1.4_eslint@8.12.0 + eslint-plugin-jsdoc: 38.1.6_eslint@8.12.0 eslint-plugin-markup: 0.8.0_eslint@8.12.0 eslint-plugin-mdx: 2.0.0-next.1_eslint@8.12.0 eslint-plugin-node: 11.1.0_eslint@8.12.0 @@ -153,7 +153,7 @@ packages: eslint-plugin-sonar: 0.8.0_a60e04c5e37e753653904e7fe35c4633 eslint-plugin-sonarjs: 0.13.0_eslint@8.12.0 eslint-plugin-svelte: 1.1.2_eslint@8.12.0 - eslint-plugin-unicorn: 41.0.1_eslint@8.12.0 + eslint-plugin-unicorn: 42.0.0_eslint@8.12.0 eslint-plugin-vue: 8.5.0_eslint@8.12.0 transitivePeerDependencies: - '@babel/core' @@ -165,14 +165,14 @@ packages: - typescript dev: true - /@1stg/lib-config/5.3.0_typescript@4.6.3: + /@1stg/lib-config/5.4.0_typescript@4.6.3: resolution: { - integrity: sha512-fvo5miq10Bv8tTZKA4MbgIcMaNnDMpnYI7VkxajkGqzyJmC07Be7SCt2DkOT22jv5hipdA3mI7ggMEIo3dF3hw==, + integrity: sha512-5JQzDYZWXTUVrZodJbbTymrGfTGLwfjZK1wwy++As5jozj7T/FqqQMuKR8PSKM2TRdWH6mstqP01SO1C8G9oJw==, } engines: { node: '>=12' } dependencies: - '@1stg/common-config': 3.3.0_jest@27.5.1+typescript@4.6.3 + '@1stg/common-config': 3.4.0_jest@27.5.1+typescript@4.6.3 '@pkgr/rollup': 3.0.1 jest: 27.5.1 transitivePeerDependencies: @@ -203,7 +203,7 @@ packages: peerDependencies: lint-staged: '>=10.0.0' dependencies: - '@1stg/prettier-config': 2.0.0_prettier@2.6.1 + '@1stg/prettier-config': 2.1.0_prettier@2.6.1 '@1stg/tsconfig': 2.0.1_typescript@4.6.3 '@pkgr/utils': 2.0.3 cross-env: 7.0.3 @@ -231,10 +231,10 @@ packages: - supports-color dev: true - /@1stg/prettier-config/2.0.0_prettier@2.6.1: + /@1stg/prettier-config/2.1.0_prettier@2.6.1: resolution: { - integrity: sha512-50X5adrROd2g6/SzSe141R67dtMAUKrHvf0l7kDJZHDMzKfN3Ci1efVuKmGJruLiyJqu+dxqM0zFVP6ccYBayQ==, + integrity: sha512-V+2iCzjtOtr5Jd9rA7qccY+dtb8OzQIVDsMsGjD9N2aR/VWBveYwhju8TrYYAIt+B8j/1y0HPJ5e3RB3tEEN+w==, } peerDependencies: prettier: '>=1.18.0' @@ -243,8 +243,8 @@ packages: '@prettier/plugin-ruby': 2.0.0 '@prettier/plugin-xml': 2.0.1 prettier: 2.6.1 - prettier-plugin-pkg: 0.11.2_prettier@2.6.1 - prettier-plugin-sh: 0.8.2_prettier@2.6.1 + prettier-plugin-pkg: 0.12.0_prettier@2.6.1 + prettier-plugin-sh: 0.9.0_prettier@2.6.1 prettier-plugin-svelte: 2.6.0_prettier@2.6.1 prettier-plugin-toml: 0.3.1 transitivePeerDependencies: @@ -6926,7 +6926,7 @@ packages: remark-mdx: 2.1.1 remark-parse: 10.0.1 remark-stringify: 10.0.2 - synckit: 0.6.0 + synckit: 0.6.2 tslib: 2.3.1 unified: 10.1.2 transitivePeerDependencies: @@ -7022,10 +7022,10 @@ packages: - typescript dev: true - /eslint-plugin-jsdoc/38.1.4_eslint@8.12.0: + /eslint-plugin-jsdoc/38.1.6_eslint@8.12.0: resolution: { - integrity: sha512-x4sG6oJ+wj7aOGXtTaUeA4EL6kkgGsaFyIf+237/cdSXmC5zKKROccZxNZVUkaXE5QEiBGi7pyRfWNZYf+2OFg==, + integrity: sha512-n4s95oYlg0L43Bs8C0dkzIldxYf8pLCutC/tCbjIdF7VDiobuzPI+HZn9Q0BvgOvgPNgh5n7CSStql25HUG4Tw==, } engines: { node: ^12 || ^14 || ^16 || ^17 } peerDependencies: @@ -7257,10 +7257,10 @@ packages: eslint: 8.12.0 dev: true - /eslint-plugin-unicorn/41.0.1_eslint@8.12.0: + /eslint-plugin-unicorn/42.0.0_eslint@8.12.0: resolution: { - integrity: sha512-gF5vo2dIj0YdNMQ/IMegiBkQdQ22GBFFVpdkJP+0og3w7XD4ypea0xQVRv6iofkLVR2w0phAdikcnU01ybd4Ow==, + integrity: sha512-ixBsbhgWuxVaNlPTT8AyfJMlhyC5flCJFjyK3oKE8TRrwBnaHvUbuIkCM1lqg8ryYrFStL/T557zfKzX4GKSlg==, } engines: { node: '>=12' } peerDependencies: @@ -10948,13 +10948,6 @@ packages: hasBin: true dev: true - /mvdan-sh/0.5.0: - resolution: - { - integrity: sha512-UWbdl4LHd2fUnaEcOUFVWRdWGLkNoV12cKVIPiirYd8qM5VkCoCTXErlDubevrkEG7kGohvjRxAlTQmOqG80tw==, - } - dev: true - /nanoid/3.3.2: resolution: { @@ -12159,27 +12152,30 @@ packages: fast-diff: 1.2.0 dev: true - /prettier-plugin-pkg/0.11.2_prettier@2.6.1: + /prettier-plugin-pkg/0.12.0_prettier@2.6.1: resolution: { - integrity: sha512-/DdxDFvPXMxbXypj6ed6gcniLMBBZy5apulGZjlbUy/pyZjCkmlWnOmEhbPt1Timb9JGNPuamZzM2Orc2Q8Eyw==, + integrity: sha512-eRIJj4CZMbVedY93KxVW5cfRB/ZC5ydKzSVNt4NkvaNBkmIsyFdgW+FZTetb9KES2CclKBF5NelU+LxPedzI/g==, } + engines: { node: '>=12.20' } peerDependencies: prettier: ^2.0.0 dependencies: prettier: 2.6.1 dev: true - /prettier-plugin-sh/0.8.2_prettier@2.6.1: + /prettier-plugin-sh/0.9.0_prettier@2.6.1: resolution: { - integrity: sha512-M8D4G5OqgZtoVKx+U/J/B/gVA4xUKmWflOjayxiDjCQbxz3HOv0zlpYeb6DXd5xMFl7jW2UY1fJjmDzI9pDBFA==, + integrity: sha512-a9oZXWf24NI86qSq0swaKDMPVff7JjzGnHiupqb/ZORJqPwqbaYR1/M7d8ZRfTLr6A8mOQe90Y4gIUr0rb2F0g==, } + engines: { node: '>=12.20' } peerDependencies: prettier: ^2.0.0 dependencies: - mvdan-sh: 0.5.0 prettier: 2.6.1 + sh-syntax: 0.0.2 + synckit: 0.6.2 dev: true /prettier-plugin-svelte/2.6.0_prettier@2.6.1: @@ -13936,6 +13932,16 @@ packages: resolution: { integrity: sha1-BF+XgtARrppoA93TgrJDkrPYkPc= } dev: true + /sh-syntax/0.0.2: + resolution: + { + integrity: sha512-xChuD/H+FB/e9JlE9+X0qi4Bani3d2OnNnCSukCazKmlF4kHw6hZ0Go2HSdJ5bcG7brJVtSyX5HYfD10wGCf+Q==, + } + engines: { node: '>=v12.20' } + dependencies: + tslib: 2.3.1 + dev: true + /shebang-command/1.2.0: resolution: { integrity: sha1-RKrGW2lbAzmJaMOfNj/uXer98eo= } engines: { node: '>=0.10.0' } @@ -14506,6 +14512,16 @@ packages: tslib: 2.3.1 dev: true + /synckit/0.6.2: + resolution: + { + integrity: sha512-Vhf+bUa//YSTYKseDiiEuQmhGCoIF3CVBhunm3r/DQnYiGT4JssmnKQc44BIyOZRK2pKjXXAgbhfmbeoC9CJpA==, + } + engines: { node: '>=12.20' } + dependencies: + tslib: 2.3.1 + dev: true + /term-size/2.2.1: resolution: { diff --git a/src/browser.ts b/src/browser.ts index 1b41bd0d..b72c3771 100644 --- a/src/browser.ts +++ b/src/browser.ts @@ -1,4 +1,4 @@ import '../vendors/wasm_exec.js' -export * from './print.js' +export * from './processor.js' export * from './types.js' diff --git a/src/index.ts b/src/index.ts index 4bd27742..c90e53b5 100644 --- a/src/index.ts +++ b/src/index.ts @@ -2,10 +2,10 @@ import fs from 'fs' import path from 'path' import { fileURLToPath } from 'url' -import { getPrinter } from './print.js' - import './shim.js' import '../vendors/wasm_exec.js' +import { getProcessor } from './processor.js' +import type { File, ShOptions } from './types.js' /* istanbul ignore next */ const _dirname = @@ -13,9 +13,14 @@ const _dirname = ? path.dirname(fileURLToPath(import.meta.url)) : __dirname -export const print = getPrinter(() => +export const processor = getProcessor(() => fs.promises.readFile(path.resolve(_dirname, '../main.wasm')), ) -export * from './print.js' +export const parse = (text: string, options?: ShOptions) => + processor(text, options) + +export const print = (ast: File, options?: ShOptions) => processor(ast, options) + +export * from './processor.js' export * from './types.js' diff --git a/src/print.ts b/src/processor.ts similarity index 56% rename from src/print.ts rename to src/processor.ts index eafa01ce..d6e787ac 100644 --- a/src/print.ts +++ b/src/processor.ts @@ -1,53 +1,41 @@ /* eslint-disable @typescript-eslint/restrict-plus-operands */ -import { ShOptions } from './types.js' - -let id = 0 - -export class ParseError extends Error { - filename: string - incomplete: boolean - text: string - pos: { - col: number - line: number - offset: number +import type { IParseError, File, ShOptions } from './types.js' + +export class ParseError extends Error implements IParseError { + Filename: string + Incomplete: boolean + Text: string + Pos: { + Col: number + Line: number + Offset: number } - constructor({ - filename, - incomplete, - text, - pos, - message, - }: { - filename: string - incomplete: boolean - text: string - pos: { - col: number - line: number - offset: number - } - message: string - }) { - super(message) - this.filename = filename - this.incomplete = incomplete - this.text = text - this.pos = pos + constructor({ Filename, Incomplete, Text, Pos }: IParseError) { + super(Text) + this.Filename = Filename + this.Incomplete = Incomplete + this.Text = Text + this.Pos = Pos } } -export const getPrinter = ( +let id = 0 + +export const getProcessor = ( getWasmFile: () => BufferSource | Promise, ) => { let wasmFile: BufferSource | undefined let wasmFilePromise: Promise | undefined - return async ( - text: string, + function processor(text: string, options?: ShOptions): Promise + function processor(ast: File, options?: ShOptions): Promise + async function processor( + textOrAst: File | string, { filepath, + originalText, + keepComments = true, stopAt, variant, @@ -61,8 +49,8 @@ export const getPrinter = ( keepPadding = false, minify = false, functionNextLine = false, - }: Partial = {}, - ) => { + }: ShOptions = {}, + ) { if (!wasmFile) { if (!wasmFilePromise) { wasmFilePromise = Promise.resolve(getWasmFile()) @@ -77,7 +65,7 @@ export const getPrinter = ( const argv = [ 'js', '-uid=' + uid, - '-text=' + text, + '-text=' + textOrAst, '-keepComments=' + keepComments, '-indent=' + indent, '-binaryNextLine=' + binaryNextLine, @@ -92,6 +80,17 @@ export const getPrinter = ( argv.push('-filepath=' + filepath) } + if (typeof textOrAst !== 'string') { + argv.push('-ast=' + JSON.stringify(textOrAst)) + if (originalText == null) { + console.warn( + '`originalText` is required for now, hope we will find better solution later', + ) + } else { + argv.push('-originalText=' + originalText) + } + } + if (stopAt != null) { argv.push('-stopAt=' + stopAt) } @@ -112,13 +111,15 @@ export const getPrinter = ( delete Go.__shProcessing[uid] - if ('error' in processed) { + if ('Error' in processed && processed.Error != null) { /* istanbul ignore next */ - throw typeof processed.error === 'string' - ? new Error(processed.error) - : new ParseError(processed.error) + throw typeof processed.Error === 'string' + ? new Error(processed.Error) + : new ParseError(processed.Error) } - return processed.text + return processed.Data } + + return processor } diff --git a/src/types.ts b/src/types.ts index d1f76ee1..c8a2456d 100644 --- a/src/types.ts +++ b/src/types.ts @@ -6,22 +6,85 @@ export enum LangVariant { } export interface ShOptions { - filepath: string + filepath?: string + originalText?: string - useTabs: boolean - tabWidth: number + useTabs?: boolean + tabWidth?: number // parser - keepComments: boolean - stopAt: string - variant: LangVariant + keepComments?: boolean + stopAt?: string + variant?: LangVariant // printer - indent: number - binaryNextLine: boolean - switchCaseIndent: boolean - spaceRedirects: boolean - keepPadding: boolean - minify: boolean - functionNextLine: boolean + indent?: number + binaryNextLine?: boolean + switchCaseIndent?: boolean + spaceRedirects?: boolean + keepPadding?: boolean + minify?: boolean + functionNextLine?: boolean +} + +export interface Pos { + Col: number + Line: number + Offset: number +} + +export interface Node { + Pos: Pos + End: Pos +} + +export interface Comment extends Node { + Text: string +} + +export interface Word extends Node { + Parts: Node[] + Lit: string +} + +export interface Lit extends Node { + ValuePos: Pos + ValueEnd: Pos + Value: string +} + +export interface Redirect extends Node { + OpPos: Pos + Op: string + N: Lit | null + Word: Word + Hdoc: Word | null +} + +export interface Stmt extends Node { + Comments: Comment[] + Cmd: Node | null + Position: Pos + Semicolon: Pos + Negated: boolean + Background: boolean + Coprocess: boolean + Redirs: Redirect[] +} + +export interface File extends Node { + Name: string + Stmts: Stmt[] +} + +export interface IParseError { + Filename: string + Incomplete: boolean + Text: string + Pos: Pos +} + +export interface ParseResult { + Data: File | string | null + Error: IParseError | string | null } diff --git a/test/__snapshots__/basic.spec.ts.snap b/test/__snapshots__/basic.spec.ts.snap new file mode 100644 index 00000000..66832721 --- /dev/null +++ b/test/__snapshots__/basic.spec.ts.snap @@ -0,0 +1,233 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`it should just work 1`] = ` +Object { + "End": Object { + "Col": 17, + "Line": 1, + "Offset": 16, + }, + "Last": Array [], + "Name": "", + "Pos": Object { + "Col": 3, + "Line": 1, + "Offset": 2, + }, + "Stmt": Array [ + Object { + "Background": false, + "Cmd": Object { + "End": Object { + "Col": 17, + "Line": 1, + "Offset": 16, + }, + "Pos": Object { + "Col": 3, + "Line": 1, + "Offset": 2, + }, + }, + "Comments": Array [], + "Coprocess": false, + "End": Object { + "Col": 17, + "Line": 1, + "Offset": 16, + }, + "Negated": false, + "Pos": Object { + "Col": 3, + "Line": 1, + "Offset": 2, + }, + "Position": Object { + "Col": 3, + "Line": 1, + "Offset": 2, + }, + "Redirs": Array [], + "Semicolon": Object { + "Col": 0, + "Line": 0, + "Offset": 0, + }, + }, + ], +} +`; + +exports[`it should just work 2`] = ` +Object { + "End": Object { + "Col": 16, + "Line": 1, + "Offset": 15, + }, + "Last": Array [], + "Name": "", + "Pos": Object { + "Col": 3, + "Line": 1, + "Offset": 2, + }, + "Stmt": Array [ + Object { + "Background": false, + "Cmd": Object { + "End": Object { + "Col": 16, + "Line": 1, + "Offset": 15, + }, + "Pos": Object { + "Col": 3, + "Line": 1, + "Offset": 2, + }, + }, + "Comments": Array [], + "Coprocess": false, + "End": Object { + "Col": 16, + "Line": 1, + "Offset": 15, + }, + "Negated": false, + "Pos": Object { + "Col": 3, + "Line": 1, + "Offset": 2, + }, + "Position": Object { + "Col": 3, + "Line": 1, + "Offset": 2, + }, + "Redirs": Array [], + "Semicolon": Object { + "Col": 0, + "Line": 0, + "Offset": 0, + }, + }, + ], +} +`; + +exports[`it should just work 3`] = ` +Object { + "End": Object { + "Col": 20, + "Line": 1, + "Offset": 19, + }, + "Last": Array [], + "Name": "", + "Pos": Object { + "Col": 3, + "Line": 1, + "Offset": 2, + }, + "Stmt": Array [ + Object { + "Background": false, + "Cmd": Object { + "End": Object { + "Col": 20, + "Line": 1, + "Offset": 19, + }, + "Pos": Object { + "Col": 3, + "Line": 1, + "Offset": 2, + }, + }, + "Comments": Array [], + "Coprocess": false, + "End": Object { + "Col": 20, + "Line": 1, + "Offset": 19, + }, + "Negated": false, + "Pos": Object { + "Col": 3, + "Line": 1, + "Offset": 2, + }, + "Position": Object { + "Col": 3, + "Line": 1, + "Offset": 2, + }, + "Redirs": Array [], + "Semicolon": Object { + "Col": 0, + "Line": 0, + "Offset": 0, + }, + }, + ], +} +`; + +exports[`it should just work 4`] = ` +Object { + "End": Object { + "Col": 20, + "Line": 1, + "Offset": 19, + }, + "Last": Array [], + "Name": "", + "Pos": Object { + "Col": 3, + "Line": 1, + "Offset": 2, + }, + "Stmt": Array [ + Object { + "Background": false, + "Cmd": Object { + "End": Object { + "Col": 20, + "Line": 1, + "Offset": 19, + }, + "Pos": Object { + "Col": 3, + "Line": 1, + "Offset": 2, + }, + }, + "Comments": Array [], + "Coprocess": false, + "End": Object { + "Col": 20, + "Line": 1, + "Offset": 19, + }, + "Negated": false, + "Pos": Object { + "Col": 3, + "Line": 1, + "Offset": 2, + }, + "Position": Object { + "Col": 3, + "Line": 1, + "Offset": 2, + }, + "Redirs": Array [], + "Semicolon": Object { + "Col": 0, + "Line": 0, + "Offset": 0, + }, + }, + ], +} +`; diff --git a/test/__snapshots__/fixture.spec.ts.snap b/test/__snapshots__/fixture.spec.ts.snap index 3464fb07..16af71f1 100644 --- a/test/__snapshots__/fixture.spec.ts.snap +++ b/test/__snapshots__/fixture.spec.ts.snap @@ -1,6 +1,298 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`parser and printer should format all fixtures: .dockerignore 1`] = ` +Object { + "End": Object { + "Col": 18, + "Line": 7, + "Offset": 104, + }, + "Last": Array [], + "Name": ".dockerignore", + "Pos": Object { + "Col": 7, + "Line": 1, + "Offset": 6, + }, + "Stmt": Array [ + Object { + "Background": false, + "Cmd": Object { + "End": Object { + "Col": 18, + "Line": 1, + "Offset": 17, + }, + "Pos": Object { + "Col": 7, + "Line": 1, + "Offset": 6, + }, + }, + "Comments": Array [], + "Coprocess": false, + "End": Object { + "Col": 18, + "Line": 1, + "Offset": 17, + }, + "Negated": false, + "Pos": Object { + "Col": 7, + "Line": 1, + "Offset": 6, + }, + "Position": Object { + "Col": 7, + "Line": 1, + "Offset": 6, + }, + "Redirs": Array [], + "Semicolon": Object { + "Col": 0, + "Line": 0, + "Offset": 0, + }, + }, + Object { + "Background": false, + "Cmd": Object { + "End": Object { + "Col": 7, + "Line": 2, + "Offset": 24, + }, + "Pos": Object { + "Col": 2, + "Line": 2, + "Offset": 19, + }, + }, + "Comments": Array [], + "Coprocess": false, + "End": Object { + "Col": 7, + "Line": 2, + "Offset": 24, + }, + "Negated": false, + "Pos": Object { + "Col": 2, + "Line": 2, + "Offset": 19, + }, + "Position": Object { + "Col": 2, + "Line": 2, + "Offset": 19, + }, + "Redirs": Array [], + "Semicolon": Object { + "Col": 0, + "Line": 0, + "Offset": 0, + }, + }, + Object { + "Background": false, + "Cmd": Object { + "End": Object { + "Col": 20, + "Line": 3, + "Offset": 44, + }, + "Pos": Object { + "Col": 7, + "Line": 3, + "Offset": 31, + }, + }, + "Comments": Array [], + "Coprocess": false, + "End": Object { + "Col": 20, + "Line": 3, + "Offset": 44, + }, + "Negated": false, + "Pos": Object { + "Col": 7, + "Line": 3, + "Offset": 31, + }, + "Position": Object { + "Col": 7, + "Line": 3, + "Offset": 31, + }, + "Redirs": Array [], + "Semicolon": Object { + "Col": 0, + "Line": 0, + "Offset": 0, + }, + }, + Object { + "Background": false, + "Cmd": Object { + "End": Object { + "Col": 13, + "Line": 4, + "Offset": 57, + }, + "Pos": Object { + "Col": 6, + "Line": 4, + "Offset": 50, + }, + }, + "Comments": Array [], + "Coprocess": false, + "End": Object { + "Col": 13, + "Line": 4, + "Offset": 57, + }, + "Negated": false, + "Pos": Object { + "Col": 6, + "Line": 4, + "Offset": 50, + }, + "Position": Object { + "Col": 6, + "Line": 4, + "Offset": 50, + }, + "Redirs": Array [], + "Semicolon": Object { + "Col": 0, + "Line": 0, + "Offset": 0, + }, + }, + Object { + "Background": false, + "Cmd": Object { + "End": Object { + "Col": 14, + "Line": 5, + "Offset": 71, + }, + "Pos": Object { + "Col": 6, + "Line": 5, + "Offset": 63, + }, + }, + "Comments": Array [], + "Coprocess": false, + "End": Object { + "Col": 14, + "Line": 5, + "Offset": 71, + }, + "Negated": false, + "Pos": Object { + "Col": 6, + "Line": 5, + "Offset": 63, + }, + "Position": Object { + "Col": 6, + "Line": 5, + "Offset": 63, + }, + "Redirs": Array [], + "Semicolon": Object { + "Col": 0, + "Line": 0, + "Offset": 0, + }, + }, + Object { + "Background": false, + "Cmd": Object { + "End": Object { + "Col": 15, + "Line": 6, + "Offset": 86, + }, + "Pos": Object { + "Col": 11, + "Line": 6, + "Offset": 82, + }, + }, + "Comments": Array [], + "Coprocess": false, + "End": Object { + "Col": 15, + "Line": 6, + "Offset": 86, + }, + "Negated": false, + "Pos": Object { + "Col": 11, + "Line": 6, + "Offset": 82, + }, + "Position": Object { + "Col": 11, + "Line": 6, + "Offset": 82, + }, + "Redirs": Array [], + "Semicolon": Object { + "Col": 0, + "Line": 0, + "Offset": 0, + }, + }, + Object { + "Background": false, + "Cmd": Object { + "End": Object { + "Col": 18, + "Line": 7, + "Offset": 104, + }, + "Pos": Object { + "Col": 6, + "Line": 7, + "Offset": 92, + }, + }, + "Comments": Array [], + "Coprocess": false, + "End": Object { + "Col": 18, + "Line": 7, + "Offset": 104, + }, + "Negated": false, + "Pos": Object { + "Col": 6, + "Line": 7, + "Offset": 92, + }, + "Position": Object { + "Col": 6, + "Line": 7, + "Offset": 92, + }, + "Redirs": Array [], + "Semicolon": Object { + "Col": 0, + "Line": 0, + "Offset": 0, + }, + }, + ], +} +`; + +exports[`parser and printer should format all fixtures: .dockerignore 2`] = ` "*.less.json *.log *.tsbuildinfo @@ -12,6 +304,179 @@ node_modules `; exports[`parser and printer should format all fixtures: .properties 1`] = ` +Object { + "End": Object { + "Col": 17, + "Line": 6, + "Offset": 151, + }, + "Last": Array [], + "Name": ".properties", + "Pos": Object { + "Col": 1, + "Line": 1, + "Offset": 0, + }, + "Stmt": Array [ + Object { + "Background": false, + "Cmd": Object { + "End": Object { + "Col": 20, + "Line": 4, + "Offset": 104, + }, + "Pos": Object { + "Col": 1, + "Line": 4, + "Offset": 85, + }, + }, + "Comments": Array [ + Object { + "End": Object { + "Col": 65, + "Line": 1, + "Offset": 64, + }, + "Hash": Object { + "Col": 1, + "Line": 1, + "Offset": 0, + }, + "Pos": Object { + "Col": 1, + "Line": 1, + "Offset": 0, + }, + "Text": " THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.", + }, + Object { + "End": Object { + "Col": 19, + "Line": 2, + "Offset": 83, + }, + "Hash": Object { + "Col": 1, + "Line": 2, + "Offset": 65, + }, + "Pos": Object { + "Col": 1, + "Line": 2, + "Offset": 65, + }, + "Text": " yarn lockfile v1", + }, + ], + "Coprocess": false, + "End": Object { + "Col": 20, + "Line": 4, + "Offset": 104, + }, + "Negated": false, + "Pos": Object { + "Col": 1, + "Line": 4, + "Offset": 85, + }, + "Position": Object { + "Col": 1, + "Line": 4, + "Offset": 85, + }, + "Redirs": Array [], + "Semicolon": Object { + "Col": 0, + "Line": 0, + "Offset": 0, + }, + }, + Object { + "Background": false, + "Cmd": Object { + "End": Object { + "Col": 30, + "Line": 5, + "Offset": 134, + }, + "Pos": Object { + "Col": 1, + "Line": 5, + "Offset": 105, + }, + }, + "Comments": Array [], + "Coprocess": false, + "End": Object { + "Col": 30, + "Line": 5, + "Offset": 134, + }, + "Negated": false, + "Pos": Object { + "Col": 1, + "Line": 5, + "Offset": 105, + }, + "Position": Object { + "Col": 1, + "Line": 5, + "Offset": 105, + }, + "Redirs": Array [], + "Semicolon": Object { + "Col": 0, + "Line": 0, + "Offset": 0, + }, + }, + Object { + "Background": false, + "Cmd": Object { + "End": Object { + "Col": 17, + "Line": 6, + "Offset": 151, + }, + "Pos": Object { + "Col": 1, + "Line": 6, + "Offset": 135, + }, + }, + "Comments": Array [], + "Coprocess": false, + "End": Object { + "Col": 17, + "Line": 6, + "Offset": 151, + }, + "Negated": false, + "Pos": Object { + "Col": 1, + "Line": 6, + "Offset": 135, + }, + "Position": Object { + "Col": 1, + "Line": 6, + "Offset": 135, + }, + "Redirs": Array [], + "Semicolon": Object { + "Col": 0, + "Line": 0, + "Offset": 0, + }, + }, + ], +} +`; + +exports[`parser and printer should format all fixtures: .properties 2`] = ` "# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. # yarn lockfile v1 @@ -22,6 +487,336 @@ username jounqin `; exports[`parser and printer should format all fixtures: Dockerfile 1`] = ` +Object { + "End": Object { + "Col": 12, + "Line": 13, + "Offset": 289, + }, + "Last": Array [], + "Name": "Dockerfile", + "Pos": Object { + "Col": 1, + "Line": 1, + "Offset": 0, + }, + "Stmt": Array [ + Object { + "Background": false, + "Cmd": Object { + "End": Object { + "Col": 32, + "Line": 1, + "Offset": 31, + }, + "Pos": Object { + "Col": 1, + "Line": 1, + "Offset": 0, + }, + }, + "Comments": Array [], + "Coprocess": false, + "End": Object { + "Col": 32, + "Line": 1, + "Offset": 31, + }, + "Negated": false, + "Pos": Object { + "Col": 1, + "Line": 1, + "Offset": 0, + }, + "Position": Object { + "Col": 1, + "Line": 1, + "Offset": 0, + }, + "Redirs": Array [], + "Semicolon": Object { + "Col": 0, + "Line": 0, + "Offset": 0, + }, + }, + Object { + "Background": false, + "Cmd": Object { + "End": Object { + "Col": 12, + "Line": 4, + "Offset": 68, + }, + "Pos": Object { + "Col": 1, + "Line": 4, + "Offset": 57, + }, + }, + "Comments": Array [ + Object { + "End": Object { + "Col": 24, + "Line": 3, + "Offset": 56, + }, + "Hash": Object { + "Col": 1, + "Line": 3, + "Offset": 33, + }, + "Pos": Object { + "Col": 1, + "Line": 3, + "Offset": 33, + }, + "Text": " 安装与编译代码", + }, + ], + "Coprocess": false, + "End": Object { + "Col": 12, + "Line": 4, + "Offset": 68, + }, + "Negated": false, + "Pos": Object { + "Col": 1, + "Line": 4, + "Offset": 57, + }, + "Position": Object { + "Col": 1, + "Line": 4, + "Offset": 57, + }, + "Redirs": Array [], + "Semicolon": Object { + "Col": 0, + "Line": 0, + "Offset": 0, + }, + }, + Object { + "Background": false, + "Cmd": Object { + "End": Object { + "Col": 13, + "Line": 5, + "Offset": 81, + }, + "Pos": Object { + "Col": 1, + "Line": 5, + "Offset": 69, + }, + }, + "Comments": Array [], + "Coprocess": false, + "End": Object { + "Col": 13, + "Line": 5, + "Offset": 81, + }, + "Negated": false, + "Pos": Object { + "Col": 1, + "Line": 5, + "Offset": 69, + }, + "Position": Object { + "Col": 1, + "Line": 5, + "Offset": 69, + }, + "Redirs": Array [], + "Semicolon": Object { + "Col": 0, + "Line": 0, + "Offset": 0, + }, + }, + Object { + "Background": false, + "Cmd": Object { + "End": Object { + "Col": 52, + "Line": 8, + "Offset": 180, + }, + "Pos": Object { + "Col": 1, + "Line": 6, + "Offset": 82, + }, + }, + "Comments": Array [], + "Coprocess": false, + "End": Object { + "Col": 52, + "Line": 8, + "Offset": 180, + }, + "Negated": false, + "Pos": Object { + "Col": 1, + "Line": 6, + "Offset": 82, + }, + "Position": Object { + "Col": 1, + "Line": 6, + "Offset": 82, + }, + "Redirs": Array [], + "Semicolon": Object { + "Col": 0, + "Line": 0, + "Offset": 0, + }, + }, + Object { + "Background": false, + "Cmd": Object { + "End": Object { + "Col": 20, + "Line": 11, + "Offset": 219, + }, + "Pos": Object { + "Col": 1, + "Line": 11, + "Offset": 200, + }, + }, + "Comments": Array [ + Object { + "End": Object { + "Col": 18, + "Line": 10, + "Offset": 199, + }, + "Hash": Object { + "Col": 1, + "Line": 10, + "Offset": 182, + }, + "Pos": Object { + "Col": 1, + "Line": 10, + "Offset": 182, + }, + "Text": " 最终的应用", + }, + ], + "Coprocess": false, + "End": Object { + "Col": 20, + "Line": 11, + "Offset": 219, + }, + "Negated": false, + "Pos": Object { + "Col": 1, + "Line": 11, + "Offset": 200, + }, + "Position": Object { + "Col": 1, + "Line": 11, + "Offset": 200, + }, + "Redirs": Array [], + "Semicolon": Object { + "Col": 0, + "Line": 0, + "Offset": 0, + }, + }, + Object { + "Background": false, + "Cmd": Object { + "End": Object { + "Col": 58, + "Line": 12, + "Offset": 277, + }, + "Pos": Object { + "Col": 1, + "Line": 12, + "Offset": 220, + }, + }, + "Comments": Array [], + "Coprocess": false, + "End": Object { + "Col": 58, + "Line": 12, + "Offset": 277, + }, + "Negated": false, + "Pos": Object { + "Col": 1, + "Line": 12, + "Offset": 220, + }, + "Position": Object { + "Col": 1, + "Line": 12, + "Offset": 220, + }, + "Redirs": Array [], + "Semicolon": Object { + "Col": 0, + "Line": 0, + "Offset": 0, + }, + }, + Object { + "Background": false, + "Cmd": Object { + "End": Object { + "Col": 12, + "Line": 13, + "Offset": 289, + }, + "Pos": Object { + "Col": 1, + "Line": 13, + "Offset": 278, + }, + }, + "Comments": Array [], + "Coprocess": false, + "End": Object { + "Col": 12, + "Line": 13, + "Offset": 289, + }, + "Negated": false, + "Pos": Object { + "Col": 1, + "Line": 13, + "Offset": 278, + }, + "Position": Object { + "Col": 1, + "Line": 13, + "Offset": 278, + }, + "Redirs": Array [], + "Semicolon": Object { + "Col": 0, + "Line": 0, + "Offset": 0, + }, + }, + ], +} +`; + +exports[`parser and printer should format all fixtures: Dockerfile 2`] = ` "FROM node:lts-alpine as builder # 安装与编译代码 @@ -38,9 +833,349 @@ EXPOSE 2015 " `; -exports[`parser and printer should format all fixtures: error.sh 1`] = `[Error: path:1:6: a command can only contain words and redirects; encountered )]`; +exports[`parser and printer should format all fixtures: error.sh 1`] = `[Error: a command can only contain words and redirects; encountered )]`; exports[`parser and printer should format all fixtures: hosts 1`] = ` +Object { + "End": Object { + "Col": 18, + "Line": 13, + "Offset": 405, + }, + "Last": Array [ + Object { + "End": Object { + "Col": 18, + "Line": 13, + "Offset": 405, + }, + "Hash": Object { + "Col": 2, + "Line": 13, + "Offset": 389, + }, + "Pos": Object { + "Col": 2, + "Line": 13, + "Offset": 389, + }, + "Text": " End of section", + }, + ], + "Name": "hosts", + "Pos": Object { + "Col": 1, + "Line": 1, + "Offset": 0, + }, + "Stmt": Array [ + Object { + "Background": false, + "Cmd": Object { + "End": Object { + "Col": 31, + "Line": 7, + "Offset": 167, + }, + "Pos": Object { + "Col": 5, + "Line": 7, + "Offset": 141, + }, + }, + "Comments": Array [ + Object { + "End": Object { + "Col": 3, + "Line": 1, + "Offset": 2, + }, + "Hash": Object { + "Col": 1, + "Line": 1, + "Offset": 0, + }, + "Pos": Object { + "Col": 1, + "Line": 1, + "Offset": 0, + }, + "Text": "#", + }, + Object { + "End": Object { + "Col": 16, + "Line": 2, + "Offset": 18, + }, + "Hash": Object { + "Col": 1, + "Line": 2, + "Offset": 3, + }, + "Pos": Object { + "Col": 1, + "Line": 2, + "Offset": 3, + }, + "Text": " Host Database", + }, + Object { + "End": Object { + "Col": 2, + "Line": 3, + "Offset": 20, + }, + "Hash": Object { + "Col": 1, + "Line": 3, + "Offset": 19, + }, + "Pos": Object { + "Col": 1, + "Line": 3, + "Offset": 19, + }, + "Text": "", + }, + Object { + "End": Object { + "Col": 56, + "Line": 4, + "Offset": 76, + }, + "Hash": Object { + "Col": 1, + "Line": 4, + "Offset": 21, + }, + "Pos": Object { + "Col": 1, + "Line": 4, + "Offset": 21, + }, + "Text": " localhost is used to configure the loopback interface", + }, + Object { + "End": Object { + "Col": 57, + "Line": 5, + "Offset": 133, + }, + "Hash": Object { + "Col": 1, + "Line": 5, + "Offset": 77, + }, + "Pos": Object { + "Col": 1, + "Line": 5, + "Offset": 77, + }, + "Text": " when the system is booting. Do not change this entry.", + }, + Object { + "End": Object { + "Col": 3, + "Line": 6, + "Offset": 136, + }, + "Hash": Object { + "Col": 1, + "Line": 6, + "Offset": 134, + }, + "Pos": Object { + "Col": 1, + "Line": 6, + "Offset": 134, + }, + "Text": "#", + }, + ], + "Coprocess": false, + "End": Object { + "Col": 31, + "Line": 7, + "Offset": 167, + }, + "Negated": false, + "Pos": Object { + "Col": 5, + "Line": 7, + "Offset": 141, + }, + "Position": Object { + "Col": 5, + "Line": 7, + "Offset": 141, + }, + "Redirs": Array [], + "Semicolon": Object { + "Col": 0, + "Line": 0, + "Offset": 0, + }, + }, + Object { + "Background": false, + "Cmd": Object { + "End": Object { + "Col": 46, + "Line": 8, + "Offset": 213, + }, + "Pos": Object { + "Col": 4, + "Line": 8, + "Offset": 171, + }, + }, + "Comments": Array [], + "Coprocess": false, + "End": Object { + "Col": 46, + "Line": 8, + "Offset": 213, + }, + "Negated": false, + "Pos": Object { + "Col": 4, + "Line": 8, + "Offset": 171, + }, + "Position": Object { + "Col": 4, + "Line": 8, + "Offset": 171, + }, + "Redirs": Array [], + "Semicolon": Object { + "Col": 0, + "Line": 0, + "Offset": 0, + }, + }, + Object { + "Background": false, + "Cmd": Object { + "End": Object { + "Col": 26, + "Line": 9, + "Offset": 239, + }, + "Pos": Object { + "Col": 1, + "Line": 9, + "Offset": 214, + }, + }, + "Comments": Array [], + "Coprocess": false, + "End": Object { + "Col": 26, + "Line": 9, + "Offset": 239, + }, + "Negated": false, + "Pos": Object { + "Col": 1, + "Line": 9, + "Offset": 214, + }, + "Position": Object { + "Col": 1, + "Line": 9, + "Offset": 214, + }, + "Redirs": Array [], + "Semicolon": Object { + "Col": 0, + "Line": 0, + "Offset": 0, + }, + }, + Object { + "Background": false, + "Cmd": Object { + "End": Object { + "Col": 44, + "Line": 12, + "Offset": 387, + }, + "Pos": Object { + "Col": 1, + "Line": 12, + "Offset": 344, + }, + }, + "Comments": Array [ + Object { + "End": Object { + "Col": 30, + "Line": 10, + "Offset": 269, + }, + "Hash": Object { + "Col": 5, + "Line": 10, + "Offset": 244, + }, + "Pos": Object { + "Col": 5, + "Line": 10, + "Offset": 244, + }, + "Text": " Added by Docker Desktop", + }, + Object { + "End": Object { + "Col": 74, + "Line": 11, + "Offset": 343, + }, + "Hash": Object { + "Col": 3, + "Line": 11, + "Offset": 272, + }, + "Pos": Object { + "Col": 3, + "Line": 11, + "Offset": 272, + }, + "Text": " To allow the same kube context to work on the host and the container:", + }, + ], + "Coprocess": false, + "End": Object { + "Col": 44, + "Line": 12, + "Offset": 387, + }, + "Negated": false, + "Pos": Object { + "Col": 1, + "Line": 12, + "Offset": 344, + }, + "Position": Object { + "Col": 1, + "Line": 12, + "Offset": 344, + }, + "Redirs": Array [], + "Semicolon": Object { + "Col": 0, + "Line": 0, + "Offset": 0, + }, + }, + ], +} +`; + +exports[`parser and printer should format all fixtures: hosts 2`] = ` "## # Host Database # @@ -58,6 +1193,122 @@ exports[`parser and printer should format all fixtures: hosts 1`] = ` `; exports[`parser and printer should format all fixtures: jvm.options 1`] = ` +Object { + "End": Object { + "Col": 41, + "Line": 4, + "Offset": 161, + }, + "Last": Array [], + "Name": "jvm.options", + "Pos": Object { + "Col": 1, + "Line": 1, + "Offset": 0, + }, + "Stmt": Array [ + Object { + "Background": false, + "Cmd": Object { + "End": Object { + "Col": 100, + "Line": 2, + "Offset": 119, + }, + "Pos": Object { + "Col": 1, + "Line": 2, + "Offset": 20, + }, + }, + "Comments": Array [ + Object { + "End": Object { + "Col": 20, + "Line": 1, + "Offset": 19, + }, + "Hash": Object { + "Col": 1, + "Line": 1, + "Offset": 0, + }, + "Pos": Object { + "Col": 1, + "Line": 1, + "Offset": 0, + }, + "Text": " this is a comment", + }, + ], + "Coprocess": false, + "End": Object { + "Col": 100, + "Line": 2, + "Offset": 119, + }, + "Negated": false, + "Pos": Object { + "Col": 1, + "Line": 2, + "Offset": 20, + }, + "Position": Object { + "Col": 1, + "Line": 2, + "Offset": 20, + }, + "Redirs": Array [], + "Semicolon": Object { + "Col": 0, + "Line": 0, + "Offset": 0, + }, + }, + Object { + "Background": false, + "Cmd": Object { + "End": Object { + "Col": 41, + "Line": 4, + "Offset": 161, + }, + "Pos": Object { + "Col": 1, + "Line": 4, + "Offset": 121, + }, + }, + "Comments": Array [], + "Coprocess": false, + "End": Object { + "Col": 41, + "Line": 4, + "Offset": 161, + }, + "Negated": false, + "Pos": Object { + "Col": 1, + "Line": 4, + "Offset": 121, + }, + "Position": Object { + "Col": 1, + "Line": 4, + "Offset": 121, + }, + "Redirs": Array [], + "Semicolon": Object { + "Col": 0, + "Line": 0, + "Offset": 0, + }, + }, + ], +} +`; + +exports[`parser and printer should format all fixtures: jvm.options 2`] = ` "# this is a comment -mx2048m -XX:MaxPermSize=2048m -Drebel.spring_plugin=true -Drebel.hibernate_plugin=true @@ -66,12 +1317,601 @@ idea.cycle.buffer.size = 1024 `; exports[`parser and printer should format all fixtures: no-ext 1`] = ` +Object { + "End": Object { + "Col": 20, + "Line": 2, + "Offset": 36, + }, + "Last": Array [], + "Name": "no-ext", + "Pos": Object { + "Col": 1, + "Line": 1, + "Offset": 0, + }, + "Stmt": Array [ + Object { + "Background": false, + "Cmd": Object { + "End": Object { + "Col": 20, + "Line": 2, + "Offset": 36, + }, + "Pos": Object { + "Col": 5, + "Line": 2, + "Offset": 21, + }, + }, + "Comments": Array [ + Object { + "End": Object { + "Col": 17, + "Line": 1, + "Offset": 16, + }, + "Hash": Object { + "Col": 1, + "Line": 1, + "Offset": 0, + }, + "Pos": Object { + "Col": 1, + "Line": 1, + "Offset": 0, + }, + "Text": "/usr/bin/env sh", + }, + ], + "Coprocess": false, + "End": Object { + "Col": 20, + "Line": 2, + "Offset": 36, + }, + "Negated": false, + "Pos": Object { + "Col": 5, + "Line": 2, + "Offset": 21, + }, + "Position": Object { + "Col": 5, + "Line": 2, + "Offset": 21, + }, + "Redirs": Array [], + "Semicolon": Object { + "Col": 0, + "Line": 0, + "Offset": 0, + }, + }, + ], +} +`; + +exports[`parser and printer should format all fixtures: no-ext 2`] = ` "#/usr/bin/env sh echo 'foo' " `; exports[`parser and printer should format all fixtures: shell.sh 1`] = ` +Object { + "End": Object { + "Col": 6, + "Line": 16, + "Offset": 357, + }, + "Last": Array [], + "Name": "shell.sh", + "Pos": Object { + "Col": 1, + "Line": 1, + "Offset": 0, + }, + "Stmt": Array [ + Object { + "Background": false, + "Cmd": Object { + "End": Object { + "Col": 23, + "Line": 2, + "Offset": 34, + }, + "Pos": Object { + "Col": 1, + "Line": 2, + "Offset": 12, + }, + }, + "Comments": Array [ + Object { + "End": Object { + "Col": 12, + "Line": 1, + "Offset": 11, + }, + "Hash": Object { + "Col": 1, + "Line": 1, + "Offset": 0, + }, + "Pos": Object { + "Col": 1, + "Line": 1, + "Offset": 0, + }, + "Text": "!/bin/bash", + }, + ], + "Coprocess": false, + "End": Object { + "Col": 23, + "Line": 2, + "Offset": 34, + }, + "Negated": false, + "Pos": Object { + "Col": 1, + "Line": 2, + "Offset": 12, + }, + "Position": Object { + "Col": 1, + "Line": 2, + "Offset": 12, + }, + "Redirs": Array [], + "Semicolon": Object { + "Col": 0, + "Line": 0, + "Offset": 0, + }, + }, + Object { + "Background": false, + "Cmd": Object { + "End": Object { + "Col": 9, + "Line": 4, + "Offset": 44, + }, + "Pos": Object { + "Col": 1, + "Line": 4, + "Offset": 36, + }, + }, + "Comments": Array [], + "Coprocess": false, + "End": Object { + "Col": 9, + "Line": 4, + "Offset": 44, + }, + "Negated": false, + "Pos": Object { + "Col": 1, + "Line": 4, + "Offset": 36, + }, + "Position": Object { + "Col": 1, + "Line": 4, + "Offset": 36, + }, + "Redirs": Array [], + "Semicolon": Object { + "Col": 0, + "Line": 0, + "Offset": 0, + }, + }, + Object { + "Background": false, + "Cmd": Object { + "End": Object { + "Col": 33, + "Line": 6, + "Offset": 78, + }, + "Pos": Object { + "Col": 1, + "Line": 6, + "Offset": 46, + }, + }, + "Comments": Array [], + "Coprocess": false, + "End": Object { + "Col": 33, + "Line": 6, + "Offset": 78, + }, + "Negated": false, + "Pos": Object { + "Col": 1, + "Line": 6, + "Offset": 46, + }, + "Position": Object { + "Col": 1, + "Line": 6, + "Offset": 46, + }, + "Redirs": Array [], + "Semicolon": Object { + "Col": 0, + "Line": 0, + "Offset": 0, + }, + }, + Object { + "Background": false, + "Cmd": Object { + "End": Object { + "Col": 64, + "Line": 7, + "Offset": 142, + }, + "Pos": Object { + "Col": 1, + "Line": 7, + "Offset": 79, + }, + }, + "Comments": Array [], + "Coprocess": false, + "End": Object { + "Col": 64, + "Line": 7, + "Offset": 142, + }, + "Negated": false, + "Pos": Object { + "Col": 1, + "Line": 7, + "Offset": 79, + }, + "Position": Object { + "Col": 1, + "Line": 7, + "Offset": 79, + }, + "Redirs": Array [], + "Semicolon": Object { + "Col": 0, + "Line": 0, + "Offset": 0, + }, + }, + Object { + "Background": false, + "Cmd": Object { + "End": Object { + "Col": 46, + "Line": 9, + "Offset": 189, + }, + "Pos": Object { + "Col": 1, + "Line": 9, + "Offset": 144, + }, + }, + "Comments": Array [], + "Coprocess": false, + "End": Object { + "Col": 46, + "Line": 9, + "Offset": 189, + }, + "Negated": false, + "Pos": Object { + "Col": 1, + "Line": 9, + "Offset": 144, + }, + "Position": Object { + "Col": 1, + "Line": 9, + "Offset": 144, + }, + "Redirs": Array [], + "Semicolon": Object { + "Col": 0, + "Line": 0, + "Offset": 0, + }, + }, + Object { + "Background": false, + "Cmd": Object { + "End": Object { + "Col": 30, + "Line": 10, + "Offset": 219, + }, + "Pos": Object { + "Col": 1, + "Line": 10, + "Offset": 190, + }, + }, + "Comments": Array [], + "Coprocess": false, + "End": Object { + "Col": 30, + "Line": 10, + "Offset": 219, + }, + "Negated": false, + "Pos": Object { + "Col": 1, + "Line": 10, + "Offset": 190, + }, + "Position": Object { + "Col": 1, + "Line": 10, + "Offset": 190, + }, + "Redirs": Array [], + "Semicolon": Object { + "Col": 0, + "Line": 0, + "Offset": 0, + }, + }, + Object { + "Background": false, + "Cmd": Object { + "End": Object { + "Col": 28, + "Line": 11, + "Offset": 247, + }, + "Pos": Object { + "Col": 1, + "Line": 11, + "Offset": 220, + }, + }, + "Comments": Array [], + "Coprocess": false, + "End": Object { + "Col": 28, + "Line": 11, + "Offset": 247, + }, + "Negated": false, + "Pos": Object { + "Col": 1, + "Line": 11, + "Offset": 220, + }, + "Position": Object { + "Col": 1, + "Line": 11, + "Offset": 220, + }, + "Redirs": Array [], + "Semicolon": Object { + "Col": 0, + "Line": 0, + "Offset": 0, + }, + }, + Object { + "Background": false, + "Cmd": Object { + "End": Object { + "Col": 28, + "Line": 13, + "Offset": 276, + }, + "Pos": Object { + "Col": 1, + "Line": 13, + "Offset": 249, + }, + }, + "Comments": Array [], + "Coprocess": false, + "End": Object { + "Col": 28, + "Line": 13, + "Offset": 276, + }, + "Negated": false, + "Pos": Object { + "Col": 1, + "Line": 13, + "Offset": 249, + }, + "Position": Object { + "Col": 1, + "Line": 13, + "Offset": 249, + }, + "Redirs": Array [], + "Semicolon": Object { + "Col": 0, + "Line": 0, + "Offset": 0, + }, + }, + Object { + "Background": false, + "Cmd": Object { + "End": Object { + "Col": 74, + "Line": 14, + "Offset": 350, + }, + "Pos": Object { + "Col": 1, + "Line": 14, + "Offset": 277, + }, + }, + "Comments": Array [], + "Coprocess": false, + "End": Object { + "Col": 74, + "Line": 14, + "Offset": 350, + }, + "Negated": false, + "Pos": Object { + "Col": 1, + "Line": 14, + "Offset": 277, + }, + "Position": Object { + "Col": 1, + "Line": 14, + "Offset": 277, + }, + "Redirs": Array [], + "Semicolon": Object { + "Col": 0, + "Line": 0, + "Offset": 0, + }, + }, + Object { + "Background": false, + "Cmd": null, + "Comments": Array [], + "Coprocess": false, + "End": Object { + "Col": 6, + "Line": 16, + "Offset": 357, + }, + "Negated": false, + "Pos": Object { + "Col": 1, + "Line": 16, + "Offset": 352, + }, + "Position": Object { + "Col": 1, + "Line": 16, + "Offset": 352, + }, + "Redirs": Array [ + Object { + "End": Object { + "Col": 3, + "Line": 16, + "Offset": 354, + }, + "Hdoc": null, + "N": null, + "Op": ">", + "OpPos": Object { + "Col": 1, + "Line": 16, + "Offset": 352, + }, + "Pos": Object { + "Col": 1, + "Line": 16, + "Offset": 352, + }, + "Word": Object { + "End": Object { + "Col": 3, + "Line": 16, + "Offset": 354, + }, + "Lit": "a", + "Parts": Array [ + Object { + "End": Object { + "Col": 3, + "Line": 16, + "Offset": 354, + }, + "Pos": Object { + "Col": 2, + "Line": 16, + "Offset": 353, + }, + }, + ], + "Pos": Object { + "Col": 2, + "Line": 16, + "Offset": 353, + }, + }, + }, + Object { + "End": Object { + "Col": 6, + "Line": 16, + "Offset": 357, + }, + "Hdoc": null, + "N": null, + "Op": "<", + "OpPos": Object { + "Col": 4, + "Line": 16, + "Offset": 355, + }, + "Pos": Object { + "Col": 4, + "Line": 16, + "Offset": 355, + }, + "Word": Object { + "End": Object { + "Col": 6, + "Line": 16, + "Offset": 357, + }, + "Lit": "b", + "Parts": Array [ + Object { + "End": Object { + "Col": 6, + "Line": 16, + "Offset": 357, + }, + "Pos": Object { + "Col": 5, + "Line": 16, + "Offset": 356, + }, + }, + ], + "Pos": Object { + "Col": 5, + "Line": 16, + "Offset": 356, + }, + }, + }, + ], + "Semicolon": Object { + "Col": 0, + "Line": 0, + "Offset": 0, + }, + }, + ], +} +`; + +exports[`parser and printer should format all fixtures: shell.sh 2`] = ` "#!/bin/bash NAME= \\"ufc-web-client\\" @@ -86,5 +1926,7 @@ docker container rm \${NAME} echo 'run a new container.' docker run -d --restart always -p 2015:2015 --name \${NAME} \${NAME}:latest + +> a < b " `; diff --git a/test/basic.spec.ts b/test/basic.spec.ts index 8a413eab..99e4d0fb 100644 --- a/test/basic.spec.ts +++ b/test/basic.spec.ts @@ -1,30 +1,39 @@ -import { LangVariant, print } from 'sh-syntax' +import { jest } from '@jest/globals' +import { Mock } from 'jest-mock' + +import { LangVariant, parse, print } from 'sh-syntax' + +const originalConsoleWarn = console.warn +let spyConsoleWarn: Mock + +beforeEach(() => { + spyConsoleWarn = console.warn = jest.fn() +}) + +afterEach(() => { + console.warn = originalConsoleWarn +}) test('it should just work', async () => { - expect(await print(' Hello World!')).toMatchInlineSnapshot(` - "Hello World! - " - `) - - expect(await print(' Hello World ! a', { stopAt: '!' })) - .toMatchInlineSnapshot(` - "Hello World - " - `) - - expect(await print(' Hello World ! b', { variant: LangVariant.LangPOSIX })) - .toMatchInlineSnapshot(` - "Hello World ! b - " - `) - - expect(await print(' Hello World ! c', { useTabs: true })) - .toMatchInlineSnapshot(` - "Hello World ! c - " - `) - - await expect(print('echo )')).rejects.toMatchInlineSnapshot( - `[Error: path:1:6: a command can only contain words and redirects; encountered )]`, + expect(await parse(' Hello World!')).toMatchSnapshot() + + expect(await parse(' Hello World ! a', { stopAt: '!' })).toMatchSnapshot() + + expect( + await parse(' Hello World ! b', { + variant: LangVariant.LangPOSIX, + }), + ).toMatchSnapshot() + + expect( + await parse(' Hello World ! c', { useTabs: true }), + ).toMatchSnapshot() + + expect(await print(null!, { filepath: 'foo.sh' })).toBe('\n') + + expect(spyConsoleWarn).toHaveBeenCalledTimes(1) + + await expect(parse('echo )')).rejects.toMatchInlineSnapshot( + `[Error: a command can only contain words and redirects; encountered )]`, ) }) diff --git a/test/fixture.spec.ts b/test/fixture.spec.ts index bd345f16..eecef2bc 100644 --- a/test/fixture.spec.ts +++ b/test/fixture.spec.ts @@ -2,7 +2,7 @@ import fs from 'fs' import path from 'path' import { fileURLToPath } from 'url' -import { print } from 'sh-syntax' +import { parse, print } from 'sh-syntax' const _dirname = typeof __dirname === 'undefined' @@ -13,13 +13,17 @@ describe('parser and printer', () => { it('should format all fixtures', async () => { const fixtures = path.resolve(_dirname, 'fixtures') for (const filepath of await fs.promises.readdir(fixtures)) { - const input = fs.readFileSync(path.resolve(fixtures, filepath), 'utf8') + const input = await fs.promises.readFile( + path.resolve(fixtures, filepath), + 'utf8', + ) try { - const output = await print(input, { - filepath, - }) - expect(output).toMatchSnapshot(filepath) + const ast = await parse(input, { filepath }) + expect(ast).toMatchSnapshot(filepath) + expect( + await print(ast, { filepath, originalText: input }), + ).toMatchSnapshot(filepath) } catch (err: unknown) { // eslint-disable-next-line jest/no-conditional-expect expect(err).toMatchSnapshot(filepath) diff --git a/test/fixtures/shell.sh b/test/fixtures/shell.sh index 8d97e9fb..42eaed13 100644 --- a/test/fixtures/shell.sh +++ b/test/fixtures/shell.sh @@ -12,3 +12,5 @@ docker container rm ${NAME} echo 'run a new container.' docker run -d --restart always -p 2015:2015 --name ${NAME} ${NAME}:latest + +>a + static readonly __shProcessing: Record _pendingEvent: { id: number } argv: string[]