diff --git a/.github/workflows/gh-pages.yml b/.github/workflows/gh-pages.yml index bea87e1a..03d99e61 100644 --- a/.github/workflows/gh-pages.yml +++ b/.github/workflows/gh-pages.yml @@ -31,16 +31,14 @@ jobs: - name: Install Node uses: actions/setup-node@v4 - with: - node-version: '16' - name: Build WASM run: wasm-pack build --target web && cd pkg && npm install - name: Setup pnpm - uses: pnpm/action-setup@v4.0.0 + uses: pnpm/action-setup@v4.1.0 with: - version: 7.12.2 + version: 9.15.1 run_install: | - recursive: true @@ -65,4 +63,4 @@ jobs: steps: - name: Deploy to GitHub Pages šŸš€ id: deployment - uses: actions/deploy-pages@v4 + uses: actions/deploy-pages@v4 \ No newline at end of file diff --git a/.oxlintrc.json b/.oxlintrc.json new file mode 100644 index 00000000..dc62a814 --- /dev/null +++ b/.oxlintrc.json @@ -0,0 +1,9 @@ +{ + "ignorePatterns": [ + "node_modules", + "dist", + "pkg", + "target", + "website/public/parsers/*.wasm" + ] +} diff --git a/Cargo.lock b/Cargo.lock index 143328ba..0dfacd02 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -11,19 +11,12 @@ dependencies = [ "memchr", ] -[[package]] -name = "anyhow" -version = "1.0.82" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f538837af36e6f6a9be0faa67f9a314f8119e4e4b5867c6ab40ed60360142519" - [[package]] name = "ast-grep-config" -version = "0.22.3" +version = "0.38.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76601296ea9f61be1cc586374a671cf2903445c318a1ab91e1857e8830fab44b" +checksum = "742665858253c18b0db5ee9da5739f1dd617c82b58ec11458644dcafed1c7f1d" dependencies = [ - "anyhow", "ast-grep-core", "bit-set", "globset", @@ -36,41 +29,26 @@ dependencies = [ [[package]] name = "ast-grep-core" -version = "0.22.3" +version = "0.38.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42741ec91a731ae12bbbaf0163c38aed1707dd577217629d97c95fc98af8e9d2" +checksum = "506bcd7f8c73ac53af75d0c11a31df48ce0af9bd35b42e9675bc5009cf7d951a" dependencies = [ "bit-set", "regex", "thiserror", - "tree-sitter-facade-sg", -] - -[[package]] -name = "ast-grep-language" -version = "0.22.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eaa521870869147980ab18c548a167e366980bf04bfa3d7e80b7dd4a023e3b7a" -dependencies = [ - "ast-grep-core", - "ignore", - "serde", ] [[package]] name = "ast-grep-wasm" -version = "0.2.6" +version = "0.38.0" dependencies = [ "ast-grep-config", "ast-grep-core", - "ast-grep-language", "console_error_panic_hook", "once_cell", "serde", "serde-wasm-bindgen", "serde_json", - "tree-sitter-facade-sg", - "tree-sitter-rust", "wasm-bindgen", "wasm-bindgen-futures", "wasm-bindgen-test", @@ -78,26 +56,20 @@ dependencies = [ "wee_alloc", ] -[[package]] -name = "autocfg" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" - [[package]] name = "bit-set" -version = "0.5.3" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1" +checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3" dependencies = [ "bit-vec", ] [[package]] name = "bit-vec" -version = "0.6.3" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" +checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" [[package]] name = "bstr" @@ -117,9 +89,12 @@ checksum = "a3e2c3daef883ecc1b5d58c15adae93470a91d425f3532ba1695849656af3fc1" [[package]] name = "cc" -version = "1.0.97" +version = "1.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "099a5357d84c4c61eb35fc8eafa9a79a902c2f76911e5747ced4e032edd8d9b4" +checksum = "8e3a13707ac958681c13b39b458c073d0d9bc8a22cb1b2f4c8e55eb72c13f362" +dependencies = [ + "shlex", +] [[package]] name = "cfg-if" @@ -143,38 +118,6 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "crossbeam-deque" -version = "0.8.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fca89a0e215bab21874660c67903c5f143333cab1da83d041c7ded6053774751" -dependencies = [ - "cfg-if 1.0.0", - "crossbeam-epoch", - "crossbeam-utils", -] - -[[package]] -name = "crossbeam-epoch" -version = "0.9.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d2fe95351b870527a5d09bf563ed3c97c0cffb87cf1c78a591bf48bb218d9aa" -dependencies = [ - "autocfg", - "cfg-if 1.0.0", - "crossbeam-utils", - "memoffset", -] - -[[package]] -name = "crossbeam-utils" -version = "0.8.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c06d96137f14f244c37f989d9fff8f95e6c18b918e71f36638f8c49112e4c78f" -dependencies = [ - "cfg-if 1.0.0", -] - [[package]] name = "dyn-clone" version = "1.0.16" @@ -206,22 +149,6 @@ version = "0.14.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" -[[package]] -name = "ignore" -version = "0.4.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b46810df39e66e925525d6e38ce1e7f6e1d208f72dc39757880fcb66e2c58af1" -dependencies = [ - "crossbeam-deque", - "globset", - "log", - "memchr", - "regex-automata", - "same-file", - "walkdir", - "winapi-util", -] - [[package]] name = "indexmap" version = "2.2.3" @@ -240,18 +167,18 @@ checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6" [[package]] name = "js-sys" -version = "0.3.69" +version = "0.3.70" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d" +checksum = "1868808506b929d7b0cfa8f75951347aa71bb21144b7791bae35d9bccfcfe37a" dependencies = [ "wasm-bindgen", ] [[package]] name = "libc" -version = "0.2.146" +version = "0.2.155" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f92be4933c13fd498862a9e02a3055f8a8d9c039ce33db97306fd5a6caa7f29b" +checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" [[package]] name = "log" @@ -265,32 +192,33 @@ version = "2.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f232d6ef707e1956a43342693d2a31e72989554d58299d7a88738cc95b0d35c" -[[package]] -name = "memoffset" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a634b1c61a95585bd15607c6ab0c4e5b226e695ff2800ba0cdccddf208c406c" -dependencies = [ - "autocfg", -] - [[package]] name = "memory_units" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8452105ba047068f40ff7093dd1d9da90898e63dd61736462e9cdda6a90ad3c3" +[[package]] +name = "minicov" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c71e683cd655513b99affab7d317deb690528255a0d5f717f1024093c12b169" +dependencies = [ + "cc", + "walkdir", +] + [[package]] name = "once_cell" -version = "1.19.0" +version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" [[package]] name = "proc-macro2" -version = "1.0.76" +version = "1.0.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95fc56cda0b5c3325f5fbbd7ff9fda9e02bb00bb3dac51252d2f1bfa1cb8cc8c" +checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0" dependencies = [ "unicode-ident", ] @@ -306,9 +234,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.10.4" +version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c117dbdfde9c8308975b6a18d71f3f385c89461f7b3fb054288ecf2a2058ba4c" +checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" dependencies = [ "aho-corasick", "memchr", @@ -318,9 +246,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.5" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5bb987efffd3c6d0d8f5f89510bb458559eab11e4f869acb20bf845e016259cd" +checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" dependencies = [ "aho-corasick", "memchr", @@ -329,9 +257,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.2" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" [[package]] name = "ryu" @@ -380,9 +308,9 @@ checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" [[package]] name = "serde" -version = "1.0.202" +version = "1.0.219" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "226b61a0d411b2ba5ff6d7f73a476ac4f8bb900373459cd00fab8512828ba395" +checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" dependencies = [ "serde_derive", ] @@ -400,9 +328,9 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.202" +version = "1.0.219" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6048858004bcff69094cd972ed40a32500f153bd3be9f716b2eed2e8217c4838" +checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" dependencies = [ "proc-macro2", "quote", @@ -422,11 +350,12 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.117" +version = "1.0.140" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "455182ea6142b14f93f4bc5320a2b31c1f266b66a4a5c858b013302a5d8cbfc3" +checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373" dependencies = [ "itoa", + "memchr", "ryu", "serde", ] @@ -444,11 +373,17 @@ dependencies = [ "unsafe-libyaml", ] +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + [[package]] name = "syn" -version = "2.0.48" +version = "2.0.90" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f3531638e407dfc0814761abb7c00a5b54992b849452a0646b7f65c9f770f3f" +checksum = "919d3b74a5dd0ccd15aeb8f93e7006bd9e14c295087c9896a110f490752bcf31" dependencies = [ "proc-macro2", "quote", @@ -457,67 +392,24 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.59" +version = "2.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0126ad08bff79f29fc3ae6a55cc72352056dfff61e3ff8bb7129476d44b23aa" +checksum = "c006c85c7651b3cf2ada4584faa36773bd07bac24acfb39f3c431b36d7e667aa" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.59" +version = "2.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1cd413b5d558b4c5bf3680e324a6fa5014e7b7c067a51e69dbdf47eb7148b66" +checksum = "f077553d607adc1caf65430528a576c757a71ed73944b66ebb58ef2bbd243568" dependencies = [ "proc-macro2", "quote", "syn", ] -[[package]] -name = "tree-sitter" -version = "0.20.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e747b1f9b7b931ed39a548c1fae149101497de3c1fc8d9e18c62c1a66c683d3d" -dependencies = [ - "cc", - "regex", -] - -[[package]] -name = "tree-sitter" -version = "0.21.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "705bf7c0958d0171dd7d3a6542f2f4f21d87ed5f1dc8db52919d3a6bed9a359a" -dependencies = [ - "cc", - "regex", -] - -[[package]] -name = "tree-sitter-facade-sg" -version = "0.21.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "906493dd94525dbb0cc38af596432245bede875810a4804dccd72433f450d223" -dependencies = [ - "js-sys", - "tree-sitter 0.21.0", - "wasm-bindgen", - "web-sys", - "web-tree-sitter-sg", -] - -[[package]] -name = "tree-sitter-rust" -version = "0.20.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0832309b0b2b6d33760ce5c0e818cb47e1d72b468516bfe4134408926fa7594" -dependencies = [ - "cc", - "tree-sitter 0.20.10", -] - [[package]] name = "unicode-ident" version = "1.0.9" @@ -542,11 +434,12 @@ dependencies = [ [[package]] name = "wasm-bindgen" -version = "0.2.92" +version = "0.2.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8" +checksum = "a82edfc16a6c469f5f44dc7b571814045d60404b55a0ee849f9bcfa2e63dd9b5" dependencies = [ "cfg-if 1.0.0", + "once_cell", "serde", "serde_json", "wasm-bindgen-macro", @@ -554,9 +447,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.92" +version = "0.2.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da" +checksum = "9de396da306523044d3302746f1208fa71d7532227f15e347e2d93e4145dd77b" dependencies = [ "bumpalo", "log", @@ -569,9 +462,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.42" +version = "0.4.43" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76bc14366121efc8dbb487ab05bcc9d346b3b5ec0eaa76e46594cabbe51762c0" +checksum = "61e9300f63a621e96ed275155c108eb6f843b6a26d053f122ab69724559dc8ed" dependencies = [ "cfg-if 1.0.0", "js-sys", @@ -581,9 +474,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.92" +version = "0.2.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726" +checksum = "585c4c91a46b072c92e908d99cb1dcdf95c5218eeb6f3bf1efa991ee7a68cccf" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -591,9 +484,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.92" +version = "0.2.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" +checksum = "afc340c74d9005395cf9dd098506f7f44e38f2b4a21c6aaacf9a105ea5e1e836" dependencies = [ "proc-macro2", "quote", @@ -604,18 +497,19 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.92" +version = "0.2.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96" +checksum = "c62a0a307cb4a311d3a07867860911ca130c3494e8c2719593806c08bc5d0484" [[package]] name = "wasm-bindgen-test" -version = "0.3.42" +version = "0.3.43" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9bf62a58e0780af3e852044583deee40983e5886da43a271dd772379987667b" +checksum = "68497a05fb21143a08a7d24fc81763384a3072ee43c44e86aad1744d6adef9d9" dependencies = [ "console_error_panic_hook", "js-sys", + "minicov", "scoped-tls", "wasm-bindgen", "wasm-bindgen-futures", @@ -624,9 +518,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-test-macro" -version = "0.3.42" +version = "0.3.43" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7f89739351a2e03cb94beb799d47fb2cac01759b40ec441f7de39b00cbf7ef0" +checksum = "4b8220be1fa9e4c889b30fd207d4906657e7e90b12e0e6b0c8b8d8709f5de021" dependencies = [ "proc-macro2", "quote", @@ -635,9 +529,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.64" +version = "0.3.70" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b85cbef8c220a6abc02aefd892dfc0fc23afb1c6a426316ec33253a3877249b" +checksum = "26fdeaafd9bd129f65e7c031593c24d62186301e0c72c8978fa1678be7d532c0" dependencies = [ "js-sys", "wasm-bindgen", @@ -645,9 +539,9 @@ dependencies = [ [[package]] name = "web-tree-sitter-sg" -version = "0.21.5" +version = "0.25.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65322f84b51c9f73698738e9d76fd3badea7c4ce584e7e19531face4c2b92acc" +checksum = "0c769bd29dd6612783fffb7090d29ed8b390672fb9e968b9e76bbc07bf78600c" dependencies = [ "js-sys", "wasm-bindgen", diff --git a/Cargo.toml b/Cargo.toml index 8009ecbc..eabcb0fd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "ast-grep-wasm" -version = "0.2.6" +version = "0.38.0" authors = ["HerringtonDarkholme <2883231+HerringtonDarkholme@users.noreply.github.com>"] edition = "2018" description = "Search and Rewrite code at large scale using precise AST pattern" @@ -16,8 +16,8 @@ crate-type = ["cdylib", "rlib"] default = [] [dependencies] -wasm-bindgen = {version = "0.2.92", features = ["serde-serialize"]} -wasm-bindgen-futures = "0.4.42" +wasm-bindgen = {version = "=0.2.93", features = ["serde-serialize"]} +wasm-bindgen-futures = "=0.4.43" serde = { version = "1.0", features = ["derive"] } # The `console_error_panic_hook` crate provides better debugging of panics by @@ -28,17 +28,14 @@ console_error_panic_hook = { version = "0.1.7", optional = true } once_cell = "1.19.0" wee_alloc = { version = "0.4.5" } -ast-grep-core = { version = "0.22.3" } -ast-grep-config = { version = "0.22.3" } -ast-grep-language = { version = "0.22.3", default-features = false } -web-tree-sitter-sg = "0.21.5" -tree-sitter = { version = "0.21.5", package = "tree-sitter-facade-sg" } -serde-wasm-bindgen = "0.6" +ast-grep-core = { version = "0.38.3", default-features = false } +ast-grep-config = { version = "0.38.3", default-features = false } +web-tree-sitter-sg = "0.25.3" +serde-wasm-bindgen = "0.6.5" serde_json = "1.0.116" [dev-dependencies] wasm-bindgen-test = "0.3.42" -tree-sitter-rust = "0.20.4" [profile.release] panic = "abort" diff --git a/dprint.json b/dprint.json new file mode 100644 index 00000000..3bd768a2 --- /dev/null +++ b/dprint.json @@ -0,0 +1,45 @@ +{ + "$schema": "https://dprint.dev/schemas/v0.json", + "projectType": "openSource", + "incremental": true, + "typescript": { + "useTabs": false, + "indentWidth": 2, + "lineWidth": 100, + "semiColons": "asi", + "quoteStyle": "preferSingle", + "nextControlFlowPosition": "sameLine", + "operatorPosition": "maintain", + "singleBodyPosition": "maintain", + "trailingCommas": "onlyMultiLine", + "useBraces": "whenNotSingleLine", + "bracePosition": "sameLineUnlessHanging", + "spaceSurroundingProperties": true, + "objectExpression.spaceSurroundingProperties": true, + "importDeclaration.spaceSurroundingNamedImports": true, + "exportDeclaration.spaceSurroundingNamedExports": true + }, + "json": { + "useTabs": false, + "indentWidth": 2, + "lineWidth": 100 + }, + "includes": [ + "**/*.{ts,tsx,js,jsx,vue,json,md,css,scss,sass,less,html,yaml,yml}" + ], + "excludes": [ + "node_modules", + "dist", + "pkg", + "target", + "**/*.d.ts", + "website/public/parsers/*.wasm", + "website/public/parsers/*.mjs", + "pnpm-lock.yaml", + "Cargo.lock" + ], + "plugins": [ + "https://plugins.dprint.dev/typescript-0.93.0.wasm", + "https://plugins.dprint.dev/json-0.19.3.wasm" + ] +} diff --git a/package.json b/package.json index 989d4f33..1681e35b 100644 --- a/package.json +++ b/package.json @@ -7,25 +7,32 @@ "dev": "vitepress dev website", "dev-force": "vitepress dev website --force", "build": "vue-tsc --noEmit && NODE_OPTIONS=--max_old_space_size=4096 vitepress build website", - "serve": "vitepress serve website" + "download-parsers": "node --experimental-strip-types website/public/parsers/downloadParsers.mjs", + "serve": "vitepress serve website", + "lint": "oxlint && dprint check", + "lint:fix": "oxlint --fix && dprint fmt" }, "dependencies": { + "@number-flow/vue": "0.4.8", "@types/js-yaml": "4.0.9", "js-yaml": "4.1.0", - "monaco-editor": "0.48.0", - "monaco-yaml": "5.1.1", - "vue": "3.4.27" + "monaco-editor": "0.52.2", + "monaco-yaml": "5.4.0", + "vitepress-plugin-llms": "1.7.0", + "vue": "3.5.17" }, "optionalDependencies": { "ast-grep-wasm": "file:./pkg/" }, "devDependencies": { - "@algolia/client-search": "4.23.3", + "dprint": "0.50.1", + "@algolia/client-search": "5.29.0", "markdown-it": "14.1.0", - "search-insights": "2.13.0", - "typescript": "5.4.5", - "vite": "5.2.11", - "vitepress": "1.2.0", - "vue-tsc": "2.0.19" + "oxlint": "1.7.0", + "search-insights": "2.17.3", + "typescript": "5.8.3", + "vite": "7.0.2", + "vitepress": "1.6.3", + "vue-tsc": "3.0.1" } } \ No newline at end of file diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 67c77a0d..2bd07968 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -8,6 +8,9 @@ importers: .: dependencies: + '@number-flow/vue': + specifier: 0.4.8 + version: 0.4.8(vue@3.5.17(typescript@5.8.3)) '@types/js-yaml': specifier: 4.0.9 version: 4.0.9 @@ -15,148 +18,169 @@ importers: specifier: 4.1.0 version: 4.1.0 monaco-editor: - specifier: 0.48.0 - version: 0.48.0 + specifier: 0.52.2 + version: 0.52.2 monaco-yaml: - specifier: 5.1.1 - version: 5.1.1(monaco-editor@0.48.0) + specifier: 5.4.0 + version: 5.4.0(monaco-editor@0.52.2) + vitepress-plugin-llms: + specifier: 1.7.0 + version: 1.7.0 vue: - specifier: 3.4.27 - version: 3.4.27(typescript@5.4.5) - optionalDependencies: - ast-grep-wasm: - specifier: file:./pkg/ - version: file:pkg + specifier: 3.5.17 + version: 3.5.17(typescript@5.8.3) devDependencies: '@algolia/client-search': - specifier: 4.23.3 - version: 4.23.3 + specifier: 5.29.0 + version: 5.29.0 + dprint: + specifier: 0.50.1 + version: 0.50.1 markdown-it: specifier: 14.1.0 version: 14.1.0 + oxlint: + specifier: 1.7.0 + version: 1.7.0 search-insights: - specifier: 2.13.0 - version: 2.13.0 + specifier: 2.17.3 + version: 2.17.3 typescript: - specifier: 5.4.5 - version: 5.4.5 + specifier: 5.8.3 + version: 5.8.3 vite: - specifier: 5.2.11 - version: 5.2.11 + specifier: 7.0.2 + version: 7.0.2(lightningcss@1.30.1)(yaml@2.7.1) vitepress: - specifier: 1.2.0 - version: 1.2.0(@algolia/client-search@4.23.3)(postcss@8.4.38)(search-insights@2.13.0)(typescript@5.4.5) + specifier: 1.6.3 + version: 1.6.3(@algolia/client-search@5.29.0)(lightningcss@1.30.1)(postcss@8.5.6)(search-insights@2.17.3)(typescript@5.8.3) vue-tsc: - specifier: 2.0.19 - version: 2.0.19(typescript@5.4.5) + specifier: 3.0.1 + version: 3.0.1(typescript@5.8.3) + optionalDependencies: + ast-grep-wasm: + specifier: file:./pkg/ + version: file:pkg packages: - '@algolia/autocomplete-core@1.9.3': - resolution: {integrity: sha512-009HdfugtGCdC4JdXUbVJClA0q0zh24yyePn+KUGk3rP7j8FEe/m5Yo/z65gn6nP/cM39PxpzqKrL7A6fP6PPw==} + '@algolia/autocomplete-core@1.17.7': + resolution: {integrity: sha512-BjiPOW6ks90UKl7TwMv7oNQMnzU+t/wk9mgIDi6b1tXpUek7MW0lbNOUHpvam9pe3lVCf4xPFT+lK7s+e+fs7Q==} - '@algolia/autocomplete-plugin-algolia-insights@1.9.3': - resolution: {integrity: sha512-a/yTUkcO/Vyy+JffmAnTWbr4/90cLzw+CC3bRbhnULr/EM0fGNvM13oQQ14f2moLMcVDyAx/leczLlAOovhSZg==} + '@algolia/autocomplete-plugin-algolia-insights@1.17.7': + resolution: {integrity: sha512-Jca5Ude6yUOuyzjnz57og7Et3aXjbwCSDf/8onLHSQgw1qW3ALl9mrMWaXb5FmPVkV3EtkD2F/+NkT6VHyPu9A==} peerDependencies: search-insights: '>= 1 < 3' - '@algolia/autocomplete-preset-algolia@1.9.3': - resolution: {integrity: sha512-d4qlt6YmrLMYy95n5TB52wtNDr6EgAIPH81dvvvW8UmuWRgxEtY0NJiPwl/h95JtG2vmRM804M0DSwMCNZlzRA==} + '@algolia/autocomplete-preset-algolia@1.17.7': + resolution: {integrity: sha512-ggOQ950+nwbWROq2MOCIL71RE0DdQZsceqrg32UqnhDz8FlO9rL8ONHNsI2R1MH0tkgVIDKI/D0sMiUchsFdWA==} peerDependencies: '@algolia/client-search': '>= 4.9.1 < 6' algoliasearch: '>= 4.9.1 < 6' - '@algolia/autocomplete-shared@1.9.3': - resolution: {integrity: sha512-Wnm9E4Ye6Rl6sTTqjoymD+l8DjSTHsHboVRYrKgEt8Q7UHm9nYbqhN/i0fhUYA3OAEH7WA8x3jfpnmJm3rKvaQ==} + '@algolia/autocomplete-shared@1.17.7': + resolution: {integrity: sha512-o/1Vurr42U/qskRSuhBH+VKxMvkkUVTLU6WZQr+L5lGZZLYWyhdzWjW0iGXY7EkwRTjBqvN2EsR81yCTGV/kmg==} peerDependencies: '@algolia/client-search': '>= 4.9.1 < 6' algoliasearch: '>= 4.9.1 < 6' - '@algolia/cache-browser-local-storage@4.19.1': - resolution: {integrity: sha512-FYAZWcGsFTTaSAwj9Std8UML3Bu8dyWDncM7Ls8g+58UOe4XYdlgzXWbrIgjaguP63pCCbMoExKr61B+ztK3tw==} + '@algolia/client-abtesting@5.23.4': + resolution: {integrity: sha512-WIMT2Kxy+FFWXWQxIU8QgbTioL+SGE24zhpj0kipG4uQbzXwONaWt7ffaYLjfge3gcGSgJVv+1VlahVckafluQ==} + engines: {node: '>= 14.0.0'} - '@algolia/cache-common@4.19.1': - resolution: {integrity: sha512-XGghi3l0qA38HiqdoUY+wvGyBsGvKZ6U3vTiMBT4hArhP3fOGLXpIINgMiiGjTe4FVlTa5a/7Zf2bwlIHfRqqg==} + '@algolia/client-analytics@5.23.4': + resolution: {integrity: sha512-4B9gChENsQA9kFmFlb+x3YhBz2Gx3vSsm81FHI1yJ3fn2zlxREHmfrjyqYoMunsU7BybT/o5Nb7ccCbm/vfseA==} + engines: {node: '>= 14.0.0'} - '@algolia/cache-common@4.23.3': - resolution: {integrity: sha512-h9XcNI6lxYStaw32pHpB1TMm0RuxphF+Ik4o7tcQiodEdpKK+wKufY6QXtba7t3k8eseirEMVB83uFFF3Nu54A==} + '@algolia/client-common@5.23.4': + resolution: {integrity: sha512-bsj0lwU2ytiWLtl7sPunr+oLe+0YJql9FozJln5BnIiqfKOaseSDdV42060vUy+D4373f2XBI009K/rm2IXYMA==} + engines: {node: '>= 14.0.0'} - '@algolia/cache-in-memory@4.19.1': - resolution: {integrity: sha512-+PDWL+XALGvIginigzu8oU6eWw+o76Z8zHbBovWYcrtWOEtinbl7a7UTt3x3lthv+wNuFr/YD1Gf+B+A9V8n5w==} + '@algolia/client-common@5.29.0': + resolution: {integrity: sha512-T0lzJH/JiCxQYtCcnWy7Jf1w/qjGDXTi2npyF9B9UsTvXB97GRC6icyfXxe21mhYvhQcaB1EQ/J2575FXxi2rA==} + engines: {node: '>= 14.0.0'} - '@algolia/client-account@4.19.1': - resolution: {integrity: sha512-Oy0ritA2k7AMxQ2JwNpfaEcgXEDgeyKu0V7E7xt/ZJRdXfEpZcwp9TOg4TJHC7Ia62gIeT2Y/ynzsxccPw92GA==} + '@algolia/client-insights@5.23.4': + resolution: {integrity: sha512-XSCtAYvJ/hnfDHfRVMbBH0dayR+2ofVZy3jf5qyifjguC6rwxDsSdQvXpT0QFVyG+h8UPGtDhMPoUIng4wIcZA==} + engines: {node: '>= 14.0.0'} - '@algolia/client-analytics@4.19.1': - resolution: {integrity: sha512-5QCq2zmgdZLIQhHqwl55ZvKVpLM3DNWjFI4T+bHr3rGu23ew2bLO4YtyxaZeChmDb85jUdPDouDlCumGfk6wOg==} + '@algolia/client-personalization@5.23.4': + resolution: {integrity: sha512-l/0QvqgRFFOf7BnKSJ3myd1WbDr86ftVaa3PQwlsNh7IpIHmvVcT83Bi5zlORozVGMwaKfyPZo6O48PZELsOeA==} + engines: {node: '>= 14.0.0'} - '@algolia/client-common@4.19.1': - resolution: {integrity: sha512-3kAIVqTcPrjfS389KQvKzliC559x+BDRxtWamVJt8IVp7LGnjq+aVAXg4Xogkur1MUrScTZ59/AaUd5EdpyXgA==} + '@algolia/client-query-suggestions@5.23.4': + resolution: {integrity: sha512-TB0htrDgVacVGtPDyENoM6VIeYqR+pMsDovW94dfi2JoaRxfqu/tYmLpvgWcOknP6wLbr8bA+G7t/NiGksNAwQ==} + engines: {node: '>= 14.0.0'} - '@algolia/client-common@4.23.3': - resolution: {integrity: sha512-l6EiPxdAlg8CYhroqS5ybfIczsGUIAC47slLPOMDeKSVXYG1n0qGiz4RjAHLw2aD0xzh2EXZ7aRguPfz7UKDKw==} + '@algolia/client-search@5.23.4': + resolution: {integrity: sha512-uBGo6KwUP6z+u6HZWRui8UJClS7fgUIAiYd1prUqCbkzDiCngTOzxaJbEvrdkK0hGCQtnPDiuNhC5MhtVNN4Eg==} + engines: {node: '>= 14.0.0'} - '@algolia/client-personalization@4.19.1': - resolution: {integrity: sha512-8CWz4/H5FA+krm9HMw2HUQenizC/DxUtsI5oYC0Jxxyce1vsr8cb1aEiSJArQT6IzMynrERif1RVWLac1m36xw==} + '@algolia/client-search@5.29.0': + resolution: {integrity: sha512-cZ0Iq3OzFUPpgszzDr1G1aJV5UMIZ4VygJ2Az252q4Rdf5cQMhYEIKArWY/oUjMhQmosM8ygOovNq7gvA9CdCg==} + engines: {node: '>= 14.0.0'} - '@algolia/client-search@4.19.1': - resolution: {integrity: sha512-mBecfMFS4N+yK/p0ZbK53vrZbL6OtWMk8YmnOv1i0LXx4pelY8TFhqKoTit3NPVPwoSNN0vdSN9dTu1xr1XOVw==} + '@algolia/ingestion@1.23.4': + resolution: {integrity: sha512-Si6rFuGnSeEUPU9QchYvbknvEIyCRK7nkeaPVQdZpABU7m4V/tsiWdHmjVodtx3h20VZivJdHeQO9XbHxBOcCw==} + engines: {node: '>= 14.0.0'} - '@algolia/client-search@4.23.3': - resolution: {integrity: sha512-P4VAKFHqU0wx9O+q29Q8YVuaowaZ5EM77rxfmGnkHUJggh28useXQdopokgwMeYw2XUht49WX5RcTQ40rZIabw==} + '@algolia/monitoring@1.23.4': + resolution: {integrity: sha512-EXGoVVTshraqPJgr5cMd1fq7Jm71Ew6MpGCEaxI5PErBpJAmKdtjRIzs6JOGKHRaWLi+jdbJPYc2y8RN4qcx5Q==} + engines: {node: '>= 14.0.0'} - '@algolia/logger-common@4.19.1': - resolution: {integrity: sha512-i6pLPZW/+/YXKis8gpmSiNk1lOmYCmRI6+x6d2Qk1OdfvX051nRVdalRbEcVTpSQX6FQAoyeaui0cUfLYW5Elw==} + '@algolia/recommend@5.23.4': + resolution: {integrity: sha512-1t6glwKVCkjvBNlng2itTf8fwaLSqkL4JaMENgR3WTGR8mmW2akocUy/ZYSQcG4TcR7qu4zW2UMGAwLoWoflgQ==} + engines: {node: '>= 14.0.0'} - '@algolia/logger-common@4.23.3': - resolution: {integrity: sha512-y9kBtmJwiZ9ZZ+1Ek66P0M68mHQzKRxkW5kAAXYN/rdzgDN0d2COsViEFufxJ0pb45K4FRcfC7+33YB4BLrZ+g==} + '@algolia/requester-browser-xhr@5.23.4': + resolution: {integrity: sha512-UUuizcgc5+VSY8hqzDFVdJ3Wcto03lpbFRGPgW12pHTlUQHUTADtIpIhkLLOZRCjXmCVhtr97Z+eR6LcRYXa3Q==} + engines: {node: '>= 14.0.0'} - '@algolia/logger-console@4.19.1': - resolution: {integrity: sha512-jj72k9GKb9W0c7TyC3cuZtTr0CngLBLmc8trzZlXdfvQiigpUdvTi1KoWIb2ZMcRBG7Tl8hSb81zEY3zI2RlXg==} + '@algolia/requester-browser-xhr@5.29.0': + resolution: {integrity: sha512-og+7Em75aPHhahEUScq2HQ3J7ULN63Levtd87BYMpn6Im5d5cNhaC4QAUsXu6LWqxRPgh4G+i+wIb6tVhDhg2A==} + engines: {node: '>= 14.0.0'} - '@algolia/requester-browser-xhr@4.19.1': - resolution: {integrity: sha512-09K/+t7lptsweRTueHnSnmPqIxbHMowejAkn9XIcJMLdseS3zl8ObnS5GWea86mu3vy4+8H+ZBKkUN82Zsq/zg==} + '@algolia/requester-fetch@5.23.4': + resolution: {integrity: sha512-UhDg6elsek6NnV5z4VG1qMwR6vbp+rTMBEnl/v4hUyXQazU+CNdYkl++cpdmLwGI/7nXc28xtZiL90Es3I7viQ==} + engines: {node: '>= 14.0.0'} - '@algolia/requester-common@4.19.1': - resolution: {integrity: sha512-BisRkcWVxrDzF1YPhAckmi2CFYK+jdMT60q10d7z3PX+w6fPPukxHRnZwooiTUrzFe50UBmLItGizWHP5bDzVQ==} + '@algolia/requester-fetch@5.29.0': + resolution: {integrity: sha512-JCxapz7neAy8hT/nQpCvOrI5JO8VyQ1kPvBiaXWNC1prVq0UMYHEL52o1BsPvtXfdQ7BVq19OIq6TjOI06mV/w==} + engines: {node: '>= 14.0.0'} - '@algolia/requester-common@4.23.3': - resolution: {integrity: sha512-xloIdr/bedtYEGcXCiF2muajyvRhwop4cMZo+K2qzNht0CMzlRkm8YsDdj5IaBhshqfgmBb3rTg4sL4/PpvLYw==} + '@algolia/requester-node-http@5.23.4': + resolution: {integrity: sha512-jXGzGBRUS0oywQwnaCA6mMDJO7LoC3dYSLsyNfIqxDR4SNGLhtg3je0Y31lc24OA4nYyKAYgVLtjfrpcpsWShg==} + engines: {node: '>= 14.0.0'} - '@algolia/requester-node-http@4.19.1': - resolution: {integrity: sha512-6DK52DHviBHTG2BK/Vv2GIlEw7i+vxm7ypZW0Z7vybGCNDeWzADx+/TmxjkES2h15+FZOqVf/Ja677gePsVItA==} + '@algolia/requester-node-http@5.29.0': + resolution: {integrity: sha512-lVBD81RBW5VTdEYgnzCz7Pf9j2H44aymCP+/eHGJu4vhU+1O8aKf3TVBgbQr5UM6xoe8IkR/B112XY6YIG2vtg==} + engines: {node: '>= 14.0.0'} - '@algolia/transporter@4.19.1': - resolution: {integrity: sha512-nkpvPWbpuzxo1flEYqNIbGz7xhfhGOKGAZS7tzC+TELgEmi7z99qRyTfNSUlW7LZmB3ACdnqAo+9A9KFBENviQ==} - - '@algolia/transporter@4.23.3': - resolution: {integrity: sha512-Wjl5gttqnf/gQKJA+dafnD0Y6Yw97yvfY8R9h0dQltX1GXTgNs1zWgvtWW0tHl1EgMdhAyw189uWiZMnL3QebQ==} - - '@babel/helper-string-parser@7.19.4': - resolution: {integrity: sha512-nHtDoQcuqFmwYNYPz3Rah5ph2p8PFeFCsZk9A/48dPc/rGocJ5J3hAAZ7pb76VWX3fZKu+uEr/FhH5jLx7umrw==} + '@babel/helper-string-parser@7.27.1': + resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==} engines: {node: '>=6.9.0'} - '@babel/helper-validator-identifier@7.19.1': - resolution: {integrity: sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==} + '@babel/helper-validator-identifier@7.27.1': + resolution: {integrity: sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==} engines: {node: '>=6.9.0'} - '@babel/parser@7.24.4': - resolution: {integrity: sha512-zTvEBcghmeBma9QIGunWevvBAp4/Qu9Bdq+2k0Ot4fVMD6v3dsC9WOcRSKk7tRRyBM/53yKMJko9xOatGQAwSg==} + '@babel/parser@7.27.5': + resolution: {integrity: sha512-OsQd175SxWkGlzbny8J3K8TnnDD0N3lrIUtB92xwyRpzaenGZhxDvxN/JgU00U3CDZNj9tPuDJ5H0WS4Nt3vKg==} engines: {node: '>=6.0.0'} hasBin: true - '@babel/types@7.20.7': - resolution: {integrity: sha512-69OnhBxSSgK0OzTJai4kyPDiKTIe3j+ctaHdIGVbRahTLAT7L3R9oeXHC2aVSuGYt3cVnoAMDmOCgJ2yaiLMvg==} + '@babel/types@7.27.6': + resolution: {integrity: sha512-ETyHEk2VHHvl9b9jZP5IHPavHYk57EhanlRRuae9XCpb/j5bDCbPPMOBfCWhnl/7EDJz0jEMCi/RhccCE8r1+Q==} engines: {node: '>=6.9.0'} - '@docsearch/css@3.6.0': - resolution: {integrity: sha512-+sbxb71sWre+PwDK7X2T8+bhS6clcVMLwBPznX45Qu6opJcgRjAp7gYSDzVFp187J+feSj5dNBN1mJoi6ckkUQ==} + '@docsearch/css@3.8.2': + resolution: {integrity: sha512-y05ayQFyUmCXze79+56v/4HpycYF3uFqB78pLPrSV5ZKAlDuIAAJNhaRi8tTdRNXh05yxX/TyNnzD6LwSM89vQ==} - '@docsearch/js@3.6.0': - resolution: {integrity: sha512-QujhqINEElrkIfKwyyyTfbsfMAYCkylInLYMRqHy7PHc8xTBQCow73tlo/Kc7oIwBrCLf0P3YhjlOeV4v8hevQ==} + '@docsearch/js@3.8.2': + resolution: {integrity: sha512-Q5wY66qHn0SwA7Taa0aDbHiJvaFJLOJyHmooQ7y8hlwwQLQ/5WwCcoX0g7ii04Qi2DJlHsd0XXzJ8Ypw9+9YmQ==} - '@docsearch/react@3.6.0': - resolution: {integrity: sha512-HUFut4ztcVNmqy9gp/wxNbC7pTOHhgVVkHVGCACTuLhUKUhKAF9KYHJtMiLUJxEqiFLQiuri1fWF8zqwM/cu1w==} + '@docsearch/react@3.8.2': + resolution: {integrity: sha512-xCRrJQlTt8N9GU0DG4ptwHRkfnSnD/YpdeaXe02iKfqs97TkZJv60yE+1eq/tjPcVnTW8dP5qLP7itifFVV5eg==} peerDependencies: '@types/react': '>= 16.8.0 < 19.0.0' react: '>= 16.8.0 < 19.0.0' @@ -172,321 +196,647 @@ packages: search-insights: optional: true - '@esbuild/aix-ppc64@0.20.2': - resolution: {integrity: sha512-D+EBOJHXdNZcLJRBkhENNG8Wji2kgc9AZ9KiPr1JuZjsNtyHzrsfLRrY0tk2H2aoFu6RANO1y1iPPUCDYWkb5g==} + '@dprint/darwin-arm64@0.50.1': + resolution: {integrity: sha512-NNKf3dxXn567pd/hpCVLHLbC0dI7s3YvQnUEwjRTOAQVMp6O7/ME+Tg1RPGsDP1IB+Y2fIYSM4qmG02zQrqjAQ==} + cpu: [arm64] + os: [darwin] + + '@dprint/darwin-x64@0.50.1': + resolution: {integrity: sha512-PcY75U3UC/0CLOxWzE0zZJZ2PxzUM5AX2baYL1ovgDGCfqO1H0hINiyxfx/8ncGgPojWBkLs+zrcFiGnXx7BQg==} + cpu: [x64] + os: [darwin] + + '@dprint/linux-arm64-glibc@0.50.1': + resolution: {integrity: sha512-q0TOGy9FsoSKsEQ4sIMKyFweF5M8rW1S5OfwJDNRR2TU2riWByU9TKYIZUzg53iuwYKRypr/kJ5kdbl516afRQ==} + cpu: [arm64] + os: [linux] + + '@dprint/linux-arm64-musl@0.50.1': + resolution: {integrity: sha512-XRtxN2cA9rc06WFzzVPDIZYGGLmUXqpVf3F0XhhHV77ikQLJZ5reF4xBOQ+0HjJ/zy8W/HzuGDAHedWyCrRf9g==} + cpu: [arm64] + os: [linux] + + '@dprint/linux-riscv64-glibc@0.50.1': + resolution: {integrity: sha512-vAk/eYhSjA3LJ/yuYgxkHamiK8+m6YdqVBO/Ka+i16VxyjQyOdcMKBkrLCIqSxgyXd6b8raf9wM59HJbaIpoOg==} + cpu: [riscv64] + os: [linux] + + '@dprint/linux-x64-glibc@0.50.1': + resolution: {integrity: sha512-EpW5KLekaq4hXmKBWWtfBgZ244S4C+vFmMOd1YaGi8+f0hmPTJzVWLdIgpO2ZwfPQ5iycaVI/JS514PQmXPOvg==} + cpu: [x64] + os: [linux] + + '@dprint/linux-x64-musl@0.50.1': + resolution: {integrity: sha512-assISBbaKKL8LkjrIy/5tpE157MVW6HbyIKAjTtg3tPNM3lDn1oH3twuGtK9WBsN/VoEP3QMZVauolcUJT/VOg==} + cpu: [x64] + os: [linux] + + '@dprint/win32-arm64@0.50.1': + resolution: {integrity: sha512-ZeaRMQYoFjrsO3lvI1SqzDWDGH1GGXWmNSeXvcFuAf2OgYQJWMBlLotCKiHNJ3uyYneoyhTg2tv9QkApNkZV4Q==} + cpu: [arm64] + os: [win32] + + '@dprint/win32-x64@0.50.1': + resolution: {integrity: sha512-pMm8l/hRZ9zYylKw/yCaYkSV3btYB9UyMDbWqyxNthkQ1gckWrk17VTI6WjwwQuHD4Iaz5JgAYLS36hlUzWkxA==} + cpu: [x64] + os: [win32] + + '@esbuild/aix-ppc64@0.21.5': + resolution: {integrity: sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==} engines: {node: '>=12'} cpu: [ppc64] os: [aix] - '@esbuild/android-arm64@0.20.2': - resolution: {integrity: sha512-mRzjLacRtl/tWU0SvD8lUEwb61yP9cqQo6noDZP/O8VkwafSYwZ4yWy24kan8jE/IMERpYncRt2dw438LP3Xmg==} + '@esbuild/aix-ppc64@0.25.2': + resolution: {integrity: sha512-wCIboOL2yXZym2cgm6mlA742s9QeJ8DjGVaL39dLN4rRwrOgOyYSnOaFPhKZGLb2ngj4EyfAFjsNJwPXZvseag==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + + '@esbuild/android-arm64@0.21.5': + resolution: {integrity: sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==} engines: {node: '>=12'} cpu: [arm64] os: [android] - '@esbuild/android-arm@0.20.2': - resolution: {integrity: sha512-t98Ra6pw2VaDhqNWO2Oph2LXbz/EJcnLmKLGBJwEwXX/JAN83Fym1rU8l0JUWK6HkIbWONCSSatf4sf2NBRx/w==} + '@esbuild/android-arm64@0.25.2': + resolution: {integrity: sha512-5ZAX5xOmTligeBaeNEPnPaeEuah53Id2tX4c2CVP3JaROTH+j4fnfHCkr1PjXMd78hMst+TlkfKcW/DlTq0i4w==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm@0.21.5': + resolution: {integrity: sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==} engines: {node: '>=12'} cpu: [arm] os: [android] - '@esbuild/android-x64@0.20.2': - resolution: {integrity: sha512-btzExgV+/lMGDDa194CcUQm53ncxzeBrWJcncOBxuC6ndBkKxnHdFJn86mCIgTELsooUmwUm9FkhSp5HYu00Rg==} + '@esbuild/android-arm@0.25.2': + resolution: {integrity: sha512-NQhH7jFstVY5x8CKbcfa166GoV0EFkaPkCKBQkdPJFvo5u+nGXLEH/ooniLb3QI8Fk58YAx7nsPLozUWfCBOJA==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + + '@esbuild/android-x64@0.21.5': + resolution: {integrity: sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==} engines: {node: '>=12'} cpu: [x64] os: [android] - '@esbuild/darwin-arm64@0.20.2': - resolution: {integrity: sha512-4J6IRT+10J3aJH3l1yzEg9y3wkTDgDk7TSDFX+wKFiWjqWp/iCfLIYzGyasx9l0SAFPT1HwSCR+0w/h1ES/MjA==} + '@esbuild/android-x64@0.25.2': + resolution: {integrity: sha512-Ffcx+nnma8Sge4jzddPHCZVRvIfQ0kMsUsCMcJRHkGJ1cDmhe4SsrYIjLUKn1xpHZybmOqCWwB0zQvsjdEHtkg==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + + '@esbuild/darwin-arm64@0.21.5': + resolution: {integrity: sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==} engines: {node: '>=12'} cpu: [arm64] os: [darwin] - '@esbuild/darwin-x64@0.20.2': - resolution: {integrity: sha512-tBcXp9KNphnNH0dfhv8KYkZhjc+H3XBkF5DKtswJblV7KlT9EI2+jeA8DgBjp908WEuYll6pF+UStUCfEpdysA==} + '@esbuild/darwin-arm64@0.25.2': + resolution: {integrity: sha512-MpM6LUVTXAzOvN4KbjzU/q5smzryuoNjlriAIx+06RpecwCkL9JpenNzpKd2YMzLJFOdPqBpuub6eVRP5IgiSA==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-x64@0.21.5': + resolution: {integrity: sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==} engines: {node: '>=12'} cpu: [x64] os: [darwin] - '@esbuild/freebsd-arm64@0.20.2': - resolution: {integrity: sha512-d3qI41G4SuLiCGCFGUrKsSeTXyWG6yem1KcGZVS+3FYlYhtNoNgYrWcvkOoaqMhwXSMrZRl69ArHsGJ9mYdbbw==} + '@esbuild/darwin-x64@0.25.2': + resolution: {integrity: sha512-5eRPrTX7wFyuWe8FqEFPG2cU0+butQQVNcT4sVipqjLYQjjh8a8+vUTfgBKM88ObB85ahsnTwF7PSIt6PG+QkA==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + + '@esbuild/freebsd-arm64@0.21.5': + resolution: {integrity: sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==} engines: {node: '>=12'} cpu: [arm64] os: [freebsd] - '@esbuild/freebsd-x64@0.20.2': - resolution: {integrity: sha512-d+DipyvHRuqEeM5zDivKV1KuXn9WeRX6vqSqIDgwIfPQtwMP4jaDsQsDncjTDDsExT4lR/91OLjRo8bmC1e+Cw==} + '@esbuild/freebsd-arm64@0.25.2': + resolution: {integrity: sha512-mLwm4vXKiQ2UTSX4+ImyiPdiHjiZhIaE9QvC7sw0tZ6HoNMjYAqQpGyui5VRIi5sGd+uWq940gdCbY3VLvsO1w==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.21.5': + resolution: {integrity: sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==} engines: {node: '>=12'} cpu: [x64] os: [freebsd] - '@esbuild/linux-arm64@0.20.2': - resolution: {integrity: sha512-9pb6rBjGvTFNira2FLIWqDk/uaf42sSyLE8j1rnUpuzsODBq7FvpwHYZxQ/It/8b+QOS1RYfqgGFNLRI+qlq2A==} + '@esbuild/freebsd-x64@0.25.2': + resolution: {integrity: sha512-6qyyn6TjayJSwGpm8J9QYYGQcRgc90nmfdUb0O7pp1s4lTY+9D0H9O02v5JqGApUyiHOtkz6+1hZNvNtEhbwRQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + + '@esbuild/linux-arm64@0.21.5': + resolution: {integrity: sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==} engines: {node: '>=12'} cpu: [arm64] os: [linux] - '@esbuild/linux-arm@0.20.2': - resolution: {integrity: sha512-VhLPeR8HTMPccbuWWcEUD1Az68TqaTYyj6nfE4QByZIQEQVWBB8vup8PpR7y1QHL3CpcF6xd5WVBU/+SBEvGTg==} + '@esbuild/linux-arm64@0.25.2': + resolution: {integrity: sha512-gq/sjLsOyMT19I8obBISvhoYiZIAaGF8JpeXu1u8yPv8BE5HlWYobmlsfijFIZ9hIVGYkbdFhEqC0NvM4kNO0g==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm@0.21.5': + resolution: {integrity: sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==} engines: {node: '>=12'} cpu: [arm] os: [linux] - '@esbuild/linux-ia32@0.20.2': - resolution: {integrity: sha512-o10utieEkNPFDZFQm9CoP7Tvb33UutoJqg3qKf1PWVeeJhJw0Q347PxMvBgVVFgouYLGIhFYG0UGdBumROyiig==} + '@esbuild/linux-arm@0.25.2': + resolution: {integrity: sha512-UHBRgJcmjJv5oeQF8EpTRZs/1knq6loLxTsjc3nxO9eXAPDLcWW55flrMVc97qFPbmZP31ta1AZVUKQzKTzb0g==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-ia32@0.21.5': + resolution: {integrity: sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==} engines: {node: '>=12'} cpu: [ia32] os: [linux] - '@esbuild/linux-loong64@0.20.2': - resolution: {integrity: sha512-PR7sp6R/UC4CFVomVINKJ80pMFlfDfMQMYynX7t1tNTeivQ6XdX5r2XovMmha/VjR1YN/HgHWsVcTRIMkymrgQ==} + '@esbuild/linux-ia32@0.25.2': + resolution: {integrity: sha512-bBYCv9obgW2cBP+2ZWfjYTU+f5cxRoGGQ5SeDbYdFCAZpYWrfjjfYwvUpP8MlKbP0nwZ5gyOU/0aUzZ5HWPuvQ==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-loong64@0.21.5': + resolution: {integrity: sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==} engines: {node: '>=12'} cpu: [loong64] os: [linux] - '@esbuild/linux-mips64el@0.20.2': - resolution: {integrity: sha512-4BlTqeutE/KnOiTG5Y6Sb/Hw6hsBOZapOVF6njAESHInhlQAghVVZL1ZpIctBOoTFbQyGW+LsVYZ8lSSB3wkjA==} + '@esbuild/linux-loong64@0.25.2': + resolution: {integrity: sha512-SHNGiKtvnU2dBlM5D8CXRFdd+6etgZ9dXfaPCeJtz+37PIUlixvlIhI23L5khKXs3DIzAn9V8v+qb1TRKrgT5w==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-mips64el@0.21.5': + resolution: {integrity: sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==} engines: {node: '>=12'} cpu: [mips64el] os: [linux] - '@esbuild/linux-ppc64@0.20.2': - resolution: {integrity: sha512-rD3KsaDprDcfajSKdn25ooz5J5/fWBylaaXkuotBDGnMnDP1Uv5DLAN/45qfnf3JDYyJv/ytGHQaziHUdyzaAg==} + '@esbuild/linux-mips64el@0.25.2': + resolution: {integrity: sha512-hDDRlzE6rPeoj+5fsADqdUZl1OzqDYow4TB4Y/3PlKBD0ph1e6uPHzIQcv2Z65u2K0kpeByIyAjCmjn1hJgG0Q==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-ppc64@0.21.5': + resolution: {integrity: sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==} engines: {node: '>=12'} cpu: [ppc64] os: [linux] - '@esbuild/linux-riscv64@0.20.2': - resolution: {integrity: sha512-snwmBKacKmwTMmhLlz/3aH1Q9T8v45bKYGE3j26TsaOVtjIag4wLfWSiZykXzXuE1kbCE+zJRmwp+ZbIHinnVg==} + '@esbuild/linux-ppc64@0.25.2': + resolution: {integrity: sha512-tsHu2RRSWzipmUi9UBDEzc0nLc4HtpZEI5Ba+Omms5456x5WaNuiG3u7xh5AO6sipnJ9r4cRWQB2tUjPyIkc6g==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-riscv64@0.21.5': + resolution: {integrity: sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==} engines: {node: '>=12'} cpu: [riscv64] os: [linux] - '@esbuild/linux-s390x@0.20.2': - resolution: {integrity: sha512-wcWISOobRWNm3cezm5HOZcYz1sKoHLd8VL1dl309DiixxVFoFe/o8HnwuIwn6sXre88Nwj+VwZUvJf4AFxkyrQ==} + '@esbuild/linux-riscv64@0.25.2': + resolution: {integrity: sha512-k4LtpgV7NJQOml/10uPU0s4SAXGnowi5qBSjaLWMojNCUICNu7TshqHLAEbkBdAszL5TabfvQ48kK84hyFzjnw==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-s390x@0.21.5': + resolution: {integrity: sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==} engines: {node: '>=12'} cpu: [s390x] os: [linux] - '@esbuild/linux-x64@0.20.2': - resolution: {integrity: sha512-1MdwI6OOTsfQfek8sLwgyjOXAu+wKhLEoaOLTjbijk6E2WONYpH9ZU2mNtR+lZ2B4uwr+usqGuVfFT9tMtGvGw==} + '@esbuild/linux-s390x@0.25.2': + resolution: {integrity: sha512-GRa4IshOdvKY7M/rDpRR3gkiTNp34M0eLTaC1a08gNrh4u488aPhuZOCpkF6+2wl3zAN7L7XIpOFBhnaE3/Q8Q==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-x64@0.21.5': + resolution: {integrity: sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==} engines: {node: '>=12'} cpu: [x64] os: [linux] - '@esbuild/netbsd-x64@0.20.2': - resolution: {integrity: sha512-K8/DhBxcVQkzYc43yJXDSyjlFeHQJBiowJ0uVL6Tor3jGQfSGHNNJcWxNbOI8v5k82prYqzPuwkzHt3J1T1iZQ==} + '@esbuild/linux-x64@0.25.2': + resolution: {integrity: sha512-QInHERlqpTTZ4FRB0fROQWXcYRD64lAoiegezDunLpalZMjcUcld3YzZmVJ2H/Cp0wJRZ8Xtjtj0cEHhYc/uUg==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + + '@esbuild/netbsd-arm64@0.25.2': + resolution: {integrity: sha512-talAIBoY5M8vHc6EeI2WW9d/CkiO9MQJ0IOWX8hrLhxGbro/vBXJvaQXefW2cP0z0nQVTdQ/eNyGFV1GSKrxfw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + + '@esbuild/netbsd-x64@0.21.5': + resolution: {integrity: sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==} engines: {node: '>=12'} cpu: [x64] os: [netbsd] - '@esbuild/openbsd-x64@0.20.2': - resolution: {integrity: sha512-eMpKlV0SThJmmJgiVyN9jTPJ2VBPquf6Kt/nAoo6DgHAoN57K15ZghiHaMvqjCye/uU4X5u3YSMgVBI1h3vKrQ==} + '@esbuild/netbsd-x64@0.25.2': + resolution: {integrity: sha512-voZT9Z+tpOxrvfKFyfDYPc4DO4rk06qamv1a/fkuzHpiVBMOhpjK+vBmWM8J1eiB3OLSMFYNaOaBNLXGChf5tg==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + + '@esbuild/openbsd-arm64@0.25.2': + resolution: {integrity: sha512-dcXYOC6NXOqcykeDlwId9kB6OkPUxOEqU+rkrYVqJbK2hagWOMrsTGsMr8+rW02M+d5Op5NNlgMmjzecaRf7Tg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + + '@esbuild/openbsd-x64@0.21.5': + resolution: {integrity: sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==} engines: {node: '>=12'} cpu: [x64] os: [openbsd] - '@esbuild/sunos-x64@0.20.2': - resolution: {integrity: sha512-2UyFtRC6cXLyejf/YEld4Hajo7UHILetzE1vsRcGL3earZEW77JxrFjH4Ez2qaTiEfMgAXxfAZCm1fvM/G/o8w==} + '@esbuild/openbsd-x64@0.25.2': + resolution: {integrity: sha512-t/TkWwahkH0Tsgoq1Ju7QfgGhArkGLkF1uYz8nQS/PPFlXbP5YgRpqQR3ARRiC2iXoLTWFxc6DJMSK10dVXluw==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + + '@esbuild/sunos-x64@0.21.5': + resolution: {integrity: sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==} engines: {node: '>=12'} cpu: [x64] os: [sunos] - '@esbuild/win32-arm64@0.20.2': - resolution: {integrity: sha512-GRibxoawM9ZCnDxnP3usoUDO9vUkpAxIIZ6GQI+IlVmr5kP3zUq+l17xELTHMWTWzjxa2guPNyrpq1GWmPvcGQ==} + '@esbuild/sunos-x64@0.25.2': + resolution: {integrity: sha512-cfZH1co2+imVdWCjd+D1gf9NjkchVhhdpgb1q5y6Hcv9TP6Zi9ZG/beI3ig8TvwT9lH9dlxLq5MQBBgwuj4xvA==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + + '@esbuild/win32-arm64@0.21.5': + resolution: {integrity: sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==} engines: {node: '>=12'} cpu: [arm64] os: [win32] - '@esbuild/win32-ia32@0.20.2': - resolution: {integrity: sha512-HfLOfn9YWmkSKRQqovpnITazdtquEW8/SoHW7pWpuEeguaZI4QnCRW6b+oZTztdBnZOS2hqJ6im/D5cPzBTTlQ==} + '@esbuild/win32-arm64@0.25.2': + resolution: {integrity: sha512-7Loyjh+D/Nx/sOTzV8vfbB3GJuHdOQyrOryFdZvPHLf42Tk9ivBU5Aedi7iyX+x6rbn2Mh68T4qq1SDqJBQO5Q==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-ia32@0.21.5': + resolution: {integrity: sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==} engines: {node: '>=12'} cpu: [ia32] os: [win32] - '@esbuild/win32-x64@0.20.2': - resolution: {integrity: sha512-N49X4lJX27+l9jbLKSqZ6bKNjzQvHaT8IIFUy+YIqmXQdjYCToGWwOItDrfby14c78aDd5NHQl29xingXfCdLQ==} + '@esbuild/win32-ia32@0.25.2': + resolution: {integrity: sha512-WRJgsz9un0nqZJ4MfhabxaD9Ft8KioqU3JMinOTvobbX6MOSUigSBlogP8QB3uxpJDsFS6yN+3FDBdqE5lg9kg==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-x64@0.21.5': + resolution: {integrity: sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==} engines: {node: '>=12'} cpu: [x64] os: [win32] - '@jridgewell/sourcemap-codec@1.4.15': - resolution: {integrity: sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==} + '@esbuild/win32-x64@0.25.2': + resolution: {integrity: sha512-kM3HKb16VIXZyIeVrM1ygYmZBKybX8N4p754bw390wGO3Tf2j4L2/WYL+4suWujpgf6GBYs3jv7TyUivdd05JA==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + + '@iconify-json/simple-icons@1.2.32': + resolution: {integrity: sha512-gxgLq0raip7SJaeJ0302vwhsqupQttS21B93Ci1kA/++B+hIgGw71HzTOWQoUhwjlrdWcoVUxSvpPJoMs7oURg==} + + '@iconify/types@2.0.0': + resolution: {integrity: sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==} + + '@isaacs/balanced-match@4.0.1': + resolution: {integrity: sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ==} + engines: {node: 20 || >=22} + + '@isaacs/brace-expansion@5.0.0': + resolution: {integrity: sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA==} + engines: {node: 20 || >=22} + + '@jridgewell/sourcemap-codec@1.5.0': + resolution: {integrity: sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==} + + '@number-flow/vue@0.4.8': + resolution: {integrity: sha512-g1kh66wndJ4MYkX5Z00GvyLxdOMbRotiTeTQujzb1XS009dL/mVH0ZRI5slXH1mzpl78yzzsjz3vjzGhTMM3CA==} + peerDependencies: + vue: ^3 + + '@oxlint/darwin-arm64@1.7.0': + resolution: {integrity: sha512-51vhCSQO4NSkedwEwOyqThiYqV0DAUkwNdqMQK0d29j5zmtNJJJRRBLeQuLGdstNmn3F7WMQ75Ci0/3Nq4ff8A==} + cpu: [arm64] + os: [darwin] + + '@oxlint/darwin-x64@1.7.0': + resolution: {integrity: sha512-c0GN52yehYZ4TYuh4lBH9wYbBOI/RDOxZhJdBsttG0GwfvKYg/tiPNrNEsPzu0/rd1j6x3yT0zt6vezDMeC1sQ==} + cpu: [x64] + os: [darwin] + + '@oxlint/linux-arm64-gnu@1.7.0': + resolution: {integrity: sha512-pam/lbzbzVMDzc3f1hoRPtnUMEIqkn0dynlB5nUll/MVBSIvIPLS9kJLrRA48lrlqbkS9LGiF37JvpwXA58A9A==} + cpu: [arm64] + os: [linux] + + '@oxlint/linux-arm64-musl@1.7.0': + resolution: {integrity: sha512-LTyPy9FYS3SZ2XxJx+ITvlAq/ek5PtZK9Z2m3W72TA8hchGhJy5eQ+aotYjd/YVXOpGRpB12RdOpOTsZRu50bA==} + cpu: [arm64] + os: [linux] + + '@oxlint/linux-x64-gnu@1.7.0': + resolution: {integrity: sha512-YtZ4DiAgjaEiqUiwnvtJ/znZMAAVPKR7pnsi6lqbA3BfXJ/IwMaNpdoGlCGVdDGeN4BuGCwnFtBVqKVvVg3DDg==} + cpu: [x64] + os: [linux] + + '@oxlint/linux-x64-musl@1.7.0': + resolution: {integrity: sha512-5aIpemNUBvwMMk4MCx1V3M6R9eMB1/SS6/24Orax9FqaI1lDX08tySdv696sr4Lms9ocA+rotxIPW9NP9439vA==} + cpu: [x64] + os: [linux] + + '@oxlint/win32-arm64@1.7.0': + resolution: {integrity: sha512-fpFpkHwbAu0NcR5bc1WapCPcM9qSYi5lCRVOp1WwDoFLKI2b9/UWB8OEg8UHWV5dnBu7HZAWH/SEslYGkZNsbQ==} + cpu: [arm64] + os: [win32] + + '@oxlint/win32-x64@1.7.0': + resolution: {integrity: sha512-0EPWBWOiD3wZHgeWDlTUaiFzhzIonXykxYUC+NRerPQFkO/G+bd9uLMJddHDKqfP/7g8s3E5V6KvBvvFpb7U6g==} + cpu: [x64] + os: [win32] - '@rollup/rollup-android-arm-eabi@4.13.0': - resolution: {integrity: sha512-5ZYPOuaAqEH/W3gYsRkxQATBW3Ii1MfaT4EQstTnLKViLi2gLSQmlmtTpGucNP3sXEpOiI5tdGhjdE111ekyEg==} + '@rollup/rollup-android-arm-eabi@4.41.1': + resolution: {integrity: sha512-NELNvyEWZ6R9QMkiytB4/L4zSEaBC03KIXEghptLGLZWJ6VPrL63ooZQCOnlx36aQPGhzuOMwDerC1Eb2VmrLw==} cpu: [arm] os: [android] - '@rollup/rollup-android-arm64@4.13.0': - resolution: {integrity: sha512-BSbaCmn8ZadK3UAQdlauSvtaJjhlDEjS5hEVVIN3A4bbl3X+otyf/kOJV08bYiRxfejP3DXFzO2jz3G20107+Q==} + '@rollup/rollup-android-arm64@4.41.1': + resolution: {integrity: sha512-DXdQe1BJ6TK47ukAoZLehRHhfKnKg9BjnQYUu9gzhI8Mwa1d2fzxA1aw2JixHVl403bwp1+/o/NhhHtxWJBgEA==} cpu: [arm64] os: [android] - '@rollup/rollup-darwin-arm64@4.13.0': - resolution: {integrity: sha512-Ovf2evVaP6sW5Ut0GHyUSOqA6tVKfrTHddtmxGQc1CTQa1Cw3/KMCDEEICZBbyppcwnhMwcDce9ZRxdWRpVd6g==} + '@rollup/rollup-darwin-arm64@4.41.1': + resolution: {integrity: sha512-5afxvwszzdulsU2w8JKWwY8/sJOLPzf0e1bFuvcW5h9zsEg+RQAojdW0ux2zyYAz7R8HvvzKCjLNJhVq965U7w==} cpu: [arm64] os: [darwin] - '@rollup/rollup-darwin-x64@4.13.0': - resolution: {integrity: sha512-U+Jcxm89UTK592vZ2J9st9ajRv/hrwHdnvyuJpa5A2ngGSVHypigidkQJP+YiGL6JODiUeMzkqQzbCG3At81Gg==} + '@rollup/rollup-darwin-x64@4.41.1': + resolution: {integrity: sha512-egpJACny8QOdHNNMZKf8xY0Is6gIMz+tuqXlusxquWu3F833DcMwmGM7WlvCO9sB3OsPjdC4U0wHw5FabzCGZg==} cpu: [x64] os: [darwin] - '@rollup/rollup-linux-arm-gnueabihf@4.13.0': - resolution: {integrity: sha512-8wZidaUJUTIR5T4vRS22VkSMOVooG0F4N+JSwQXWSRiC6yfEsFMLTYRFHvby5mFFuExHa/yAp9juSphQQJAijQ==} + '@rollup/rollup-freebsd-arm64@4.41.1': + resolution: {integrity: sha512-DBVMZH5vbjgRk3r0OzgjS38z+atlupJ7xfKIDJdZZL6sM6wjfDNo64aowcLPKIx7LMQi8vybB56uh1Ftck/Atg==} + cpu: [arm64] + os: [freebsd] + + '@rollup/rollup-freebsd-x64@4.41.1': + resolution: {integrity: sha512-3FkydeohozEskBxNWEIbPfOE0aqQgB6ttTkJ159uWOFn42VLyfAiyD9UK5mhu+ItWzft60DycIN1Xdgiy8o/SA==} + cpu: [x64] + os: [freebsd] + + '@rollup/rollup-linux-arm-gnueabihf@4.41.1': + resolution: {integrity: sha512-wC53ZNDgt0pqx5xCAgNunkTzFE8GTgdZ9EwYGVcg+jEjJdZGtq9xPjDnFgfFozQI/Xm1mh+D9YlYtl+ueswNEg==} cpu: [arm] os: [linux] - '@rollup/rollup-linux-arm64-gnu@4.13.0': - resolution: {integrity: sha512-Iu0Kno1vrD7zHQDxOmvweqLkAzjxEVqNhUIXBsZ8hu8Oak7/5VTPrxOEZXYC1nmrBVJp0ZcL2E7lSuuOVaE3+w==} + '@rollup/rollup-linux-arm-musleabihf@4.41.1': + resolution: {integrity: sha512-jwKCca1gbZkZLhLRtsrka5N8sFAaxrGz/7wRJ8Wwvq3jug7toO21vWlViihG85ei7uJTpzbXZRcORotE+xyrLA==} + cpu: [arm] + os: [linux] + + '@rollup/rollup-linux-arm64-gnu@4.41.1': + resolution: {integrity: sha512-g0UBcNknsmmNQ8V2d/zD2P7WWfJKU0F1nu0k5pW4rvdb+BIqMm8ToluW/eeRmxCared5dD76lS04uL4UaNgpNA==} cpu: [arm64] os: [linux] - '@rollup/rollup-linux-arm64-musl@4.13.0': - resolution: {integrity: sha512-C31QrW47llgVyrRjIwiOwsHFcaIwmkKi3PCroQY5aVq4H0A5v/vVVAtFsI1nfBngtoRpeREvZOkIhmRwUKkAdw==} + '@rollup/rollup-linux-arm64-musl@4.41.1': + resolution: {integrity: sha512-XZpeGB5TKEZWzIrj7sXr+BEaSgo/ma/kCgrZgL0oo5qdB1JlTzIYQKel/RmhT6vMAvOdM2teYlAaOGJpJ9lahg==} cpu: [arm64] os: [linux] - '@rollup/rollup-linux-riscv64-gnu@4.13.0': - resolution: {integrity: sha512-Oq90dtMHvthFOPMl7pt7KmxzX7E71AfyIhh+cPhLY9oko97Zf2C9tt/XJD4RgxhaGeAraAXDtqxvKE1y/j35lA==} + '@rollup/rollup-linux-loongarch64-gnu@4.41.1': + resolution: {integrity: sha512-bkCfDJ4qzWfFRCNt5RVV4DOw6KEgFTUZi2r2RuYhGWC8WhCA8lCAJhDeAmrM/fdiAH54m0mA0Vk2FGRPyzI+tw==} + cpu: [loong64] + os: [linux] + + '@rollup/rollup-linux-powerpc64le-gnu@4.41.1': + resolution: {integrity: sha512-3mr3Xm+gvMX+/8EKogIZSIEF0WUu0HL9di+YWlJpO8CQBnoLAEL/roTCxuLncEdgcfJcvA4UMOf+2dnjl4Ut1A==} + cpu: [ppc64] + os: [linux] + + '@rollup/rollup-linux-riscv64-gnu@4.41.1': + resolution: {integrity: sha512-3rwCIh6MQ1LGrvKJitQjZFuQnT2wxfU+ivhNBzmxXTXPllewOF7JR1s2vMX/tWtUYFgphygxjqMl76q4aMotGw==} + cpu: [riscv64] + os: [linux] + + '@rollup/rollup-linux-riscv64-musl@4.41.1': + resolution: {integrity: sha512-LdIUOb3gvfmpkgFZuccNa2uYiqtgZAz3PTzjuM5bH3nvuy9ty6RGc/Q0+HDFrHrizJGVpjnTZ1yS5TNNjFlklw==} cpu: [riscv64] os: [linux] - '@rollup/rollup-linux-x64-gnu@4.13.0': - resolution: {integrity: sha512-yUD/8wMffnTKuiIsl6xU+4IA8UNhQ/f1sAnQebmE/lyQ8abjsVyDkyRkWop0kdMhKMprpNIhPmYlCxgHrPoXoA==} + '@rollup/rollup-linux-s390x-gnu@4.41.1': + resolution: {integrity: sha512-oIE6M8WC9ma6xYqjvPhzZYk6NbobIURvP/lEbh7FWplcMO6gn7MM2yHKA1eC/GvYwzNKK/1LYgqzdkZ8YFxR8g==} + cpu: [s390x] + os: [linux] + + '@rollup/rollup-linux-x64-gnu@4.41.1': + resolution: {integrity: sha512-cWBOvayNvA+SyeQMp79BHPK8ws6sHSsYnK5zDcsC3Hsxr1dgTABKjMnMslPq1DvZIp6uO7kIWhiGwaTdR4Og9A==} cpu: [x64] os: [linux] - '@rollup/rollup-linux-x64-musl@4.13.0': - resolution: {integrity: sha512-9RyNqoFNdF0vu/qqX63fKotBh43fJQeYC98hCaf89DYQpv+xu0D8QFSOS0biA7cGuqJFOc1bJ+m2rhhsKcw1hw==} + '@rollup/rollup-linux-x64-musl@4.41.1': + resolution: {integrity: sha512-y5CbN44M+pUCdGDlZFzGGBSKCA4A/J2ZH4edTYSSxFg7ce1Xt3GtydbVKWLlzL+INfFIZAEg1ZV6hh9+QQf9YQ==} cpu: [x64] os: [linux] - '@rollup/rollup-win32-arm64-msvc@4.13.0': - resolution: {integrity: sha512-46ue8ymtm/5PUU6pCvjlic0z82qWkxv54GTJZgHrQUuZnVH+tvvSP0LsozIDsCBFO4VjJ13N68wqrKSeScUKdA==} + '@rollup/rollup-win32-arm64-msvc@4.41.1': + resolution: {integrity: sha512-lZkCxIrjlJlMt1dLO/FbpZbzt6J/A8p4DnqzSa4PWqPEUUUnzXLeki/iyPLfV0BmHItlYgHUqJe+3KiyydmiNQ==} cpu: [arm64] os: [win32] - '@rollup/rollup-win32-ia32-msvc@4.13.0': - resolution: {integrity: sha512-P5/MqLdLSlqxbeuJ3YDeX37srC8mCflSyTrUsgbU1c/U9j6l2g2GiIdYaGD9QjdMQPMSgYm7hgg0551wHyIluw==} + '@rollup/rollup-win32-ia32-msvc@4.41.1': + resolution: {integrity: sha512-+psFT9+pIh2iuGsxFYYa/LhS5MFKmuivRsx9iPJWNSGbh2XVEjk90fmpUEjCnILPEPJnikAU6SFDiEUyOv90Pg==} cpu: [ia32] os: [win32] - '@rollup/rollup-win32-x64-msvc@4.13.0': - resolution: {integrity: sha512-UKXUQNbO3DOhzLRwHSpa0HnhhCgNODvfoPWv2FCXme8N/ANFfhIPMGuOT+QuKd16+B5yxZ0HdpNlqPvTMS1qfw==} + '@rollup/rollup-win32-x64-msvc@4.41.1': + resolution: {integrity: sha512-Wq2zpapRYLfi4aKxf2Xff0tN+7slj2d4R87WEzqw7ZLsVvO5zwYCIuEGSZYiK41+GlwUo1HiR+GdkLEJnCKTCw==} cpu: [x64] os: [win32] - '@shikijs/core@1.5.2': - resolution: {integrity: sha512-wSAOgaz48GmhILFElMCeQypSZmj6Ru6DttOOtl3KNkdJ17ApQuGNCfzpk4cClasVrnIu45++2DBwG4LNMQAfaA==} + '@shikijs/core@2.5.0': + resolution: {integrity: sha512-uu/8RExTKtavlpH7XqnVYBrfBkUc20ngXiX9NSrBhOVZYv/7XQRKUyhtkeflY5QsxC0GbJThCerruZfsUaSldg==} + + '@shikijs/engine-javascript@2.5.0': + resolution: {integrity: sha512-VjnOpnQf8WuCEZtNUdjjwGUbtAVKuZkVQ/5cHy/tojVVRIRtlWMYVjyWhxOmIq05AlSOv72z7hRNRGVBgQOl0w==} + + '@shikijs/engine-oniguruma@2.5.0': + resolution: {integrity: sha512-pGd1wRATzbo/uatrCIILlAdFVKdxImWJGQ5rFiB5VZi2ve5xj3Ax9jny8QvkaV93btQEwR/rSz5ERFpC5mKNIw==} + + '@shikijs/langs@2.5.0': + resolution: {integrity: sha512-Qfrrt5OsNH5R+5tJ/3uYBBZv3SuGmnRPejV9IlIbFH3HTGLDlkqgHymAlzklVmKBjAaVmkPkyikAV/sQ1wSL+w==} + + '@shikijs/themes@2.5.0': + resolution: {integrity: sha512-wGrk+R8tJnO0VMzmUExHR+QdSaPUl/NKs+a4cQQRWyoc3YFbUzuLEi/KWK1hj+8BfHRKm2jNhhJck1dfstJpiw==} + + '@shikijs/transformers@2.5.0': + resolution: {integrity: sha512-SI494W5X60CaUwgi8u4q4m4s3YAFSxln3tzNjOSYqq54wlVgz0/NbbXEb3mdLbqMBztcmS7bVTaEd2w0qMmfeg==} - '@shikijs/transformers@1.5.2': - resolution: {integrity: sha512-/Sh64rKOFGMQLCvtHeL1Y7EExdq8LLxcdVkvoGx2aMHsYMOn8DckYl2gYKMHRBu/YUt1C38/Amd1Jdh48tWHgw==} + '@shikijs/types@2.5.0': + resolution: {integrity: sha512-ygl5yhxki9ZLNuNpPitBWvcy9fsSKKaRuO4BAlMyagszQidxcpLAr0qiW/q43DtSIDxO6hEbtYLiFZNXO/hdGw==} - '@types/estree@1.0.5': - resolution: {integrity: sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==} + '@shikijs/vscode-textmate@10.0.2': + resolution: {integrity: sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==} + + '@types/debug@4.1.12': + resolution: {integrity: sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==} + + '@types/estree@1.0.7': + resolution: {integrity: sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==} + + '@types/hast@3.0.4': + resolution: {integrity: sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==} '@types/js-yaml@4.0.9': resolution: {integrity: sha512-k4MGaQl5TGo/iipqb2UDG2UwjXziSWkh0uysQelTlJpX1qGlpUZYm8PnO4DxG1qBomtJUdYJ6qR6xdIah10JLg==} - '@types/json-schema@7.0.11': - resolution: {integrity: sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==} - '@types/linkify-it@5.0.0': resolution: {integrity: sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q==} - '@types/markdown-it@14.1.1': - resolution: {integrity: sha512-4NpsnpYl2Gt1ljyBGrKMxFYAYvpqbnnkgP/i/g+NLpjEUa3obn1XJCur9YbEXKDAkaXqsR1LbDnGEJ0MmKFxfg==} + '@types/markdown-it@14.1.2': + resolution: {integrity: sha512-promo4eFwuiW+TfGxhi+0x3czqTYJkG8qB17ZUJiVF10Xm7NLVRSLUsfRTU/6h1e24VvRnXCx+hG7li58lkzog==} + + '@types/mdast@4.0.4': + resolution: {integrity: sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==} '@types/mdurl@2.0.0': resolution: {integrity: sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==} - '@types/web-bluetooth@0.0.20': - resolution: {integrity: sha512-g9gZnnXVq7gM7v3tJCWV/qw7w+KeOlSHAhgF9RytFyifW6AF61hdT2ucrYhPq9hLs5JIryeupHV3qGk95dH9ow==} + '@types/ms@2.1.0': + resolution: {integrity: sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==} + + '@types/unist@3.0.3': + resolution: {integrity: sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==} + + '@types/web-bluetooth@0.0.21': + resolution: {integrity: sha512-oIQLCGWtcFZy2JW77j9k8nHzAOpqMHLQejDA48XXMWH6tjCQHz5RCFz1bzsmROyL6PUm+LLnUiI4BCn221inxA==} + + '@ungap/structured-clone@1.3.0': + resolution: {integrity: sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==} - '@vitejs/plugin-vue@5.0.4': - resolution: {integrity: sha512-WS3hevEszI6CEVEx28F8RjTX97k3KsrcY6kvTg7+Whm5y3oYvcqzVeGCU3hxSAn4uY2CLCkeokkGKpoctccilQ==} + '@vitejs/plugin-vue@5.2.3': + resolution: {integrity: sha512-IYSLEQj4LgZZuoVpdSUCw3dIynTWQgPlaRP6iAvMle4My0HdYwr5g5wQAfwOeHQBmYwEkqF70nRpSilr6PoUDg==} engines: {node: ^18.0.0 || >=20.0.0} peerDependencies: - vite: ^5.0.0 + vite: ^5.0.0 || ^6.0.0 vue: ^3.2.25 - '@volar/language-core@2.2.4': - resolution: {integrity: sha512-7As47GndxGxsqqYnbreLrfB5NDUeQioPM2LJKUuB4/34c0NpEJ2byVl3c9KYdjIdiEstWZ9JLtLKNTaPWb5jtA==} + '@volar/language-core@2.4.17': + resolution: {integrity: sha512-chmRZMbKmcGpKMoO7Reb70uiLrzo0KWC2CkFttKUuKvrE+VYgi+fL9vWMJ07Fv5ulX0V1TAyyacN9q3nc5/ecA==} - '@volar/source-map@2.2.4': - resolution: {integrity: sha512-m92FLpR9vB1YEZfiZ+bfgpLrToL/DNkOrorWVep3pffHrwwI4Tx2oIQN+sqHJfKkiT5N3J1owC+8crhAEinfjg==} + '@volar/source-map@2.4.17': + resolution: {integrity: sha512-QDybtQyO3Ms/NjFqNHTC5tbDN2oK5VH7ZaKrcubtfHBDj63n2pizHC3wlMQ+iT55kQXZUUAbmBX5L1C8CHFeBw==} - '@volar/typescript@2.2.4': - resolution: {integrity: sha512-uAQC53tgEbHO62G8NXMfmBrJAlP2QJ9WxVEEQqqK3I6VSy8frL5LbH3hAWODxiwMWixv74wJLWlKbWXOgdIoRQ==} + '@volar/typescript@2.4.17': + resolution: {integrity: sha512-3paEFNh4P5DkgNUB2YkTRrfUekN4brAXxd3Ow1syMqdIPtCZHbUy4AW99S5RO/7mzyTWPMdDSo3mqTpB/LPObQ==} - '@vue/compiler-core@3.4.27': - resolution: {integrity: sha512-E+RyqY24KnyDXsCuQrI+mlcdW3ALND6U7Gqa/+bVwbcpcR3BRRIckFoz7Qyd4TTlnugtwuI7YgjbvsLmxb+yvg==} + '@vue/compiler-core@3.5.17': + resolution: {integrity: sha512-Xe+AittLbAyV0pabcN7cP7/BenRBNcteM4aSDCtRvGw0d9OL+HG1u/XHLY/kt1q4fyMeZYXyIYrsHuPSiDPosA==} - '@vue/compiler-dom@3.4.27': - resolution: {integrity: sha512-kUTvochG/oVgE1w5ViSr3KUBh9X7CWirebA3bezTbB5ZKBQZwR2Mwj9uoSKRMFcz4gSMzzLXBPD6KpCLb9nvWw==} + '@vue/compiler-dom@3.5.17': + resolution: {integrity: sha512-+2UgfLKoaNLhgfhV5Ihnk6wB4ljyW1/7wUIog2puUqajiC29Lp5R/IKDdkebh9jTbTogTbsgB+OY9cEWzG95JQ==} - '@vue/compiler-sfc@3.4.27': - resolution: {integrity: sha512-nDwntUEADssW8e0rrmE0+OrONwmRlegDA1pD6QhVeXxjIytV03yDqTey9SBDiALsvAd5U4ZrEKbMyVXhX6mCGA==} + '@vue/compiler-sfc@3.5.17': + resolution: {integrity: sha512-rQQxbRJMgTqwRugtjw0cnyQv9cP4/4BxWfTdRBkqsTfLOHWykLzbOc3C4GGzAmdMDxhzU/1Ija5bTjMVrddqww==} - '@vue/compiler-ssr@3.4.27': - resolution: {integrity: sha512-CVRzSJIltzMG5FcidsW0jKNQnNRYC8bT21VegyMMtHmhW3UOI7knmUehzswXLrExDLE6lQCZdrhD4ogI7c+vuw==} + '@vue/compiler-ssr@3.5.17': + resolution: {integrity: sha512-hkDbA0Q20ZzGgpj5uZjb9rBzQtIHLS78mMilwrlpWk2Ep37DYntUz0PonQ6kr113vfOEdM+zTBuJDaceNIW0tQ==} - '@vue/devtools-api@7.2.0': - resolution: {integrity: sha512-92RsjyH9WKNFO6U/dECUMakq4dm2CeqEDJYLJ8wZ81AnCifpXE7d4jPIjK34ENsPaapA6BSfIZdH/qzLOHiepA==} + '@vue/compiler-vue2@2.7.16': + resolution: {integrity: sha512-qYC3Psj9S/mfu9uVi5WvNZIzq+xnXMhOwbTFKKDD7b1lhpnn71jXSFdTQ+WsIEk0ONCd7VV2IMm7ONl6tbQ86A==} - '@vue/devtools-kit@7.2.0': - resolution: {integrity: sha512-Kx+U0QiQg/g714euYKfnCdhTcOycSlH1oyTE57D0sAmisdsRCNLfXcnnIwcFY2jdCpuz9DNbuE0VWQuYF5zAZQ==} - peerDependencies: - vue: ^3.0.0 + '@vue/devtools-api@7.7.2': + resolution: {integrity: sha512-1syn558KhyN+chO5SjlZIwJ8bV/bQ1nOVTG66t2RbG66ZGekyiYNmRO7X9BJCXQqPsFHlnksqvPhce2qpzxFnA==} + + '@vue/devtools-kit@7.7.2': + resolution: {integrity: sha512-CY0I1JH3Z8PECbn6k3TqM1Bk9ASWxeMtTCvZr7vb+CHi+X/QwQm5F1/fPagraamKMAHVfuuCbdcnNg1A4CYVWQ==} - '@vue/devtools-shared@7.2.0': - resolution: {integrity: sha512-gVr3IjKjU7axNvclRgICgy1gq/TDnF1hhBAEox+l5mMXZiTIFVIm1zpcIPssc0HxMDgzy+lXqOVsY4DGyZ+ZeA==} + '@vue/devtools-shared@7.7.2': + resolution: {integrity: sha512-uBFxnp8gwW2vD6FrJB8JZLUzVb6PNRG0B0jBnHsOH8uKyva2qINY8PTF5Te4QlTbMDqU5K6qtJDr6cNsKWhbOA==} - '@vue/language-core@2.0.19': - resolution: {integrity: sha512-A9EGOnvb51jOvnCYoRLnMP+CcoPlbZVxI9gZXE/y2GksRWM6j/PrLEIC++pnosWTN08tFpJgxhSS//E9v/Sg+Q==} + '@vue/language-core@3.0.1': + resolution: {integrity: sha512-sq+/Mc1IqIexWEQ+Q2XPiDb5SxSvY5JPqHnMOl/PlF5BekslzduX8dglSkpC17VeiAQB6dpS+4aiwNLJRduCNw==} peerDependencies: typescript: '*' peerDependenciesMeta: typescript: optional: true - '@vue/reactivity@3.4.27': - resolution: {integrity: sha512-kK0g4NknW6JX2yySLpsm2jlunZJl2/RJGZ0H9ddHdfBVHcNzxmQ0sS0b09ipmBoQpY8JM2KmUw+a6sO8Zo+zIA==} + '@vue/reactivity@3.5.17': + resolution: {integrity: sha512-l/rmw2STIscWi7SNJp708FK4Kofs97zc/5aEPQh4bOsReD/8ICuBcEmS7KGwDj5ODQLYWVN2lNibKJL1z5b+Lw==} - '@vue/runtime-core@3.4.27': - resolution: {integrity: sha512-7aYA9GEbOOdviqVvcuweTLe5Za4qBZkUY7SvET6vE8kyypxVgaT1ixHLg4urtOlrApdgcdgHoTZCUuTGap/5WA==} + '@vue/runtime-core@3.5.17': + resolution: {integrity: sha512-QQLXa20dHg1R0ri4bjKeGFKEkJA7MMBxrKo2G+gJikmumRS7PTD4BOU9FKrDQWMKowz7frJJGqBffYMgQYS96Q==} - '@vue/runtime-dom@3.4.27': - resolution: {integrity: sha512-ScOmP70/3NPM+TW9hvVAz6VWWtZJqkbdf7w6ySsws+EsqtHvkhxaWLecrTorFxsawelM5Ys9FnDEMt6BPBDS0Q==} + '@vue/runtime-dom@3.5.17': + resolution: {integrity: sha512-8El0M60TcwZ1QMz4/os2MdlQECgGoVHPuLnQBU3m9h3gdNRW9xRmI8iLS4t/22OQlOE6aJvNNlBiCzPHur4H9g==} - '@vue/server-renderer@3.4.27': - resolution: {integrity: sha512-dlAMEuvmeA3rJsOMJ2J1kXU7o7pOxgsNHVr9K8hB3ImIkSuBrIdy0vF66h8gf8Tuinf1TK3mPAz2+2sqyf3KzA==} + '@vue/server-renderer@3.5.17': + resolution: {integrity: sha512-BOHhm8HalujY6lmC3DbqF6uXN/K00uWiEeF22LfEsm9Q93XeJ/plHTepGwf6tqFcF7GA5oGSSAAUock3VvzaCA==} peerDependencies: - vue: 3.4.27 + vue: 3.5.17 - '@vue/shared@3.4.27': - resolution: {integrity: sha512-DL3NmY2OFlqmYYrzp39yi3LDkKxa5vZVwxWdQ3rG0ekuWscHraeIbnI8t+aZK7qhYqEqWKTUdijadunb9pnrgA==} + '@vue/shared@3.5.13': + resolution: {integrity: sha512-/hnE/qP5ZoGpol0a5mDi45bOd7t3tjYJBjsgCsivow7D48cJeV5l05RD82lPqi7gRiphZM37rnhW1l6ZoCNNnQ==} - '@vueuse/core@10.9.0': - resolution: {integrity: sha512-/1vjTol8SXnx6xewDEKfS0Ra//ncg4Hb0DaZiwKf7drgfMsKFExQ+FnnENcN6efPen+1kIzhLQoGSy0eDUVOMg==} + '@vue/shared@3.5.17': + resolution: {integrity: sha512-CabR+UN630VnsJO/jHWYBC1YVXyMq94KKp6iF5MQgZJs5I8cmjw6oVMO1oDbtBkENSHSSn/UadWlW/OAgdmKrg==} - '@vueuse/integrations@10.9.0': - resolution: {integrity: sha512-acK+A01AYdWSvL4BZmCoJAcyHJ6EqhmkQEXbQLwev1MY7NBnS+hcEMx/BzVoR9zKI+UqEPMD9u6PsyAuiTRT4Q==} + '@vueuse/core@12.8.2': + resolution: {integrity: sha512-HbvCmZdzAu3VGi/pWYm5Ut+Kd9mn1ZHnn4L5G8kOQTPs/IwIAmJoBrmYk2ckLArgMXZj0AW3n5CAejLUO+PhdQ==} + + '@vueuse/integrations@12.8.2': + resolution: {integrity: sha512-fbGYivgK5uBTRt7p5F3zy6VrETlV9RtZjBqd1/HxGdjdckBgBM4ugP8LHpjolqTj14TXTxSK1ZfgPbHYyGuH7g==} peerDependencies: - async-validator: '*' - axios: '*' - change-case: '*' - drauu: '*' - focus-trap: '*' - fuse.js: '*' - idb-keyval: '*' - jwt-decode: '*' - nprogress: '*' - qrcode: '*' - sortablejs: '*' - universal-cookie: '*' + async-validator: ^4 + axios: ^1 + change-case: ^5 + drauu: ^0.4 + focus-trap: ^7 + fuse.js: ^7 + idb-keyval: ^6 + jwt-decode: ^4 + nprogress: ^0.2 + qrcode: ^1.5 + sortablejs: ^1 + universal-cookie: ^7 peerDependenciesMeta: async-validator: optional: true @@ -513,14 +863,29 @@ packages: universal-cookie: optional: true - '@vueuse/metadata@10.9.0': - resolution: {integrity: sha512-iddNbg3yZM0X7qFY2sAotomgdHK7YJ6sKUvQqbvwnf7TmaVPxS4EJydcNsVejNdS8iWCtDk+fYXr7E32nyTnGA==} + '@vueuse/metadata@12.8.2': + resolution: {integrity: sha512-rAyLGEuoBJ/Il5AmFHiziCPdQzRt88VxR+Y/A/QhJ1EWtWqPBBAxTAFaSkviwEuOEZNtW8pvkPgoCZQ+HxqW1A==} + + '@vueuse/shared@12.8.2': + resolution: {integrity: sha512-dznP38YzxZoNloI0qpEfpkms8knDtaoQ6Y/sfS0L7Yki4zh40LFHEhur0odJC6xTHG5dxWVPiUWBXn+wCG2s5w==} - '@vueuse/shared@10.9.0': - resolution: {integrity: sha512-Uud2IWncmAfJvRaFYzv5OHDli+FbOzxiVEQdLCKQKLyhz94PIyFC3CHcH7EDMwIn8NPtD06+PNbC/PiO0LGLtw==} + algoliasearch@5.23.4: + resolution: {integrity: sha512-QzAKFHl3fm53s44VHrTdEo0TkpL3XVUYQpnZy1r6/EHvMAyIg+O4hwprzlsNmcCHTNyVcF2S13DAUn7XhkC6qg==} + engines: {node: '>= 14.0.0'} - algoliasearch@4.19.1: - resolution: {integrity: sha512-IJF5b93b2MgAzcE/tuzW0yOPnuUyRgGAtaPv5UUywXM8kzqfdwZTO4sPJBzoGz1eOy6H9uEchsJsBFTELZSu+g==} + alien-signals@2.0.5: + resolution: {integrity: sha512-PdJB6+06nUNAClInE3Dweq7/2xVAYM64vvvS1IHVHSJmgeOtEdrAGyp7Z2oJtYm0B342/Exd2NT0uMJaThcjLQ==} + + ansi-regex@5.0.1: + resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} + engines: {node: '>=8'} + + ansi-styles@4.3.0: + resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} + engines: {node: '>=8'} + + argparse@1.0.10: + resolution: {integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==} argparse@2.0.1: resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} @@ -528,14 +893,50 @@ packages: ast-grep-wasm@file:pkg: resolution: {directory: pkg, type: directory} - balanced-match@1.0.2: - resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + bail@2.0.2: + resolution: {integrity: sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==} + + birpc@0.2.19: + resolution: {integrity: sha512-5WeXXAvTmitV1RqJFppT5QtUiz2p1mRSYU000Jkft5ZUCLJIk4uQriYNO50HknxKwM6jd8utNc66K1qGIwwWBQ==} + + byte-size@9.0.1: + resolution: {integrity: sha512-YLe9x3rabBrcI0cueCdLS2l5ONUKywcRpTs02B8KP9/Cimhj7o3ZccGrPnRvcbyHMbb7W79/3MUJl7iGgTXKEw==} + engines: {node: '>=12.17'} + peerDependencies: + '@75lb/nature': latest + peerDependenciesMeta: + '@75lb/nature': + optional: true + + ccount@2.0.1: + resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==} + + character-entities-html4@2.1.0: + resolution: {integrity: sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==} + + character-entities-legacy@3.0.0: + resolution: {integrity: sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==} + + character-entities@2.0.2: + resolution: {integrity: sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==} - brace-expansion@2.0.1: - resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==} + cliui@8.0.1: + resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==} + engines: {node: '>=12'} + + color-convert@2.0.1: + resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} + engines: {node: '>=7.0.0'} + + color-name@1.1.4: + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + + comma-separated-tokens@2.0.3: + resolution: {integrity: sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==} - computeds@0.0.1: - resolution: {integrity: sha512-7CEBgcMjVmitjYo5q8JTJVra6X5mQ20uTThdK+0kR7UEaDrAWEQcRiBtWJzga4eRpP6afNwwLsX2SET2JhVB1Q==} + copy-anything@3.0.5: + resolution: {integrity: sha512-yCEafptTtb4bk7GLEQoM8KVJpxAfdBJYaXyzQEgQQQgYrZiDp8SJmGKlYza6CYjEDNstAdNdKA3UuoULlEbS6w==} + engines: {node: '>=12.13'} csstype@3.1.3: resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==} @@ -543,26 +944,116 @@ packages: de-indent@1.0.2: resolution: {integrity: sha512-e/1zu3xH5MQryN2zdVaF0OrdNLUbvWxzMbi+iNA6Bky7l1RoP8a2fIbRocyHclXt/arDrrR6lL3TqFD9pMQTsg==} + debug@4.4.0: + resolution: {integrity: sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + decode-named-character-reference@1.1.0: + resolution: {integrity: sha512-Wy+JTSbFThEOXQIR2L6mxJvEs+veIzpmqD7ynWxMXGpnk3smkHQOp6forLdHsKpAMW9iJpaBBIxz285t1n1C3w==} + + dequal@2.0.3: + resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} + engines: {node: '>=6'} + + detect-libc@2.0.4: + resolution: {integrity: sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==} + engines: {node: '>=8'} + + devlop@1.1.0: + resolution: {integrity: sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==} + + dprint@0.50.1: + resolution: {integrity: sha512-s+kUyQp2rGpwsM3vVmXySOY3v1NjYyRpKfQZdP4rfNTz6zQuICSO6nqIXNm3YdK1MwNFR/EXSFMuE1YPuulhow==} + hasBin: true + + emoji-regex-xs@1.0.0: + resolution: {integrity: sha512-LRlerrMYoIDrT6jgpeZ2YYl/L8EulRTt5hQcYjy5AInh7HWXKimpqx68aknBFpGL2+/IcogTcaydJEgaTmOpDg==} + + emoji-regex@8.0.0: + resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} + entities@4.5.0: resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} engines: {node: '>=0.12'} - esbuild@0.20.2: - resolution: {integrity: sha512-WdOOppmUNU+IbZ0PaDiTst80zjnrOkyJNHoKupIcVyU8Lvla3Ugx94VzkQ32Ijqd7UhHJy75gNWDMUekcrSJ6g==} + esbuild@0.21.5: + resolution: {integrity: sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==} + engines: {node: '>=12'} + hasBin: true + + esbuild@0.25.2: + resolution: {integrity: sha512-16854zccKPnC+toMywC+uKNeYSv+/eXkevRAfwRD/G9Cleq66m8XFIrigkbvauLLlCfDL45Q2cWegSg53gGBnQ==} + engines: {node: '>=18'} + hasBin: true + + escalade@3.2.0: + resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} + engines: {node: '>=6'} + + escape-string-regexp@5.0.0: + resolution: {integrity: sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==} engines: {node: '>=12'} + + esm-env@1.2.2: + resolution: {integrity: sha512-Epxrv+Nr/CaL4ZcFGPJIYLWFom+YeV1DqMLHJoEd9SYRxNbaFruBwfEX/kkHUJf55j2+TUbmDcmuilbP1TmXHA==} + + esprima@4.0.1: + resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==} + engines: {node: '>=4'} hasBin: true estree-walker@2.0.2: resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==} - focus-trap@7.5.4: - resolution: {integrity: sha512-N7kHdlgsO/v+iD/dMoJKtsSqs5Dz/dXZVebRgJw23LDk+jMi/974zyiOYDziY2JPp8xivq9BmUGwIJMiuSBi7w==} + extend-shallow@2.0.1: + resolution: {integrity: sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==} + engines: {node: '>=0.10.0'} + + extend@3.0.2: + resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==} + + fault@2.0.1: + resolution: {integrity: sha512-WtySTkS4OKev5JtpHXnib4Gxiurzh5NCGvWrFaZ34m6JehfTUhKZvn9njTfw48t6JumVQOmrKqpmGcdwxnhqBQ==} + + fdir@6.4.6: + resolution: {integrity: sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w==} + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + + focus-trap@7.6.4: + resolution: {integrity: sha512-xx560wGBk7seZ6y933idtjJQc1l+ck+pI3sKvhKozdBV1dRZoKhkW5xoCaFv9tQiX5RH1xfSxjuNu6g+lmN/gw==} + + format@0.2.2: + resolution: {integrity: sha512-wzsgA6WOq+09wrU1tsJ09udeR/YZRaeArL9e1wPbFg3GG2yDnC2ldKpxs4xunpFF9DgqCqOIra3bc1HWrJ37Ww==} + engines: {node: '>=0.4.x'} fsevents@2.3.3: resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} os: [darwin] + get-caller-file@2.0.5: + resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} + engines: {node: 6.* || 8.* || >= 10.*} + + gray-matter@4.0.3: + resolution: {integrity: sha512-5v6yZd4JK3eMI3FqqCouswVqwugaA9r4dNZB1wwcmrD02QkV5H0y7XBQW8QwQqEaZY1pM9aqORSORhJRdNK44Q==} + engines: {node: '>=6.0'} + + hast-util-to-html@9.0.5: + resolution: {integrity: sha512-OguPdidb+fbHQSU4Q4ZiLKnzWo8Wwsf5bZfbvu7//a9oTYoqD/fWpe96NuHkoS9h0ccGOTe0C4NGXdtS0iObOw==} + + hast-util-whitespace@3.0.0: + resolution: {integrity: sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==} + he@1.2.0: resolution: {integrity: sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==} hasBin: true @@ -570,53 +1061,233 @@ packages: hookable@5.5.3: resolution: {integrity: sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==} - js-yaml@4.1.0: - resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} - hasBin: true + html-void-elements@3.0.0: + resolution: {integrity: sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==} - jsonc-parser@3.2.0: - resolution: {integrity: sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==} + is-extendable@0.1.1: + resolution: {integrity: sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==} + engines: {node: '>=0.10.0'} - linkify-it@5.0.0: - resolution: {integrity: sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==} + is-fullwidth-code-point@3.0.0: + resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} + engines: {node: '>=8'} - lru-cache@6.0.0: - resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==} - engines: {node: '>=10'} + is-plain-obj@4.1.0: + resolution: {integrity: sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==} + engines: {node: '>=12'} - magic-string@0.30.10: - resolution: {integrity: sha512-iIRwTIf0QKV3UAnYK4PU8uiEc4SRh5jX0mwpIwETPpHdhVM4f53RSwS/vXvN1JhGX+Cs7B8qIq3d6AH49O5fAQ==} + is-what@4.1.16: + resolution: {integrity: sha512-ZhMwEosbFJkA0YhFnNDgTM4ZxDRsS6HqTo7qsZM08fehyRYIYa0yHu5R6mgo1n/8MgaPBXiPimPD77baVFYg+A==} + engines: {node: '>=12.13'} - mark.js@8.11.1: - resolution: {integrity: sha512-1I+1qpDt4idfgLQG+BNWmrqku+7/2bi5nLf4YwF8y8zXvmfiTBY3PV3ZibfrjBueCByROpuBjLLFCajqkgYoLQ==} + js-yaml@3.14.1: + resolution: {integrity: sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==} + hasBin: true - markdown-it@14.1.0: - resolution: {integrity: sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg==} + js-yaml@4.1.0: + resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} hasBin: true - mdurl@2.0.0: - resolution: {integrity: sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==} + jsonc-parser@3.3.1: + resolution: {integrity: sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ==} - minimatch@9.0.3: - resolution: {integrity: sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==} - engines: {node: '>=16 || 14 >=14.17'} + kind-of@6.0.3: + resolution: {integrity: sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==} + engines: {node: '>=0.10.0'} - minisearch@6.3.0: - resolution: {integrity: sha512-ihFnidEeU8iXzcVHy74dhkxh/dn8Dc08ERl0xwoMMGqp4+LvRSCgicb+zGqWthVokQKvCSxITlh3P08OzdTYCQ==} + lightningcss-darwin-arm64@1.30.1: + resolution: {integrity: sha512-c8JK7hyE65X1MHMN+Viq9n11RRC7hgin3HhYKhrMyaXflk5GVplZ60IxyoVtzILeKr+xAJwg6zK6sjTBJ0FKYQ==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [darwin] - mitt@3.0.1: - resolution: {integrity: sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==} + lightningcss-darwin-x64@1.30.1: + resolution: {integrity: sha512-k1EvjakfumAQoTfcXUcHQZhSpLlkAuEkdMBsI/ivWw9hL+7FtilQc0Cy3hrx0AAQrVtQAbMI7YjCgYgvn37PzA==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [darwin] - monaco-editor@0.48.0: - resolution: {integrity: sha512-goSDElNqFfw7iDHMg8WDATkfcyeLTNpBHQpO8incK6p5qZt5G/1j41X0xdGzpIkGojGXM+QiRQyLjnfDVvrpwA==} + lightningcss-freebsd-x64@1.30.1: + resolution: {integrity: sha512-kmW6UGCGg2PcyUE59K5r0kWfKPAVy4SltVeut+umLCFoJ53RdCUWxcRDzO1eTaxf/7Q2H7LTquFHPL5R+Gjyig==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [freebsd] - monaco-languageserver-types@0.3.2: - resolution: {integrity: sha512-KiGVYK/DiX1pnacnOjGNlM85bhV3ZTyFlM+ce7B8+KpWCbF1XJVovu51YyuGfm+K7+K54mIpT4DFX16xmi+tYA==} + lightningcss-linux-arm-gnueabihf@1.30.1: + resolution: {integrity: sha512-MjxUShl1v8pit+6D/zSPq9S9dQ2NPFSQwGvxBCYaBYLPlCWuPh9/t1MRS8iUaR8i+a6w7aps+B4N0S1TYP/R+Q==} + engines: {node: '>= 12.0.0'} + cpu: [arm] + os: [linux] - monaco-marker-data-provider@1.1.1: - resolution: {integrity: sha512-PGB7TJSZE5tmHzkxv/OEwK2RGNC2A7dcq4JRJnnj31CUAsfmw0Gl+1QTrH0W0deKhcQmQM0YVPaqgQ+0wCt8Mg==} - peerDependencies: - monaco-editor: '>=0.30.0' + lightningcss-linux-arm64-gnu@1.30.1: + resolution: {integrity: sha512-gB72maP8rmrKsnKYy8XUuXi/4OctJiuQjcuqWNlJQ6jZiWqtPvqFziskH3hnajfvKB27ynbVCucKSm2rkQp4Bw==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + + lightningcss-linux-arm64-musl@1.30.1: + resolution: {integrity: sha512-jmUQVx4331m6LIX+0wUhBbmMX7TCfjF5FoOH6SD1CttzuYlGNVpA7QnrmLxrsub43ClTINfGSYyHe2HWeLl5CQ==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + + lightningcss-linux-x64-gnu@1.30.1: + resolution: {integrity: sha512-piWx3z4wN8J8z3+O5kO74+yr6ze/dKmPnI7vLqfSqI8bccaTGY5xiSGVIJBDd5K5BHlvVLpUB3S2YCfelyJ1bw==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + + lightningcss-linux-x64-musl@1.30.1: + resolution: {integrity: sha512-rRomAK7eIkL+tHY0YPxbc5Dra2gXlI63HL+v1Pdi1a3sC+tJTcFrHX+E86sulgAXeI7rSzDYhPSeHHjqFhqfeQ==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + + lightningcss-win32-arm64-msvc@1.30.1: + resolution: {integrity: sha512-mSL4rqPi4iXq5YVqzSsJgMVFENoa4nGTT/GjO2c0Yl9OuQfPsIfncvLrEW6RbbB24WtZ3xP/2CCmI3tNkNV4oA==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [win32] + + lightningcss-win32-x64-msvc@1.30.1: + resolution: {integrity: sha512-PVqXh48wh4T53F/1CCu8PIPCxLzWyCnn/9T5W1Jpmdy5h9Cwd+0YQS6/LwhHXSafuc61/xg9Lv5OrCby6a++jg==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [win32] + + lightningcss@1.30.1: + resolution: {integrity: sha512-xi6IyHML+c9+Q3W0S4fCQJOym42pyurFiJUHEcEyHS0CeKzia4yZDEsLlqOFykxOdHpNy0NmvVO31vcSqAxJCg==} + engines: {node: '>= 12.0.0'} + + linkify-it@5.0.0: + resolution: {integrity: sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==} + + longest-streak@3.1.0: + resolution: {integrity: sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==} + + magic-string@0.30.17: + resolution: {integrity: sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==} + + mark.js@8.11.1: + resolution: {integrity: sha512-1I+1qpDt4idfgLQG+BNWmrqku+7/2bi5nLf4YwF8y8zXvmfiTBY3PV3ZibfrjBueCByROpuBjLLFCajqkgYoLQ==} + + markdown-it@14.1.0: + resolution: {integrity: sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg==} + hasBin: true + + markdown-title@1.0.2: + resolution: {integrity: sha512-MqIQVVkz+uGEHi3TsHx/czcxxCbRIL7sv5K5DnYw/tI+apY54IbPefV/cmgxp6LoJSEx/TqcHdLs/298afG5QQ==} + engines: {node: '>=6'} + + mdast-util-from-markdown@2.0.2: + resolution: {integrity: sha512-uZhTV/8NBuw0WHkPTrCqDOl0zVe1BIng5ZtHoDk49ME1qqcjYmmLmOf0gELgcRMxN4w2iuIeVso5/6QymSrgmA==} + + mdast-util-frontmatter@2.0.1: + resolution: {integrity: sha512-LRqI9+wdgC25P0URIJY9vwocIzCcksduHQ9OF2joxQoyTNVduwLAFUzjoopuRJbJAReaKrNQKAZKL3uCMugWJA==} + + mdast-util-phrasing@4.1.0: + resolution: {integrity: sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==} + + mdast-util-to-hast@13.2.0: + resolution: {integrity: sha512-QGYKEuUsYT9ykKBCMOEDLsU5JRObWQusAolFMeko/tYPufNkRffBAQjIE+99jbA87xv6FgmjLtwjh9wBWajwAA==} + + mdast-util-to-markdown@2.1.2: + resolution: {integrity: sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA==} + + mdast-util-to-string@4.0.0: + resolution: {integrity: sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==} + + mdurl@2.0.0: + resolution: {integrity: sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==} + + micromark-core-commonmark@2.0.3: + resolution: {integrity: sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==} + + micromark-extension-frontmatter@2.0.0: + resolution: {integrity: sha512-C4AkuM3dA58cgZha7zVnuVxBhDsbttIMiytjgsM2XbHAB2faRVaHRle40558FBN+DJcrLNCoqG5mlrpdU4cRtg==} + + micromark-factory-destination@2.0.1: + resolution: {integrity: sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==} + + micromark-factory-label@2.0.1: + resolution: {integrity: sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg==} + + micromark-factory-space@2.0.1: + resolution: {integrity: sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==} + + micromark-factory-title@2.0.1: + resolution: {integrity: sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw==} + + micromark-factory-whitespace@2.0.1: + resolution: {integrity: sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ==} + + micromark-util-character@2.1.1: + resolution: {integrity: sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==} + + micromark-util-chunked@2.0.1: + resolution: {integrity: sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA==} + + micromark-util-classify-character@2.0.1: + resolution: {integrity: sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q==} + + micromark-util-combine-extensions@2.0.1: + resolution: {integrity: sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg==} + + micromark-util-decode-numeric-character-reference@2.0.2: + resolution: {integrity: sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw==} + + micromark-util-decode-string@2.0.1: + resolution: {integrity: sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ==} + + micromark-util-encode@2.0.1: + resolution: {integrity: sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==} + + micromark-util-html-tag-name@2.0.1: + resolution: {integrity: sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA==} + + micromark-util-normalize-identifier@2.0.1: + resolution: {integrity: sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q==} + + micromark-util-resolve-all@2.0.1: + resolution: {integrity: sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg==} + + micromark-util-sanitize-uri@2.0.1: + resolution: {integrity: sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==} + + micromark-util-subtokenize@2.1.0: + resolution: {integrity: sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA==} + + micromark-util-symbol@2.0.1: + resolution: {integrity: sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==} + + micromark-util-types@2.0.2: + resolution: {integrity: sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==} + + micromark@4.0.2: + resolution: {integrity: sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA==} + + millify@6.1.0: + resolution: {integrity: sha512-H/E3J6t+DQs/F2YgfDhxUVZz/dF8JXPPKTLHL/yHCcLZLtCXJDUaqvhJXQwqOVBvbyNn4T0WjLpIHd7PAw7fBA==} + hasBin: true + + minimatch@10.0.3: + resolution: {integrity: sha512-IPZ167aShDZZUMdRk66cyQAW3qr0WzbHkPdMYa8bzZhlHhO3jALbKdxcaak7W9FfT2rZNpQuUu4Od7ILEpXSaw==} + engines: {node: 20 || >=22} + + minisearch@7.1.2: + resolution: {integrity: sha512-R1Pd9eF+MD5JYDDSPAp/q1ougKglm14uEkPMvQ/05RGmx6G9wvmLTrTI/Q5iPNJLYqNdsDQ7qTGIcNWR+FrHmA==} + + mitt@3.0.1: + resolution: {integrity: sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==} + + monaco-editor@0.52.2: + resolution: {integrity: sha512-GEQWEZmfkOGLdd3XK8ryrfWz3AIP8YymVXiPHEdewrUq7mh0qrKrfHLNCXcbB6sTnMLnOZ3ztSiKcciFUkIJwQ==} + + monaco-languageserver-types@0.4.0: + resolution: {integrity: sha512-QQ3BZiU5LYkJElGncSNb5AKoJ/LCs6YBMCJMAz9EA7v+JaOdn3kx2cXpPTcZfKA5AEsR0vc97sAw+5mdNhVBmw==} + + monaco-marker-data-provider@1.2.4: + resolution: {integrity: sha512-4DsPgsAqpTyUDs3humXRBPUJoihTv+L6v9aupQWD80X2YXaCXUd11mWYeSCYHuPgdUmjFaNWCEOjQ6ewf/QA1Q==} monaco-types@0.1.0: resolution: {integrity: sha512-aWK7SN9hAqNYi0WosPoMjenMeXJjwCxDibOqWffyQ/qXdzB/86xshGQobRferfmNz7BSNQ8GB0MD0oby9/5fTQ==} @@ -626,88 +1297,198 @@ packages: peerDependencies: monaco-editor: '>=0.30.0' - monaco-yaml@5.1.1: - resolution: {integrity: sha512-BuZ0/ZCGjrPNRzYMZ/MoxH8F/SdM+mATENXnpOhDYABi1Eh+QvxSszEct+ACSCarZiwLvy7m6yEF/pvW8XJkyQ==} + monaco-yaml@5.4.0: + resolution: {integrity: sha512-tuBVDy1KAPrgO905GHTItu8AaA5bIzF5S4X0JVRAE/D66FpRhkDUk7tKi5bwKMVTTugtpMLsXN4ewh4CgE/FtQ==} peerDependencies: monaco-editor: '>=0.36' + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + muggle-string@0.4.1: resolution: {integrity: sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ==} - nanoid@3.3.7: - resolution: {integrity: sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==} + nanoid@3.3.11: + resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==} engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true + number-flow@0.5.8: + resolution: {integrity: sha512-FPr1DumWyGi5Nucoug14bC6xEz70A1TnhgSHhKyfqjgji2SOTz+iLJxKtv37N5JyJbteGYCm6NQ9p1O4KZ7iiA==} + + oniguruma-to-es@3.1.1: + resolution: {integrity: sha512-bUH8SDvPkH3ho3dvwJwfonjlQ4R80vjyvrU8YpxuROddv55vAEJrTuCuCVUhhsHbtlD9tGGbaNApGQckXhS8iQ==} + + oxlint@1.7.0: + resolution: {integrity: sha512-krJN1fIRhs3xK1FyVyPtYIV9tkT4WDoIwI7eiMEKBuCjxqjQt5ZemQm1htPvHqNDOaWFRFt4btcwFdU8bbwgvA==} + engines: {node: '>=8.*'} + hasBin: true + path-browserify@1.0.1: resolution: {integrity: sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==} + path-to-regexp@8.2.0: + resolution: {integrity: sha512-TdrF7fW9Rphjq4RjrW0Kp2AW0Ahwu9sRGTkS6bvDi0SCwZlEZYmcfDbEsTz8RVk0EHIS/Vd1bv3JhG+1xZuAyQ==} + engines: {node: '>=16'} + perfect-debounce@1.0.0: resolution: {integrity: sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==} - picocolors@1.0.0: - resolution: {integrity: sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==} + picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + + picomatch@4.0.2: + resolution: {integrity: sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==} + engines: {node: '>=12'} - postcss@8.4.38: - resolution: {integrity: sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==} + postcss@8.5.6: + resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==} engines: {node: ^10 || ^12 || >=14} - preact@10.11.3: - resolution: {integrity: sha512-eY93IVpod/zG3uMF22Unl8h9KkrcKIRs2EGar8hwLZZDU1lkjph303V9HZBwufh2s736U6VXuhD109LYqPoffg==} + preact@10.26.5: + resolution: {integrity: sha512-fmpDkgfGU6JYux9teDWLhj9mKN55tyepwYbxHgQuIxbWQzgFg5vk7Mrrtfx7xRxq798ynkY4DDDxZr235Kk+4w==} - prettier@2.8.2: - resolution: {integrity: sha512-BtRV9BcncDyI2tsuS19zzhzoxD8Dh8LiCx7j7tHzrkz8GFXAexeWFdi22mjE1d16dftH2qNaytVxqiRTGlMfpw==} - engines: {node: '>=10.13.0'} + prettier@3.5.3: + resolution: {integrity: sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw==} + engines: {node: '>=14'} hasBin: true + property-information@7.0.0: + resolution: {integrity: sha512-7D/qOz/+Y4X/rzSB6jKxKUsQnphO046ei8qxG59mtM3RG3DHgTK81HrxrmoDVINJb8NKT5ZsRbwHvQ6B68Iyhg==} + punycode.js@2.3.1: resolution: {integrity: sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==} engines: {node: '>=6'} - rfdc@1.3.1: - resolution: {integrity: sha512-r5a3l5HzYlIC68TpmYKlxWjmOP6wiPJ1vWv2HeLhNsRZMrCkxeqxiHlQ21oXmQ4F3SiryXBHhAD7JZqvOJjFmg==} + regex-recursion@6.0.2: + resolution: {integrity: sha512-0YCaSCq2VRIebiaUviZNs0cBz1kg5kVS2UKUfNIx8YVs1cN3AV7NTctO5FOKBA+UT2BPJIWZauYHPqJODG50cg==} + + regex-utilities@2.3.0: + resolution: {integrity: sha512-8VhliFJAWRaUiVvREIiW2NXXTmHs4vMNnSzuJVhscgmGav3g9VDxLrQndI3dZZVVdp0ZO/5v0xmX516/7M9cng==} + + regex@6.0.1: + resolution: {integrity: sha512-uorlqlzAKjKQZ5P+kTJr3eeJGSVroLKoHmquUj4zHWuR+hEyNqlXsSKlYYF5F4NI6nl7tWCs0apKJ0lmfsXAPA==} + + remark-frontmatter@5.0.0: + resolution: {integrity: sha512-XTFYvNASMe5iPN0719nPrdItC9aU0ssC4v14mH1BCi1u0n1gAocqcujWUrByftZTbLhRtiKRyjYTSIOcr69UVQ==} + + remark-parse@11.0.0: + resolution: {integrity: sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==} + + remark-stringify@11.0.0: + resolution: {integrity: sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw==} + + remark@15.0.1: + resolution: {integrity: sha512-Eht5w30ruCXgFmxVUSlNWQ9iiimq07URKeFS3hNc8cUWy1llX4KDWfyEDZRycMc+znsN9Ux5/tJ/BFdgdOwA3A==} + + require-directory@2.1.1: + resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} + engines: {node: '>=0.10.0'} + + rfdc@1.4.1: + resolution: {integrity: sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==} - rollup@4.13.0: - resolution: {integrity: sha512-3YegKemjoQnYKmsBlOHfMLVPPA5xLkQ8MHLLSw/fBrFaVkEayL51DilPpNNLq1exr98F2B1TzrV0FUlN3gWRPg==} + rollup@4.41.1: + resolution: {integrity: sha512-cPmwD3FnFv8rKMBc1MxWCwVQFxwf1JEmSX3iQXrRVVG15zerAIXRjMFVWnd5Q5QvgKF7Aj+5ykXFhUl+QGnyOw==} engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true - search-insights@2.13.0: - resolution: {integrity: sha512-Orrsjf9trHHxFRuo9/rzm0KIWmgzE8RMlZMzuhZOJ01Rnz3D0YBAe+V6473t6/H6c7irs6Lt48brULAiRWb3Vw==} + search-insights@2.17.3: + resolution: {integrity: sha512-RQPdCYTa8A68uM2jwxoY842xDhvx3E5LFL1LxvxCNMev4o5mLuokczhzjAgGwUZBAmOKZknArSxLKmXtIi2AxQ==} - semver@7.5.4: - resolution: {integrity: sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==} - engines: {node: '>=10'} - hasBin: true + section-matter@1.0.0: + resolution: {integrity: sha512-vfD3pmTzGpufjScBh50YHKzEu2lxBWhVEHsNGoEXmCmn2hKGfeNLYMzCJpe8cD7gqX7TJluOVpBkAequ6dgMmA==} + engines: {node: '>=4'} - shiki@1.5.2: - resolution: {integrity: sha512-fpPbuSaatinmdGijE7VYUD3hxLozR3ZZ+iAx8Iy2X6REmJGyF5hQl94SgmiUNTospq346nXUVZx0035dyGvIVw==} + shiki@2.5.0: + resolution: {integrity: sha512-mI//trrsaiCIPsja5CNfsyNOqgAZUb6VpJA+340toL42UpzQlXpwRV9nch69X6gaUxrr9kaOOa6e3y3uAkGFxQ==} - source-map-js@1.2.0: - resolution: {integrity: sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==} + source-map-js@1.2.1: + resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} engines: {node: '>=0.10.0'} + space-separated-tokens@2.0.2: + resolution: {integrity: sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==} + speakingurl@14.0.1: resolution: {integrity: sha512-1POYv7uv2gXoyGFpBCmpDVSNV74IfsWlDW216UPjbWufNf+bSU6GdbDsxdcxtfwb4xlI3yxzOTKClUosxARYrQ==} engines: {node: '>=0.10.0'} + sprintf-js@1.0.3: + resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==} + + string-width@4.2.3: + resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} + engines: {node: '>=8'} + + stringify-entities@4.0.4: + resolution: {integrity: sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==} + + strip-ansi@6.0.1: + resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} + engines: {node: '>=8'} + + strip-bom-string@1.0.0: + resolution: {integrity: sha512-uCC2VHvQRYu+lMh4My/sFNmF2klFymLX1wHJeXnbEJERpV/ZsVuonzerjfrGpIGF7LBVa1O7i9kjiWvJiFck8g==} + engines: {node: '>=0.10.0'} + + superjson@2.2.2: + resolution: {integrity: sha512-5JRxVqC8I8NuOUjzBbvVJAKNM8qoVuH0O77h4WInc/qC2q5IreqKxYwgkga3PfA22OayK2ikceb/B26dztPl+Q==} + engines: {node: '>=16'} + tabbable@6.2.0: resolution: {integrity: sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew==} - to-fast-properties@2.0.0: - resolution: {integrity: sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==} - engines: {node: '>=4'} + tinyglobby@0.2.14: + resolution: {integrity: sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==} + engines: {node: '>=12.0.0'} + + tokenx@1.1.0: + resolution: {integrity: sha512-KCjtiC2niPwTSuz4ktM82Ki5bjqBwYpssiHDsGr5BpejN/B3ksacRvrsdoxljdMIh2nCX78alnDkeemBmYUmTA==} + + trim-lines@3.0.1: + resolution: {integrity: sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==} + + trough@2.2.0: + resolution: {integrity: sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==} - typescript@5.4.5: - resolution: {integrity: sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==} + typescript@5.8.3: + resolution: {integrity: sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==} engines: {node: '>=14.17'} hasBin: true uc.micro@2.1.0: resolution: {integrity: sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==} - vite@5.2.11: - resolution: {integrity: sha512-HndV31LWW05i1BLPMUCE1B9E9GFbOu1MbenhS58FuK6owSO5qHm7GiCotrNY1YE5rMeQSFBGmT5ZaLEjFizgiQ==} + unified@11.0.5: + resolution: {integrity: sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==} + + unist-util-is@6.0.0: + resolution: {integrity: sha512-2qCTHimwdxLfz+YzdGfkqNlH0tLi9xjTnHddPmJwtIG9MGsdbutfTc4P+haPD7l7Cjxf/WZj+we5qfVPvvxfYw==} + + unist-util-position@5.0.0: + resolution: {integrity: sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==} + + unist-util-remove@4.0.0: + resolution: {integrity: sha512-b4gokeGId57UVRX/eVKej5gXqGlc9+trkORhFJpu9raqZkZhU0zm8Doi05+HaiBsMEIJowL+2WtQ5ItjsngPXg==} + + unist-util-stringify-position@4.0.0: + resolution: {integrity: sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==} + + unist-util-visit-parents@6.0.1: + resolution: {integrity: sha512-L/PqWzfTP9lzzEa6CKs0k2nARxTdZduw3zyh8d2NVBnsyvHjSX4TWse388YrrQKbvI8w20fGjGlhgT96WwKykw==} + + unist-util-visit@5.0.0: + resolution: {integrity: sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==} + + vfile-message@4.0.2: + resolution: {integrity: sha512-jRDZ1IMLttGj41KcZvlrYAaI3CfqpLpfpf+Mfig13viT6NKvRzWZ+lXz0Y5D60w6uJIBAOGq9mSHf0gktF0duw==} + + vfile@6.0.3: + resolution: {integrity: sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==} + + vite@5.4.19: + resolution: {integrity: sha512-qO3aKv3HoQC8QKiNSTuUM1l9o/XX3+c+VTgLHbJWHZGeTPVAg2XwazI9UWzoxjIJCGCV2zU60uqMzjeLZuULqA==} engines: {node: ^18.0.0 || >=20.0.0} hasBin: true peerDependencies: @@ -715,6 +1496,7 @@ packages: less: '*' lightningcss: ^1.21.0 sass: '*' + sass-embedded: '*' stylus: '*' sugarss: '*' terser: ^5.4.0 @@ -727,15 +1509,60 @@ packages: optional: true sass: optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + + vite@7.0.2: + resolution: {integrity: sha512-hxdyZDY1CM6SNpKI4w4lcUc3Mtkd9ej4ECWVHSMrOdSinVc2zYOAppHeGc/hzmRo3pxM5blMzkuWHOJA/3NiFw==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + peerDependencies: + '@types/node': ^20.19.0 || >=22.12.0 + jiti: '>=1.21.0' + less: ^4.0.0 + lightningcss: ^1.21.0 + sass: ^1.70.0 + sass-embedded: ^1.70.0 + stylus: '>=0.54.8' + sugarss: ^5.0.0 + terser: ^5.16.0 + tsx: ^4.8.1 + yaml: ^2.4.2 + peerDependenciesMeta: + '@types/node': + optional: true + jiti: + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + sass-embedded: + optional: true stylus: optional: true sugarss: optional: true terser: optional: true + tsx: + optional: true + yaml: + optional: true + + vitepress-plugin-llms@1.7.0: + resolution: {integrity: sha512-J4va/lPTBcrlV5RXmuML8Lg1nzRR8cB1nJszqjITtayOrliR1jDbho31nkzJCdU/SmwdR7PFVQDRwwOTEDdoHA==} - vitepress@1.2.0: - resolution: {integrity: sha512-m/4PAQVyPBvKHV7sFKwcmNmrsoSxdjnw/Eg40YyuBSaBHhrro9ubnfWk5GT0xGfE98LqjZkHCWKNJlR6G/7Ayg==} + vitepress@1.6.3: + resolution: {integrity: sha512-fCkfdOk8yRZT8GD9BFqusW3+GggWYZ/rYncOfmgcDtP3ualNHCAg+Robxp2/6xfH1WwPHtGpPwv7mbA3qomtBw==} hasBin: true peerDependencies: markdown-it-mathjax3: ^4 @@ -746,198 +1573,210 @@ packages: postcss: optional: true - vscode-jsonrpc@8.1.0: - resolution: {integrity: sha512-6TDy/abTQk+zDGYazgbIPc+4JoXdwC8NHU9Pbn4UJP1fehUyZmM4RHp5IthX7A6L5KS30PRui+j+tbbMMMafdw==} + vscode-jsonrpc@8.2.0: + resolution: {integrity: sha512-C+r0eKJUIfiDIfwJhria30+TYWPtuHJXHtI7J0YlOmKAo7ogxP20T0zxB7HZQIFhIyvoBPwWskjxrvAtfjyZfA==} engines: {node: '>=14.0.0'} - vscode-languageserver-protocol@3.17.3: - resolution: {integrity: sha512-924/h0AqsMtA5yK22GgMtCYiMdCOtWTSGgUOkgEDX+wk2b0x4sAfLiO4NxBxqbiVtz7K7/1/RgVrVI0NClZwqA==} - - vscode-languageserver-textdocument@1.0.8: - resolution: {integrity: sha512-1bonkGqQs5/fxGT5UchTgjGVnfysL0O8v1AYMBjqTbWQTFn721zaPGDYFkOKtfDgFiSgXM3KwaG3FMGfW4Ed9Q==} - - vscode-languageserver-types@3.17.3: - resolution: {integrity: sha512-SYU4z1dL0PyIMd4Vj8YOqFvHu7Hz/enbWtpfnVbJHU4Nd1YNYx8u0ennumc6h48GQNeOLxmwySmnADouT/AuZA==} + vscode-languageserver-protocol@3.17.5: + resolution: {integrity: sha512-mb1bvRJN8SVznADSGWM9u/b07H7Ecg0I3OgXDuLdn307rl/J3A9YD6/eYOssqhecL27hK1IPZAsaqh00i/Jljg==} - vscode-uri@3.0.7: - resolution: {integrity: sha512-eOpPHogvorZRobNqJGhapa0JdwaxpjVvyBp0QIUMRMSf8ZAlqOdEquKuRmw9Qwu0qXtJIWqFtMkmvJjUZmMjVA==} + vscode-languageserver-textdocument@1.0.12: + resolution: {integrity: sha512-cxWNPesCnQCcMPeenjKKsOCKQZ/L6Tv19DTRIGuLWe32lyzWhihGVJ/rcckZXJxfdKCFvRLS3fpBIsV/ZGX4zA==} - vue-demi@0.14.7: - resolution: {integrity: sha512-EOG8KXDQNwkJILkx/gPcoL/7vH+hORoBaKgGe+6W7VFMvCYJfmF2dGbvgDroVnI8LU7/kTu8mbjRZGBU1z9NTA==} - engines: {node: '>=12'} - hasBin: true - peerDependencies: - '@vue/composition-api': ^1.0.0-rc.1 - vue: ^3.0.0-0 || ^2.6.0 - peerDependenciesMeta: - '@vue/composition-api': - optional: true + vscode-languageserver-types@3.17.5: + resolution: {integrity: sha512-Ld1VelNuX9pdF39h2Hgaeb5hEZM2Z3jUrrMgWQAu82jMtZp7p3vJT3BzToKtZI7NgQssZje5o0zryOrhQvzQAg==} - vue-template-compiler@2.7.14: - resolution: {integrity: sha512-zyA5Y3ArvVG0NacJDkkzJuPQDF8RFeRlzV2vLeSnhSpieO6LK2OVbdLPi5MPPs09Ii+gMO8nY4S3iKQxBxDmWQ==} + vscode-uri@3.1.0: + resolution: {integrity: sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ==} - vue-tsc@2.0.19: - resolution: {integrity: sha512-JWay5Zt2/871iodGF72cELIbcAoPyhJxq56mPPh+M2K7IwI688FMrFKc/+DvB05wDWEuCPexQJ6L10zSwzzapg==} + vue-tsc@3.0.1: + resolution: {integrity: sha512-UvMLQD0hAGL1g/NfEQelnSVB4H5gtf/gz2lJKjMMwWNOUmSNyWkejwJagAxEbSjtV5CPPJYslOtoSuqJ63mhdg==} hasBin: true peerDependencies: - typescript: '*' + typescript: '>=5.0.0' - vue@3.4.27: - resolution: {integrity: sha512-8s/56uK6r01r1icG/aEOHqyMVxd1bkYcSe9j8HcKtr/xTOFWvnzIVTehNW+5Yt89f+DLBe4A569pnZLS5HzAMA==} + vue@3.5.17: + resolution: {integrity: sha512-LbHV3xPN9BeljML+Xctq4lbz2lVHCR6DtbpTf5XIO6gugpXUN49j2QQPcMj086r9+AkJ0FfUT8xjulKKBkkr9g==} peerDependencies: typescript: '*' peerDependenciesMeta: typescript: optional: true - web-tree-sitter-sg@0.21.5: - resolution: {integrity: sha512-EWDu77thpWKy2p3Z+yGEQWZId1XGTwPwdgucbvDcnjRzjOhcO/RP5103/yJQyGThM92/0ffkAm4+JaYgzxuaBA==} + web-tree-sitter@0.25.3: + resolution: {integrity: sha512-e0hdXG+nJ18Zd/QJFhSx0DNTSMz7miwUjKyJ/lglTnZo6ke08++BQzXkaeaqnGJFi9qq+nPJg2L8hYAjduToHQ==} - web-tree-sitter@0.22.6: - resolution: {integrity: sha512-hS87TH71Zd6mGAmYCvlgxeGDjqd9GTeqXNqTT+u0Gs51uIozNIaaq/kUAbV/Zf56jb2ZOyG8BxZs2GG9wbLi6Q==} + wrap-ansi@7.0.0: + resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} + engines: {node: '>=10'} - yallist@4.0.0: - resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} + y18n@5.0.8: + resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} + engines: {node: '>=10'} - yaml@2.2.1: - resolution: {integrity: sha512-e0WHiYql7+9wr4cWMx3TVQrNwejKaEe7/rHNmQmqRjazfOP5W8PB6Jpebb5o6fIapbz9o9+2ipcaTM2ZwDI6lw==} + yaml@2.7.1: + resolution: {integrity: sha512-10ULxpnOCQXxJvBgxsn9ptjq6uviG/htZKk9veJGhlqn3w/DxQ631zFF+nlQXLwmImeS5amR2dl2U8sg6U9jsQ==} engines: {node: '>= 14'} + hasBin: true + + yargs-parser@21.1.1: + resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} + engines: {node: '>=12'} + + yargs@17.7.2: + resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==} + engines: {node: '>=12'} + + zwitch@2.0.4: + resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==} snapshots: - '@algolia/autocomplete-core@1.9.3(@algolia/client-search@4.23.3)(algoliasearch@4.19.1)(search-insights@2.13.0)': + '@algolia/autocomplete-core@1.17.7(@algolia/client-search@5.29.0)(algoliasearch@5.23.4)(search-insights@2.17.3)': dependencies: - '@algolia/autocomplete-plugin-algolia-insights': 1.9.3(@algolia/client-search@4.23.3)(algoliasearch@4.19.1)(search-insights@2.13.0) - '@algolia/autocomplete-shared': 1.9.3(@algolia/client-search@4.23.3)(algoliasearch@4.19.1) + '@algolia/autocomplete-plugin-algolia-insights': 1.17.7(@algolia/client-search@5.29.0)(algoliasearch@5.23.4)(search-insights@2.17.3) + '@algolia/autocomplete-shared': 1.17.7(@algolia/client-search@5.29.0)(algoliasearch@5.23.4) transitivePeerDependencies: - '@algolia/client-search' - algoliasearch - search-insights - '@algolia/autocomplete-plugin-algolia-insights@1.9.3(@algolia/client-search@4.23.3)(algoliasearch@4.19.1)(search-insights@2.13.0)': + '@algolia/autocomplete-plugin-algolia-insights@1.17.7(@algolia/client-search@5.29.0)(algoliasearch@5.23.4)(search-insights@2.17.3)': dependencies: - '@algolia/autocomplete-shared': 1.9.3(@algolia/client-search@4.23.3)(algoliasearch@4.19.1) - search-insights: 2.13.0 + '@algolia/autocomplete-shared': 1.17.7(@algolia/client-search@5.29.0)(algoliasearch@5.23.4) + search-insights: 2.17.3 transitivePeerDependencies: - '@algolia/client-search' - algoliasearch - '@algolia/autocomplete-preset-algolia@1.9.3(@algolia/client-search@4.23.3)(algoliasearch@4.19.1)': + '@algolia/autocomplete-preset-algolia@1.17.7(@algolia/client-search@5.29.0)(algoliasearch@5.23.4)': dependencies: - '@algolia/autocomplete-shared': 1.9.3(@algolia/client-search@4.23.3)(algoliasearch@4.19.1) - '@algolia/client-search': 4.23.3 - algoliasearch: 4.19.1 + '@algolia/autocomplete-shared': 1.17.7(@algolia/client-search@5.29.0)(algoliasearch@5.23.4) + '@algolia/client-search': 5.29.0 + algoliasearch: 5.23.4 - '@algolia/autocomplete-shared@1.9.3(@algolia/client-search@4.23.3)(algoliasearch@4.19.1)': + '@algolia/autocomplete-shared@1.17.7(@algolia/client-search@5.29.0)(algoliasearch@5.23.4)': dependencies: - '@algolia/client-search': 4.23.3 - algoliasearch: 4.19.1 + '@algolia/client-search': 5.29.0 + algoliasearch: 5.23.4 - '@algolia/cache-browser-local-storage@4.19.1': + '@algolia/client-abtesting@5.23.4': dependencies: - '@algolia/cache-common': 4.19.1 + '@algolia/client-common': 5.23.4 + '@algolia/requester-browser-xhr': 5.23.4 + '@algolia/requester-fetch': 5.23.4 + '@algolia/requester-node-http': 5.23.4 - '@algolia/cache-common@4.19.1': {} + '@algolia/client-analytics@5.23.4': + dependencies: + '@algolia/client-common': 5.23.4 + '@algolia/requester-browser-xhr': 5.23.4 + '@algolia/requester-fetch': 5.23.4 + '@algolia/requester-node-http': 5.23.4 - '@algolia/cache-common@4.23.3': {} + '@algolia/client-common@5.23.4': {} - '@algolia/cache-in-memory@4.19.1': - dependencies: - '@algolia/cache-common': 4.19.1 + '@algolia/client-common@5.29.0': {} - '@algolia/client-account@4.19.1': + '@algolia/client-insights@5.23.4': dependencies: - '@algolia/client-common': 4.19.1 - '@algolia/client-search': 4.19.1 - '@algolia/transporter': 4.19.1 + '@algolia/client-common': 5.23.4 + '@algolia/requester-browser-xhr': 5.23.4 + '@algolia/requester-fetch': 5.23.4 + '@algolia/requester-node-http': 5.23.4 - '@algolia/client-analytics@4.19.1': + '@algolia/client-personalization@5.23.4': dependencies: - '@algolia/client-common': 4.19.1 - '@algolia/client-search': 4.19.1 - '@algolia/requester-common': 4.19.1 - '@algolia/transporter': 4.19.1 + '@algolia/client-common': 5.23.4 + '@algolia/requester-browser-xhr': 5.23.4 + '@algolia/requester-fetch': 5.23.4 + '@algolia/requester-node-http': 5.23.4 - '@algolia/client-common@4.19.1': + '@algolia/client-query-suggestions@5.23.4': dependencies: - '@algolia/requester-common': 4.19.1 - '@algolia/transporter': 4.19.1 + '@algolia/client-common': 5.23.4 + '@algolia/requester-browser-xhr': 5.23.4 + '@algolia/requester-fetch': 5.23.4 + '@algolia/requester-node-http': 5.23.4 - '@algolia/client-common@4.23.3': + '@algolia/client-search@5.23.4': dependencies: - '@algolia/requester-common': 4.23.3 - '@algolia/transporter': 4.23.3 + '@algolia/client-common': 5.23.4 + '@algolia/requester-browser-xhr': 5.23.4 + '@algolia/requester-fetch': 5.23.4 + '@algolia/requester-node-http': 5.23.4 - '@algolia/client-personalization@4.19.1': + '@algolia/client-search@5.29.0': dependencies: - '@algolia/client-common': 4.19.1 - '@algolia/requester-common': 4.19.1 - '@algolia/transporter': 4.19.1 + '@algolia/client-common': 5.29.0 + '@algolia/requester-browser-xhr': 5.29.0 + '@algolia/requester-fetch': 5.29.0 + '@algolia/requester-node-http': 5.29.0 - '@algolia/client-search@4.19.1': + '@algolia/ingestion@1.23.4': dependencies: - '@algolia/client-common': 4.19.1 - '@algolia/requester-common': 4.19.1 - '@algolia/transporter': 4.19.1 + '@algolia/client-common': 5.23.4 + '@algolia/requester-browser-xhr': 5.23.4 + '@algolia/requester-fetch': 5.23.4 + '@algolia/requester-node-http': 5.23.4 - '@algolia/client-search@4.23.3': + '@algolia/monitoring@1.23.4': dependencies: - '@algolia/client-common': 4.23.3 - '@algolia/requester-common': 4.23.3 - '@algolia/transporter': 4.23.3 + '@algolia/client-common': 5.23.4 + '@algolia/requester-browser-xhr': 5.23.4 + '@algolia/requester-fetch': 5.23.4 + '@algolia/requester-node-http': 5.23.4 - '@algolia/logger-common@4.19.1': {} - - '@algolia/logger-common@4.23.3': {} - - '@algolia/logger-console@4.19.1': + '@algolia/recommend@5.23.4': dependencies: - '@algolia/logger-common': 4.19.1 + '@algolia/client-common': 5.23.4 + '@algolia/requester-browser-xhr': 5.23.4 + '@algolia/requester-fetch': 5.23.4 + '@algolia/requester-node-http': 5.23.4 - '@algolia/requester-browser-xhr@4.19.1': + '@algolia/requester-browser-xhr@5.23.4': dependencies: - '@algolia/requester-common': 4.19.1 + '@algolia/client-common': 5.23.4 - '@algolia/requester-common@4.19.1': {} + '@algolia/requester-browser-xhr@5.29.0': + dependencies: + '@algolia/client-common': 5.29.0 - '@algolia/requester-common@4.23.3': {} + '@algolia/requester-fetch@5.23.4': + dependencies: + '@algolia/client-common': 5.23.4 - '@algolia/requester-node-http@4.19.1': + '@algolia/requester-fetch@5.29.0': dependencies: - '@algolia/requester-common': 4.19.1 + '@algolia/client-common': 5.29.0 - '@algolia/transporter@4.19.1': + '@algolia/requester-node-http@5.23.4': dependencies: - '@algolia/cache-common': 4.19.1 - '@algolia/logger-common': 4.19.1 - '@algolia/requester-common': 4.19.1 + '@algolia/client-common': 5.23.4 - '@algolia/transporter@4.23.3': + '@algolia/requester-node-http@5.29.0': dependencies: - '@algolia/cache-common': 4.23.3 - '@algolia/logger-common': 4.23.3 - '@algolia/requester-common': 4.23.3 + '@algolia/client-common': 5.29.0 - '@babel/helper-string-parser@7.19.4': {} + '@babel/helper-string-parser@7.27.1': {} - '@babel/helper-validator-identifier@7.19.1': {} + '@babel/helper-validator-identifier@7.27.1': {} - '@babel/parser@7.24.4': + '@babel/parser@7.27.5': dependencies: - '@babel/types': 7.20.7 + '@babel/types': 7.27.6 - '@babel/types@7.20.7': + '@babel/types@7.27.6': dependencies: - '@babel/helper-string-parser': 7.19.4 - '@babel/helper-validator-identifier': 7.19.1 - to-fast-properties: 2.0.0 + '@babel/helper-string-parser': 7.27.1 + '@babel/helper-validator-identifier': 7.27.1 - '@docsearch/css@3.6.0': {} + '@docsearch/css@3.8.2': {} - '@docsearch/js@3.6.0(@algolia/client-search@4.23.3)(search-insights@2.13.0)': + '@docsearch/js@3.8.2(@algolia/client-search@5.29.0)(search-insights@2.17.3)': dependencies: - '@docsearch/react': 3.6.0(@algolia/client-search@4.23.3)(search-insights@2.13.0) - preact: 10.11.3 + '@docsearch/react': 3.8.2(@algolia/client-search@5.29.0)(search-insights@2.17.3) + preact: 10.26.5 transitivePeerDependencies: - '@algolia/client-search' - '@types/react' @@ -945,376 +1784,803 @@ snapshots: - react-dom - search-insights - '@docsearch/react@3.6.0(@algolia/client-search@4.23.3)(search-insights@2.13.0)': + '@docsearch/react@3.8.2(@algolia/client-search@5.29.0)(search-insights@2.17.3)': dependencies: - '@algolia/autocomplete-core': 1.9.3(@algolia/client-search@4.23.3)(algoliasearch@4.19.1)(search-insights@2.13.0) - '@algolia/autocomplete-preset-algolia': 1.9.3(@algolia/client-search@4.23.3)(algoliasearch@4.19.1) - '@docsearch/css': 3.6.0 - algoliasearch: 4.19.1 + '@algolia/autocomplete-core': 1.17.7(@algolia/client-search@5.29.0)(algoliasearch@5.23.4)(search-insights@2.17.3) + '@algolia/autocomplete-preset-algolia': 1.17.7(@algolia/client-search@5.29.0)(algoliasearch@5.23.4) + '@docsearch/css': 3.8.2 + algoliasearch: 5.23.4 optionalDependencies: - search-insights: 2.13.0 + search-insights: 2.17.3 transitivePeerDependencies: - '@algolia/client-search' - '@esbuild/aix-ppc64@0.20.2': + '@dprint/darwin-arm64@0.50.1': optional: true - '@esbuild/android-arm64@0.20.2': + '@dprint/darwin-x64@0.50.1': optional: true - '@esbuild/android-arm@0.20.2': + '@dprint/linux-arm64-glibc@0.50.1': optional: true - '@esbuild/android-x64@0.20.2': + '@dprint/linux-arm64-musl@0.50.1': optional: true - '@esbuild/darwin-arm64@0.20.2': + '@dprint/linux-riscv64-glibc@0.50.1': optional: true - '@esbuild/darwin-x64@0.20.2': + '@dprint/linux-x64-glibc@0.50.1': optional: true - '@esbuild/freebsd-arm64@0.20.2': + '@dprint/linux-x64-musl@0.50.1': optional: true - '@esbuild/freebsd-x64@0.20.2': + '@dprint/win32-arm64@0.50.1': optional: true - '@esbuild/linux-arm64@0.20.2': + '@dprint/win32-x64@0.50.1': optional: true - '@esbuild/linux-arm@0.20.2': + '@esbuild/aix-ppc64@0.21.5': optional: true - '@esbuild/linux-ia32@0.20.2': + '@esbuild/aix-ppc64@0.25.2': optional: true - '@esbuild/linux-loong64@0.20.2': + '@esbuild/android-arm64@0.21.5': optional: true - '@esbuild/linux-mips64el@0.20.2': + '@esbuild/android-arm64@0.25.2': optional: true - '@esbuild/linux-ppc64@0.20.2': + '@esbuild/android-arm@0.21.5': optional: true - '@esbuild/linux-riscv64@0.20.2': + '@esbuild/android-arm@0.25.2': optional: true - '@esbuild/linux-s390x@0.20.2': + '@esbuild/android-x64@0.21.5': optional: true - '@esbuild/linux-x64@0.20.2': + '@esbuild/android-x64@0.25.2': optional: true - '@esbuild/netbsd-x64@0.20.2': + '@esbuild/darwin-arm64@0.21.5': optional: true - '@esbuild/openbsd-x64@0.20.2': + '@esbuild/darwin-arm64@0.25.2': optional: true - '@esbuild/sunos-x64@0.20.2': + '@esbuild/darwin-x64@0.21.5': optional: true - '@esbuild/win32-arm64@0.20.2': + '@esbuild/darwin-x64@0.25.2': optional: true - '@esbuild/win32-ia32@0.20.2': + '@esbuild/freebsd-arm64@0.21.5': optional: true - '@esbuild/win32-x64@0.20.2': + '@esbuild/freebsd-arm64@0.25.2': optional: true - '@jridgewell/sourcemap-codec@1.4.15': {} - - '@rollup/rollup-android-arm-eabi@4.13.0': + '@esbuild/freebsd-x64@0.21.5': optional: true - '@rollup/rollup-android-arm64@4.13.0': + '@esbuild/freebsd-x64@0.25.2': optional: true - '@rollup/rollup-darwin-arm64@4.13.0': + '@esbuild/linux-arm64@0.21.5': optional: true - '@rollup/rollup-darwin-x64@4.13.0': + '@esbuild/linux-arm64@0.25.2': optional: true - '@rollup/rollup-linux-arm-gnueabihf@4.13.0': + '@esbuild/linux-arm@0.21.5': optional: true - '@rollup/rollup-linux-arm64-gnu@4.13.0': + '@esbuild/linux-arm@0.25.2': optional: true - '@rollup/rollup-linux-arm64-musl@4.13.0': + '@esbuild/linux-ia32@0.21.5': optional: true - '@rollup/rollup-linux-riscv64-gnu@4.13.0': + '@esbuild/linux-ia32@0.25.2': optional: true - '@rollup/rollup-linux-x64-gnu@4.13.0': + '@esbuild/linux-loong64@0.21.5': optional: true - '@rollup/rollup-linux-x64-musl@4.13.0': + '@esbuild/linux-loong64@0.25.2': optional: true - '@rollup/rollup-win32-arm64-msvc@4.13.0': + '@esbuild/linux-mips64el@0.21.5': optional: true - '@rollup/rollup-win32-ia32-msvc@4.13.0': + '@esbuild/linux-mips64el@0.25.2': optional: true - '@rollup/rollup-win32-x64-msvc@4.13.0': + '@esbuild/linux-ppc64@0.21.5': optional: true - '@shikijs/core@1.5.2': {} + '@esbuild/linux-ppc64@0.25.2': + optional: true - '@shikijs/transformers@1.5.2': - dependencies: - shiki: 1.5.2 + '@esbuild/linux-riscv64@0.21.5': + optional: true - '@types/estree@1.0.5': {} + '@esbuild/linux-riscv64@0.25.2': + optional: true - '@types/js-yaml@4.0.9': {} + '@esbuild/linux-s390x@0.21.5': + optional: true - '@types/json-schema@7.0.11': {} + '@esbuild/linux-s390x@0.25.2': + optional: true - '@types/linkify-it@5.0.0': {} + '@esbuild/linux-x64@0.21.5': + optional: true - '@types/markdown-it@14.1.1': - dependencies: - '@types/linkify-it': 5.0.0 - '@types/mdurl': 2.0.0 + '@esbuild/linux-x64@0.25.2': + optional: true - '@types/mdurl@2.0.0': {} + '@esbuild/netbsd-arm64@0.25.2': + optional: true - '@types/web-bluetooth@0.0.20': {} + '@esbuild/netbsd-x64@0.21.5': + optional: true - '@vitejs/plugin-vue@5.0.4(vite@5.2.11)(vue@3.4.27(typescript@5.4.5))': - dependencies: - vite: 5.2.11 - vue: 3.4.27(typescript@5.4.5) + '@esbuild/netbsd-x64@0.25.2': + optional: true - '@volar/language-core@2.2.4': - dependencies: - '@volar/source-map': 2.2.4 + '@esbuild/openbsd-arm64@0.25.2': + optional: true - '@volar/source-map@2.2.4': - dependencies: - muggle-string: 0.4.1 + '@esbuild/openbsd-x64@0.21.5': + optional: true - '@volar/typescript@2.2.4': - dependencies: - '@volar/language-core': 2.2.4 - path-browserify: 1.0.1 + '@esbuild/openbsd-x64@0.25.2': + optional: true - '@vue/compiler-core@3.4.27': - dependencies: - '@babel/parser': 7.24.4 - '@vue/shared': 3.4.27 - entities: 4.5.0 - estree-walker: 2.0.2 - source-map-js: 1.2.0 + '@esbuild/sunos-x64@0.21.5': + optional: true - '@vue/compiler-dom@3.4.27': - dependencies: - '@vue/compiler-core': 3.4.27 - '@vue/shared': 3.4.27 + '@esbuild/sunos-x64@0.25.2': + optional: true - '@vue/compiler-sfc@3.4.27': - dependencies: - '@babel/parser': 7.24.4 - '@vue/compiler-core': 3.4.27 - '@vue/compiler-dom': 3.4.27 - '@vue/compiler-ssr': 3.4.27 - '@vue/shared': 3.4.27 - estree-walker: 2.0.2 - magic-string: 0.30.10 - postcss: 8.4.38 - source-map-js: 1.2.0 + '@esbuild/win32-arm64@0.21.5': + optional: true - '@vue/compiler-ssr@3.4.27': - dependencies: - '@vue/compiler-dom': 3.4.27 - '@vue/shared': 3.4.27 + '@esbuild/win32-arm64@0.25.2': + optional: true - '@vue/devtools-api@7.2.0(vue@3.4.27(typescript@5.4.5))': - dependencies: - '@vue/devtools-kit': 7.2.0(vue@3.4.27(typescript@5.4.5)) - transitivePeerDependencies: - - vue + '@esbuild/win32-ia32@0.21.5': + optional: true - '@vue/devtools-kit@7.2.0(vue@3.4.27(typescript@5.4.5))': - dependencies: - '@vue/devtools-shared': 7.2.0 - hookable: 5.5.3 - mitt: 3.0.1 - perfect-debounce: 1.0.0 - speakingurl: 14.0.1 - vue: 3.4.27(typescript@5.4.5) + '@esbuild/win32-ia32@0.25.2': + optional: true - '@vue/devtools-shared@7.2.0': - dependencies: - rfdc: 1.3.1 + '@esbuild/win32-x64@0.21.5': + optional: true - '@vue/language-core@2.0.19(typescript@5.4.5)': - dependencies: - '@volar/language-core': 2.2.4 - '@vue/compiler-dom': 3.4.27 - '@vue/shared': 3.4.27 - computeds: 0.0.1 - minimatch: 9.0.3 - path-browserify: 1.0.1 - vue-template-compiler: 2.7.14 - optionalDependencies: - typescript: 5.4.5 + '@esbuild/win32-x64@0.25.2': + optional: true - '@vue/reactivity@3.4.27': + '@iconify-json/simple-icons@1.2.32': dependencies: - '@vue/shared': 3.4.27 + '@iconify/types': 2.0.0 - '@vue/runtime-core@3.4.27': - dependencies: - '@vue/reactivity': 3.4.27 - '@vue/shared': 3.4.27 + '@iconify/types@2.0.0': {} - '@vue/runtime-dom@3.4.27': - dependencies: - '@vue/runtime-core': 3.4.27 - '@vue/shared': 3.4.27 - csstype: 3.1.3 + '@isaacs/balanced-match@4.0.1': {} - '@vue/server-renderer@3.4.27(vue@3.4.27(typescript@5.4.5))': + '@isaacs/brace-expansion@5.0.0': dependencies: - '@vue/compiler-ssr': 3.4.27 - '@vue/shared': 3.4.27 - vue: 3.4.27(typescript@5.4.5) + '@isaacs/balanced-match': 4.0.1 - '@vue/shared@3.4.27': {} + '@jridgewell/sourcemap-codec@1.5.0': {} - '@vueuse/core@10.9.0(vue@3.4.27(typescript@5.4.5))': + '@number-flow/vue@0.4.8(vue@3.5.17(typescript@5.8.3))': dependencies: - '@types/web-bluetooth': 0.0.20 - '@vueuse/metadata': 10.9.0 - '@vueuse/shared': 10.9.0(vue@3.4.27(typescript@5.4.5)) - vue-demi: 0.14.7(vue@3.4.27(typescript@5.4.5)) - transitivePeerDependencies: - - '@vue/composition-api' - - vue + esm-env: 1.2.2 + number-flow: 0.5.8 + vue: 3.5.17(typescript@5.8.3) - '@vueuse/integrations@10.9.0(focus-trap@7.5.4)(vue@3.4.27(typescript@5.4.5))': - dependencies: - '@vueuse/core': 10.9.0(vue@3.4.27(typescript@5.4.5)) - '@vueuse/shared': 10.9.0(vue@3.4.27(typescript@5.4.5)) - vue-demi: 0.14.7(vue@3.4.27(typescript@5.4.5)) - optionalDependencies: - focus-trap: 7.5.4 - transitivePeerDependencies: - - '@vue/composition-api' - - vue + '@oxlint/darwin-arm64@1.7.0': + optional: true - '@vueuse/metadata@10.9.0': {} + '@oxlint/darwin-x64@1.7.0': + optional: true - '@vueuse/shared@10.9.0(vue@3.4.27(typescript@5.4.5))': - dependencies: - vue-demi: 0.14.7(vue@3.4.27(typescript@5.4.5)) - transitivePeerDependencies: - - '@vue/composition-api' - - vue - - algoliasearch@4.19.1: - dependencies: - '@algolia/cache-browser-local-storage': 4.19.1 - '@algolia/cache-common': 4.19.1 - '@algolia/cache-in-memory': 4.19.1 - '@algolia/client-account': 4.19.1 - '@algolia/client-analytics': 4.19.1 - '@algolia/client-common': 4.19.1 - '@algolia/client-personalization': 4.19.1 - '@algolia/client-search': 4.19.1 - '@algolia/logger-common': 4.19.1 - '@algolia/logger-console': 4.19.1 - '@algolia/requester-browser-xhr': 4.19.1 - '@algolia/requester-common': 4.19.1 - '@algolia/requester-node-http': 4.19.1 - '@algolia/transporter': 4.19.1 + '@oxlint/linux-arm64-gnu@1.7.0': + optional: true - argparse@2.0.1: {} + '@oxlint/linux-arm64-musl@1.7.0': + optional: true - ast-grep-wasm@file:pkg: - dependencies: - web-tree-sitter-sg: 0.21.5 + '@oxlint/linux-x64-gnu@1.7.0': optional: true - balanced-match@1.0.2: {} + '@oxlint/linux-x64-musl@1.7.0': + optional: true - brace-expansion@2.0.1: - dependencies: - balanced-match: 1.0.2 + '@oxlint/win32-arm64@1.7.0': + optional: true - computeds@0.0.1: {} + '@oxlint/win32-x64@1.7.0': + optional: true - csstype@3.1.3: {} + '@rollup/rollup-android-arm-eabi@4.41.1': + optional: true - de-indent@1.0.2: {} + '@rollup/rollup-android-arm64@4.41.1': + optional: true - entities@4.5.0: {} + '@rollup/rollup-darwin-arm64@4.41.1': + optional: true - esbuild@0.20.2: - optionalDependencies: - '@esbuild/aix-ppc64': 0.20.2 - '@esbuild/android-arm': 0.20.2 - '@esbuild/android-arm64': 0.20.2 - '@esbuild/android-x64': 0.20.2 - '@esbuild/darwin-arm64': 0.20.2 - '@esbuild/darwin-x64': 0.20.2 - '@esbuild/freebsd-arm64': 0.20.2 - '@esbuild/freebsd-x64': 0.20.2 - '@esbuild/linux-arm': 0.20.2 - '@esbuild/linux-arm64': 0.20.2 - '@esbuild/linux-ia32': 0.20.2 - '@esbuild/linux-loong64': 0.20.2 - '@esbuild/linux-mips64el': 0.20.2 - '@esbuild/linux-ppc64': 0.20.2 - '@esbuild/linux-riscv64': 0.20.2 - '@esbuild/linux-s390x': 0.20.2 - '@esbuild/linux-x64': 0.20.2 - '@esbuild/netbsd-x64': 0.20.2 - '@esbuild/openbsd-x64': 0.20.2 - '@esbuild/sunos-x64': 0.20.2 - '@esbuild/win32-arm64': 0.20.2 - '@esbuild/win32-ia32': 0.20.2 - '@esbuild/win32-x64': 0.20.2 + '@rollup/rollup-darwin-x64@4.41.1': + optional: true - estree-walker@2.0.2: {} + '@rollup/rollup-freebsd-arm64@4.41.1': + optional: true - focus-trap@7.5.4: - dependencies: - tabbable: 6.2.0 + '@rollup/rollup-freebsd-x64@4.41.1': + optional: true - fsevents@2.3.3: + '@rollup/rollup-linux-arm-gnueabihf@4.41.1': optional: true - he@1.2.0: {} + '@rollup/rollup-linux-arm-musleabihf@4.41.1': + optional: true + + '@rollup/rollup-linux-arm64-gnu@4.41.1': + optional: true + + '@rollup/rollup-linux-arm64-musl@4.41.1': + optional: true + + '@rollup/rollup-linux-loongarch64-gnu@4.41.1': + optional: true + + '@rollup/rollup-linux-powerpc64le-gnu@4.41.1': + optional: true + + '@rollup/rollup-linux-riscv64-gnu@4.41.1': + optional: true + + '@rollup/rollup-linux-riscv64-musl@4.41.1': + optional: true + + '@rollup/rollup-linux-s390x-gnu@4.41.1': + optional: true + + '@rollup/rollup-linux-x64-gnu@4.41.1': + optional: true + + '@rollup/rollup-linux-x64-musl@4.41.1': + optional: true + + '@rollup/rollup-win32-arm64-msvc@4.41.1': + optional: true + + '@rollup/rollup-win32-ia32-msvc@4.41.1': + optional: true + + '@rollup/rollup-win32-x64-msvc@4.41.1': + optional: true + + '@shikijs/core@2.5.0': + dependencies: + '@shikijs/engine-javascript': 2.5.0 + '@shikijs/engine-oniguruma': 2.5.0 + '@shikijs/types': 2.5.0 + '@shikijs/vscode-textmate': 10.0.2 + '@types/hast': 3.0.4 + hast-util-to-html: 9.0.5 + + '@shikijs/engine-javascript@2.5.0': + dependencies: + '@shikijs/types': 2.5.0 + '@shikijs/vscode-textmate': 10.0.2 + oniguruma-to-es: 3.1.1 + + '@shikijs/engine-oniguruma@2.5.0': + dependencies: + '@shikijs/types': 2.5.0 + '@shikijs/vscode-textmate': 10.0.2 + + '@shikijs/langs@2.5.0': + dependencies: + '@shikijs/types': 2.5.0 + + '@shikijs/themes@2.5.0': + dependencies: + '@shikijs/types': 2.5.0 + + '@shikijs/transformers@2.5.0': + dependencies: + '@shikijs/core': 2.5.0 + '@shikijs/types': 2.5.0 + + '@shikijs/types@2.5.0': + dependencies: + '@shikijs/vscode-textmate': 10.0.2 + '@types/hast': 3.0.4 + + '@shikijs/vscode-textmate@10.0.2': {} + + '@types/debug@4.1.12': + dependencies: + '@types/ms': 2.1.0 + + '@types/estree@1.0.7': {} + + '@types/hast@3.0.4': + dependencies: + '@types/unist': 3.0.3 + + '@types/js-yaml@4.0.9': {} + + '@types/linkify-it@5.0.0': {} + + '@types/markdown-it@14.1.2': + dependencies: + '@types/linkify-it': 5.0.0 + '@types/mdurl': 2.0.0 + + '@types/mdast@4.0.4': + dependencies: + '@types/unist': 3.0.3 + + '@types/mdurl@2.0.0': {} + + '@types/ms@2.1.0': {} + + '@types/unist@3.0.3': {} + + '@types/web-bluetooth@0.0.21': {} + + '@ungap/structured-clone@1.3.0': {} + + '@vitejs/plugin-vue@5.2.3(vite@5.4.19(lightningcss@1.30.1))(vue@3.5.17(typescript@5.8.3))': + dependencies: + vite: 5.4.19(lightningcss@1.30.1) + vue: 3.5.17(typescript@5.8.3) + + '@volar/language-core@2.4.17': + dependencies: + '@volar/source-map': 2.4.17 + + '@volar/source-map@2.4.17': {} + + '@volar/typescript@2.4.17': + dependencies: + '@volar/language-core': 2.4.17 + path-browserify: 1.0.1 + vscode-uri: 3.1.0 + + '@vue/compiler-core@3.5.17': + dependencies: + '@babel/parser': 7.27.5 + '@vue/shared': 3.5.17 + entities: 4.5.0 + estree-walker: 2.0.2 + source-map-js: 1.2.1 + + '@vue/compiler-dom@3.5.17': + dependencies: + '@vue/compiler-core': 3.5.17 + '@vue/shared': 3.5.17 + + '@vue/compiler-sfc@3.5.17': + dependencies: + '@babel/parser': 7.27.5 + '@vue/compiler-core': 3.5.17 + '@vue/compiler-dom': 3.5.17 + '@vue/compiler-ssr': 3.5.17 + '@vue/shared': 3.5.17 + estree-walker: 2.0.2 + magic-string: 0.30.17 + postcss: 8.5.6 + source-map-js: 1.2.1 + + '@vue/compiler-ssr@3.5.17': + dependencies: + '@vue/compiler-dom': 3.5.17 + '@vue/shared': 3.5.17 + + '@vue/compiler-vue2@2.7.16': + dependencies: + de-indent: 1.0.2 + he: 1.2.0 + + '@vue/devtools-api@7.7.2': + dependencies: + '@vue/devtools-kit': 7.7.2 + + '@vue/devtools-kit@7.7.2': + dependencies: + '@vue/devtools-shared': 7.7.2 + birpc: 0.2.19 + hookable: 5.5.3 + mitt: 3.0.1 + perfect-debounce: 1.0.0 + speakingurl: 14.0.1 + superjson: 2.2.2 + + '@vue/devtools-shared@7.7.2': + dependencies: + rfdc: 1.4.1 + + '@vue/language-core@3.0.1(typescript@5.8.3)': + dependencies: + '@volar/language-core': 2.4.17 + '@vue/compiler-dom': 3.5.17 + '@vue/compiler-vue2': 2.7.16 + '@vue/shared': 3.5.17 + alien-signals: 2.0.5 + minimatch: 10.0.3 + muggle-string: 0.4.1 + path-browserify: 1.0.1 + optionalDependencies: + typescript: 5.8.3 + + '@vue/reactivity@3.5.17': + dependencies: + '@vue/shared': 3.5.17 + + '@vue/runtime-core@3.5.17': + dependencies: + '@vue/reactivity': 3.5.17 + '@vue/shared': 3.5.17 + + '@vue/runtime-dom@3.5.17': + dependencies: + '@vue/reactivity': 3.5.17 + '@vue/runtime-core': 3.5.17 + '@vue/shared': 3.5.17 + csstype: 3.1.3 + + '@vue/server-renderer@3.5.17(vue@3.5.17(typescript@5.8.3))': + dependencies: + '@vue/compiler-ssr': 3.5.17 + '@vue/shared': 3.5.17 + vue: 3.5.17(typescript@5.8.3) + + '@vue/shared@3.5.13': {} + + '@vue/shared@3.5.17': {} + + '@vueuse/core@12.8.2(typescript@5.8.3)': + dependencies: + '@types/web-bluetooth': 0.0.21 + '@vueuse/metadata': 12.8.2 + '@vueuse/shared': 12.8.2(typescript@5.8.3) + vue: 3.5.17(typescript@5.8.3) + transitivePeerDependencies: + - typescript + + '@vueuse/integrations@12.8.2(focus-trap@7.6.4)(typescript@5.8.3)': + dependencies: + '@vueuse/core': 12.8.2(typescript@5.8.3) + '@vueuse/shared': 12.8.2(typescript@5.8.3) + vue: 3.5.17(typescript@5.8.3) + optionalDependencies: + focus-trap: 7.6.4 + transitivePeerDependencies: + - typescript + + '@vueuse/metadata@12.8.2': {} + + '@vueuse/shared@12.8.2(typescript@5.8.3)': + dependencies: + vue: 3.5.17(typescript@5.8.3) + transitivePeerDependencies: + - typescript + + algoliasearch@5.23.4: + dependencies: + '@algolia/client-abtesting': 5.23.4 + '@algolia/client-analytics': 5.23.4 + '@algolia/client-common': 5.23.4 + '@algolia/client-insights': 5.23.4 + '@algolia/client-personalization': 5.23.4 + '@algolia/client-query-suggestions': 5.23.4 + '@algolia/client-search': 5.23.4 + '@algolia/ingestion': 1.23.4 + '@algolia/monitoring': 1.23.4 + '@algolia/recommend': 5.23.4 + '@algolia/requester-browser-xhr': 5.23.4 + '@algolia/requester-fetch': 5.23.4 + '@algolia/requester-node-http': 5.23.4 + + alien-signals@2.0.5: {} + + ansi-regex@5.0.1: {} + + ansi-styles@4.3.0: + dependencies: + color-convert: 2.0.1 + + argparse@1.0.10: + dependencies: + sprintf-js: 1.0.3 + + argparse@2.0.1: {} + + ast-grep-wasm@file:pkg: + dependencies: + web-tree-sitter: 0.25.3 + optional: true + + bail@2.0.2: {} + + birpc@0.2.19: {} + + byte-size@9.0.1: {} + + ccount@2.0.1: {} + + character-entities-html4@2.1.0: {} + + character-entities-legacy@3.0.0: {} + + character-entities@2.0.2: {} + + cliui@8.0.1: + dependencies: + string-width: 4.2.3 + strip-ansi: 6.0.1 + wrap-ansi: 7.0.0 + + color-convert@2.0.1: + dependencies: + color-name: 1.1.4 + + color-name@1.1.4: {} + + comma-separated-tokens@2.0.3: {} + + copy-anything@3.0.5: + dependencies: + is-what: 4.1.16 + + csstype@3.1.3: {} + + de-indent@1.0.2: {} + + debug@4.4.0: + dependencies: + ms: 2.1.3 + + decode-named-character-reference@1.1.0: + dependencies: + character-entities: 2.0.2 + + dequal@2.0.3: {} + + detect-libc@2.0.4: + optional: true + + devlop@1.1.0: + dependencies: + dequal: 2.0.3 + + dprint@0.50.1: + optionalDependencies: + '@dprint/darwin-arm64': 0.50.1 + '@dprint/darwin-x64': 0.50.1 + '@dprint/linux-arm64-glibc': 0.50.1 + '@dprint/linux-arm64-musl': 0.50.1 + '@dprint/linux-riscv64-glibc': 0.50.1 + '@dprint/linux-x64-glibc': 0.50.1 + '@dprint/linux-x64-musl': 0.50.1 + '@dprint/win32-arm64': 0.50.1 + '@dprint/win32-x64': 0.50.1 + + emoji-regex-xs@1.0.0: {} + + emoji-regex@8.0.0: {} + + entities@4.5.0: {} + + esbuild@0.21.5: + optionalDependencies: + '@esbuild/aix-ppc64': 0.21.5 + '@esbuild/android-arm': 0.21.5 + '@esbuild/android-arm64': 0.21.5 + '@esbuild/android-x64': 0.21.5 + '@esbuild/darwin-arm64': 0.21.5 + '@esbuild/darwin-x64': 0.21.5 + '@esbuild/freebsd-arm64': 0.21.5 + '@esbuild/freebsd-x64': 0.21.5 + '@esbuild/linux-arm': 0.21.5 + '@esbuild/linux-arm64': 0.21.5 + '@esbuild/linux-ia32': 0.21.5 + '@esbuild/linux-loong64': 0.21.5 + '@esbuild/linux-mips64el': 0.21.5 + '@esbuild/linux-ppc64': 0.21.5 + '@esbuild/linux-riscv64': 0.21.5 + '@esbuild/linux-s390x': 0.21.5 + '@esbuild/linux-x64': 0.21.5 + '@esbuild/netbsd-x64': 0.21.5 + '@esbuild/openbsd-x64': 0.21.5 + '@esbuild/sunos-x64': 0.21.5 + '@esbuild/win32-arm64': 0.21.5 + '@esbuild/win32-ia32': 0.21.5 + '@esbuild/win32-x64': 0.21.5 + + esbuild@0.25.2: + optionalDependencies: + '@esbuild/aix-ppc64': 0.25.2 + '@esbuild/android-arm': 0.25.2 + '@esbuild/android-arm64': 0.25.2 + '@esbuild/android-x64': 0.25.2 + '@esbuild/darwin-arm64': 0.25.2 + '@esbuild/darwin-x64': 0.25.2 + '@esbuild/freebsd-arm64': 0.25.2 + '@esbuild/freebsd-x64': 0.25.2 + '@esbuild/linux-arm': 0.25.2 + '@esbuild/linux-arm64': 0.25.2 + '@esbuild/linux-ia32': 0.25.2 + '@esbuild/linux-loong64': 0.25.2 + '@esbuild/linux-mips64el': 0.25.2 + '@esbuild/linux-ppc64': 0.25.2 + '@esbuild/linux-riscv64': 0.25.2 + '@esbuild/linux-s390x': 0.25.2 + '@esbuild/linux-x64': 0.25.2 + '@esbuild/netbsd-arm64': 0.25.2 + '@esbuild/netbsd-x64': 0.25.2 + '@esbuild/openbsd-arm64': 0.25.2 + '@esbuild/openbsd-x64': 0.25.2 + '@esbuild/sunos-x64': 0.25.2 + '@esbuild/win32-arm64': 0.25.2 + '@esbuild/win32-ia32': 0.25.2 + '@esbuild/win32-x64': 0.25.2 + + escalade@3.2.0: {} + + escape-string-regexp@5.0.0: {} + + esm-env@1.2.2: {} + + esprima@4.0.1: {} + + estree-walker@2.0.2: {} + + extend-shallow@2.0.1: + dependencies: + is-extendable: 0.1.1 + + extend@3.0.2: {} + + fault@2.0.1: + dependencies: + format: 0.2.2 + + fdir@6.4.6(picomatch@4.0.2): + optionalDependencies: + picomatch: 4.0.2 + + focus-trap@7.6.4: + dependencies: + tabbable: 6.2.0 + + format@0.2.2: {} + + fsevents@2.3.3: + optional: true + + get-caller-file@2.0.5: {} + + gray-matter@4.0.3: + dependencies: + js-yaml: 3.14.1 + kind-of: 6.0.3 + section-matter: 1.0.0 + strip-bom-string: 1.0.0 + + hast-util-to-html@9.0.5: + dependencies: + '@types/hast': 3.0.4 + '@types/unist': 3.0.3 + ccount: 2.0.1 + comma-separated-tokens: 2.0.3 + hast-util-whitespace: 3.0.0 + html-void-elements: 3.0.0 + mdast-util-to-hast: 13.2.0 + property-information: 7.0.0 + space-separated-tokens: 2.0.2 + stringify-entities: 4.0.4 + zwitch: 2.0.4 + + hast-util-whitespace@3.0.0: + dependencies: + '@types/hast': 3.0.4 + + he@1.2.0: {} hookable@5.5.3: {} + html-void-elements@3.0.0: {} + + is-extendable@0.1.1: {} + + is-fullwidth-code-point@3.0.0: {} + + is-plain-obj@4.1.0: {} + + is-what@4.1.16: {} + + js-yaml@3.14.1: + dependencies: + argparse: 1.0.10 + esprima: 4.0.1 + js-yaml@4.1.0: dependencies: argparse: 2.0.1 - jsonc-parser@3.2.0: {} + jsonc-parser@3.3.1: {} + + kind-of@6.0.3: {} + + lightningcss-darwin-arm64@1.30.1: + optional: true + + lightningcss-darwin-x64@1.30.1: + optional: true + + lightningcss-freebsd-x64@1.30.1: + optional: true + + lightningcss-linux-arm-gnueabihf@1.30.1: + optional: true + + lightningcss-linux-arm64-gnu@1.30.1: + optional: true + + lightningcss-linux-arm64-musl@1.30.1: + optional: true + + lightningcss-linux-x64-gnu@1.30.1: + optional: true + + lightningcss-linux-x64-musl@1.30.1: + optional: true + + lightningcss-win32-arm64-msvc@1.30.1: + optional: true + + lightningcss-win32-x64-msvc@1.30.1: + optional: true + + lightningcss@1.30.1: + dependencies: + detect-libc: 2.0.4 + optionalDependencies: + lightningcss-darwin-arm64: 1.30.1 + lightningcss-darwin-x64: 1.30.1 + lightningcss-freebsd-x64: 1.30.1 + lightningcss-linux-arm-gnueabihf: 1.30.1 + lightningcss-linux-arm64-gnu: 1.30.1 + lightningcss-linux-arm64-musl: 1.30.1 + lightningcss-linux-x64-gnu: 1.30.1 + lightningcss-linux-x64-musl: 1.30.1 + lightningcss-win32-arm64-msvc: 1.30.1 + lightningcss-win32-x64-msvc: 1.30.1 + optional: true linkify-it@5.0.0: dependencies: uc.micro: 2.1.0 - lru-cache@6.0.0: - dependencies: - yallist: 4.0.0 + longest-streak@3.1.0: {} - magic-string@0.30.10: + magic-string@0.30.17: dependencies: - '@jridgewell/sourcemap-codec': 1.4.15 + '@jridgewell/sourcemap-codec': 1.5.0 mark.js@8.11.1: {} @@ -1327,148 +2593,560 @@ snapshots: punycode.js: 2.3.1 uc.micro: 2.1.0 + markdown-title@1.0.2: {} + + mdast-util-from-markdown@2.0.2: + dependencies: + '@types/mdast': 4.0.4 + '@types/unist': 3.0.3 + decode-named-character-reference: 1.1.0 + devlop: 1.1.0 + mdast-util-to-string: 4.0.0 + micromark: 4.0.2 + micromark-util-decode-numeric-character-reference: 2.0.2 + micromark-util-decode-string: 2.0.1 + micromark-util-normalize-identifier: 2.0.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + unist-util-stringify-position: 4.0.0 + transitivePeerDependencies: + - supports-color + + mdast-util-frontmatter@2.0.1: + dependencies: + '@types/mdast': 4.0.4 + devlop: 1.1.0 + escape-string-regexp: 5.0.0 + mdast-util-from-markdown: 2.0.2 + mdast-util-to-markdown: 2.1.2 + micromark-extension-frontmatter: 2.0.0 + transitivePeerDependencies: + - supports-color + + mdast-util-phrasing@4.1.0: + dependencies: + '@types/mdast': 4.0.4 + unist-util-is: 6.0.0 + + mdast-util-to-hast@13.2.0: + dependencies: + '@types/hast': 3.0.4 + '@types/mdast': 4.0.4 + '@ungap/structured-clone': 1.3.0 + devlop: 1.1.0 + micromark-util-sanitize-uri: 2.0.1 + trim-lines: 3.0.1 + unist-util-position: 5.0.0 + unist-util-visit: 5.0.0 + vfile: 6.0.3 + + mdast-util-to-markdown@2.1.2: + dependencies: + '@types/mdast': 4.0.4 + '@types/unist': 3.0.3 + longest-streak: 3.1.0 + mdast-util-phrasing: 4.1.0 + mdast-util-to-string: 4.0.0 + micromark-util-classify-character: 2.0.1 + micromark-util-decode-string: 2.0.1 + unist-util-visit: 5.0.0 + zwitch: 2.0.4 + + mdast-util-to-string@4.0.0: + dependencies: + '@types/mdast': 4.0.4 + mdurl@2.0.0: {} - minimatch@9.0.3: + micromark-core-commonmark@2.0.3: + dependencies: + decode-named-character-reference: 1.1.0 + devlop: 1.1.0 + micromark-factory-destination: 2.0.1 + micromark-factory-label: 2.0.1 + micromark-factory-space: 2.0.1 + micromark-factory-title: 2.0.1 + micromark-factory-whitespace: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-chunked: 2.0.1 + micromark-util-classify-character: 2.0.1 + micromark-util-html-tag-name: 2.0.1 + micromark-util-normalize-identifier: 2.0.1 + micromark-util-resolve-all: 2.0.1 + micromark-util-subtokenize: 2.1.0 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-extension-frontmatter@2.0.0: + dependencies: + fault: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-factory-destination@2.0.1: + dependencies: + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-factory-label@2.0.1: + dependencies: + devlop: 1.1.0 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-factory-space@2.0.1: + dependencies: + micromark-util-character: 2.1.1 + micromark-util-types: 2.0.2 + + micromark-factory-title@2.0.1: + dependencies: + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-factory-whitespace@2.0.1: + dependencies: + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-util-character@2.1.1: + dependencies: + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-util-chunked@2.0.1: + dependencies: + micromark-util-symbol: 2.0.1 + + micromark-util-classify-character@2.0.1: + dependencies: + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-util-combine-extensions@2.0.1: + dependencies: + micromark-util-chunked: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-util-decode-numeric-character-reference@2.0.2: + dependencies: + micromark-util-symbol: 2.0.1 + + micromark-util-decode-string@2.0.1: + dependencies: + decode-named-character-reference: 1.1.0 + micromark-util-character: 2.1.1 + micromark-util-decode-numeric-character-reference: 2.0.2 + micromark-util-symbol: 2.0.1 + + micromark-util-encode@2.0.1: {} + + micromark-util-html-tag-name@2.0.1: {} + + micromark-util-normalize-identifier@2.0.1: dependencies: - brace-expansion: 2.0.1 + micromark-util-symbol: 2.0.1 - minisearch@6.3.0: {} + micromark-util-resolve-all@2.0.1: + dependencies: + micromark-util-types: 2.0.2 + + micromark-util-sanitize-uri@2.0.1: + dependencies: + micromark-util-character: 2.1.1 + micromark-util-encode: 2.0.1 + micromark-util-symbol: 2.0.1 + + micromark-util-subtokenize@2.1.0: + dependencies: + devlop: 1.1.0 + micromark-util-chunked: 2.0.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-util-symbol@2.0.1: {} + + micromark-util-types@2.0.2: {} + + micromark@4.0.2: + dependencies: + '@types/debug': 4.1.12 + debug: 4.4.0 + decode-named-character-reference: 1.1.0 + devlop: 1.1.0 + micromark-core-commonmark: 2.0.3 + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-chunked: 2.0.1 + micromark-util-combine-extensions: 2.0.1 + micromark-util-decode-numeric-character-reference: 2.0.2 + micromark-util-encode: 2.0.1 + micromark-util-normalize-identifier: 2.0.1 + micromark-util-resolve-all: 2.0.1 + micromark-util-sanitize-uri: 2.0.1 + micromark-util-subtokenize: 2.1.0 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + transitivePeerDependencies: + - supports-color + + millify@6.1.0: + dependencies: + yargs: 17.7.2 + + minimatch@10.0.3: + dependencies: + '@isaacs/brace-expansion': 5.0.0 + + minisearch@7.1.2: {} mitt@3.0.1: {} - monaco-editor@0.48.0: {} + monaco-editor@0.52.2: {} - monaco-languageserver-types@0.3.2: + monaco-languageserver-types@0.4.0: dependencies: monaco-types: 0.1.0 - vscode-languageserver-protocol: 3.17.3 - vscode-uri: 3.0.7 + vscode-languageserver-protocol: 3.17.5 + vscode-uri: 3.1.0 - monaco-marker-data-provider@1.1.1(monaco-editor@0.48.0): + monaco-marker-data-provider@1.2.4: dependencies: - monaco-editor: 0.48.0 + monaco-types: 0.1.0 monaco-types@0.1.0: {} - monaco-worker-manager@2.0.1(monaco-editor@0.48.0): + monaco-worker-manager@2.0.1(monaco-editor@0.52.2): dependencies: - monaco-editor: 0.48.0 + monaco-editor: 0.52.2 - monaco-yaml@5.1.1(monaco-editor@0.48.0): + monaco-yaml@5.4.0(monaco-editor@0.52.2): dependencies: - '@types/json-schema': 7.0.11 - jsonc-parser: 3.2.0 - monaco-editor: 0.48.0 - monaco-languageserver-types: 0.3.2 - monaco-marker-data-provider: 1.1.1(monaco-editor@0.48.0) + jsonc-parser: 3.3.1 + monaco-editor: 0.52.2 + monaco-languageserver-types: 0.4.0 + monaco-marker-data-provider: 1.2.4 monaco-types: 0.1.0 - monaco-worker-manager: 2.0.1(monaco-editor@0.48.0) + monaco-worker-manager: 2.0.1(monaco-editor@0.52.2) path-browserify: 1.0.1 - prettier: 2.8.2 - vscode-languageserver-textdocument: 1.0.8 - vscode-languageserver-types: 3.17.3 - vscode-uri: 3.0.7 - yaml: 2.2.1 + prettier: 3.5.3 + vscode-languageserver-textdocument: 1.0.12 + vscode-languageserver-types: 3.17.5 + vscode-uri: 3.1.0 + yaml: 2.7.1 + + ms@2.1.3: {} muggle-string@0.4.1: {} - nanoid@3.3.7: {} + nanoid@3.3.11: {} + + number-flow@0.5.8: + dependencies: + esm-env: 1.2.2 + + oniguruma-to-es@3.1.1: + dependencies: + emoji-regex-xs: 1.0.0 + regex: 6.0.1 + regex-recursion: 6.0.2 + + oxlint@1.7.0: + optionalDependencies: + '@oxlint/darwin-arm64': 1.7.0 + '@oxlint/darwin-x64': 1.7.0 + '@oxlint/linux-arm64-gnu': 1.7.0 + '@oxlint/linux-arm64-musl': 1.7.0 + '@oxlint/linux-x64-gnu': 1.7.0 + '@oxlint/linux-x64-musl': 1.7.0 + '@oxlint/win32-arm64': 1.7.0 + '@oxlint/win32-x64': 1.7.0 path-browserify@1.0.1: {} + path-to-regexp@8.2.0: {} + perfect-debounce@1.0.0: {} - picocolors@1.0.0: {} + picocolors@1.1.1: {} + + picomatch@4.0.2: {} - postcss@8.4.38: + postcss@8.5.6: dependencies: - nanoid: 3.3.7 - picocolors: 1.0.0 - source-map-js: 1.2.0 + nanoid: 3.3.11 + picocolors: 1.1.1 + source-map-js: 1.2.1 + + preact@10.26.5: {} - preact@10.11.3: {} + prettier@3.5.3: {} - prettier@2.8.2: {} + property-information@7.0.0: {} punycode.js@2.3.1: {} - rfdc@1.3.1: {} + regex-recursion@6.0.2: + dependencies: + regex-utilities: 2.3.0 + + regex-utilities@2.3.0: {} + + regex@6.0.1: + dependencies: + regex-utilities: 2.3.0 + + remark-frontmatter@5.0.0: + dependencies: + '@types/mdast': 4.0.4 + mdast-util-frontmatter: 2.0.1 + micromark-extension-frontmatter: 2.0.0 + unified: 11.0.5 + transitivePeerDependencies: + - supports-color - rollup@4.13.0: + remark-parse@11.0.0: dependencies: - '@types/estree': 1.0.5 + '@types/mdast': 4.0.4 + mdast-util-from-markdown: 2.0.2 + micromark-util-types: 2.0.2 + unified: 11.0.5 + transitivePeerDependencies: + - supports-color + + remark-stringify@11.0.0: + dependencies: + '@types/mdast': 4.0.4 + mdast-util-to-markdown: 2.1.2 + unified: 11.0.5 + + remark@15.0.1: + dependencies: + '@types/mdast': 4.0.4 + remark-parse: 11.0.0 + remark-stringify: 11.0.0 + unified: 11.0.5 + transitivePeerDependencies: + - supports-color + + require-directory@2.1.1: {} + + rfdc@1.4.1: {} + + rollup@4.41.1: + dependencies: + '@types/estree': 1.0.7 optionalDependencies: - '@rollup/rollup-android-arm-eabi': 4.13.0 - '@rollup/rollup-android-arm64': 4.13.0 - '@rollup/rollup-darwin-arm64': 4.13.0 - '@rollup/rollup-darwin-x64': 4.13.0 - '@rollup/rollup-linux-arm-gnueabihf': 4.13.0 - '@rollup/rollup-linux-arm64-gnu': 4.13.0 - '@rollup/rollup-linux-arm64-musl': 4.13.0 - '@rollup/rollup-linux-riscv64-gnu': 4.13.0 - '@rollup/rollup-linux-x64-gnu': 4.13.0 - '@rollup/rollup-linux-x64-musl': 4.13.0 - '@rollup/rollup-win32-arm64-msvc': 4.13.0 - '@rollup/rollup-win32-ia32-msvc': 4.13.0 - '@rollup/rollup-win32-x64-msvc': 4.13.0 + '@rollup/rollup-android-arm-eabi': 4.41.1 + '@rollup/rollup-android-arm64': 4.41.1 + '@rollup/rollup-darwin-arm64': 4.41.1 + '@rollup/rollup-darwin-x64': 4.41.1 + '@rollup/rollup-freebsd-arm64': 4.41.1 + '@rollup/rollup-freebsd-x64': 4.41.1 + '@rollup/rollup-linux-arm-gnueabihf': 4.41.1 + '@rollup/rollup-linux-arm-musleabihf': 4.41.1 + '@rollup/rollup-linux-arm64-gnu': 4.41.1 + '@rollup/rollup-linux-arm64-musl': 4.41.1 + '@rollup/rollup-linux-loongarch64-gnu': 4.41.1 + '@rollup/rollup-linux-powerpc64le-gnu': 4.41.1 + '@rollup/rollup-linux-riscv64-gnu': 4.41.1 + '@rollup/rollup-linux-riscv64-musl': 4.41.1 + '@rollup/rollup-linux-s390x-gnu': 4.41.1 + '@rollup/rollup-linux-x64-gnu': 4.41.1 + '@rollup/rollup-linux-x64-musl': 4.41.1 + '@rollup/rollup-win32-arm64-msvc': 4.41.1 + '@rollup/rollup-win32-ia32-msvc': 4.41.1 + '@rollup/rollup-win32-x64-msvc': 4.41.1 fsevents: 2.3.3 - search-insights@2.13.0: {} + search-insights@2.17.3: {} - semver@7.5.4: + section-matter@1.0.0: dependencies: - lru-cache: 6.0.0 + extend-shallow: 2.0.1 + kind-of: 6.0.3 - shiki@1.5.2: + shiki@2.5.0: dependencies: - '@shikijs/core': 1.5.2 + '@shikijs/core': 2.5.0 + '@shikijs/engine-javascript': 2.5.0 + '@shikijs/engine-oniguruma': 2.5.0 + '@shikijs/langs': 2.5.0 + '@shikijs/themes': 2.5.0 + '@shikijs/types': 2.5.0 + '@shikijs/vscode-textmate': 10.0.2 + '@types/hast': 3.0.4 + + source-map-js@1.2.1: {} - source-map-js@1.2.0: {} + space-separated-tokens@2.0.2: {} speakingurl@14.0.1: {} + sprintf-js@1.0.3: {} + + string-width@4.2.3: + dependencies: + emoji-regex: 8.0.0 + is-fullwidth-code-point: 3.0.0 + strip-ansi: 6.0.1 + + stringify-entities@4.0.4: + dependencies: + character-entities-html4: 2.1.0 + character-entities-legacy: 3.0.0 + + strip-ansi@6.0.1: + dependencies: + ansi-regex: 5.0.1 + + strip-bom-string@1.0.0: {} + + superjson@2.2.2: + dependencies: + copy-anything: 3.0.5 + tabbable@6.2.0: {} - to-fast-properties@2.0.0: {} + tinyglobby@0.2.14: + dependencies: + fdir: 6.4.6(picomatch@4.0.2) + picomatch: 4.0.2 + + tokenx@1.1.0: {} + + trim-lines@3.0.1: {} + + trough@2.2.0: {} - typescript@5.4.5: {} + typescript@5.8.3: {} uc.micro@2.1.0: {} - vite@5.2.11: + unified@11.0.5: dependencies: - esbuild: 0.20.2 - postcss: 8.4.38 - rollup: 4.13.0 + '@types/unist': 3.0.3 + bail: 2.0.2 + devlop: 1.1.0 + extend: 3.0.2 + is-plain-obj: 4.1.0 + trough: 2.2.0 + vfile: 6.0.3 + + unist-util-is@6.0.0: + dependencies: + '@types/unist': 3.0.3 + + unist-util-position@5.0.0: + dependencies: + '@types/unist': 3.0.3 + + unist-util-remove@4.0.0: + dependencies: + '@types/unist': 3.0.3 + unist-util-is: 6.0.0 + unist-util-visit-parents: 6.0.1 + + unist-util-stringify-position@4.0.0: + dependencies: + '@types/unist': 3.0.3 + + unist-util-visit-parents@6.0.1: + dependencies: + '@types/unist': 3.0.3 + unist-util-is: 6.0.0 + + unist-util-visit@5.0.0: + dependencies: + '@types/unist': 3.0.3 + unist-util-is: 6.0.0 + unist-util-visit-parents: 6.0.1 + + vfile-message@4.0.2: + dependencies: + '@types/unist': 3.0.3 + unist-util-stringify-position: 4.0.0 + + vfile@6.0.3: + dependencies: + '@types/unist': 3.0.3 + vfile-message: 4.0.2 + + vite@5.4.19(lightningcss@1.30.1): + dependencies: + esbuild: 0.21.5 + postcss: 8.5.6 + rollup: 4.41.1 optionalDependencies: fsevents: 2.3.3 + lightningcss: 1.30.1 - vitepress@1.2.0(@algolia/client-search@4.23.3)(postcss@8.4.38)(search-insights@2.13.0)(typescript@5.4.5): - dependencies: - '@docsearch/css': 3.6.0 - '@docsearch/js': 3.6.0(@algolia/client-search@4.23.3)(search-insights@2.13.0) - '@shikijs/core': 1.5.2 - '@shikijs/transformers': 1.5.2 - '@types/markdown-it': 14.1.1 - '@vitejs/plugin-vue': 5.0.4(vite@5.2.11)(vue@3.4.27(typescript@5.4.5)) - '@vue/devtools-api': 7.2.0(vue@3.4.27(typescript@5.4.5)) - '@vue/shared': 3.4.27 - '@vueuse/core': 10.9.0(vue@3.4.27(typescript@5.4.5)) - '@vueuse/integrations': 10.9.0(focus-trap@7.5.4)(vue@3.4.27(typescript@5.4.5)) - focus-trap: 7.5.4 + vite@7.0.2(lightningcss@1.30.1)(yaml@2.7.1): + dependencies: + esbuild: 0.25.2 + fdir: 6.4.6(picomatch@4.0.2) + picomatch: 4.0.2 + postcss: 8.5.6 + rollup: 4.41.1 + tinyglobby: 0.2.14 + optionalDependencies: + fsevents: 2.3.3 + lightningcss: 1.30.1 + yaml: 2.7.1 + + vitepress-plugin-llms@1.7.0: + dependencies: + byte-size: 9.0.1 + gray-matter: 4.0.3 + markdown-it: 14.1.0 + markdown-title: 1.0.2 + millify: 6.1.0 + minimatch: 10.0.3 + path-to-regexp: 8.2.0 + picocolors: 1.1.1 + remark: 15.0.1 + remark-frontmatter: 5.0.0 + tokenx: 1.1.0 + unist-util-remove: 4.0.0 + unist-util-visit: 5.0.0 + transitivePeerDependencies: + - '@75lb/nature' + - supports-color + + vitepress@1.6.3(@algolia/client-search@5.29.0)(lightningcss@1.30.1)(postcss@8.5.6)(search-insights@2.17.3)(typescript@5.8.3): + dependencies: + '@docsearch/css': 3.8.2 + '@docsearch/js': 3.8.2(@algolia/client-search@5.29.0)(search-insights@2.17.3) + '@iconify-json/simple-icons': 1.2.32 + '@shikijs/core': 2.5.0 + '@shikijs/transformers': 2.5.0 + '@shikijs/types': 2.5.0 + '@types/markdown-it': 14.1.2 + '@vitejs/plugin-vue': 5.2.3(vite@5.4.19(lightningcss@1.30.1))(vue@3.5.17(typescript@5.8.3)) + '@vue/devtools-api': 7.7.2 + '@vue/shared': 3.5.13 + '@vueuse/core': 12.8.2(typescript@5.8.3) + '@vueuse/integrations': 12.8.2(focus-trap@7.6.4)(typescript@5.8.3) + focus-trap: 7.6.4 mark.js: 8.11.1 - minisearch: 6.3.0 - shiki: 1.5.2 - vite: 5.2.11 - vue: 3.4.27(typescript@5.4.5) + minisearch: 7.1.2 + shiki: 2.5.0 + vite: 5.4.19(lightningcss@1.30.1) + vue: 3.5.17(typescript@5.8.3) optionalDependencies: - postcss: 8.4.38 + postcss: 8.5.6 transitivePeerDependencies: - '@algolia/client-search' - '@types/node' - '@types/react' - - '@vue/composition-api' - async-validator - axios - change-case @@ -1483,6 +3161,7 @@ snapshots: - react - react-dom - sass + - sass-embedded - search-insights - sortablejs - stylus @@ -1491,53 +3170,58 @@ snapshots: - typescript - universal-cookie - vscode-jsonrpc@8.1.0: {} + vscode-jsonrpc@8.2.0: {} - vscode-languageserver-protocol@3.17.3: + vscode-languageserver-protocol@3.17.5: dependencies: - vscode-jsonrpc: 8.1.0 - vscode-languageserver-types: 3.17.3 + vscode-jsonrpc: 8.2.0 + vscode-languageserver-types: 3.17.5 - vscode-languageserver-textdocument@1.0.8: {} + vscode-languageserver-textdocument@1.0.12: {} - vscode-languageserver-types@3.17.3: {} + vscode-languageserver-types@3.17.5: {} - vscode-uri@3.0.7: {} + vscode-uri@3.1.0: {} - vue-demi@0.14.7(vue@3.4.27(typescript@5.4.5)): + vue-tsc@3.0.1(typescript@5.8.3): dependencies: - vue: 3.4.27(typescript@5.4.5) + '@volar/typescript': 2.4.17 + '@vue/language-core': 3.0.1(typescript@5.8.3) + typescript: 5.8.3 - vue-template-compiler@2.7.14: + vue@3.5.17(typescript@5.8.3): dependencies: - de-indent: 1.0.2 - he: 1.2.0 + '@vue/compiler-dom': 3.5.17 + '@vue/compiler-sfc': 3.5.17 + '@vue/runtime-dom': 3.5.17 + '@vue/server-renderer': 3.5.17(vue@3.5.17(typescript@5.8.3)) + '@vue/shared': 3.5.17 + optionalDependencies: + typescript: 5.8.3 - vue-tsc@2.0.19(typescript@5.4.5): - dependencies: - '@volar/typescript': 2.2.4 - '@vue/language-core': 2.0.19(typescript@5.4.5) - semver: 7.5.4 - typescript: 5.4.5 + web-tree-sitter@0.25.3: + optional: true - vue@3.4.27(typescript@5.4.5): + wrap-ansi@7.0.0: dependencies: - '@vue/compiler-dom': 3.4.27 - '@vue/compiler-sfc': 3.4.27 - '@vue/runtime-dom': 3.4.27 - '@vue/server-renderer': 3.4.27(vue@3.4.27(typescript@5.4.5)) - '@vue/shared': 3.4.27 - optionalDependencies: - typescript: 5.4.5 + ansi-styles: 4.3.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 - web-tree-sitter-sg@0.21.5: - dependencies: - web-tree-sitter: 0.22.6 - optional: true + y18n@5.0.8: {} - web-tree-sitter@0.22.6: - optional: true + yaml@2.7.1: {} - yallist@4.0.0: {} + yargs-parser@21.1.1: {} + + yargs@17.7.2: + dependencies: + cliui: 8.0.1 + escalade: 3.2.0 + get-caller-file: 2.0.5 + require-directory: 2.1.1 + string-width: 4.2.3 + y18n: 5.0.8 + yargs-parser: 21.1.1 - yaml@2.2.1: {} + zwitch@2.0.4: {} diff --git a/src/dump_tree.rs b/src/dump_tree.rs index a80a1ec6..17f7091b 100644 --- a/src/dump_tree.rs +++ b/src/dump_tree.rs @@ -1,5 +1,10 @@ use serde::{Deserialize, Serialize}; -use tree_sitter as ts; +use crate::wasm_lang::{WasmDoc, WasmLang}; +use ast_grep_core::{ + matcher::PatternNode, AstGrep, Language, Node, Pattern +}; +use wasm_bindgen::prelude::JsError; +use web_tree_sitter_sg::{Point, TreeCursor}; #[derive(Deserialize, Serialize)] #[serde(rename_all = "camelCase")] @@ -19,26 +24,25 @@ pub struct Pos { column: u32, } -impl From for Pos { - #[inline] - fn from(pt: ts::Point) -> Self { - Pos { - row: pt.row(), - column: pt.column(), +impl From for Pos { + fn from(point: Point) -> Self { + Self { + row: point.row(), + column: point.column(), } } } -pub fn dump_one_node(cursor: &mut ts::TreeCursor, target: &mut Vec) { - let node = cursor.node(); +pub fn dump_one_node(cursor: &mut TreeCursor, target: &mut Vec) { + let node = cursor.current_node(); let kind = if node.is_missing() { - format!("MISSING {}", node.kind()) + format!("MISSING {}", node.type_()) } else { - node.kind().to_string() + format!("{}", node.type_()) }; let start = node.start_position().into(); let end = node.end_position().into(); - let field = cursor.field_name().map(|c| c.to_string()); + let field = cursor.current_field_name().map(|c| format!("{}", c)); let mut children = vec![]; if cursor.goto_first_child() { dump_nodes(cursor, &mut children); @@ -54,7 +58,7 @@ pub fn dump_one_node(cursor: &mut ts::TreeCursor, target: &mut Vec) { }) } -fn dump_nodes(cursor: &mut ts::TreeCursor, target: &mut Vec) { +fn dump_nodes(cursor: &mut TreeCursor, target: &mut Vec) { loop { dump_one_node(cursor, target); if !cursor.goto_next_sibling() { @@ -62,3 +66,118 @@ fn dump_nodes(cursor: &mut ts::TreeCursor, target: &mut Vec) { } } } + +pub fn dump_pattern(query: String, selector: Option) -> Result { + let lang = WasmLang::get_current(); + let processed = lang.pre_process_pattern(&query); + let doc = WasmDoc::try_new(processed.to_string(), lang)?; + let root = AstGrep::doc(doc); + let pattern = if let Some(sel) = selector { + Pattern::contextual(&query, &sel, lang)? + } else { + Pattern::try_new(&query, lang)? + }; + let found = root.root().find(&pattern).ok_or_else(|| JsError::new("pattern node not found"))?; + let ret = dump_pattern_tree(root.root(), found.node_id(), &pattern.node); + Ok(ret) +} + +fn dump_pattern_tree(node: Node, node_id: usize, pattern: &PatternNode) -> PatternTree { + if node.node_id() == node_id { + return dump_pattern_impl(node, pattern) + } + let children: Vec<_> = node.children().map(|n| dump_pattern_tree(n, node_id, pattern)).collect(); + let ts = node.get_inner_node().0; + let text = if children.is_empty() { + Some(node.text().into()) + } else { + None + }; + let kind = if ts.is_missing() { + format!("MISSING {}", node.kind()) + } else { + node.kind().to_string() + }; + PatternTree { + kind, + start: ts.start_position().into(), + end: ts.end_position().into(), + is_named: node.is_named(), + children, + text, + pattern: None, + } +} + +fn dump_pattern_impl(node: Node, pattern: &PatternNode) -> PatternTree { + use PatternNode as PN; + let ts = node.get_inner_node().0; + let kind = if ts.is_missing() { + format!("MISSING {}", node.kind()) + } else { + node.kind().to_string() + }; + match pattern { + PN::MetaVar { .. } => { + let lang = node.lang(); + let expando = lang.expando_char(); + let text = node.text().to_string(); + let text = text.replace(expando, "$"); + PatternTree { + kind, + start: ts.start_position().into(), + end: ts.end_position().into(), + is_named: true, + children: vec![], + text: Some(text), + pattern: Some(PatternKind::MetaVar), + } + } + PN::Terminal { is_named, .. } => { + PatternTree { + kind, + start: ts.start_position().into(), + end: ts.end_position().into(), + is_named: *is_named, + children: vec![], + text: Some(node.text().into()), + pattern: Some(PatternKind::Terminal), + } + } + PN::Internal { children, .. } => { + let children = children.iter().zip(node.children()).map(|(pn, n)| { + dump_pattern_impl(n, pn) + }).collect(); + PatternTree { + kind, + start: ts.start_position().into(), + end: ts.end_position().into(), + is_named: true, + children, + text: None, + pattern: Some(PatternKind::Internal), + } + } + } +} + +#[derive(Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +enum PatternKind { + Terminal, + MetaVar, + Internal, +} + + +#[derive(Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct PatternTree { + kind: String, + start: Pos, + end: Pos, + is_named: bool, + children: Vec, + text: Option, + pattern: Option, +} \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs index 4211a237..ca97dce8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,16 +3,15 @@ mod utils; mod wasm_lang; use wasm_lang::{WasmDoc, WasmLang}; -use dump_tree::{dump_one_node, DumpNode}; +use dump_tree::{dump_one_node, DumpNode, dump_pattern as dump_pattern_impl}; use utils::WasmMatch; use ast_grep_config::{RuleConfig, SerializableRuleConfig, CombinedScan}; -use ast_grep_core::language::Language; use ast_grep_core::{AstGrep, Node as SgNode}; use serde_wasm_bindgen::from_value as from_js_val; use std::collections::HashMap; use std::error::Error; -use tree_sitter as ts; +use web_tree_sitter_sg::TreeSitter; use wasm_bindgen::prelude::*; type Node<'a> = SgNode<'a, WasmDoc>; @@ -22,7 +21,7 @@ static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT; #[wasm_bindgen(js_name = initializeTreeSitter)] pub async fn initialize_tree_sitter() -> Result<(), JsError> { - ts::TreeSitter::init().await + TreeSitter::init().await } #[wasm_bindgen(js_name = setupParser)] @@ -39,12 +38,13 @@ pub fn find_nodes(src: String, configs: Vec) -> Result = combined.scan(&root, sets, false).matches.into_iter().map(|(id, matches)| { - let rule = combined.get_rule(id).id.clone(); - let matches: Vec<_> = matches.into_iter().map(WasmMatch::from).collect(); - (rule, matches) + let doc = WasmDoc::try_new(src.clone(), lang)?; + let root = AstGrep::doc(doc); + let ret: HashMap<_, _> = combined.scan(&root, false).matches.into_iter().map(|(rule, matches)| { + let matches: Vec<_> = matches.into_iter().map(|m| { + WasmMatch::from_match(m, rule) + }).collect(); + (rule.id.clone(), matches) }).collect(); let ret = serde_wasm_bindgen::to_value(&ret)?; Ok(ret) @@ -59,24 +59,23 @@ pub fn fix_errors(src: String, configs: Vec) -> Result rules.push(finder); } let combined = CombinedScan::new(rules.iter().collect()); - let doc = WasmDoc::new(src.clone(), lang); + let doc = WasmDoc::try_new(src.clone(), lang)?; let root = AstGrep::doc(doc); - let sets = combined.find(&root); - let diffs = combined.scan(&root, sets, true).diffs; + let diffs = combined.scan(&root, true).diffs; if diffs.is_empty() { return Ok(src); } let mut start = 0; let src: Vec<_> = src.chars().collect(); let mut new_content = Vec::::new(); - for (idx, nm) in diffs { + for (rule, nm) in diffs { let range = nm.range(); if start > range.start { continue; } - let rule = combined.get_rule(idx); - let fixer = rule.get_fixer()?.expect("rule returned by diff must have fixer"); - let edit = nm.make_edit(&rule.matcher, &fixer); + let fixers = rule.get_fixer()?; + let fixer = fixers.first().expect("rule returned by diff must have fixer"); + let edit = nm.make_edit(&rule.matcher, fixer); new_content.extend(&src[start..edit.position]); new_content.extend(&edit.inserted_text); start = edit.position + edit.deleted_length; @@ -87,7 +86,7 @@ pub fn fix_errors(src: String, configs: Vec) -> Result } fn convert_to_debug_node(n: Node) -> DumpNode { - let mut cursor = n.get_ts_node().walk(); + let mut cursor = n.get_inner_node().0.walk(); let mut target = vec![]; dump_one_node(&mut cursor, &mut target); target.pop().expect_throw("found empty node") @@ -96,17 +95,18 @@ fn convert_to_debug_node(n: Node) -> DumpNode { #[wasm_bindgen(js_name = dumpASTNodes)] pub fn dump_ast_nodes(src: String) -> Result { let lang = WasmLang::get_current(); - let doc = WasmDoc::new(src, lang); + let doc = WasmDoc::try_new(src, lang)?; let root = AstGrep::doc(doc); let debug_node = convert_to_debug_node(root.root()); let ret = serde_wasm_bindgen::to_value(&debug_node)?; Ok(ret) } -#[wasm_bindgen(js_name = preProcessPattern)] -pub fn pre_process_pattern(query: String) -> Result { - let lang = WasmLang::get_current(); - Ok(lang.pre_process_pattern(&query).into()) +#[wasm_bindgen(js_name = dumpPattern)] +pub fn dump_pattern(src: String, selector: Option) -> Result { + let dumped = dump_pattern_impl(src, selector)?; + let ret = serde_wasm_bindgen::to_value(&dumped)?; + Ok(ret) } fn try_get_rule_config(config: JsValue) -> Result, JsError> { diff --git a/src/utils.rs b/src/utils.rs index 8a810b9f..8f61685a 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -1,8 +1,10 @@ -use crate::wasm_lang::WasmLang; +use crate::wasm_lang::{WasmLang, WasmDoc}; use ast_grep_core::{ meta_var::{MetaVarEnv, MetaVariable}, - Node as SgNode, NodeMatch as SgNodeMatch, StrDoc, + Node as SgNode, NodeMatch as SgNodeMatch, + replacer::Replacer, }; +use ast_grep_config::{RuleConfig, Fixer}; use serde::{Deserialize, Serialize}; use std::collections::BTreeMap; @@ -17,8 +19,8 @@ pub fn set_panic_hook() { console_error_panic_hook::set_once(); } -type Node<'a> = SgNode<'a, StrDoc>; -type NodeMatch<'a> = SgNodeMatch<'a, StrDoc>; +type Node<'a> = SgNode<'a, WasmDoc>; +type NodeMatch<'a> = SgNodeMatch<'a, WasmDoc>; #[derive(Serialize, Deserialize)] pub struct WasmNode { @@ -28,21 +30,33 @@ pub struct WasmNode { #[derive(Serialize, Deserialize)] pub struct WasmMatch { + pub kind: String, pub node: WasmNode, pub env: BTreeMap, + pub message: String, } -impl From> for WasmMatch { - fn from(nm: NodeMatch) -> Self { + +// TODO: move to ast-grep-core +fn get_message(rule: &RuleConfig, node: &NodeMatch) -> String { + let parsed = Fixer::from_str(&rule.message, &rule.language).expect("should work"); + let bytes = parsed.generate_replacement(node); + bytes.into_iter().collect() +} + +impl WasmMatch { + pub fn from_match(nm: NodeMatch, rule: &RuleConfig) -> Self { let node = nm.get_node().clone(); + let kind = node.kind().to_string(); let node = WasmNode::from(node); let env = nm.get_env().clone(); let env = env_to_map(env); - Self { node, env } + let message = get_message(rule, &nm); + Self { node, env, message, kind } } } -fn env_to_map(env: MetaVarEnv<'_, StrDoc>) -> BTreeMap { +fn env_to_map(env: MetaVarEnv<'_, WasmDoc>) -> BTreeMap { let mut map = BTreeMap::new(); for id in env.get_matched_variables() { match id { @@ -51,7 +65,7 @@ fn env_to_map(env: MetaVarEnv<'_, StrDoc>) -> BTreeMap>) -> BTreeMap> for WasmNode { let end = nm.end_pos(); Self { text: nm.text().to_string(), - range: (start.0, start.1, end.0, end.1), + range: (start.line(), start.column(&nm), end.line(), end.column(&nm)), } } } \ No newline at end of file diff --git a/src/wasm_lang.rs b/src/wasm_lang.rs index 440ca90a..92903b5b 100644 --- a/src/wasm_lang.rs +++ b/src/wasm_lang.rs @@ -1,14 +1,14 @@ use std::str::FromStr; use ast_grep_core::language::Language; -use ast_grep_core::meta_var::MetaVariable; -use ast_grep_core::source::{Content, Doc, Edit, TSParseError}; -use ast_grep_language as L; +use ast_grep_core::source::{Content, Doc, Edit, SgNode}; +use ast_grep_core::matcher::{PatternBuilder, PatternError, Pattern}; +use ast_grep_core::Position; use std::borrow::Cow; use std::ops::Range; use std::sync::Mutex; -use tree_sitter as ts; -use tree_sitter::{InputEdit, Node, Parser, ParserError, Point, Tree}; +use web_tree_sitter_sg::{SyntaxNode, Parser, Point, Tree}; +use web_tree_sitter_sg as ts; use wasm_bindgen::prelude::*; use serde::{Deserialize, Deserializer, de}; @@ -21,20 +21,23 @@ pub enum WasmLang { Bash, C, CSharp, + Css, Cpp, - Dart, Elixir, Go, + Haskell, Html, Java, Json, Kotlin, + Lua, Php, Python, Ruby, Rust, Scala, Swift, + Yaml, } use WasmLang::*; @@ -60,13 +63,15 @@ impl FromStr for WasmLang { "bash" => Bash, "c" => C, "csharp" => CSharp, + "css" => Css, "cpp" => Cpp, - "dart" => Dart, "elixir" => Elixir, "go" => Go, "html" => Html, + "haskell" => Haskell, "java" => Java, "json" => Json, + "lua" => Lua, "kotlin" => Kotlin, "php" => Php, "python" => Python, @@ -74,6 +79,7 @@ impl FromStr for WasmLang { "rust" => Rust, "scala" => Scala, "swift" => Swift, + "yaml" => Yaml, _ => return Err(NotSupport(s.to_string())), }) } @@ -88,7 +94,13 @@ impl<'de> Deserialize<'de> for WasmLang { } } -static TS_LANG: Mutex> = Mutex::new(None); +#[derive(Clone)] +struct TsLang(ts::Language); + +unsafe impl Send for TsLang {} +unsafe impl Sync for TsLang {} + +static TS_LANG: Mutex> = Mutex::new(None); static LANG: Mutex = Mutex::new(JavaScript); impl WasmLang { @@ -103,101 +115,117 @@ impl WasmLang { pub fn get_current() -> Self { *LANG.lock().expect_throw("get language error") } + + fn get_ts_language(&self) -> ts::Language { + TS_LANG + .lock() + .expect_throw("get language error") + .clone() + .expect_throw("current language is not set") + .0 + } + } -async fn setup_parser(parser_path: &str) -> Result<(), JsError> { - let mut parser = ts::Parser::new()?; +async fn setup_parser(parser_path: &str) -> Result<(), SgWasmError> { + let parser = ts::Parser::new()?; let lang = get_lang(parser_path).await?; - parser.set_language(&lang)?; + parser.set_language(Some(&lang))?; let mut curr_lang = TS_LANG.lock().expect_throw("set language error"); - *curr_lang = Some(lang); + *curr_lang = Some(TsLang(lang)); Ok(()) } #[cfg(target_arch = "wasm32")] -async fn get_lang(parser_path: &str) -> Result { - let lang = web_tree_sitter_sg::Language::load_path(parser_path) - .await - .map_err(ts::LanguageError::from)?; - Ok(ts::Language::from(lang)) +async fn get_lang(parser_path: &str) -> Result { + let lang = ts::Language::load_path(parser_path) + .await?; + Ok(lang) } #[cfg(not(target_arch = "wasm32"))] -async fn get_lang(_path: &str) -> Result { +async fn get_lang(_path: &str) -> Result { unreachable!() } -macro_rules! execute_lang_method { - ($me: path, $method: ident, $($pname:tt),*) => { +impl Language for WasmLang { + fn expando_char(&self) -> char { use WasmLang as W; - match $me { - W::Bash => L::Bash.$method($($pname,)*), - W::C => L::C.$method($($pname,)*), - W::Cpp => L::Cpp.$method($($pname,)*), - W::CSharp => L::CSharp.$method($($pname,)*), - W::Dart => L::Dart.$method($($pname,)*), - W::Elixir => L::Elixir.$method($($pname,)*), - W::Go => L::Go.$method($($pname,)*), - W::Html => L::Html.$method($($pname,)*), - W::Java => L::Java.$method($($pname,)*), - W::Json => L::Json.$method($($pname,)*), - W::Kotlin => L::Kotlin.$method($($pname,)*), - W::JavaScript => L::JavaScript.$method($($pname,)*), - W::Php => L::Php.$method($($pname,)*), - W::Python => L::Python.$method($($pname,)*), - W::Ruby => L::Ruby.$method($($pname,)*), - W::Rust => L::Rust.$method($($pname,)*), - W::Scala => L::Scala.$method($($pname,)*), - W::Swift => L::Swift.$method($($pname,)*), - W::TypeScript => L::TypeScript.$method($($pname,)*), - W::Tsx => L::Tsx.$method($($pname,)*), + match self { + W::Bash => '$', + W::C => '_', + W::Cpp => '_', + W::CSharp => 'µ', + W::Css => '_', + W::Elixir => 'µ', + W::Go => 'µ', + W::Html => 'z', + W::Java => '$', + W::JavaScript => '$', + W::Json => '$', + W::Haskell => 'µ', + W::Kotlin => 'µ', + W::Lua => '$', + W::Php => 'µ', + W::Python => 'µ', + W::Ruby => 'µ', + W::Rust => 'µ', + W::Scala => '$', + W::Swift => 'µ', + W::TypeScript => '$', + W::Tsx => '$', + W::Yaml => '$', } } -} - -macro_rules! impl_lang_method { - ($method: ident, ($($pname:tt: $ptype:ty),*) => $return_type: ty) => { - #[inline] - fn $method(&self, $($pname: $ptype),*) -> $return_type { - execute_lang_method!{ self, $method, $($pname),* } - } - }; -} -impl Language for WasmLang { - fn get_ts_language(&self) -> ts::Language { - TS_LANG - .lock() - .expect_throw("get language error") - .clone() - .expect_throw("current language is not set") + fn build_pattern(&self, builder: &PatternBuilder) -> Result { + builder.build(|src| { + let src = src.to_string(); + let ret = WasmDoc::try_new(src, self.clone()).map_err(|e| e.to_string()); + Ok(ret?) + }) } - impl_lang_method!(meta_var_char, () => char); - impl_lang_method!(extract_meta_var, (source: &str) => Option); - impl_lang_method!(expando_char, () => char); - fn pre_process_pattern<'q>(&self, query: &'q str) -> Cow<'q, str> { - execute_lang_method! { self, pre_process_pattern, query } + pre_process_pattern(self.expando_char(), query) + } + fn kind_to_id(&self, kind: &str) -> u16 { + let lang = self.get_ts_language(); + lang.id_for_node_kind(kind, true) + } + fn field_to_id(&self, field: &str) -> Option { + let lang = self.get_ts_language(); + lang.field_id_for_name(field) + } +} + +fn pre_process_pattern(expando: char, query: &str) -> Cow { + let mut ret = Vec::with_capacity(query.len()); + let mut dollar_count = 0; + for c in query.chars() { + if c == '$' { + dollar_count += 1; + continue; + } + let need_replace = matches!(c, 'A'..='Z' | '_') // $A or $$A or $$$A + || dollar_count == 3; // anonymous multiple + let sigil = if need_replace { expando } else { '$' }; + ret.extend(std::iter::repeat(sigil).take(dollar_count)); + dollar_count = 0; + ret.push(c); } + // trailing anonymous multiple + let sigil = if dollar_count == 3 { expando } else { '$' }; + ret.extend(std::iter::repeat(sigil).take(dollar_count)); + std::borrow::Cow::Owned(ret.into_iter().collect()) } #[derive(Clone)] pub struct Wrapper { inner: Vec, } - -impl Content for Wrapper { - type Underlying = char; - fn parse_tree_sitter( - &self, - parser: &mut Parser, - tree: Option<&Tree>, - ) -> std::result::Result, ParserError> { - let s: String = self.inner.iter().cloned().collect(); - parser.parse(&s, tree) - } - fn accept_edit(&mut self, edit: &Edit) -> InputEdit { +impl Wrapper { + fn accept_edit(&mut self, edit: &Edit) -> ts::Edit { let start_byte = edit.position; let old_end_byte = edit.position + edit.deleted_length; let new_end_byte = edit.position + edit.inserted_text.len(); @@ -206,7 +234,7 @@ impl Content for Wrapper { let old_end_position = pos_for_char_offset(&input, old_end_byte); input.splice(start_byte..old_end_byte, edit.inserted_text.clone()); let new_end_position = pos_for_char_offset(&input, new_end_byte); - InputEdit::new( + ts::Edit::new( start_byte as u32, old_end_byte as u32, new_end_byte as u32, @@ -215,10 +243,10 @@ impl Content for Wrapper { &new_end_position, ) } - fn get_text<'a>(&'a self, node: &Node) -> Cow<'a, str> { - // dummy for wasm tree! - node.utf8_text(&[]).expect("get_text should work") - } +} + +impl Content for Wrapper { + type Underlying = char; fn get_range(&self, range: Range) -> &[char] { &self.inner[range] } @@ -228,6 +256,10 @@ impl Content for Wrapper { fn encode_bytes(bytes: &[Self::Underlying]) -> Cow { Cow::Owned(bytes.iter().collect()) } + + fn get_char_column(&self, column: usize, _: usize) -> usize { + column + } } fn pos_for_char_offset(input: &[char], offset: usize) -> Point { @@ -248,65 +280,186 @@ fn pos_for_char_offset(input: &[char], offset: usize) -> Point { pub struct WasmDoc { lang: WasmLang, source: Wrapper, + tree: Tree, +} + +#[derive(Clone, Debug)] +pub enum SgWasmError { + ParserError(ts::ParserError), + LanguageError(ts::LanguageError), + FailedToParse, +} + +impl std::fmt::Display for SgWasmError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + SgWasmError::ParserError(err) => write!(f, "Parser error: {}", err.message()), + SgWasmError::LanguageError(err) => write!(f, "Language error: {}", err.message()), + SgWasmError::FailedToParse => write!(f, "Failed to parse"), + } + } +} + +impl std::error::Error for SgWasmError {} + +impl From for SgWasmError { + fn from(err: ts::ParserError) -> Self { + SgWasmError::ParserError(err) + } +} +impl From for SgWasmError { + fn from(err: ts::LanguageError) -> Self { + SgWasmError::LanguageError(err) + } } impl WasmDoc { - pub fn new(src: String, lang: WasmLang) -> Self { + pub fn try_new(src: String, lang: WasmLang) -> Result { let source = Wrapper { inner: src.chars().collect(), }; - Self { source, lang } + let parser = Parser::new()?; + let ts_lang = lang.get_ts_language(); + parser.set_language(Some(&ts_lang))?; + let Some(tree) = parser.parse_with_string(&src.into(), None, None)? else { + return Err(SgWasmError::FailedToParse); + }; + Ok(Self { source, lang, tree }) + } +} + +#[derive(Clone)] +pub struct Node(pub SyntaxNode); + +impl<'a> SgNode<'a> for Node { + fn parent(&self) -> Option { + self.0.parent().map(Node) + } + fn ancestors(&self, _root: Self) -> impl Iterator { + let mut parent = self.0.parent(); + std::iter::from_fn(move || { + let inner = parent.clone()?; + let ret = Some(Node(inner.clone())); + parent = inner.parent(); + ret + }) + } + // fn dfs(&self) -> impl Iterator { + // TsPre::new(self) + // } + fn child(&self, nth: usize) -> Option { + self.0.child(nth as u32).map(Node) + } + fn children(&self) -> impl ExactSizeIterator { + self.0.children().to_vec().into_iter().map(|n| n.unchecked_into::()).map(Node) + } + fn child_by_field_id(&self, field_id: u16) -> Option { + self.0.child_for_field_id(field_id).map(Node) + } + fn next(&self) -> Option { + self.0.next_sibling().map(Node) + } + fn prev(&self) -> Option { + self.0.previous_sibling().map(Node) + } + // fn next_all(&self) -> impl Iterator { } + // fn prev_all(&self) -> impl Iterator {} + fn is_named(&self) -> bool { + self.0.is_named() + } + /// N.B. it is different from is_named && is_leaf + /// if a node has no named children. + fn is_named_leaf(&self) -> bool { + self.0.named_child_count() == 0 + } + fn is_leaf(&self) -> bool { + self.0.child_count() == 0 + } + fn kind(&self) -> Cow { + Cow::Owned(self.0.type_().into()) + } + fn kind_id(&self) -> u16 { + self.0.type_id() + } + fn node_id(&self) -> usize { + self.0.id() as usize + } + fn range(&self) -> std::ops::Range { + (self.0.start_index() as usize)..(self.0.end_index() as usize) + } + fn start_pos(&self) -> Position { + let start = self.0.start_position(); + let offset = self.0.start_index(); + Position::new(start.row() as usize, start.column() as usize, offset as usize) + } + fn end_pos(&self) -> Position { + let end = self.0.end_position(); + let offset = self.0.end_index(); + Position::new(end.row() as usize, end.column() as usize, offset as usize) + } + // missing node is a tree-sitter specific concept + fn is_missing(&self) -> bool { + self.0.is_missing() + } + fn is_error(&self) -> bool { + self.0.is_error() + } + + fn field(&self, name: &str) -> Option { + self.0.child_for_field_name(name).map(Node) + } + fn field_children(&self, field_id: Option) -> impl Iterator { + let cursor = self.0.walk(); + cursor.goto_first_child(); + // if field_id is not found, iteration is done + let mut done = field_id.is_none(); + + std::iter::from_fn(move || { + if done { + return None; + } + while cursor.current_field_id() != field_id { + if !cursor.goto_next_sibling() { + return None; + } + } + let ret = cursor.current_node(); + if !cursor.goto_next_sibling() { + done = true; + } + Some(Node(ret)) + }) } } impl Doc for WasmDoc { type Lang = WasmLang; type Source = Wrapper; - fn parse(&self, old_tree: Option<&Tree>) -> std::result::Result { - let mut parser = Parser::new()?; - let ts_lang = self.lang.get_ts_language(); - parser.set_language(&ts_lang)?; - if let Some(tree) = self.source.parse_tree_sitter(&mut parser, old_tree)? { - Ok(tree) - } else { - Err(TSParseError::TreeUnavailable) - } - } + type Node<'a> = Node; fn get_lang(&self) -> &Self::Lang { &self.lang } fn get_source(&self) -> &Self::Source { &self.source } - fn get_source_mut(&mut self) -> &mut Self::Source { - &mut self.source + fn root_node(&self) -> Self::Node<'_> { + Node(self.tree.root_node()) } - fn from_str(src: &str, lang: Self::Lang) -> Self { - Self { - lang, - source: Wrapper { - inner: src.chars().collect(), - }, - } + fn do_edit(&mut self, edit: &ast_grep_core::source::Edit) -> Result<(), String> { + let edit = self.source.accept_edit(edit); + self.tree.edit(&edit); + let parser = Parser::new().map_err(|e| e.to_string())?; + let ts_lang = self.lang.get_ts_language(); + parser.set_language(Some(&ts_lang)).map_err(|e| e.to_string())?; + let src = self.source.inner.iter().collect::(); + let parse_ret = parser.parse_with_string(&src.into(), Some(&self.tree), None); + let Some(tree) = parse_ret.map_err(|e| e.to_string())? else { + return Err("Failed to parse".to_string()); + }; + self.tree = tree; + Ok(()) } -} - -#[cfg(test)] -mod test { - use super::*; - use tree_sitter_rust; - - // https://github.com/tree-sitter/tree-sitter-rust/issues/82 - // sadly, this does not test what tree-sitter-wasm actually does - // wasm uses UTF16 which counts different "error cost" than utf8 - // native tree-sitter can use parse_with_utf16 :( - #[test] - fn test_process_pattern() { - let mut curr_lang = TS_LANG.lock().expect_throw("set language error"); - *curr_lang = Some(tree_sitter_rust::language().into()); - drop(curr_lang); - let grep = WasmLang::Rust.ast_grep("fn test() { Some(123) }"); - let root = grep.root(); - assert!(root.find("Some($A)").is_some()); + fn get_node_text<'a>(&'a self, node: &Self::Node<'a>) -> Cow<'a, str> { + Cow::Owned(node.0.text().into()) } } \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json index d4aefa2c..db5ed8d5 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -3,7 +3,7 @@ "target": "ESNext", "useDefineForClassFields": true, "module": "ESNext", - "moduleResolution": "Node", + "moduleResolution": "node", "strict": true, "jsx": "preserve", "sourceMap": true, @@ -13,6 +13,6 @@ "lib": ["ESNext", "DOM"], "skipLibCheck": true }, - "include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"], + "include": ["website/**/*.ts", "website/**/*.d.ts", "website/**/*.tsx", "website/**/*.vue"], "references": [{ "path": "./tsconfig.node.json" }] } diff --git a/website/.vitepress/config.ts b/website/.vitepress/config.ts index a3e6a5cc..8880ad40 100644 --- a/website/.vitepress/config.ts +++ b/website/.vitepress/config.ts @@ -1,4 +1,5 @@ -import { defineConfig } from 'vitepress' +import { DefaultTheme, defineConfig } from 'vitepress' +import llmstxt from 'vitepress-plugin-llms' const gaScript = ` window.dataLayer = window.dataLayer || []; @@ -7,20 +8,188 @@ gtag('js', new Date()); gtag('config', 'G-EZSJ3YF2RG'); ` -const monacoPrefix = 'monaco-editor/esm/vs' +const sidebar: DefaultTheme.Sidebar = [ + { + text: 'Guide', + link: '/guide/introduction.html', + items: [ + { text: 'Quick Start', link: '/guide/quick-start.html' }, + { text: 'Pattern Syntax', link: '/guide/pattern-syntax.html' }, + { + text: 'Rule Essentials', + link: '/guide/rule-config.html', + collapsed: true, + items: [ + { text: 'Atomic Rule', link: '/guide/rule-config/atomic-rule.html' }, + { text: 'Relational Rule', link: '/guide/rule-config/relational-rule.html' }, + { text: 'Composite Rule', link: '/guide/rule-config/composite-rule.html' }, + { text: 'Utility Rule', link: '/guide/rule-config/utility-rule.html' }, + ], + }, + { + text: 'Project Setup', + collapsed: true, + link: '/guide/scan-project.html', + items: [ + { text: 'Project Configuration', link: '/guide/project/project-config.html' }, + { text: 'Lint Rule', link: '/guide/project/lint-rule.html' }, + { text: 'Test Your Rule', link: '/guide/test-rule.html' }, + { text: 'Error Report', link: '/guide/project/severity.html' }, + ], + }, + { + text: 'Rewrite Code', + link: '/guide/rewrite-code.html', + collapsed: true, + items: [ + { text: 'Transform Code', link: '/guide/rewrite/transform.html' }, + { text: 'Rewriter Rule', link: '/guide/rewrite/rewriter.html' }, + ], + }, + { + text: 'Tooling Overview', + link: '/guide/tooling-overview.html', + collapsed: true, + items: [ + { text: 'Editor Integration', link: '/guide/tools/editors.html' }, + { text: 'JSON mode', link: '/guide/tools/json.html' }, + ], + }, + { + text: 'API Usage', + link: '/guide/api-usage.html', + collapsed: true, + items: [ + { text: 'JavaScript API', link: '/guide/api-usage/js-api.html' }, + { text: 'Python API', link: '/guide/api-usage/py-api.html' }, + { text: 'Performance Tip', link: '/guide/api-usage/performance-tip.html' }, + ], + }, + ], + collapsed: false, + }, + { + text: 'Examples', + link: '/catalog', + items: [ + { text: 'C', link: '/catalog/c/' }, + { text: 'C++', link: '/catalog/cpp/' }, + { text: 'Go', link: '/catalog/go/' }, + { text: 'HTML', link: '/catalog/html/' }, + { text: 'Java', link: '/catalog/java/' }, + { text: 'Kotlin', link: '/catalog/kotlin/' }, + { text: 'Python', link: '/catalog/python/' }, + { text: 'Ruby', link: '/catalog/ruby/' }, + { text: 'Rust', link: '/catalog/rust/' }, + { text: 'TypeScript', link: '/catalog/typescript/' }, + { text: 'TSX', link: '/catalog/tsx/' }, + { text: 'YAML', link: '/catalog/yaml/' }, + ], + collapsed: true, + }, + { + text: 'Cheat Sheet', + items: [ + { text: 'Rule Cheat Sheet', link: '/cheatsheet/rule.html' }, + { text: 'Config Cheat Sheet', link: '/cheatsheet/yaml.html' }, + ], + collapsed: true, + }, + { + text: 'Reference', + items: [ + { + text: 'Command Line Interface', + link: '/reference/cli.html', + collapsed: true, + items: [ + { text: 'ast-grep run', link: '/reference/cli/run.html' }, + { text: 'ast-grep scan', link: '/reference/cli/scan.html' }, + { text: 'ast-grep test', link: '/reference/cli/test.html' }, + { text: 'ast-grep new', link: '/reference/cli/new.html' }, + ], + }, + { text: 'Project Config', link: '/reference/sgconfig.html' }, + { + text: 'Rule Config', + link: '/reference/yaml.html', + collapsed: false, + items: [ + { text: 'fix', link: '/reference/yaml/fix.html' }, + { text: 'transformation', link: '/reference/yaml/transformation.html' }, + { text: 'rewriter', link: '/reference/yaml/rewriter.html' }, + ], + }, + { text: 'Rule Object', link: '/reference/rule.html' }, + { text: 'API Reference', link: '/reference/api.html' }, + { text: 'Language List', link: '/reference/languages.html' }, + { text: 'Playground Manual', link: '/reference/playground.html' }, + ], + collapsed: true, + }, + { + text: 'Advanced Topics', + items: [ + { text: 'Frequently Asked Questions', link: '/advanced/faq.html' }, + { + text: 'How ast-grep Works', + link: '/advanced/how-ast-grep-works.html', + collapsed: false, + items: [ + { text: 'Core Concepts', link: '/advanced/core-concepts.html' }, + { text: 'Pattern Syntax', link: '/advanced/pattern-parse.html' }, + { text: 'Pattern Match Algorithm', link: '/advanced/match-algorithm.html' }, + { text: 'How Rewrite Works', link: '/advanced/find-n-patch.html' }, + ], + }, + { text: 'Custom Language Support', link: '/advanced/custom-language.html' }, + { text: 'Multi-Language Documents', link: '/advanced/language-injection.html' }, + { text: 'Comparison with Other Tools', link: '/advanced/tool-comparison.html' }, + ], + collapsed: true, + }, + { + text: 'Contributing', + items: [ + { text: 'Guide', link: '/contributing/how-to.html' }, + { text: 'Development', link: '/contributing/development.html' }, + { text: 'Add New Language', link: '/contributing/add-lang.html' }, + ], + collapsed: true, + }, + { + text: 'Links', + items: [ + { text: 'Playground', link: '/playground.html' }, + { text: 'Codemod Studio', link: 'https://app.codemod.com/studio' }, + { text: 'Blog', link: '/blog.html' }, + { + text: 'VSCode', + link: 'https://marketplace.visualstudio.com/items?itemName=ast-grep.ast-grep-vscode', + }, + { text: 'Discord', link: 'https://discord.com/invite/4YZjf6htSQ' }, + { text: 'StackOverflow', link: 'https://stackoverflow.com/questions/tagged/ast-grep' }, + { text: 'Reddit', link: 'https://www.reddit.com/r/astgrep/' }, + { text: 'Docs.rs', link: 'https://docs.rs/ast-grep-core/latest/ast_grep_core/' }, + ], + collapsed: true, + }, +] export default defineConfig({ lang: 'en-US', title: 'ast-grep', - description: 'ast-grep(sg) is a lightning fast and user friendly tool for code searching, linting, rewriting at large scale.', + description: + 'ast-grep(sg) is a lightning fast and user friendly tool for code searching, linting, rewriting at large scale.', head: [ - ['script', {async: 'async', src: 'https://www.googletagmanager.com/gtag/js?id=G-EZSJ3YF2RG'}], + ['script', { async: 'async', src: 'https://www.googletagmanager.com/gtag/js?id=G-EZSJ3YF2RG' }], ['script', {}, gaScript], ], outDir: './dist', // appearance: false, lastUpdated: true, vite: { + plugins: [llmstxt()], build: { target: 'es2020', }, @@ -31,128 +200,52 @@ export default defineConfig({ }, }, themeConfig: { - logo: 'logo.svg', + logo: '/logo.svg', nav: [ { text: 'Guide', link: '/guide/introduction.html' }, - { text: 'Reference', link: '/reference/cli.html' }, - { text: 'Examples', link: '/catalog/' }, - { text: 'Playground', link: '/playground.html' }, - ], - socialLinks: [ - { icon: 'github', link: 'https://github.com/ast-grep/ast-grep' }, - { icon: 'discord', link: 'https://discord.com/invite/4YZjf6htSQ' }, - ], - editLink: { - pattern: 'https://github.com/ast-grep/ast-grep.github.io/edit/main/website/:path' - }, - sidebar: [ - { - text: 'Guide', - link: '/guide/introduction.html', - items: [ - { text: 'Quick Start', link: '/guide/quick-start.html' }, - { text: 'Pattern Syntax', link: '/guide/pattern-syntax.html' }, - { text: 'Rule Essentials', link: '/guide/rule-config.html', collapsed: true, - items:[ - { text: 'Atomic Rule', link: '/guide/rule-config/atomic-rule.html' }, - { text: 'Relational Rule', link: '/guide/rule-config/relational-rule.html' }, - { text: 'Composite Rule', link: '/guide/rule-config/composite-rule.html' }, - { text: 'Utility Rule', link: '/guide/rule-config/utility-rule.html' }, - ],}, - { - text: 'Project Setup', collapsed: true, link: '/guide/scan-project.html', - items: [ - { text: 'Project Configuration', link: '/guide/project/project-config.html' }, - { text: 'Lint Rule', link: '/guide/project/lint-rule.html' }, - { text: 'Test Your Rule', link: '/guide/test-rule.html' }, - ], - }, - { text: 'Rewrite Code', link: '/guide/rewrite-code.html' }, - { - text: 'Tooling Overview', link: '/guide/tooling-overview.html', collapsed: true, - items: [ - { text: 'Editor Integration', link: '/guide/tools/editors.html' }, - ], - }, - { text: 'API Usage', link: '/guide/api-usage.html', collapsed: true, - items:[ - { text: 'JavaScript API', link: '/guide/api-usage/js-api.html' }, - { text: 'Python API', link: '/guide/api-usage/py-api.html' }, - ]}, - ], - collapsed: false, - }, - { - text: 'Examples', - link: '/catalog/', - items: [ - { text: 'C', link: '/catalog/c/'}, - { text: 'Go', link: '/catalog/go/'}, - { text: 'Python', link: '/catalog/python/'}, - { text: 'Ruby', link: '/catalog/ruby/'}, - { text: 'Rust', link: '/catalog/rust/'}, - { text: 'TypeScript', link: '/catalog/typescript/'}, - { text: 'TSX', link: '/catalog/tsx/'}, - ], - collapsed: true, - }, { text: 'Reference', items: [ - { text: 'Command Line Interface', link: '/reference/cli.html', collapsed: true, - items: [ - { text: 'sg run', link: '/reference/cli/run.html' }, - { text: 'sg scan', link: '/reference/cli/scan.html' }, - { text: 'sg test', link: '/reference/cli/test.html' }, - { text: 'sg new', link: '/reference/cli/new.html' }, - ], - }, - { text: 'Project Config', link: '/reference/sgconfig.html' }, - { text: 'Rule Config', link: '/reference/yaml.html', collapsed: true, - items: [ - { text: 'fix', link: '/reference/yaml/fix.html' }, - { text: 'transformation', link: '/reference/yaml/transformation.html' }, - { text: 'rewriter', link: '/reference/yaml/rewriter.html' }, - ], - }, + { text: 'Command Line Interface', link: '/reference/cli.html' }, + { text: 'Rule Config', link: '/reference/yaml.html' }, { text: 'Rule Object', link: '/reference/rule.html' }, - { text: 'API Reference', link: '/reference/api.html' }, - { text: 'Language List', link: '/reference/languages.html' }, { text: 'Playground Manual', link: '/reference/playground.html' }, ], - collapsed: true, - }, - { - text: 'Advanced Topics', - items: [ - { text: 'Core Concepts', link: '/advanced/core-concepts.html'}, - { text: 'How Rewrite Works', link: '/advanced/find-n-patch.html'}, - { text: 'Pattern Match Algorithm', link: '/advanced/match-algorithm.html'}, - { text: 'Custom Language Support', link: '/advanced/custom-language.html'}, - { text: 'Comparison with Other Tools', link: '/advanced/tool-comparison.html'}, - ], - collapsed: true, - }, - { - text: 'Contributing', - items: [ - { text: 'Guide', link: '/contributing/how-to.html' }, - { text: 'Development', link: '/contributing/development.html' }, - { text: 'Add New Language', link: '/contributing/add-lang.html' }, - ], - collapsed: true, }, { - text: 'Links', + text: 'Resources', items: [ - { text: 'Playground', link: '/playground.html' }, - { text: 'VSCode', link: 'https://marketplace.visualstudio.com/items?itemName=ast-grep.ast-grep-vscode'}, - { text: 'Discord', link: 'https://discord.com/invite/4YZjf6htSQ'}, - { text: 'Docs.rs', link: 'https://docs.rs/ast-grep-core/latest/ast_grep_core/' }, + { text: 'FAQ', link: '/advanced/faq.html' }, + { text: 'Rule Examples', link: '/catalog' }, + { text: 'Custom Language', link: '/advanced/custom-language.html' }, + { text: 'Contributing', link: '/contributing/how-to.html' }, + { text: 'Blog', link: '/blog.html' }, ], - collapsed: true, }, + { text: 'Playground', link: '/playground.html' }, + ], + socialLinks: [ + { icon: 'github', link: 'https://github.com/ast-grep/ast-grep' }, + { icon: 'discord', link: 'https://discord.com/invite/4YZjf6htSQ' }, ], + editLink: { + pattern: 'https://github.com/ast-grep/ast-grep.github.io/edit/main/website/:path', + }, + sidebar: { + '/blog/': [ + { text: 'Blog List', link: '/blog.html' }, + { text: 'Homepage', link: '/' }, + { + text: 'VSCode', + link: 'https://marketplace.visualstudio.com/items?itemName=ast-grep.ast-grep-vscode', + }, + { text: 'Discord', link: 'https://discord.com/invite/4YZjf6htSQ' }, + { text: 'StackOverflow', link: 'https://stackoverflow.com/questions/tagged/ast-grep' }, + { text: 'Reddit', link: 'https://www.reddit.com/r/astgrep/' }, + { text: 'Docs.rs', link: 'https://docs.rs/ast-grep-core/latest/ast_grep_core/' }, + ], + '/': sidebar, + }, footer: { message: 'Made with ā¤ļø with Rust', copyright: 'Copyright Ā© 2022-present Herrington Darkholme', @@ -161,4 +254,7 @@ export default defineConfig({ provider: 'local', }, }, -}) \ No newline at end of file + sitemap: { + hostname: 'https://ast-grep.github.io', + }, +}) diff --git a/website/.vitepress/theme/custom.css b/website/.vitepress/theme/custom.css index 3e5eefaf..587b4958 100644 --- a/website/.vitepress/theme/custom.css +++ b/website/.vitepress/theme/custom.css @@ -5,7 +5,7 @@ --vp-c-brand-2: #a89f2a; --vp-c-brand-1: #918623; --vp-c-brand-3: #2f4f6d; - --vp-custom-selctor-option-text: #213547; + --vp-custom-selector-option-text: #213547; --vp-custom-block-tip-text: var(--vp-c-text-1); @@ -13,5 +13,5 @@ --vp-custom-block-tip-code-bg: var(--vp-c-green-soft); } .dark { - --vp-custom-selctor-option-text: #213547; + --vp-custom-selector-option-text: #213547; } \ No newline at end of file diff --git a/website/_data/blog.data.ts b/website/_data/blog.data.ts new file mode 100644 index 00000000..67bce79c --- /dev/null +++ b/website/_data/blog.data.ts @@ -0,0 +1,44 @@ +// Created from https://github.com/vitejs/vite/blob/d7c8603897a8d78b83a4420846581a2e80cb57dd/docs/.vitepress/config.ts +import { createContentLoader } from 'vitepress' + +interface Post { + title: string + url: string + date: { + time: number + string: string + } + description: string +} + +declare const data: Post[] +export { data } + +export default createContentLoader('blog/*.md', { + // excerpt: true, + transform(raw): Post[] { + return raw + .map(({ url, frontmatter }) => ({ + title: frontmatter.head.find((e: any) => e[1].property === 'og:title')[1] + .content, + url, + date: formatDate(frontmatter.date), + description: frontmatter.head.find((e: any) => e[1].property === 'og:description')[1] + .content, + })) + .sort((a, b) => b.date.time - a.date.time) + }, +}) + +function formatDate(raw: string): Post['date'] { + const date = new Date(raw) + date.setUTCHours(12) + return { + time: +date, + string: date.toLocaleDateString('en-US', { + year: 'numeric', + month: 'long', + day: 'numeric', + }), + } +} diff --git a/website/_data/catalog.data.ts b/website/_data/catalog.data.ts new file mode 100644 index 00000000..2c45cdd3 --- /dev/null +++ b/website/_data/catalog.data.ts @@ -0,0 +1,117 @@ +import { JSON_SCHEMA, loadAll } from 'js-yaml' +import { type ContentData, createContentLoader } from 'vitepress' +import { ExampleLangs } from '../src/catalog/data' + +export interface RuleMeta { + id: string + name: string + type: string + link: string + playgroundLink: string + language: ExampleLangs + hasFix: boolean + rules: string[] + features: string[] +} + +declare const data: RuleMeta[] +export { data } + +export default createContentLoader('catalog/**/*.md', { + includeSrc: true, + transform(raw): RuleMeta[] { + return raw + .filter(({ url }) => { + return url.endsWith('.html') && !url.includes('rule-template') + }) + .map(extractRuleInfo) + .sort((a, b) => a.id.localeCompare(b.id)) + }, +}) + +function extractRuleInfo({ url, src }: ContentData): RuleMeta { + const source = src! + const id = url.split('/').pop()?.replace(/\.md$/, '') || '' + const type = source.includes('```yml') || source.includes('```yaml') + ? 'YAML' : + 'Pattern' + const playgroundLink = /\[Playground Link\]\((.+)\)/.exec(source)?.[1] || '' + const hasFix = source.includes('[] { + const yaml = source.match(/```ya?ml\n([\s\S]+?)\n```/)?.[1] || '' + if (!yaml) { + return [] + } + return loadAll(yaml, null, { schema: JSON_SCHEMA }) as Record[] +} + +function extractUsedRules(yamls: Record[]): string[] { + const rules = new Set() + for (const yaml of yamls) { + if (typeof yaml !== 'object' || yaml === null) { + continue + } + extractOneRuleObject(yaml.rule as object, rules) + for (const util of Object.values(yaml.utils as object || {})) { + extractOneRuleObject(util as object, rules) + } + } + return [...rules] +} + +function extractOneRuleObject(rule: object, rules: Set) { + if (!rule) { + return + } + for (const ruleName of Object.keys(rule)) { + rules.add(ruleName) + if (ruleName === 'any' || ruleName === 'all') { + // @ts-expect-error + for (const subRule of rule[ruleName] as object[]) { + extractOneRuleObject(subRule, rules) + } + } + } +} + +function extractUsedFeatures(yamls: Record[]): string[] { + const features = new Set() + for (const yaml of yamls) { + if (typeof yaml === 'object' && yaml !== null) { + if ('utils' in yaml) { + features.add('utils') + } + if ('constraints' in yaml) { + features.add('constraints') + } + if ('rewriters' in yaml) { + features.add('rewriters') + } + if ('transform' in yaml) { + features.add('transform') + } + if ('labels' in yaml) { + features.add('labels') + } + } + } + return [...features] +} diff --git a/website/_data/parsers.ts b/website/_data/parsers.ts new file mode 100644 index 00000000..a1f5615d --- /dev/null +++ b/website/_data/parsers.ts @@ -0,0 +1,65 @@ +export const parserPaths = { + javascript: 'tree-sitter-javascript.wasm', + typescript: 'tree-sitter-typescript.wasm', + tsx: 'tree-sitter-tsx.wasm', + // not so well supported lang... + bash: 'tree-sitter-bash.wasm', + c: 'tree-sitter-c.wasm', + csharp: 'tree-sitter-c_sharp.wasm', + css: 'tree-sitter-css.wasm', + cpp: 'tree-sitter-cpp.wasm', + elixir: 'tree-sitter-elixir.wasm', + go: 'tree-sitter-go.wasm', + html: 'tree-sitter-html.wasm', + java: 'tree-sitter-java.wasm', + json: 'tree-sitter-json.wasm', + kotlin: 'tree-sitter-kotlin.wasm', + php: 'tree-sitter-php_only.wasm', + python: 'tree-sitter-python.wasm', + ruby: 'tree-sitter-ruby.wasm', + rust: 'tree-sitter-rust.wasm', + scala: 'tree-sitter-scala.wasm', + swift: 'tree-sitter-swift.wasm', + yaml: 'tree-sitter-yaml.wasm', +} + +export const repos: Record = { + bash: 'https://unpkg.com/tree-sitter-bash', + c: 'https://unpkg.com/tree-sitter-c', + cpp: 'https://unpkg.com/tree-sitter-cpp', + csharp: 'https://unpkg.com/tree-sitter-c-sharp', + css: 'https://unpkg.com/tree-sitter-css', + go: 'https://unpkg.com/tree-sitter-go', + html: 'https://unpkg.com/tree-sitter-html', + java: 'https://unpkg.com/tree-sitter-java', + javascript: 'https://unpkg.com/tree-sitter-javascript', + json: 'https://unpkg.com/tree-sitter-json', + php: 'https://unpkg.com/tree-sitter-php', + python: 'https://unpkg.com/tree-sitter-python', + ruby: 'https://unpkg.com/tree-sitter-ruby', + rust: 'https://unpkg.com/tree-sitter-rust', + scala: 'https://unpkg.com/tree-sitter-scala', + tsx: 'https://unpkg.com/tree-sitter-typescript', + typescript: 'https://unpkg.com/tree-sitter-typescript', +} + +// https://github.com/ast-grep/ast-grep/blob/main/crates/language/Cargo.toml +export const versions: Record = { + bash: '0.23.3', + c: '0.23.5', + cpp: '0.23.4', + csharp: '0.23.1', + css: '0.23.1', + go: '0.23.4', + html: '0.23.2', + java: '0.23.5', + javascript: '0.23.1', + json: '0.24.2', + php: '0.23.11', + python: '0.23.3', + ruby: '0.23.1', + rust: '0.23.2', + scala: '0.23.3', + tsx: '0.23.2', + typescript: '0.23.2', +} diff --git a/website/advanced/core-concepts.md b/website/advanced/core-concepts.md index a67c5ea7..43f09b18 100644 --- a/website/advanced/core-concepts.md +++ b/website/advanced/core-concepts.md @@ -1,8 +1,8 @@ -# Deep Dive into ast-grep's Pattern +# Core Concepts in ast-grep's Pattern One key highlight of ast-grep is its pattern. -_Pattern is a convenient way to write and read expressions that describe syntax trees_. It resembles code, but with some special syntax and semantics that allow you to match parts of a syntax tree based on their structure, type or content. +_Pattern is a convenient way to write and read expressions that describe syntax trees_. It resembles code, but with some special syntax and structure that allow you to match parts of a syntax tree based on their structure, type or content. While ast-grep's pattern is **easy to learn**, it is **hard to master**. It requires you to know the Tree-sitter grammar and meaning of the target language, as well as the rules and conventions of ast-grep. @@ -30,7 +30,7 @@ When you use ast-grep to search for patterns in source code, you need to underst Source code input is text, a sequence of characters that follows certain syntax rules. You can use common search tools like [silver-searcher](https://github.com/ggreer/the_silver_searcher) or [ripgrep](https://github.com/BurntSushi/ripgrep) to search for text patterns in source code. -However, ast-grep does not match patterns against the text directly. Instead, it parses the text into a tree structure that represents the syntax of the code. This allows ast-grep to match patterns based on the semantic meaning of the code, not just its surface appearance. This is known as [structural](https://docs.sourcegraph.com/code_search/reference/structural) [search](https://docs.sourcegraph.com/code_search/reference/structural), which searches for code with a specific structure, not just a specific text. +However, ast-grep does not match patterns against the text directly. Instead, it parses the text into a tree structure that represents the syntax of the code. This allows ast-grep to match patterns based on the structure of the code, not just its surface appearance. This is known as [structural](https://docs.sourcegraph.com/code_search/reference/structural) [search](https://docs.sourcegraph.com/code_search/reference/structural), which searches for code with a specific structure, not just a specific text. _Therefore, the patterns you write must also be of valid syntax that can be compared with the code tree._ @@ -40,7 +40,7 @@ Though `pattern` structurally matches code, you can use [the atomic rule `regex` ## AST vs CST -To represent the syntax and semantics of code, we have two types of tree structures: [AST](https://www.wikiwand.com/en/Abstract_syntax_tree) and [CST](https://eli.thegreenplace.net/2009/02/16/abstract-vs-concrete-syntax-trees/). +To represent the syntax and structure of code, we have two types of tree structures: [AST](https://www.wikiwand.com/en/Abstract_syntax_tree) and [CST](https://eli.thegreenplace.net/2009/02/16/abstract-vs-concrete-syntax-trees/). AST stands for Abstract Syntax Tree, which is a **simplified** representation of the code that _omits some details_ like punctuation and whitespaces. CST stands for Concrete Syntax Tree, which is a more **faithful** representation of the code that _includes all the details_. @@ -94,18 +94,18 @@ rule: # matches `1 + 1` ``` -Further more, ast-grep's meta variable matches only named nodes by default. `return $A` matches only the first statement below. [Playground link](https://ast-grep.github.io/playground.html#eyJtb2RlIjoiUGF0Y2giLCJsYW5nIjoiamF2YXNjcmlwdCIsInF1ZXJ5IjoicmV0dXJuICRBIiwiY29uZmlnIjoiIyBDb25maWd1cmUgUnVsZSBpbiBZQU1MXG5ydWxlOlxuICBhbnk6XG4gICAgLSBwYXR0ZXJuOiBpZiAoZmFsc2UpIHsgJCQkIH1cbiAgICAtIHBhdHRlcm46IGlmICh0cnVlKSB7ICQkJCB9XG5jb25zdHJhaW50czpcbiAgIyBNRVRBX1ZBUjogcGF0dGVybiIsInNvdXJjZSI6InJldHVybiAxMjNcbnJldHVybjsifQ==). +Further more, ast-grep's meta variable matches only named nodes by default. `return $A` matches only the first statement below. [Playground link](/playground.html#eyJtb2RlIjoiUGF0Y2giLCJsYW5nIjoiamF2YXNjcmlwdCIsInF1ZXJ5IjoicmV0dXJuICRBIiwicmV3cml0ZSI6IiIsInN0cmljdG5lc3MiOiJzbWFydCIsInNlbGVjdG9yIjoiIiwiY29uZmlnIjoiIiwic291cmNlIjoicmV0dXJuIDEyM1xucmV0dXJuOyJ9). ```js return 123 // `123` is named `number` and matched. return; // `;` is unnamed and not matched. ``` -We can use double dollar `$$VAR` to _include unnamed nodes_ in the pattern result. `return $$A` will match both statement above. [Playground link](https://ast-grep.github.io/playground.html#eyJtb2RlIjoiUGF0Y2giLCJsYW5nIjoiamF2YXNjcmlwdCIsInF1ZXJ5IjoicmV0dXJuICQkQSIsImNvbmZpZyI6IiMgQ29uZmlndXJlIFJ1bGUgaW4gWUFNTFxucnVsZTpcbiAgYW55OlxuICAgIC0gcGF0dGVybjogaWYgKGZhbHNlKSB7ICQkJCB9XG4gICAgLSBwYXR0ZXJuOiBpZiAodHJ1ZSkgeyAkJCQgfVxuY29uc3RyYWludHM6XG4gICMgTUVUQV9WQVI6IHBhdHRlcm4iLCJzb3VyY2UiOiJyZXR1cm4gMTIzXG5yZXR1cm47In0=). +We can use double dollar `$$VAR` to _include unnamed nodes_ in the pattern result. `return $$A` will match both statement above. [Playground link](/playground.html#eyJtb2RlIjoiUGF0Y2giLCJsYW5nIjoiamF2YXNjcmlwdCIsInF1ZXJ5IjoicmV0dXJuICQkQSIsInJld3JpdGUiOiIiLCJzdHJpY3RuZXNzIjoic21hcnQiLCJzZWxlY3RvciI6IiIsImNvbmZpZyI6IiIsInNvdXJjZSI6InJldHVybiAxMjNcbnJldHVybjsifQ==). ## Kind vs Field -Sometimes, using kind alone is not enough to find the nodes we want. A node may have several children with the same kind, but different roles in the code. For [example](https://ast-grep.github.io/playground.html#eyJtb2RlIjoiQ29uZmlnIiwibGFuZyI6ImphdmFzY3JpcHQiLCJxdWVyeSI6ImNvbnNvbGUubG9nKCRNQVRDSCkiLCJjb25maWciOiJydWxlOlxuICBraW5kOiBzdHJpbmciLCJzb3VyY2UiOiJ2YXIgYSA9IHtcbiAgJ2tleSc6ICd2YWx1ZSdcbn0ifQ==), in JavaScript, an object may have multiple keys and values, all with the string kind. +Sometimes, using kind alone is not enough to find the nodes we want. A node may have several children with the same kind, but different roles in the code. For [example](/playground.html#eyJtb2RlIjoiQ29uZmlnIiwibGFuZyI6ImphdmFzY3JpcHQiLCJxdWVyeSI6ImNvbnNvbGUubG9nKCRNQVRDSCkiLCJjb25maWciOiJydWxlOlxuICBraW5kOiBzdHJpbmciLCJzb3VyY2UiOiJ2YXIgYSA9IHtcbiAgJ2tleSc6ICd2YWx1ZSdcbn0ifQ==), in JavaScript, an object may have multiple keys and values, all with the string kind. To distinguish them, we can use `field` to specify the relation between a node and its parent. In ast-grep, `field` can be specified in two [relational rules](/guide/rule-config/relational-rule.html#relational-rule-mnemonics): `has` and `inside`. @@ -144,7 +144,7 @@ ast-grep goes further beyond Tree-sitter. It has a concept about the "significan * Otherwise, the node is a **trivial** node. :::warning Even significance is not enough -Most Tree-sitter languages do not encode all critical semantics in AST, the tree with named nodes only. +Most Tree-sitter languages do not encode all critical structures in AST, the tree with named nodes only. Even significant nodes are not sufficient to represent the meaning of code. We have to preserve some trivial nodes for precise matching. ::: @@ -156,4 +156,4 @@ If you do not care about if the method is a getter method, a static method or an ## Summary Thank you for reading until here! There are many concepts in this article. Let's summarize them in one paragraph. -ast-grep uses Tree-sitter to parse _textual_ source code into a detailed tree _structure_ called **CST**. We can get **AST** from CST by only keeping **named nodes**, which have kinds. To search nodes in a syntax tree, you can use both node **kind** and node **field**, which is a special role of a child node relative to its parent node. A node with either a kind or a field is a **significant** node. +ast-grep uses Tree-sitter to parse _textual_ source code into a detailed tree _structure_ called **CST**. We can get **AST** from CST by only keeping **named nodes**, which have kinds. To search nodes in a syntax tree, you can use both node **kind** and node **field**, which is a special role of a child node relative to its parent node. A node with either a kind or a field is a **significant** node. \ No newline at end of file diff --git a/website/advanced/custom-language.md b/website/advanced/custom-language.md index a3a6d9bd..b80e88eb 100644 --- a/website/advanced/custom-language.md +++ b/website/advanced/custom-language.md @@ -1,9 +1,5 @@ # Custom Language Support -:::danger Experimental Feature -Custom language in ast-grep is an experimental option. Use it with caution! -::: - In this guide, we will show you how to use a custom language that is not built into ast-grep. We will use [Mojo šŸ”„](https://www.modular.com/mojo) as an example! @@ -50,21 +46,17 @@ git clone https://github.com/HerringtonDarkholme/tree-sitter-mojo.git ## Compile the Parser as Dynamic Library Once we have prepared the tool and the grammar, we can compile the parser as dynamic library. +_`tree-sitter-cli` is the preferred way to compile dynamic library._ -There are no official instructions on how to do this on the internet, but we can get some hints from Tree-sitter's [source code](https://github.com/tree-sitter/tree-sitter/blob/a62bac5370dc5c76c75935834ef083457a6dd0e1/cli/loader/src/lib.rs#L111). - -One way is to set an environment variable called `TREE_SITTER_LIBDIR` to the path where you want to store the dynamic library, and then run `tree-sitter test` in the directory of your custom language parser. - -This will generate a dynamic library at the `TREE_SITTER_LIBDIR` path. - -For example: +The [official way](https://tree-sitter.github.io/tree-sitter/cli/build.html) to compile a parser as a dynamic library is to use the `tree-sitter build` command. ```sh -cd path/to/mojo/parser -export TREE_SITTER_LIBDIR=path/to/your/dir -tree-sitter test +tree-sitter build --output mojo.so ``` +The build command compiles your parser into a dynamically-loadable library as a shared object (.so, .dylib, or .dll). + + Another way is to use the following [commands](https://github.com/tree-sitter/tree-sitter/blob/a62bac5370dc5c76c75935834ef083457a6dd0e1/cli/loader/src/lib.rs#L380-L410) to compile the parser manually: ```shell @@ -79,14 +71,27 @@ For example, in mojo's case, the full command will be: gcc -shared -fPIC -fno-exceptions -g -I 'src' -o mojo.so -O2 src/scanner.cc -xc src/parser.c -lstdc++ ``` -:::warning -`tree-sitter-cli` is the preferred way to compile dynamic library. +:::details Old tree-sitter does not have build command + +[Previously](https://github.com/tree-sitter/tree-sitter/pull/3174) there are no official instructions on how to do this on the internet, but we can get some hints from Tree-sitter's [source code](https://github.com/tree-sitter/tree-sitter/blob/a62bac5370dc5c76c75935834ef083457a6dd0e1/cli/loader/src/lib.rs#L111). + +One way is to set an environment variable called `TREE_SITTER_LIBDIR` to the path where you want to store the dynamic library, and then run `tree-sitter test` in the directory of your custom language parser. + +This will generate a dynamic library at the `TREE_SITTER_LIBDIR` path. + +For example: + +```sh +cd path/to/mojo/parser +export TREE_SITTER_LIBDIR=path/to/your/dir +tree-sitter test +``` ::: ## Register Language in `sgconfig.yml` Once you have compiled the dynamic library for your custom language, you need to register it in the `sgconfig.yml` file. -You can use the command [`sg new`](/guide/scan-project.html#create-scaffolding) to create a project and find the configuration file in the project root. +You can use the command [`ast-grep new`](/guide/scan-project.html#create-scaffolding) to create a project and find the configuration file in the project root. You need to add a new entry under the `customLanguages` key with the name of your custom language and some properties: @@ -120,7 +125,7 @@ Now you are ready to use your custom language with ast-grep! You can use it as a For example, to search for all occurrences of `print` in mojo files, you can run: ```bash -sg -p "print" -l mojo +ast-grep -p "print" -l mojo ``` Or you can write a rule in yaml like this: diff --git a/website/advanced/faq.md b/website/advanced/faq.md new file mode 100644 index 00000000..c6779472 --- /dev/null +++ b/website/advanced/faq.md @@ -0,0 +1,259 @@ +# Frequently Asked Questions + +## My pattern does not work, why? + +1. **Use the Playground**: Test your pattern in the [ast-grep playground](/playground.html). +2. **Check for Valid Code**: Make sure your pattern is valid code that tree-sitter can parse. +3. **Ensure Correctness**: Use a [pattern object](/guide/rule-config/atomic-rule.html#pattern) to ensure your code is correct and unambiguous. +4. **Explore Examples**: See ast-grep's [catalog](/catalog/) for more examples. + + +The most common scenario is that you only want to match a sub-expression or one specific AST node in a whole syntax tree. +However, the code fragment corresponding to the sub-expression may not be valid code. +To make the code can be parsed by tree-sitter, you probably need more context instead of providing just code fragment. + +For example, if you want to match key-value pair in JSON, writing `"key": "$VAL"` will not work because it is not a legal JSON. + +Instead, you can provide context via the pattern object. See [playground code](/playground.html#eyJtb2RlIjoiQ29uZmlnIiwibGFuZyI6Impzb24iLCJxdWVyeSI6ImZvbygkJCRBLCBiLCAkJCRDKSIsInJld3JpdGUiOiIiLCJjb25maWciOiJydWxlOlxuICBwYXR0ZXJuOiBcbiAgICBjb250ZXh0OiAne1widmVyc2lvblwiOiBcIiRWRVJcIiB9J1xuICAgIHNlbGVjdG9yOiBwYWlyIiwic291cmNlIjoie1xuICAgIFwidmVyc2lvblwiOiBcInZlclwiXG59In0=). + +```YAML +rule: + pattern: + context: '{"key": "$VAL"}' + selector: pair +``` + +The idea is that you can write full an valid code in the `context` field and use `selector` to select the sub-AST node. + +This trick can be used in other languages as well, like [C](/catalog/c/#match-function-call) and [Go](/catalog/go/#match-function-call-in-golang). That said, pattern is not always the best choice for code search. [Rule](/guide/rule-config.html) can be more expressive and powerful. + +## My Rule does not work, why? +Here are some tips to debug your rule: +* Use the [ast-grep playground](/playground.html) to test your rule. +* Simplify your rule to the minimal possible code that reproduces the issue. +* Confirm pattern's matched AST nodes are expected. e.g. statement and expression are [different matches](/advanced/pattern-parse.html#extract-effective-ast-for-pattern). This usually happens when you use `follows` or `precedes` in the rule. +* Check the [rule order](/advanced/faq.html#why-is-rule-matching-order-sensitive). The order of rules matters in ast-grep especially when using meta variables with relational rules. + +## CLI and Playground produce different results, why? + +There are two main reasons why the results may differ: + +* **Parser Version**: The CLI may use a different version of the tree-sitter parser than the Playground. +Playground parsers are updated less frequently than the CLI, so there may be differences in the results. +* **Text Encoding**: The CLI and Playground use different text encodings. CLI uses utf-8, while the Playground uses utf-16. +The encoding difference may cause different fallback parsing during [error recovery](https://github.com/tree-sitter/tree-sitter/issues/224). + +To debug the issue, you can use the [`--debug-query`](/reference/cli/run.html#debug-query-format) in the CLI to see the parsed AST nodes and meta variables. + +```sh +ast-grep run -p --debug-query ast +``` + +The debug output will show the parsed AST nodes and you can compare them with the [Playground](/playground.html). You can also use different debug formats like `cst` or `pattern`. + +Different results are usually caused by incomplete or wrong code snippet in the pattern. A common fix is to provide a complete context code via the [pattern object](/reference/rule.html#atomic-rules). + +```yaml +rule: + pattern: + context: 'int main() { return 0; }' + selector: function +``` + +See [Pattern Deep Dive](/advanced/pattern-parse.html) for more context. Alternatively, you can try [rule](/guide/rule-config.html) instead. + +Note `--debug-query` is not only for pattern, you can pass source code as `pattern` to see the parsed AST. + +:::details Text encoding impacts tree-sitter error recovery. +Tree-sitter is a robust parser that can recover from syntax errors and continue parsing the rest of the code. +The exact strategy for error recovery is implementation-defined and uses a heuristic to determine the best recovery strategy. +See [tree-sitter issue](https://github.com/tree-sitter/tree-sitter/issues/224) for more details. + +Text-encoding will affect the error recovery because it changed the cost of different recovery strategies. +::: + +If you find the inconsistency between CLI and Playground, try confirming the playground version by hovering over the language label in playground, and the CLI version by [this file](https://github.com/ast-grep/ast-grep/blob/main/crates/language/Cargo.toml). + +![Playground Version](/image/playground-parser-version.png) + +:::tip Found inconsistency? +You can also [open an issue in the Playground repository](https://github.com/ast-grep/ast-grep.github.io/issues) if you find outdated parsers. Contribution to update the Playground parser is warmly welcome! +::: + + +## MetaVariable does not work, why? + +1. **Correct Naming**: Start meta variables with the `$` sign, followed by uppercase letters (A-Z), underscores (`_`), or digits (1-9). +2. **Single AST Node**: A meta variable should be a single AST node. Avoid mixing meta variables with other text in one AST node. For example, `mix$OTHER_VAR` or `use$HOOK` will not work. +3. **Named AST Nodes**: By default, a meta variable matches only named AST nodes. Use double dollar signs like `$$UNNAMED` to match unnamed nodes. + +## Multiple MetaVariable does not work + +Multiple meta variables in ast-grep, such as `$$$MULTI`, are lazy. They stop matching nodes if the first node after them can match. + +For example, `foo($$$A, b, $$$C)` matches `foo(a, c, b, b, c)`. `$$$A` stops before the first `b` and only matches `a, c`. + +This design follows TypeScript's template literal types (`${infer VAR}Literal`) to ensure multiple meta variables always produce a match or non-match in linear time. + +## Pattern cannot match my use case, how? + +Patterns are a quick and easy way to match code in ast-grep, but they might not handle complex code. YAML rules are much more expressive and make it easier to specify complex code. + +## I want to pattern match function call starts with some prefix string, how can I do that? + +It is common to find function name or variable name following some naming convention like a function must starts with specific prefix. + +For example, [React Hook](https://react.dev/learn/reusing-logic-with-custom-hooks#hook-names-always-start-with-use) in JavaScript requires function names start with `use`. Another example will be using `io_uring` in [Linux asynchronous programming](https://unixism.net/loti/genindex.html). + +You may start with pattern like `use$HOOK` or `io_uring_$FUNC`. However, they are not valid meta variable names since the AST node text does not start with the dollar sign. + +The workaround is using [`constraints`](https://ast-grep.github.io/guide/project/lint-rule.html#constraints) in [YAML rule](https://ast-grep.github.io/guide/project/lint-rule.html) and [`regex`](https://ast-grep.github.io/guide/rule-config/atomic-rule.html#regex) rule. + +```yaml +rule: + pattern: $HOOK($$$ARGS) +constraints: + HOOK: { regex: '^use' } +``` + +[Example usage](/playground.html#eyJtb2RlIjoiQ29uZmlnIiwibGFuZyI6ImphdmFzY3JpcHQiLCJxdWVyeSI6ImZvbygkJCRBLCBiLCAkJCRDKSIsInJld3JpdGUiOiIiLCJjb25maWciOiJydWxlOlxuICBwYXR0ZXJuOiAkSE9PSygkJCRBUkdTKVxuY29uc3RyYWludHM6XG4gIEhPT0s6IHsgcmVnZXg6IF51c2UgfSIsInNvdXJjZSI6ImZ1bmN0aW9uIFJlYWN0Q29tcG9uZW50KCkge1xuICAgIGNvbnN0IGRhdGEgPSBub3RIb28oKVxuICAgIGNvbnN0IFtmb28sIHNldEZvb10gPSB1c2VTdGF0ZSgnJylcbn0ifQ==). + + +:::danger MetaVariable must be one single AST node +Meta variables cannot be mixed with prefix/suffix string . `use$HOOK` and `io_uring_$FUNC` are not valid meta variables. They are parsed as one AST node as whole, and +ast-grep will not treat them as valid meta variable name. +::: + +## How to reuse rule for similar languages like TS/JS or C/C++? + +ast-grep does not support multiple languages in one rule because: + +1. **Different ASTs**: Similar languages still have different ASTs. For instance, [JS](/playground.html#eyJtb2RlIjoiUGF0Y2giLCJsYW5nIjoiamF2YXNjcmlwdCIsInF1ZXJ5IjoiIiwicmV3cml0ZSI6IiIsInN0cmljdG5lc3MiOiJyZWxheGVkIiwic2VsZWN0b3IiOiIiLCJjb25maWciOiIiLCJzb3VyY2UiOiJmdW5jdGlvbiB0ZXN0KGEpIHt9In0=) and [TS](#eyJtb2RlIjoiUGF0Y2giLCJsYW5nIjoidHlwZXNjcmlwdCIsInF1ZXJ5IjoiIiwicmV3cml0ZSI6IiIsInN0cmljdG5lc3MiOiJyZWxheGVkIiwic2VsZWN0b3IiOiIiLCJjb25maWciOiIiLCJzb3VyY2UiOiJmdW5jdGlvbiB0ZXN0KGEpIHt9In0=) have different parsing trees for the same function declaration code. +2. **Different Kinds**: Similar languages may have different AST node kinds. Since ast-grep reports non-existing kinds as errors, there is no straightforward way to report error for kind only existing in one language. +3. **Debugging Experience**: Mixing languages in one rule requires users to test the rule in both languages. This can be confusing and error-prone, especially when unexpected results occur. + +Supporting multi-lang rule is a challenging task for both tool developers and users. Instead, we recommend two approaches: +* **Always use the superset language**: Rule reusing usually happens when one language is a superset of another, e.g., TS and JS. In this case, you can use [`languageGlobs`](/reference/sgconfig.html#languageglobs) to parse files in the superset language. This is more suitable if you don't need to distinguish between the two languages. +* **Write Separate Rules**: Generate separate rules for each language. This approach is suitable when you do need to handle the differences between the languages. + +If you have a better, clearer and easier proposal to support multi-lang rule, please leave a comment under [this issue](https://github.com/ast-grep/ast-grep/issues/525). + + +## Why is rule matching order sensitive? + +ast-grep's rule matching is a step-by-step process. It matches one atomic rule at a time, stores the matched meta-variable, and proceeds to the next rule until all rules are matched. + +**Rule matching is ordered** because previous rules' matched meta-variables can affect later rules. Only the first rule can specify what a `$META_VAR` matches, and later rules can only match the content captured by the first rule without modifying it. + +Let's see an example. Suppose we want to find a recursive function in JavaScript. [This rule](https://ast-grep.github.io/playground.html#eyJtb2RlIjoiQ29uZmlnIiwibGFuZyI6ImphdmFzY3JpcHQiLCJxdWVyeSI6ImZvbygkJCRBLCBiLCAkJCRDKSIsInJld3JpdGUiOiIiLCJjb25maWciOiJpZDogcmVjdXJzaXZlLWNhbGxcbmxhbmd1YWdlOiBKYXZhU2NyaXB0XG5ydWxlOlxuICBhbGw6XG4gIC0gcGF0dGVybjogZnVuY3Rpb24gJEYoKSB7ICQkJCB9XG4gIC0gaGFzOlxuICAgICAgcGF0dGVybjogJEYoKVxuICAgICAgc3RvcEJ5OiBlbmRcbiIsInNvdXJjZSI6ImZ1bmN0aW9uIHJlY3Vyc2UoKSB7XG4gICAgZm9vKClcbiAgICByZWN1cnNlKClcbn0ifQ==) can do the trick. + +:::code-group + +```yml [rule.yml] +id: recursive-call +language: JavaScript +rule: + all: + - pattern: function $F() { $$$ } + - has: + pattern: $F() + stopBy: end +``` + +```js [match.js] +function recurse() { + foo() + recurse() +} +``` +::: + +The rule works because the pattern `function $F() { $$$ }` matches first, capturing `$F` as `recurse`. The later `has` rule then looks for a `recurse()` call based on the matched `$F`. + +If we [swap the order of rules](https://ast-grep.github.io/playground.html#eyJtb2RlIjoiQ29uZmlnIiwibGFuZyI6ImphdmFzY3JpcHQiLCJxdWVyeSI6ImZvbygkJCRBLCBiLCAkJCRDKSIsInJld3JpdGUiOiIiLCJjb25maWciOiJpZDogcmVjdXJzaXZlLWNhbGxcbmxhbmd1YWdlOiBKYXZhU2NyaXB0XG5ydWxlOlxuICBhbGw6XG4gIC0gaGFzOlxuICAgICAgcGF0dGVybjogJEYoKVxuICAgICAgc3RvcEJ5OiBlbmRcbiAgLSBwYXR0ZXJuOiBmdW5jdGlvbiAkRigpIHsgJCQkIH1cbiIsInNvdXJjZSI6ImZ1bmN0aW9uIHJlY3Vyc2UoKSB7XG4gICAgZm9vKClcbiAgICByZWN1cnNlKClcbn0ifQ==), it will produce no match. + +```yml [rule.yml] +id: recursive-call +language: JavaScript +rule: + all: + - has: # N.B. has is the first rule + pattern: $F() + stopBy: end + - pattern: function $F() { $$$ } +``` + +In this case, the `has` rule matches first and captures `$F` as `foo` since `foo()` is the first function call matching the pattern `$F()`. The later rule `function $F() { $$$ }` will only find the `foo` declaration instead of `recurse`. + +:::tip +Using `all` to specify the order of rule matching can be helpful when debugging YAML rules. +::: + +## What does unordered rule object imply? + +A rule object in ast-grep is an unordered dictionary. The order of rule application is implementation-defined. Currently, ast-grep applies atomic rules first, then composite rules, and finally relational rules. + +If your rule depends on using meta variables in later rules, the best way is to use the `all` rule to specify the order of rules. + +## `kind` and `pattern` rules are not working together, why? + +The most common scenario is that your pattern is parsed as a different AST node than you expected. And you may use `kind` rule to filter out the AST node you want to match. This does not work in ast-grep for two reasons: +1. tree-sitter, the underlying parser library, does not offer a way to parse a string of a specific kind. So `kind` rule cannot be used to change the parsing outcome of a `pattern`. +2. ast-grep rules are mostly independent of each other, except sharing meta-variables during a match. `pattern` will behave the same regardless of another `kind` rule. + +To specify the `kind` of a `pattern`, you need to use [pattern](/guide/rule-config/atomic-rule.html#pattern-object) [object](/advanced/pattern-parse.html#incomplete-pattern-code). + +For example, to match class field in JavaScript, a kind + pattern rule [will not work](/playground.html#eyJtb2RlIjoiQ29uZmlnIiwibGFuZyI6ImphdmFzY3JpcHQiLCJxdWVyeSI6IiIsInJld3JpdGUiOiIiLCJzdHJpY3RuZXNzIjoic21hcnQiLCJzZWxlY3RvciI6IiIsImNvbmZpZyI6InJ1bGU6XG4gIHBhdHRlcm46IGEgPSAxMjNcbiAga2luZDogZmllbGRfZGVmaW5pdGlvbiIsInNvdXJjZSI6ImNsYXNzIEEge1xuICAgIGEgPSAxMjNcbn0ifQ==): + +```yaml +# these are two separate rules +pattern: a = 123 # rule 1 +kind: field_definition # rule 2 +``` + +This is because pattern `a = 123` is parsed as [`assignment_expression`](/playground.html#eyJtb2RlIjoiUGF0Y2giLCJsYW5nIjoiamF2YXNjcmlwdCIsInF1ZXJ5IjoiYSA9IDEyMyIsInJld3JpdGUiOiIiLCJzdHJpY3RuZXNzIjoic21hcnQiLCJzZWxlY3RvciI6IiIsImNvbmZpZyI6IiIsInNvdXJjZSI6IiJ9). Pattern and kind are two separate rules. And using them together will match nothing because no AST will have both `assignment_expression` and `field_definition` kind at once. + +Instead, you need to use pattern object to provide enough context code for the parser to parse the code snippet as `field_definition`: + +```yaml +# this is one single pattern rule! +pattern: + context: 'class A { a = 123 }' # provide full context code + selector: field_definition # select the effective pattern +``` + +Note the rule above is one single pattern rule, instead of two. The `context` field provides the full unambiguous code snippet of `class`. So the `a = 123` will be parsed as `field_definition`. The `selector` field then selects the `field_definition` node as the [effective pattern](/advanced/pattern-parse.html#steps-to-create-a-pattern) matcher. + +## Does ast-grep support some advanced static analysis? + +Short answer: **NO**. + +Long answer: ast-grep at the moment does not support the following information: +* [scope analysis](https://eslint.org/docs/latest/extend/scope-manager-interface) +* [type information](https://semgrep.dev/docs/writing-rules/pattern-syntax#typed-metavariables) +* [control flow analysis](https://en.wikipedia.org/wiki/Control-flow_analysis) +* [data flow analysis](https://en.wikipedia.org/wiki/Data-flow_analysis) +* [taint analysis](https://semgrep.dev/docs/writing-rules/data-flow/taint-mode) +* [constant propagation](https://semgrep.dev/docs/writing-rules/data-flow/constant-propagation) + +More concretely, it is not easy, or even possible, to achieve the following tasks in ast-grep: + +* Find variables that are not defined/used in the current scope. +* Find variables of a specific type. +* Find code that is unreachable. +* Find code that is always executed. +* Identify the flow of user input. + +Also see [tool comparison](/advanced/tool-comparison.html) for more information. + +## I don't want to read the docs / I don't understand the docs / The docs are too long / I have an urgent request + +[Open Source Software](https://antfu.me/posts/why-reproductions-are-required) is served "as-is" by volunteers. We appreciate your interest in ast-grep, but we also have limited time and resources to address every request. + +We appreciate constructive feedback and are always looking for ways to improve the documentation and the tool itself. There are several ways you can help us or yourself: + +* Ask [Copilot](https://copilot.microsoft.com/) or other AI assistants to help you understand the docs. +* Provide feedbacks or pull requests on the [documentation](https://github.com/ast-grep/ast-grep.github.io). +* Browse [Discord](https://discord.com/invite/4YZjf6htSQ), [StackOverflow](https://stackoverflow.com/questions/tagged/ast-grep) or [Reddit](https://www.reddit.com/r/astgrep/). + +~~If you just want an answer without effort, let the author [write a rule for you](https://github.com/sponsors/HerringtonDarkholme).~~ \ No newline at end of file diff --git a/website/advanced/how-ast-grep-works.md b/website/advanced/how-ast-grep-works.md new file mode 100644 index 00000000..907d81c5 --- /dev/null +++ b/website/advanced/how-ast-grep-works.md @@ -0,0 +1,62 @@ +# How ast-grep Works: A bird's-eye view + +In the world of software development, efficiently searching, rewriting, linting, and analyzing code is essential for maintaining high-quality projects. + +This is where **ast-grep** comes into play. Designed as a powerful structural search tool, ast-grep simplifies these tasks by leveraging the Abstract Syntax Tree (AST) representation of code. Let's break down how ast-grep works with the help of a diagram. + +![Workflow](/image/diagram.png) + +## The Workflow of ast-grep + +Generally speaking, ast-grep takes user _queries of various input_ formats, _parses the code into an AST_ using TreeSitter, and performs _search, rewrite, lint, and analysis_, utilizing the full power of CPU cores. + +### **Query via Various Formats** + +ast-grep can accept queries in multiple formats, making it flexible and user-friendly. Here are some common query formats: + +- **Pattern Query**: Users can define [specific patterns](/guide/pattern-syntax.html) to search for within their codebase. +- **YAML Rule**: Structured rules written in [YAML](/guide/rule-config.html) format to guide the search and analysis process. +- **API Code**: Direct [API calls](/guide/api-usage.html) for more programmatic control over the searching and rewriting tasks. + +### ast-grep's Core + +ast-grep's core functionality is divided into two main components: parsing and matching. + +#### 1. **Parsing with Tree-Sitter** + +The core of ast-grep's functionality relies on **Tree-Sitter Parsers**. [TreeSitter](https://tree-sitter.github.io/) is a powerful parsing library that converts source code into an Abstract Syntax Tree (AST). +This tree structure represents the syntactic structure of the code, making it easier to analyze and manipulate. + +#### 2. **Tree Matching** + +Once the code is parsed into an AST, the ast-grep core takes over and finds the matching AST nodes based on the input queries. +Written in **Rust**, ast-grep ensures efficient performance by utilizing full CPU cores. This means it can handle large codebases and perform complex searches and transformations quickly. + +### **Usage Scenarios** + +ast-grep can be helpful for these scenarios. + +- **Search**: Find specific patterns or constructs within the code. +- **Rewrite**: Automatically refactor or transform code based on predefined rules or patterns. +- **Lint**: Identify and report potential issues or code smells. +- **Analyze**: Perform in-depth code analysis to gather insights and metrics. + +## Benefits of Using ast-grep + +- **Multi-Core Processing**: ast-grep can handle multiple files in parallel by taking full advantage of multi-core processors. Typically ast-grep performs tasks faster than many other tools, making it suitable for large projects. +- **Versatility**: Whether you need to search for a specific code pattern, rewrite sections of code, lint for potential issues, or perform detailed analysis, ast-grep has you covered. + +## Example in the Real World + +- **Pattern + Search**: [CodeRabbit](https://coderabbit.ai/) uses ast-grep patterns to search code repo for code review knowledge. +This example is collected from ast-grep's own [dogfooding](https://github.com/ast-grep/ast-grep/pull/780#discussion_r1425817237). + +- **API + Rewrite**: [@vue-macros/cli](https://github.com/vue-macros/vue-macros-cli) is a CLI for rewriting at Vue Macros powered by ast-grep. + +- **YAML + Lint**: [Vercel turbo](https://github.com/vercel/turbo/pull/8275) is using ast-grep to lint their Rust code with [custom rules](https://github.com/vercel/turbo/blob/main/.config/ast-grep/rules/no-context.yml). + +## Conclusion + +ast-grep is a versatile and efficient tool for modern software development needs. By parsing code into an Abstract Syntax Tree and leveraging the power of Rust, it provides robust capabilities for searching, rewriting, linting, and analyzing code. With multiple input formats and the ability to utilize full CPU cores, ast-grep is designed to handle the demands of today's complex codebases. + +Whether you are maintaining a small project or a large enterprise codebase, ast-grep can help streamline your development workflow. diff --git a/website/advanced/language-injection.md b/website/advanced/language-injection.md new file mode 100644 index 00000000..9bb1b453 --- /dev/null +++ b/website/advanced/language-injection.md @@ -0,0 +1,245 @@ +# Search Multi-language Documents in ast-grep + +## Introduction + + + +ast-grep works well searching files of one single language, but it is hard to extract a sub language embedded inside a document. + +However, in modern development, it's common to encounter **multi-language documents**. These are source files containing code written in multiple different languages. Notable examples include: + +- **HTML files**: These can contain JavaScript inside ` +``` + +Running this ast-grep command will extract the matching CSS style code out of the HTML file! + +```sh +ast-grep run -p 'color: $COLOR' +``` + +ast-grep outputs this beautiful CLI report. +```shell +test.html +2│ h1 { color: red; } +``` + +ast-grep works well even if just providing the pattern without specifying the pattern language! + + +### **Using `ast-grep scan`**: find JavaScript in HTML with rule files + +You can also use ast-grep's [rule file](https://ast-grep.github.io/guide/rule-config.html) to search injected languages. + +For example, we can warn the use of `alert` in JavaScript, even if it is inside the HTML file. + +```yml +id: no-alert +language: JavaScript +severity: warning +rule: + pattern: alert($MSG) +message: Prefer use appropriate custom UI instead of obtrusive alert call. +``` + +The rule above will detect usage of `alert` in JavaScript. Running the rule via `ast-grep scan`. + +```sh +ast-grep scan --rule no-alert.yml +``` + +The command leverages built-in behaviors in ast-grep to handle language injection seamlessly. It will produce the following warning message for the HTML file above. + +```sh +warning[no-alert]: Prefer use appropriate custom UI instead of obtrusive alert call. + ā”Œā”€ test.html:8:3 + │ +8 │ alert('hello world!') + │ ^^^^^^^^^^^^^^^^^^^^^ +``` + +## How language injections work? + +ast-grep employs a multi-step process to handle language injections effectively. Here's a detailed breakdown of the workflow: + +1. **File Discovery**: The CLI first discovers files on the disk via the venerable [ignore](https://crates.io/crates/ignore) crate, the same library under [ripgrep](https://github.com/BurntSushi/ripgrep)'s hood. + +2. **Language Inference**: ast-grep infers the language of each discovered file based on file extensions. + +3. **Injection Extraction**: For documents that contain code written in multiple languages (e.g., HTML with embedded JS), ast-grep extracts the injected language sub-regions. _At the moment, ast-grep handles HTML/JS/CSS natively_. + +4. **Code Matching**: ast-grep matches the specified patterns or rules against these regions. Pattern code will be interpreted according to the injected language (e.g. JS/CSS), instead of the parent document language (e.g. HTML). + +## Customize Language Injection: styled-components in JavaScript + +You can customize language injection via the `sgconfig.yml` [configuration file](https://ast-grep.github.io/reference/sgconfig.html). This allows you to specify how ast-grep handles multi-language documents based on your specific needs, without modifying ast-grep's built-in behaviors. + + +Let's see an example of searching CSS code in JavaScript. [styled-components](https://styled-components.com/) is a library for styling React applications using [CSS-in-JS](https://bootcamp.uxdesign.cc/css-in-js-libraries-for-styling-react-components-a-comprehensive-comparison-56600605a5a1). It allows you to write CSS directly within your JavaScript via [tagged template literals](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals), creating styled elements as React components. + +The example will configure ast-grep to detect styled-components' CSS. + +### Injection Configuration + +You can add the `languageInjections` section in the project configuration file `sgconfig.yml`. + +```yaml +languageInjections: +- hostLanguage: js + rule: + pattern: styled.$TAG`$CONTENT` + injected: css +``` + +Let's break the configuration down. + + +1. `hostLanguage`: Specifies the main language of the document. In this example, it is set to `js` (JavaScript). + +2. `rule`: Defines the ast-grep rule to identify the injected language region within the host language. + + * `pattern`: The pattern matches styled components syntax where `styled` is followed by a tag (e.g., `button`, `div`) and a template literal containing CSS. + * the rule should have a meta variable `$CONTENT` to specify the subregion of injected language. In this case, it is the content inside the template string. + +3. `injected`: Specifies the injected language within the identified regions. In this case, it is `css`. + +### Example Match + +Consider a JSX file using styled components: + +```js +import styled from 'styled-components'; + +const Button = styled.button` + background: red; + color: white; + padding: 10px 20px; + border-radius: 3px; +` + +export default function App() { + return +} +``` + +With the above `languageInjections` configuration, ast-grep will: + +1. Identify the `styled.button` block as a CSS region. +2. Extract the CSS code inside the template literal. +3. Apply any CSS-specific pattern searches within this extracted region. + +You can search the CSS inside JavaScript in the project configuration folder using this command: + +```sh +ast-grep -p 'background: $COLOR' -C 2 +``` + +It will produce the match result: + +```shell +styled.js +2│ +3│const Button = styled.button` +4│ background: red; +5│ color: white; +6│ padding: 10px 20px; +``` + +## Using Custom Language with Injection + +Finally, let's look at an example of searching for GraphQL within JavaScript files. +This demonstrates ast-grep's flexibility in handling custom language injections. + +### Define graphql custom language in `sgconfig.yml`. + +First, we need to register graphql as a custom language in ast-grep. See [custom language reference](https://ast-grep.github.io/advanced/custom-language.html) for more details. + +```yaml +customLanguages: + graphql: + libraryPath: graphql.so # the graphql tree-sitter parser dynamic library + extensions: [graphql] # graphql file extension + expandoChar: $ # see reference above for explanation +``` + +### Define graphql injection in `sgconfig.yml`. + +Next, we need to customize what region should be parsed as graphql string in JavaScript. This is similar to styled-components example above. + +```yaml +languageInjections: +- hostLanguage: js + rule: + pattern: graphql`$CONTENT` + injected: graphql +``` + +### Search GraphQL in JavaScript + +Suppose we have this JavaScript file from [Relay](https://relay.dev/), a GraphQL client framework. + +```js +import React from "react" +import { graphql } from "react-relay" + +const artistsQuery = graphql` + query ArtistQuery($artistID: String!) { + artist(id: $artistID) { + name + ...ArtistDescription_artist + } + } +` +``` + +We can search the GraphQL fragment via this `--inline-rules` scan. + +```sh +ast-grep scan --inline-rules="{id: test, language: graphql, rule: {kind: fragment_spread}}" +``` + +Output + +```sh +help[test]: + ā”Œā”€ relay.js:8:7 + │ +8 │ ...ArtistDescription_artist + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^ +``` + +## More Possibility to be Unlocked... + +By following these steps, you can effectively use ast-grep to search and analyze code across multiple languages within the same document, enhancing your ability to manage and understand complex codebases. + +This feature extends to various frameworks like [Vue](https://vuejs.org/) and [Svelte](https://svelte.dev/), enables searching for [SQL in React server actions](https://x.com/peer_rich/status/1717609270475194466), and supports new patterns like [Vue-Vine](https://x.com/hd_nvim/status/1815300932793663658). + +Hope you enjoy the feature! Happy ast-grepping! \ No newline at end of file diff --git a/website/advanced/match-algorithm.md b/website/advanced/match-algorithm.md index 66a91109..a0690fd4 100644 --- a/website/advanced/match-algorithm.md +++ b/website/advanced/match-algorithm.md @@ -1,11 +1,151 @@ # Deep Dive into ast-grep's Match Algorithm -In descending order of strict-ness +By default, ast-grep uses a smart strategy to match pattern against the AST node. All nodes in the pattern must be matched, but it will skip unnamed nodes in target code. -* CST: all nodes are matched -* Smart: all nodes except source trivial nodes are matched. -* Significant: only significant nodes are matched -* AST: only ast nodes are matched -* Lenient: ast-nodes excluding comments are matched +For background and the definition of __*named*__ and __*unnamed*__ nodes, please refer to the [core concepts](/advanced/core-concepts.html) doc. -Currently ast-grep only supports Smart. +## How ast-grep's Smart Matching Works + +Let's see an example in action. + +The following pattern `function $A() {}` will match both plain function and async function in JavaScript. See [playground](/playground.html#eyJtb2RlIjoiUGF0Y2giLCJsYW5nIjoiamF2YXNjcmlwdCIsInF1ZXJ5IjoiZnVuY3Rpb24gJEEoKSB7fSIsInJld3JpdGUiOiJEZWJ1Zy5hc3NlcnQiLCJjb25maWciOiJydWxlOlxuICBwYXR0ZXJuOiBcbiAgICBjb250ZXh0OiAneyAkTTogKCQkJEEpID0+ICRNQVRDSCB9J1xuICAgIHNlbGVjdG9yOiBwYWlyXG4iLCJzb3VyY2UiOiJmdW5jdGlvbiBhKCkge31cbmFzeW5jIGZ1bmN0aW9uIGEoKSB7fSJ9) + +```js +// function $A() {} +function foo() {} // matched +async function bar() {} // matched +``` + +This is because the keyword `async` is an unnamed node in the syntax tree, so the `async` in the code to search is skipped. As long as `function`, `$A` and `{}` are matched, the pattern is considered matched. + +However, if the `async` keyword appears in the pattern code, it will [not be skipped](/playground.html#eyJtb2RlIjoiUGF0Y2giLCJsYW5nIjoiamF2YXNjcmlwdCIsInF1ZXJ5IjoiYXN5bmMgZnVuY3Rpb24gJEEoKSB7fSIsInJld3JpdGUiOiJ1c2luZyBuYW1lc3BhY2UgZm9vOjokQTsiLCJjb25maWciOiJcbmlkOiB0ZXN0YmFzZV9pbml0aWFsaXplclxubGFuZ3VhZ2U6IENQUFxucnVsZTpcbiAgcGF0dGVybjpcbiAgICBzZWxlY3RvcjogY29tcG91bmRfc3RhdGVtZW50XG4gICAgY29udGV4dDogXCJ7ICQkJEIgfVwiXG5maXg6IHwtXG4gIHtcbiAgICBmKCk7XG4gICAgJCQkQlxuICB9Iiwic291cmNlIjoiLy8gYXN5bmMgZnVuY3Rpb24gJEEoKSB7fVxuZnVuY3Rpb24gZm9vKCkge30gICAgLy8gbm90IG1hdGNoZWRcbmFzeW5jIGZ1bmN0aW9uIGJhcigpIHt9IC8vIG1hdGNoZWRcbiJ9) and is required to match node in the code. + +```js +// async function $A() {} +function foo() {} // not matched +async function bar() {} // matched +``` + +The design principle here is that the less a pattern specifies, the more code it can match. Every nodes the pattern author spells out will be respected by ast-grep's matching algorithm by default. + +## Smart is Sometimes Dumb + +The smart algorithm does not always behave as desired. There are cases where we need more flexibility in the matching algorithm. We may want to ignore all CST trivia nodes. Or even we want to ignore comment AST nodes. + +Suppose we want to write a pattern to match import statement in JavaScript. The pattern `import $A from 'lib'` will match only `import A from 'lib'`, but not `import A from "lib"`. This is because the import string has different quotation marks. We do want to ignore the trivial unnamed nodes here. + +To this end, ast-grep implements different pattern matching algorithms to provide more flexibility to the users, and every pattern can have their own matching algorithm to fine-tune the matching behavior. + +## Matching Algorithm Strictness + +Different matching algorithm is controlled by **pattern strictness**. + +:::tip Strictness +Strictness is defined in terms of what nodes can be *skipped* during matching. + +A *stricter* matching algorithm will *skip fewer nodes* and accordingly *produce fewer matches*. +::: + + +Currently, ast-grep has these strictness levels. + +* `cst`: All nodes in the pattern and target code must be matched. No node is skipped. +* `smart`: All nodes in the pattern must be matched, but it will skip unnamed nodes in target code. This is the default behavior. +* `ast`: Only named AST nodes in both pattern and target code are matched. All unnamed nodes are skipped. +* `relaxed`: Named AST nodes in both pattern and target code are matched. Comments and unnamed nodes are ignored. +* `signature`: Only named AST nodes' kinds are matched. Comments, unnamed nodes and text are ignored. + +## Strictness Examples + +Let's see how strictness `ast` will impact matching. In our previous import lib example, the pattern `import $A from 'lib'` will match both two statements. + +```js +import $A from 'lib' // pattern +import A1 from 'lib' // match, quotation is ignored +import A2 from "lib" // match, quotation is ignored +import A3 from "not" // no match, string_fragment is checked +``` + +First, the pattern and code will be parsed as the tree below. Named + +The unnamed nodes are skipped during the matching. Nodes' namedness is annotated beside them. + +``` +import_statement // named + import // unnamed + import_clause // named + identifier // named + from // unnamed + string // named + " // unnamed + string_fragment // named + " // unnamed +``` + +Under the strictness of `ast`, the full syntax tree will be reduced to an Abstract Syntax Tree where only named nodes are kept. + +``` +import_statement + import_clause + identifier // $A + string + string_fragment // lib +``` + +As long as the tree structure matches and the meta-variable `$A` and string_fragment `lib` are matched, the pattern and code are counted as a match. + +--- + +Another example will be matching the pattern `foo(bar)` across different strictness levels: + +```ts +// exact match in all levels +foo(bar) +// match in all levels except cst due to the trailing comma in code +foo(bar,) +// match in relaxed and signature because comment is skipped +foo(/* comment */ bar) +// match in signature because text content is ignored +bar(baz) +``` + +## Strictness Table + +Strictness considers both nodes' namedness and their locations, i.e, +_is the node named_ and _is the node in pattern or code_ + +The table below summarize how nodes are skipped during matching. + +|Strictness|Named Node in Pattern|Named Node in Code to Search|Unnamed Node in Pattern| Unnamed Node in Code to Search| +|---|----|---|---|---| +|`cst`| Keep | Keep| Keep | Keep | +|`smart`| Keep| Keep | Keep | Skip | +|`ast`| Keep| Keep | Skip| Skip | +|`relaxed`| Skip comment | Skip comment | Skip | Skip | +|`signature`| Skip comment. Ignore text | Skip comment. Ignore text | Skip | Skip | + + +## Configure Strictness + +ast-grep has two ways to configure pattern strictness. + +1. Using `--strictness` in `ast-grep run` + +You can use the `--strictness` flag in [`ast-grep run`](/reference/cli/run.html) + +```bash +ast-grep run -p '$FOO($BAR)' --strictness ast +``` + +2. Using `strictness` in Pattern Object + +[Pattern object](/reference/rule.html#pattern) in YAML has an optional `strictness` field. + +``` +id: test-pattern-strictness +language: JavaScript +rule: + pattern: + context: $FOO($BAR) + strictness: ast +``` \ No newline at end of file diff --git a/website/advanced/pattern-parse.md b/website/advanced/pattern-parse.md new file mode 100644 index 00000000..c4635e24 --- /dev/null +++ b/website/advanced/pattern-parse.md @@ -0,0 +1,275 @@ +# Deep Dive into ast-grep's Pattern Syntax + +ast-grep's pattern is easy to learn but hard to master. While it's easy to get started with, mastering its nuances can greatly enhance your code searching capabilities. + +This article aims to provide you with a deep understanding of how ast-grep's patterns are parsed, created, and effectively used in code matching. + +## Steps to Create a Pattern + +Parsing a pattern in ast-grep involves these keys steps: + +1. Preprocess the pattern text, e.g, replacing `$` with [expando_char](/advanced/custom-language.html#register-language-in-sgconfig-yml). +2. Parse the preprocessed pattern text into AST. +3. Extract effective AST nodes based on builtin heuristics or user provided [selector](/reference/rule.html#pattern). +4. Detect AST with wildcard text and convert them into [meta variables](/guide/pattern-syntax.html#meta-variable). + +![image](/image/parse-pattern.jpg) + +Let's dive deep into each of these steps. + +## Pattern is AST based + +_**First and foremost, pattern is AST based**._ + +ast-grep's pattern code will be converted into the Abstract Syntax Tree (AST) format, which is a tree structure that represents the code snippet you want to match. + +Therefore pattern cannot be arbitrary text, but a valid code with meta variables as placeholders. +If the pattern cannot be parsed by the underlying parser tree-sitter, ast-grep won't be able to find valid matching for it. + +There are several common pitfalls to avoid when creating patterns. + +### Invalid Pattern Code + +ast-grep pattern must be parsable valid code. While this may seem obvious, newcomers sometimes make mistakes when creating patterns with meta-variables. + +_**Meta-variable is usually parsed as identifier in most languages.**_ + +When using meta-variables, make sure they are placed in a valid context and not used as a keyword or an operator. +For example, you may want to use `$OP` to match binary expressions like `a + b`. +The pattern below will not work because parsers see it as three consecutive identifiers separated by spaces. + +``` +$LEFT $OP $RIGHT +``` + +You can instead use [atomic rule](/guide/rule-config/atomic-rule.html#kind) `kind: binary_expression` to [match binary expressions](/playground.html#eyJtb2RlIjoiQ29uZmlnIiwibGFuZyI6ImphdmFzY3JpcHQiLCJxdWVyeSI6IiIsInJld3JpdGUiOiIiLCJzdHJpY3RuZXNzIjoic21hcnQiLCJzZWxlY3RvciI6IiIsImNvbmZpZyI6InJ1bGU6XG4gIGtpbmQ6IGJpbmFyeV9leHByZXNzaW9uIiwic291cmNlIjoiYSArIGIgXHJcbmEgLSBiXHJcbmEgPT0gYiAifQ==). + +Similarly, in JavaScript you may want to match [object accessors](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Object_initializer#method_definitions) like `{ get foo() {}, set bar() { } }`. +The pattern below will not work because meta-variable is not parsed as the keywords `get` and `set`. + +```js +obj = { $KIND foo() { } } +``` + +Again [rule](/guide/rule-config.html) is more suitable for [this scenario](/playground.html#eyJtb2RlIjoiQ29uZmlnIiwibGFuZyI6ImphdmFzY3JpcHQiLCJxdWVyeSI6IiIsInJld3JpdGUiOiIiLCJzdHJpY3RuZXNzIjoic21hcnQiLCJzZWxlY3RvciI6IiIsImNvbmZpZyI6InJ1bGU6XG4gIGtpbmQ6IG1ldGhvZF9kZWZpbml0aW9uXG4gIHJlZ2V4OiAnXmdldHxzZXRcXHMnIiwic291cmNlIjoidmFyIGEgPSB7XHJcbiAgICBmb28oKSB7fVxyXG4gICAgZ2V0IGZvbygpIHt9LFxyXG4gICAgc2V0IGJhcigpIHt9LFxyXG59In0=). + +```yaml +rule: + kind: method_definition + regex: '^get|set\s' +``` + +### Incomplete Pattern Code + +It is very common and even attempting to write incomplete code snippet in patterns. However, incomplete code does not _always_ work. + +Consider the following JSON code snippet as pattern: + +```json +"a": 123 +``` + +While the intention here is clearly to match a key-value pair, tree-sitter does not treat it as valid JSON code because it is missing the enclosing `{}`. Consequently ast-grep will not be able to parse it. + +The solution here is to use [pattern object](/guide/rule-config/atomic-rule.html#pattern-object) to provide complete code snippet. + +```yaml +pattern: + context: '{ "a": 123 }' + selector: pair +``` + +You can use both ast-grep playground's [pattern tab](/playground.html#eyJtb2RlIjoiUGF0Y2giLCJsYW5nIjoianNvbiIsInF1ZXJ5IjoieyBcImFcIjogMTIzIH0iLCJyZXdyaXRlIjoiIiwic3RyaWN0bmVzcyI6InNtYXJ0Iiwic2VsZWN0b3IiOiJwYWlyIiwiY29uZmlnIjoicnVsZTpcbiAga2luZDogbWV0aG9kX2RlZmluaXRpb25cbiAgcmVnZXg6ICdeZ2V0fHNldFxccyciLCJzb3VyY2UiOiJ7IFwiYVwiOiAxMjMgfSAifQ==) or [rule tab](/playground.html#eyJtb2RlIjoiQ29uZmlnIiwibGFuZyI6Impzb24iLCJxdWVyeSI6InsgXCJhXCI6IDEyMyB9IiwicmV3cml0ZSI6IiIsInN0cmljdG5lc3MiOiJzbWFydCIsInNlbGVjdG9yIjoicGFpciIsImNvbmZpZyI6InJ1bGU6XG4gIHBhdHRlcm46IFxuICAgIGNvbnRleHQ6ICd7XCJhXCI6IDEyM30nXG4gICAgc2VsZWN0b3I6IHBhaXIiLCJzb3VyY2UiOiJ7IFwiYVwiOiAxMjMgfSAifQ==) to verify it. + + +_**Incomplete pattern code sometimes works fine due to error-tolerance.**_ + +For better _user experience_, ast-grep parse pattern code as lenient as possible. ast-grep parsers will try recovering parsing errors and ignoring missing language constructs. + +For example, the pattern `foo(bar)` in Java cannot be parsed as valid code. However, ast-grep recover the parsing error, ignoring missing semicolon and treat it as a method call. So the pattern [still works](/playground.html#eyJtb2RlIjoiUGF0Y2giLCJsYW5nIjoiamF2YSIsInF1ZXJ5IjoiZm9vKGJhcikiLCJyZXdyaXRlIjoiIiwic3RyaWN0bmVzcyI6InNtYXJ0Iiwic2VsZWN0b3IiOiIiLCJjb25maWciOiJydWxlOlxuICBwYXR0ZXJuOiBcbiAgICBjb250ZXh0OiAne1wiYVwiOiAxMjN9J1xuICAgIHNlbGVjdG9yOiBwYWlyIiwic291cmNlIjoiY2xhc3MgQSB7XG4gICAgZm9vKCkge1xuICAgICAgICBmb28oYmFyKTtcbiAgICB9XG59In0=). + +### Ambiguous Pattern Code + +Just as programming languages have ambiguous grammar, so ast-grep patterns can be ambiguous. + +Let's consider the JavaScript code snippet below: + +```js +a: 123 +``` + +It can be interpreted as an object key-value pair or a labeled statement. + +Without other hints, ast-grep will parse it as labeled statement by default. To match object key-value pair, we need to provide more context by [using pattern object](/playground.html#eyJtb2RlIjoiUGF0Y2giLCJsYW5nIjoiamF2YXNjcmlwdCIsInF1ZXJ5IjoieyBhOiAxMjMgfSIsInJld3JpdGUiOiIiLCJzdHJpY3RuZXNzIjoic21hcnQiLCJzZWxlY3RvciI6InBhaXIiLCJjb25maWciOiJydWxlOlxuICBwYXR0ZXJuOiBcbiAgICBjb250ZXh0OiAne1wiYVwiOiAxMjN9J1xuICAgIHNlbGVjdG9yOiBwYWlyIiwic291cmNlIjoiYSA9IHsgYTogIDEyMyB9In0=). + +```yaml +pattern: + context: '{ a: 123 }' + selector: pair +``` + +Other examples of ambiguous patterns include: +* Match function call in [Golang](/catalog/go/#match-function-call-in-golang) and [C](/catalog/c/#match-function-call) +* Match [class field](/guide/rule-config/atomic-rule.html#pattern-object) in JavaScript + +### How ast-grep Handles Pattern Code? + +ast-grep uses best efforts to parse pattern code for best user experience. + +Here are some strategies ast-grep uses to handle code snippet: + +* **Replace `$` with expando_char**: +some languages use `$` as a special character, so ast-grep replace it with [expando_char](/advanced/custom-language.html#register-language-in-sgconfig-yml) in order to make the pattern code parsable. + +* **Ignore missing nodes**: ast-grep will ignore missing nodes in pattern like trailing semicolon in Java/C/C++. + +* **Treat root error as normal node**: if the parser error has no siblings, ast-grep will treat it as a normal node. + +* If all above fails, users should provide more code via pattern object + +:::warning Pattern Error Recovery is useful, but not guaranteed + +ast-grep's recovery mechanism heavily depends on tree-sitter's behavior. We cannot guarantee invalid patterns will be parsed consistently between different versions. So using invalid pattern may lead to unexpected results after upgrading ast-grep. + +When in doubt, always use valid code snippets with pattern object. +::: + +## Extract Effective AST for Pattern + +After parsing the pattern code, ast-grep needs to extract AST nodes to make the actual pattern. + +Normally, a code snippet generated by tree-sitter will be a full AST tree. Yet it is unlikely that the entire tree will be used as a pattern. The code `123` will produce a tree like `program -> expression_statement -> number` in many languages. But we want to match a number literal in the code, not a program containing just a number. + +ast-grep uses two strategies to extract **effective AST nodes** that will be used to match code. + +### Builtin Heuristic + +_**By default, at-grep extracts the leaf node or the innermost node with more than one child.**_ + +This heuristic extracts the most specific node while still keeping all structural information in the pattern. +If a node has only one child, it is atomic and cannot be further decomposed. We can safely assume the node contains no structural information for matching. In contrast, a node with more than one child contains a structure that we want to search. + +Examples: + +* `123` will be extracted as `number` because it is the leaf node. + +```yaml +program + expression_statement + number <--- effective node +``` + +See [Playground](/playground.html#eyJtb2RlIjoiUGF0Y2giLCJsYW5nIjoiamF2YXNjcmlwdCIsInF1ZXJ5IjoiMTIzIiwicmV3cml0ZSI6IiIsInN0cmljdG5lc3MiOiJzbWFydCIsInNlbGVjdG9yIjoiIiwiY29uZmlnIjoiIiwic291cmNlIjoiIn0=). + +* `foo(bar)` will be extracted as `call_expression` because it is the innermost node that has more than one child. + +```yaml +program + expression_statement + call_expression <--- effective node + identifier + arguments + identifier +``` + +See [Playground](/playground.html#eyJtb2RlIjoiUGF0Y2giLCJsYW5nIjoiamF2YXNjcmlwdCIsInF1ZXJ5IjoiZm9vKGJhcikiLCJyZXdyaXRlIjoiIiwic3RyaWN0bmVzcyI6InNtYXJ0Iiwic2VsZWN0b3IiOiJjYWxsX2V4cHJlc3Npb24iLCJjb25maWciOiIiLCJzb3VyY2UiOiIifQ==). + +### User Defined Selector + +Sometimes the effective node extracted by the builtin heuristic may not be what you want. +You can explicitly specify the node to extract using the [selector](/reference/rule.html#pattern) field in the rule configuration. + +For example, you may want to match the whole `console.log` statement in JavaScript code. The effective node extracted by the builtin heuristic is `call_expression`, but you want to match the whole `expression_statement`. + +Using `console.log($$$)` directly will not include the trailing `;` in the pattern, see [Playground](/playground.html#eyJtb2RlIjoiUGF0Y2giLCJsYW5nIjoiamF2YXNjcmlwdCIsInF1ZXJ5IjoiY29uc29sZS5sb2coJCQkKSIsInJld3JpdGUiOiIiLCJzdHJpY3RuZXNzIjoic2lnbmF0dXJlIiwic2VsZWN0b3IiOiJjYWxsX2V4cHJlc3Npb24iLCJjb25maWciOiIiLCJzb3VyY2UiOiJjb25zb2xlLmxvZyhmb28pXG5jb25zb2xlLmxvZyhiYXIpOyJ9). + +```js +console.log("Hello") +console.log("World"); +``` + +You can use pattern object to explicitly specify the effective node to be `expression_statement`. [Playground](/playground.html#eyJtb2RlIjoiQ29uZmlnIiwibGFuZyI6ImphdmFzY3JpcHQiLCJxdWVyeSI6ImNvbnNvbGUubG9nKCQkJCkiLCJyZXdyaXRlIjoiIiwic3RyaWN0bmVzcyI6InNpZ25hdHVyZSIsInNlbGVjdG9yIjoiY2FsbF9leHByZXNzaW9uIiwiY29uZmlnIjoicnVsZTpcbiAgcGF0dGVybjpcbiAgICBjb250ZXh0OiBjb25zb2xlLmxvZygkJCQpXG4gICAgc2VsZWN0b3I6IGV4cHJlc3Npb25fc3RhdGVtZW50XG5maXg6ICcnIiwic291cmNlIjoiY29uc29sZS5sb2coZm9vKVxuY29uc29sZS5sb2coYmFyKTsifQ==) + +```yaml +pattern: + context: console.log($$$) + selector: expression_statement +``` + +Using `selector` is especially helpful when you are also using relational rules like `follows` and `precedes`. +You want to match the statement instead of the default inner expression node, and [match other statements around it](https://github.com/ast-grep/ast-grep/issues/1427). + +:::tip +When in doubt, try pattern object first. +::: + +## Meta Variable Deep Dive + +ast-grep's meta variables are also AST based and are detected in the effective nodes extracted from the pattern code. + +### Meta Variable Detection in Pattern + +Not all `$` prefixed strings will be detected as meta variables. + +Only AST nodes that match meta variable syntax will be detected. +If meta variable text is not the only text in the node or it spans multiple nodes, it will not be detected as a meta variable. + +**Working meta variable examples:** + +* `$A` works + * `$A` is one single `identifier` +* `$A.$B` works + * `$A` is `identifier` inside `member_expression` + * `$B` is the `property_identifier`. +* `$A.method($B)` works + * `$A` is `identifier` inside `member_expression` + * `$B` is `identifier` inside `arguments` + +**Non working meta variable examples:** + +* `obj.on$EVENT` does not work + * `on$EVENT` is `property_identifier` but `$EVENT` is not the only text +* `"Hello $WORLD"` does not work + * `$WORLD` is inside `string_content` and is not the only text +* `a $OP b` does not work + * the whole pattern does not parse +* `$jq` does not work + * meta variable does not accept lower case letters + +See all examples in [Playground](/playground.html#eyJtb2RlIjoiUGF0Y2giLCJsYW5nIjoiamF2YXNjcmlwdCIsInF1ZXJ5IjoiIiwicmV3cml0ZSI6IiIsInN0cmljdG5lc3MiOiJzaWduYXR1cmUiLCJzZWxlY3RvciI6ImNhbGxfZXhwcmVzc2lvbiIsImNvbmZpZyI6IiIsInNvdXJjZSI6Ii8vIHdvcmtpbmdcbiRBXG4kQS4kQlxuJEEubWV0aG9kKCRCKVxuXG4vLyBub24gd29ya2luZ1xub2JqLm9uJEVWRU5UXG5cIkhlbGxvICRXT1JMRFwiXG5hICRPUCBiIn0=). + +### Matching Unnamed Nodes + +A meta variable pattern `$META` will capture [named nodes](/advanced/core-concepts.html#named-vs-unnamed) by default. +To capture [unnamed nodes](/advanced/core-concepts.html#named-vs-unnamed), you can use double dollar sign `$$VAR`. + +Let's go back to the binary expression example. It is impossible to match arbitrary binary expression in one single pattern. But we can combine `kind` and `has` to match the operator in binary expressions. + +Note, `$OP` cannot match the operator because operator is not a named node. We need to use `$$OP` instead. + +```yaml +rule: + kind: binary_expression + has: + field: operator + pattern: $$OP + # pattern: $OP +``` + +See the above rule to match all arithmetic expressions in [action](/playground.html#eyJtb2RlIjoiQ29uZmlnIiwibGFuZyI6ImphdmFzY3JpcHQiLCJxdWVyeSI6ImNvbnNvbGUubG9nKCQkJCkiLCJyZXdyaXRlIjoiIiwic3RyaWN0bmVzcyI6InNpZ25hdHVyZSIsInNlbGVjdG9yIjoiY2FsbF9leHByZXNzaW9uIiwiY29uZmlnIjoicnVsZTpcbiAga2luZDogYmluYXJ5X2V4cHJlc3Npb25cbiAgaGFzOlxuICAgIGZpZWxkOiBvcGVyYXRvclxuICAgIHBhdHRlcm46ICQkT1BcbiAgICAjIHBhdHRlcm46ICRPUCIsInNvdXJjZSI6IjEgKyAxIn0=). + +### How Multi Meta Variables Match Code + +Multiple meta variables like `$$$ARGS` has special matching behavior. It will match multiple nodes in the AST. + +`$$$ARGS` will match multiple nodes in source code when the meta variable starts to match. It will match as many nodes as possible until the first AST node after the meta var in pattern is matched. + +The behavior is like [non-greedy](https://stackoverflow.com/questions/11898998/how-can-i-write-a-regex-which-matches-non-greedy) matching in regex and template string literal `infer` in [TypeScript](https://github.com/microsoft/TypeScript/pull/40336). + +## Use ast-grep playground to debug pattern + +ast-grep playground is a great tool to debug pattern code. The pattern tab and pattern panel can help you visualize the AST tree, effective nodes and meta variables. + +![playground](/image/pattern-debugger.jpg) + +In next article, we will explain how ast-grep's pattern is used to match code, the pattern matching algorithm. diff --git a/website/advanced/tool-comparison.md b/website/advanced/tool-comparison.md index ddfa84e8..a2942957 100644 --- a/website/advanced/tool-comparison.md +++ b/website/advanced/tool-comparison.md @@ -9,7 +9,7 @@ The author is grateful to these predecessor tools for inspiring ast-grep! The re ## ast-grep **Pros**: -* It is very performant. It uses ignore to do multi-thread processing, which makes it utilize all your CPU cores. +* It is very performant. It uses [ignore](https://docs.rs/ignore/latest/ignore/) to do multi-thread processing, which makes it utilize all your CPU cores. * It is language aware. It uses tree-sitter, a real parser, to parse the code into ASTs, which enables more precise and accurate matching and fixing. * It has a powerful and flexible rule system. It allows you to write patterns, AST types and regular expressions to match code. It provides operators to compose complex matching rules for various scenarios. * It can be used as a lightweight CLI tool or as a library, depending on your usage. It has a simple and user-friendly interface, and it also exposes its core functionality as a library for other applications. @@ -17,6 +17,12 @@ The author is grateful to these predecessor tools for inspiring ast-grep! The re **Cons**: * It is still young and under development. It may have some bugs or limitations that need to be fixed or improved. * It does not have deep semantic information or comparison equivalence. It only operates on the syntactic level of the code, which may miss some matches or may be too cumbersome to match certain code. +* More specifically, ast-grep at the moment does not support the following information: + * [type information](https://semgrep.dev/docs/writing-rules/pattern-syntax#typed-metavariables) + * [control flow analysis](https://en.wikipedia.org/wiki/Control-flow_analysis) + * [data flow analysis](https://en.wikipedia.org/wiki/Data-flow_analysis) + * [taint analysis](https://semgrep.dev/docs/writing-rules/data-flow/taint-mode) + * [constant propagation](https://semgrep.dev/docs/writing-rules/data-flow/constant-propagation) ## [Semgrep](https://semgrep.dev/) @@ -31,6 +37,18 @@ Semgrep is a well-established tool that uses code patterns to find and fix bugs * It is relatively slow when used as command line tools. * It cannot be used as a library in other applications, which may reduce its integration and customization options. +## [GritQL](https://about.grit.io/) + +[GritQL](https://docs.grit.io/language/overview) language is [Grit](https://docs.grit.io/)'s embedded query language for searching and transforming source code. + +**Pros**: + +* GritQL is generally more powerful. It has features like [clause](https://docs.grit.io/language/modifiers) from [logic programming language](https://en.wikipedia.org/wiki/Logic_programming#:~:text=A%20logic%20program%20is%20a,Programming%20(ASP)%20and%20Datalog.) and [operations](https://docs.grit.io/language/conditions#match-condition) from imperative programming languages. +* It is used as [linter plugins](https://biomejs.dev/linter/plugins/) in [Biome](https://biomejs.dev/), a toolchain for JS ecosystem. + +**Cons**: +* Depending on different background, developers may find it harder to learn a multi-paradigm DSL. + ## [Comby](https://comby.dev/) Comby is a fast and flexible tool that uses structural patterns to match and rewrite code across languages and file formats. @@ -45,6 +63,16 @@ Comby is a fast and flexible tool that uses structural patterns to match and rew * It does not support indentation-sensitive languages like Python or Haskell, which require special handling for whitespace and indentation. * It is hard to write complex queries with Comby, such as finding a function that does not call another function. It does not support logical operators or filters for patterns. +## [IntelliJ Structural Search Replace](https://www.jetbrains.com/help/idea/structural-search-and-replace.html) + +IntelliJ Structural Search Replace is not a standalone tool, but a feature of the IntelliJ IDE that allows users to search and replace code using structural patterns. + +**Pros**: +* It is integrated with the IntelliJ IDE, which makes it easy to use and customize. + +**Cons**: +* Currently, IntelliJ IDEA supports the structural search and replace for Java, Kotlin and Groovy. + ## [Shisho](https://docs.shisho.dev/shisho) Shisho is a new and promising tool that uses code patterns to search and manipulate code in various languages. @@ -55,13 +83,4 @@ Shisho is a new and promising tool that uses code patterns to search and manipul **Cons**: * It is still in development and it has limited language support compared to the other tools. It currently supports only 3 languages, while the other tools support over 20 languages. - -## [IntelliJ Structural Search Replace](https://www.jetbrains.com/help/idea/structural-search-and-replace.html) - -IntelliJ Structural Search Replace is not a standalone tool, but a feature of the IntelliJ IDE that allows users to search and replace code using structural patterns. - -**Pros**: -* It is integrated with the IntelliJ IDE, which makes it easy to use and customize. - -**Cons**: -* Currently, IntelliJ IDEA supports the structural search and replace for Java, Kotlin and Groovy. +* The tool's parent company seems to have changed their business direction. diff --git a/website/blog.md b/website/blog.md new file mode 100644 index 00000000..2c47e2f4 --- /dev/null +++ b/website/blog.md @@ -0,0 +1,12 @@ +--- +sidebar: false +outline: false +--- + +# ast-grep Blog + + + + diff --git a/website/blog/ast-grep-agent.md b/website/blog/ast-grep-agent.md new file mode 100644 index 00000000..c26d3a99 --- /dev/null +++ b/website/blog/ast-grep-agent.md @@ -0,0 +1,80 @@ +--- +author: + - name: Herrington Darkholme +date: 2025-06-21 +head: + - - meta + - property: og:type + content: website + - - meta + - property: og:title + content: ast-grep's Journey to AI Generated Rules + - - meta + - property: og:url + content: https://ast-grep.github.io/blog/ast-grep-agent.html + - - meta + - property: og:description + content: Advancements in AI have made it possible to generate ast-grep rules with a well-written prompt. +--- + +# ast-grep's Journey to AI Generated Rules + +ast-grep is a command-line tool that empowers developers to find and replace code with precision. It operates directly on the syntax tree (AST), the true structure of your code. While powerful, writing ast-grep rules is a hurdle that requires grokking the tool. + +This project has always been about designed and built as a tool for human beings, not for AI hype. But ast-grep been [exploring how to use Al](/blog/more-llm-support.html) to improve the human experience. This is the story of my journey into using Al to generate YAML rules. + +## Why ast-grep Rules are Hard for AI + +Generating ast-grep rules is not an easy task,especially for AI. First, ast-grep is a relatively new tool, so Large Language Models have not been extensively trained on its specific syntax, leading to hallucinations or a complete lack of understanding. While [in-context learning](https://www.prompthub.us/blog/in-context-learning-guide) can help an LLM grasp the basic syntax, it often fails to address the problem of [compounding errors](https://arxiv.org/abs/2505.24187v1). + +This is particularly true for ast-grep, where rules are frequently composed of smaller, [atomic rules](/guide/rule-config/atomic-rule.html). Human developers often need to debug these smaller rules through [trial and error](/advanced/faq.html#my-pattern-does-not-work-why), and pay extra attention to how they are orchestrated into a [working composition](/advanced/faq.html#my-rule-does-not-work-why). For an LLM, a small mistake in one part of the rule can snowball into a completely incorrect rule. Quoting from the paper [Faith and Fate](https://arxiv.org/abs/2305.18654): + +> _These tasks require breaking problems down into sub-steps and synthesizing these steps into a precise answer._ + +This explains why a simple LLM ask won't work for ast-grep. However, recent advancements in "thinking" and "agentic" models, which can correct their mistakes, may [reduce the probability of such errors](https://arxiv.org/abs/2501.15602v2). This makes it a good time to give AI-powered ast-grep rule generation another try. + + +## The First Foray a Year Ago + +My initial attempt, about a year ago, was a lesson in humility. I tried to build an agent using [DSPy](https://dspy.ai/) with a rigid, fixed pipeline: generate use cases, create code examples, describe the rule in natural language, translate that description to an ast-grep rule, and finally, verify it. + +The results were, to put it mildly, not good. + +The models I was using are mostly free-tier APIs from [Ollama](https://ollama.com/) and [aistudio](https://aistudio.google.com)(thank [$GOOG](https://finance.yahoo.com/quote/GOOG/)). They struggled to adhere to a consistent output format. They often failed to generate correct code examples, let alone valid ast-grep rules. The limited context windows meant I couldn't even provide sufficient instructions to guide them properly. It felt like I was missing two crucial pieces: a way for the agent to refine a rule based on search result feedback, and a method to dynamically break down complex requests into smaller, manageable rules. The agent architecture could solve these, but the underlying models could not. + +## One Year After: Better Models, Better Agents + +Fast forward to today. The landscape has changed dramatically. Modern LLMs boast long context windows and a much-improved ability to follow complex instructions. Armed with these new capabilities and inspired by [Anthropic's new guide](https://www.anthropic.com/engineering/building-effective-agents), I tried again. + +This time, I discovered that a complex agent framework wasn't necessary. AI programming tools like [Cursor](https://www.cursor.com/) have sufficient agent frameworks for tool developers. The secret sauce was something far simpler: prompt engineering. And by "[prompt engineering](https://www.promptingguide.ai/)", I don't mean any framework or trick. It's just writing a clear, human-readable manual for both human and Al. + +Before the prompt engineering, however, I saw some interesting failure modes across different models that somehow reflects different LLM vendors' training setup. All of them did a decent job of interpreting the user's intent and creating relevant code examples, but their approaches to rule generation were wildly different. + +* **OpenAI O3** hallucinated with wild abandon. It invented syntax that looked more like [CodeQL](https://codeql.github.com/) or [jscodeshift](https://github.com/facebook/jscodeshift), completely ignoring the ast-grep documentation available online. It couldn't recover from tool errors and would quickly give up on using the tools I gave it. It felt like OpenAI's pretraining dataset glanced at my documentation, decided it knew better, and threw it in the bin. (Oops, like how OpenAI treated my resume) + +* **Gemini** was a bit more grounded. Its hallucinations were at least in the right ballpark, borrowing syntax from a [related but more established](/advanced/tool-comparison.html#semgrep) tool, [Semgrep](https://semgrep.dev/). (Thanks for the flattery, again, $GOOG). It also showed a decent ability to recover from errors but had a stubborn streak, preferring to invent its own ast-grep cli commands rather than using the [MCP](https://modelcontextprotocol.io) tools I [provided](https://github.com/ast-grep/ast-grep-mcp). +* **Claude 4** was the most promising out of the box. It correctly identified ast-grep and produced syntactically valid rules. Looks like Anthropic's training data is indexing ast-grep's doc! Hoooray! However, it struggled with subtle semantic details that would make a rule functionally correct. To its credit, it tried very hard, retrying the tools I gave it over and over with different inputs, demonstrating a dogged persistence the others lacked. + +## Prompting AI Agents like Teaching a Human + +After iterating on the prompt, I found that all three models could perform remarkably well. The key was to treat the prompt not as a magic incantation, but as a straightforward instruction manual. + +The core of my final prompt gives the Al a simple, five-step plan: + +1. First, clearly understand the user's request. +2. Next, write a simple code snippet that the user wants to find. +3. Then, write an ast-grep rule that precisely matches that code snippet. +4. [Test the rule](https://github.com/ast-grep/ast-grep-mcp/blob/b69eb5391bd93d46ef3dec07de814c3c39675c8f/main.py#L33-L57) against the example to ensure it works as expected. +5. Finally, [search](https://github.com/ast-grep/ast-grep-mcp/blob/b69eb5391bd93d46ef3dec07de814c3c39675c8f/main.py#L72-L82) the codebase with the verified rule. + +This clear, step-by-step process turned erratic geniuses into more reliable assistants. By breaking the problem down and providing a verification loop, I gave the models the structure they needed to succeed. You can see the full prompt in the [sg-mcp GitHub repository](https://github.com/ast-grep/ast-grep-mcp/blob/main/ast-grep.mdc). + +The end result is quite impressive, see the [demo video](https://youtube.com/shorts/2hah-9N5YQ8?si=bzl6PF2tuFbBwXpL) below. No speed up, but with music and cats. (It is oddly satisfactory to watch AI generating the rules while I myself is nodding like the kitty in the video) + + + + +This journey has reinforced a core belief: Al's true power isn't about replacing the developer, but about building better tools for them. It also demonstrates that the best way to teach an AI, with the progress of more capable Large Language Models, is not through complex frameworks or rigid pipelines, but through clear, human-readable instructions. + +By teaching an Al to write rules, I hope it makes ast-grep more accessible, more powerful, and easier to use. +Happy grepping! diff --git a/website/blog/code-search-design-space.md b/website/blog/code-search-design-space.md new file mode 100644 index 00000000..6cb44cdf --- /dev/null +++ b/website/blog/code-search-design-space.md @@ -0,0 +1,390 @@ +--- +author: + - name: Herrington Darkholme +date: 2024-12-25 +head: + - - meta + - property: og:type + content: website + - - meta + - property: og:title + content: Design Space for Code Search Query + - - meta + - property: og:url + content: https://ast-grep.github.io/blog/code-search-design-space.html + - - meta + - property: og:description + content: A review of the design space for code search tools. + - - meta + - property: og:image + content: https://ast-grep.github.io/image/blog/query-design.png +--- + +# Design Space for Code Search Query + +Code search is a critical tool for modern software development. It enables developers to quickly locate, understand, and reuse existing code, boosting productivity and ensuring code consistency across projects. + +At its core, ast-grep is a [code search](/guide/introduction.html#motivation) tool. Its other features, such as [linting](/guide/scan-project.html) and code [rewriting](/guide/rewrite-code.html), are built upon the foundation of its code search capabilities. + +This blog post delves into the design space of code search, with a particular focus on how queries are designed and used. We'll be drawing inspiration from the excellent paper, "[Code Search: A Survey of Techniques for Finding Code](https://www.lucadigrazia.com/papers/acmcsur2022.pdf)". But we won't be covering every single detail from that paper. Instead, our focus will be on the diverse ways that code search tools allow users to express their search intent. + + +## Query Design and Query Types + +Every code search begins with a query, which is simply a way to tell the search engine what kind of code we're looking for. The way these queries are designed is crucial. Code search tool designers aim to achieve several key goals: + +#### Easy +A query should be easy to write, allowing users to quickly search without needing extensive learning. If it's too difficult to write a query, people might get discouraged from using the tool altogether. +#### Expressive +Users should be able to express whatever they're looking for. If the query language is too limited, you simply cannot find some results. +#### Precise + +The query should be specific enough to yield relevant results, avoiding irrelevant findings. An imprecise query will lead to a lot of noise. + +--- + +Achieving all three of these goals simultaneously is challenging, as they often pull in opposing directions. For example, a very simple and easy query language might be expressive enough, or a very precise query language might be too complex for the average user. + +How do code search tools balance these goals? The blog categorizes code search queries into a few main types, each with its own characteristics: informal queries, formal queries, and hybrid queries. + +![query design](/image/blog/query-design.png) + +## Informal Queries + +These queries are closest to how we naturally express ourselves, and can be further divided into: + +### Free-Form Queries + +These are often free-form, using natural language to describe the desired code functionality, like web search. For example, "read file line by line" or "FileReader close." + +* **Pros:** Easy for users to formulate, similar to using a web search engine, and highly expressive. +* **Cons:** Can be ambiguous and less precise due to the nature of natural language and potential vocabulary mismatches between the query and the code base. + +Tools like [GitHub Copilot](https://docs.github.com/en/enterprise-cloud@latest/copilot/using-github-copilot/asking-github-copilot-questions-in-github) use this approach. + +### Input-Output Examples +These queries specify the desired behavior of the code by providing input-output pairs. You specify the desired behavior using pairs of inputs and their corresponding outputs. For example, the input "susie@mail.com" should result in the output "susie". + +* **Pros**: Allows to precisely specify desired behavior +* **Cons**: May require some effort to provide sufficient examples + +This approach is more common in academic research than practical tools. This blog has not been aware of open source tools that use this approach. + + +_We will not discuss informal queries in detail, as it is not precise._ + +## **Formal Queries Based on Existing Programming Languages** + +Formal queries use a structured approach, making them more precise. They can be further divided into several subcategories. + +### Plain Code + +The simplest version involves providing an exact code snippet that needs to be matched in the codebase. For instance, a user might search for instances of the following Java snippet: + +```java +try { + File file = File.createTempFile("foo", "bar"); +} catch (IOException e) { +} +``` + +Not many tools directly support plain code search. They usually break search queries into smaller parts through the tokenization process, like traditional search engines. + +A notable example may be [grep.app](https://grep.app). + +### Code with Holes + +This approach involves providing code snippets with placeholders to search for code fragments. For example, a user might search for the following pattern in Java: + +```java +public void actionClose (JButton a, JFrame f) { + $$$BODY +} +``` + +Here, `$$$BODY` is a placeholder, and the code search engine will try to locate all matching code. ast-grep falls into this category, treating the query as an Abstract Syntax Tree (AST) with holes. The holes in ast-grep are called metavariables. + +Other tools like gritql and the [structural search feature](https://www.jetbrains.com/help/idea/tutorial-work-with-structural-search-and-replace.html) in IntelliJ IDEA also use this technique. + +### Code with Pattern Matching Symbols + +These queries make use of special symbols to represent and match code structures. For example, the following query in [Comby](https://comby.dev/docs/basic-usage#how-matching-works) attempts to find all if statements where the condition is a comparison. + +```comby +if (:[var] <= :[rest]) +``` + +In Comby, the `:[var]` and `:[rest]` are special markers that match strings of code. + +```java{1} +if (width <= 1280 && height <= 800) { + return 1; +} +``` + + The `:[var]` matches any string until a `<=` character is found and in this case is `width`. `:[rest]` matches everything that follows, `1280 && height <= 800`. Unlike ast-grep, Comby is not AST-aware, as the `:[rest]` in the example spans across multiple AST nodes. Tools like [Comby](https://comby.dev/) and [Shisho](https://github.com/flatt-security/shisho) use this approach. + + +### Pros and Cons + +**Pros:** Easy to formulate for developers familiar with programming languages. + +**Cons:** Parsing incomplete code snippets can be a challenge. + +The downside of using existing languages is also emphasized in the IntelliJ IDEA documentation: + +> Any (SSR) template entered should be a well formed Java construction ... + +An off-the-shelf grammar of the programming language may not be able to parse a query because the query is [incomplete or ambiguous](/advanced/pattern-parse.html#pattern-is-ast-based). +For example, `"key": "value"` is not a valid JSON object, a JSON parser will reject and will fail to create a query. Maybe it is clear to a human that it is a key-value pair, but the parser does not know that. Other examples will be like [distinguishing function calls](/catalog/c/) and macro invocation in C/C++. + +:::tip +ast-grep takes a unique approach to this problem. It uses a [pattern object](/guide/rule-config/atomic-rule.html#pattern-object) to represent and disambiguate a complete and valid code snippet, and then leverages a [`selector`](/reference/rule.html#pattern) to extract the part that matches the query. +::: + + +## Formal Queries using Custom Languages + +### Significant Extensions of Existing Programming Languages + +These languages extend existing programming languages with features like wildcard tokens or regular expression operators. For example, the pattern `$(if $$ else $) $+` might be used to find all nested if-else statements in a codebase. [Coccinelle](https://coccinelle.gitlabpages.inria.fr/website/) and [Semgrep](https://semgrep.dev/) are tools that take this approach. + + +Semgrep's pattern-syntax, for example, has extensive features such as [ellipsis metavariables](https://semgrep.dev/docs/writing-rules/pattern-syntax#ellipsis-metavariables), [typed metavariables](https://semgrep.dev/docs/writing-rules/pattern-syntax#typed-metavariables), and [deep expression operators](https://semgrep.dev/docs/writing-rules/pattern-syntax#deep-expression-operator), that cannot be parsed by a standard programming language' implementation. + +:::code-group +```yaml [Ellipsis Metavariables] +# combine ellipses and metavariables to match a sequence of ASTs +# note the ellipsis is not valid programming language syntax +pattern: foo($...ARGS, 3, $...ARGS) +# this pattern will match foo(1, 2, 3, 4, 5) +``` + +```yaml [Typed Metavariables] +# look for calls to the log method on Logger objects. +# A simple pattern like this will match `Math.log()` as well +pattern: $LOGGER.log(...) +# typed metavariable can put a type constraint on the metavariable +# but it is no longer valid Java code +pattern: (java.util.logging.Logger $LOGGER).log(...) +``` + +```yaml [Deep Expression operators] +# Use the deep expression operator <... [your_pattern] ...> +# to match an expression that +# could be deeply nested within another expression +pattern: | + if <... $USER.is_admin() ...>: + ... +``` +::: + +**Pros**: These languages can be more expressive than plain programming languages. + +**Cons**: Users need to learn new syntax and semantics and tool developers to support the extension + +:::warning Difference from ast-grep +Note ast-grep also supports multi meta variables in the form of `$$$VARS`. Compared to Semgrep, ast-grep's metavariables still produce valid code snippets. +::: + +We can represent also search query using **Domain Specific Language** + +### Logic-based Querying Languages + +These languages utilize first-order logic or languages like Datalog to express code properties. For example, a user can find all classes with the name "HelloWorld". Some of these languages also resemble SQL. [CodeQL](https://codeql.github.com/) and [Glean](https://glean.software/docs/angle/intro/) are two notable examples. Here is an example from CodeQL: + +```sql +from If ifstmt, Stmt pass +where pass = ifstmt.getStmt(0) and + pass instanceof Pass +select ifstmt, "This 'if' statement is redundant." +``` + +This CodeQL query will identify redundant if statements in Python, where the first statement within the if block is a pass statement. + +:::details Explaination of the query +* `from If ifstmt, Stmt pass`: This part of the query defines two variables, `ifstmt` and `pass`, which will be used in the query. +* `where pass = ifstmt.getStmt(0) and pass instanceof Pass`: This part of the query filters the results. It checks if the first statement in the `ifstmt` is a `Pass` statement. +* `select ifstmt, "This 'if' statement is redundant."`: This part of the query selects the results. It returns the `ifstmt` and a message. +::: + +**Pros:** These languages can precisely express complex code properties beyond syntax. + +**Cons:** Learning curve is steep. + +### Embedded Domain Specific Language + +Embedded DSLs are using the host language to express the query. The query is embedded in the host language, and the host language provides the necessary constructs to express the query. The query is then parsed and interpreted by the tool. + +There are further two flavors of embedded DSLs: configuration-based and program-based. + +#### Configuration-based eDSL + +Configuration-based eDSLs allow user to provide configuration objects that describes the query. The tool then interprets this configuration object to perform the search. ast-grep CLI and semgrep CLI both adopt this approach using YAML files. + +:::code-group +```yaml [ast-grep YAML rule] +id: match-function-call +language: c +rule: + pattern: + context: $M($$$); + selector: call_expression +``` + +```yaml [Semgrep YAML rule] +rules: + - id: my-pattern-name + pattern: | + TODO + message: "Some message to display to the user" + languages: [python] + severity: ERROR +``` + +::: + +Configuration files are more expressive than patterns and still relatively easy to write. Users usually already know the host language (YAML) and can leverage its constructs to express the query. + + +#### Program-based eDSL + +Program-based eDSLs provide direct access to the AST through AST node objects. + +Examples of programmatic APIs include [JSCodeshift](https://jscodeshift.com/build/api-reference/), the [Code Property Graph](https://docs.joern.io/code-property-graph/) from [Joern](https://joern.io/), and ast-grep's [NAPI](https://ast-grep.github.io/guide/api-usage.html). + +:::code-group +```typescript [@ast-grep/napi] +import { parse, Lang } from '@ast-grep/napi' + +let source = `console.log("hello world")` +const ast = parse(Lang.JavaScript, source) // 1. parse the source +const root = ast.root() // 2. get the root +const node = root.find('console.log($A)') // 3. find the node +node.getMatch('A').text() // 4. collect the info +// "hello world" +``` + +```javascript [JSCodeshift] +const j = require('jscodeshift'); + +const root = j(`const a = 1; const b = 2;`); + +const types = root.find(j.VariableDeclarator).getTypes(); +console.log(types); // Set { 'VariableDeclarator' } +``` + +```scala [Code Property Graph] +import io.shiftleft.codepropertygraph.Cpg +import io.shiftleft.semanticcpg.language._ + +object FindExecCalls { + def main(args: Array[String]): Unit = { + // Load the C codebase + val cpg: Cpg = Cpg.apply("path/to/your/codebase") + + // Find all `exec` function calls and print their locations + cpg.call("exec").location.l.foreach(println) + } +} +``` +::: + + +**Pros:** Offer more precision and expressiveness and are relatively easy to write. + +**Cons**: The overhead to communicate between the host language and the search tool can be high. + +### General Purpose Like Programming Language + +Finally, tools can also design their own general purpose programming languages. These languages provide a full programming language to describe code properties. [GritQL](https://about.grit.io/) is an example of this approach. + +For example, this GritQL query rewrites all `console.log` calls to `winston.debug` and all `console.error` calls to `winston.warn`: + +```gritql +`console.$method($msg)` => `winston.$method($msg)` where { + $method <: or { + `log` => `debug`, + `error` => `warn` + } +} +``` + +:::details Explaination of the Query + +1. **Pattern Matching**: The pattern `console.$method($msg)` is used to match code where there is a `console` object with a method (`$method`) and an argument (`$msg`). Here, `$method` and `$msg` are placeholders for any method and argument, respectively. + +2. **Rewrite**: The rewrite symbole `=>` specifies that the matched `console` code should be transformed to use `winston`, followed by the method (`$method`) and the argument (`$msg`). + +3. **Method Mapping**: The `where` clause specifies additional constraints on the rewrite. Specifically, `$method <: or { 'log' => 'debug', 'error' => 'warn' }` means: + - If `$method` is `log`, it should be transformed to `debug`. + - If `$method` is `error`, it should be transformed to `warn`. + +In sum, this rule replaces console logging methods with their corresponding Winston logging methods: +- `console.log('message')` becomes `winston.debug('message')` +- `console.error('message')` becomes `winston.warn('message')` +::: + +**Pros:** It offers more precision and expressiveness compared to simple patterns and configuration-based embedded DSLs. But it may not be as flexible as program-based eDSL nor as powerful as logic-based languages. + +**Cons:** Have the drawback of requiring users to learn the custom language first. It is easier to learn than logic-based languages, but still requires some learning compared to using embedded DSL. + + +## Hybrid Queries + +Hybrid queries combine multiple query types. For example, you can combine free-form queries with input-output examples, or combine natural language queries with program element references. + + +ast-grep is a great example of a tool that uses hybrid queries. You can define patterns directly in a YAML rule or use a programmatic API. + +First, you can embed the pattern in the YAML rule, like this: + +```yaml +rule: + pattern: console.log($A) + inside: + kind: function_declaration +``` + +You can also use the similar concept in the programmatic API + +```typescript +import { Lang, parse } from '@ast-grep/napi' + +const sg = parse(Lang.JavaScript, code) +sg.root().find({ + rule: { + pattern: 'console.log($A)', + inside: { + kind: 'function_declaration' + } + } +}) +``` + +This flexible design allows you to combine basic queries into larger, more complex ones, and you can always use a general-purpose language for very complex and specific searches. + +:::warning ast-grep favors existing programming languages +We don't want the user to learn a new language, but rather use the existing language constructs to describe the query. We also think TypeScript is a great language with [great type system](/blog/typed-napi.html). There is no need to reinvent a new language to express code search logic. +::: + +## ast-grep's Design Choices + +Designing a code search tool involves a delicate balancing act. It's challenging to simultaneously achieve ease of use, expressiveness, and precision, as these goals often conflict. Code search tools must carefully navigate these trade-offs to meet the diverse needs of their users. + +ast-grep makes specific choices to address this challenge: + +* **Prioritizing Familiarity**: It uses pattern matching based on existing programming language syntax, making it easy for developers to start using the tool with familiar coding structures. +* **Extending with Flexibility**: It incorporates configuration-based (YAML) and program-based (NAPI) embedded DSLs, providing additional expressiveness for complex searches. +* **Hybrid, and Progressive, Design**: Its pattern matching, YAML rules, and NAPI are designed for hybrid use, allowing users to start simple and gradually add complexity. The concepts in each API are also transferable, enabling users to progressively learn more advanced techniques. +* **AST-Based Precision**: It emphasizes precision by requiring all queries to be AST-based, ensuring accurate results. Though it comes with the trade-off that queries should be carefully crafted. +* **Multi-language Support**: Instead of creating a new query language for all programming languages or significantly extending existing ones for code search purposes, which would be an enormous undertaking, ast-grep reuses the familiar syntax of the existing programming languages in its patterns. This makes the tool more approachable for developers working across multiple languages. + +## Additional Considerations + +While we've focused on query design, there are other factors that influence the effectiveness of code search tools. These include: + +* Offline Indexing: This is crucial for rapid offline searching. Currently, ast-grep always builds an AST in memory for each query, meaning it doesn't support offline indexing. Tools like grep.app, which do use indexing, is faster for searching across millions of repositories. +* Information Indexing: Code search can index various kinds of information besides just code elements. Variable scopes, type information, definitions, and control and data flow are all valuable data for code search. Currently, ast-grep only indexes the AST itself. +* Retrieval Techniques: How a tool finds matching code given a query is a critical aspect. Various algorithmic and machine learning approaches exist for this. ast-grep uses a manual implementation that compares the query's AST with the code's AST. +* Ranking and Pruning: How search results are ordered is also a critical factor in providing good search results. \ No newline at end of file diff --git a/website/blog/fearless-concurrency.md b/website/blog/fearless-concurrency.md new file mode 100644 index 00000000..f203157e --- /dev/null +++ b/website/blog/fearless-concurrency.md @@ -0,0 +1,170 @@ +--- +author: + - name: Herrington Darkholme +date: 2025-01-01 +head: + - - meta + - property: og:type + content: website + - - meta + - property: og:title + content: An Example of Rust's Fearless Concurrency + - - meta + - property: og:url + content: https://ast-grep.github.io/blog/fearless-concurrency.html + - - meta + - property: og:description + content: ast-grep shows how Rust's fearless concurrency works in practice. Learn how to design concurrent systems in Rust and the trade-offs involved. + - - meta + - property: og:image + content: https://ast-grep.github.io/image/blog/concurrent.jpg +--- + +# An Example of Rust's Fearless Concurrency + +Rust is famous for its "fearless concurrency." It's a bold claim, but what does it actually *mean*? How does Rust let you write concurrent code without constantly battling race conditions? [ast-grep](https://ast-grep.github.io/)'s [recent refactor](https://github.com/ast-grep/ast-grep/discussions/1710) is a great example of Rust's concurrency model in action. + +## Old Architecture of ast-grep's Printer + +`ast-grep` is basically a syntax-aware `grep` that understands code. It lets you search for specific patterns within files in a directory. To make things fast, it uses multiple worker threads to churn through files simultaneously. The results then need to be printed to the console, and that's where our concurrency story begins. + +Initially, ast-grep had a single `Printer` object, shared by *all* worker threads. This was designed for maximum parallelism – print the results as soon as you find them! Therefore, the `Printer` had to be thread-safe, meaning it had to implement the `Send + Sync` traits in Rust. These traits are like stamps of approval, saying "this type is safe to move between threads (`Send`) and share between threads (`Sync`)." + +```rust +trait Printer: Send + Sync { + fn print(&self, result: ...); +} + +// demo Printer implementation +struct StdoutPrinter { + // output is shared between threads + output: Mutex, +} +impl Printer for StdoutPrinter { + fn print(&self, result: ...) { + // lock the output to print + let stdout = self.output.lock().unwrap(); + writeln!(stdout, "{}", result).unwrap(); + } +} +``` + +And `Printer` would be used in worker threads like this: + +```rust +// in the worker thread +struct Worker { + // printer is shareable between threads + // because it implements Send + Sync + printer: P, +} +impl

Worker

{ + fn search(&self, file: &File) { + let results = self.search_in_file(file); + self.printer.print(results); + } + // other methods not using printer... +} +``` + +While this got results quickly, it wasn't ideal from a user experience perspective. Search results were printed all over the place, not grouped by file, and often out of order. Not exactly user-friendly. + +## Migrate to Message-Passing Model + +The architecture needed a shift. Instead of sharing a printer, we moved to a message-passing model, using an [`mpsc` channel](https://doc.rust-lang.org/std/sync/mpsc/). `mpsc` stands for Multi-Producer, Single-Consumer FIFO queue, where a `Sender` is used to send data to a `Receiver`. + +Now, worker threads would send search results to a single dedicated *printer thread*. This printer thread then handles the printing sequentially and neatly. + +Here's the magic: because the printer is no longer shared between threads, we could remove the `Send + Sync` constraint! No more complex locking mechanisms! The printer could be a simple struct with a mutable reference to the standard output. + + +![concurrent programming bell curve](/image/blog/concurrent.jpg) + + +Here are some more concrete changes we made: + +### Remove Generics + +The printer used to be a field of `Worker`. Now, we had to move it out to the main thread. + +```rust +struct Worker { + sender: Sender<...>, +} + +impl Worker { + fn search(&self, file: &File) { + let results = self.search_in_file(file); + self.sender.send(results).unwrap(); + } + // other methods, no generic used +} + +fn main() { + let (sender, receiver) = mpsc::channel(); + let mut printer = StdoutPrinter::new(); + let printer_thread = thread::spawn(move || { + for result in receiver { + printer.print(result); + } + }); + // spawn worker threads +} +``` + +So, what did we gain? **Smaller binary size**. + +Previously, the worker struct was generic over the printer trait, which meant that the compiler had to generate code for each printer implementation. This resulted in a larger binary size. By removing generics over the printer trait, the worker struct no longer needs multiple copies. + +### Remove `Send + Sync` Bounds + +The `Send + Sync` bounds on the printer trait were no longer needed. The CLI changed the printer signature to use a mutable reference instead of an immutable reference. + +In the previous version, we couldn't use `&mut self` because it cannot be shared between threads. So we had to use `&self` and wrap the output in a `Mutex`. Now we can simply use a mutable reference since it is no longer shared between threads. + +```rust +trait Printer { + fn print(&mut self, result: ...); +} +// stdout printer implementation +struct StdoutPrinter { + output: Stdout, // no more Mutex +} +impl Printer for StdoutPrinter { + fn print(&mut self, result: ...) { + writeln!(self.output, "{}", result).unwrap(); + } +} +``` + +Without the need to lock the printer object, the code became **faster** in a single thread, without data-racing. + + +Thanks to Rust, this big architectural change was relatively painless. The compiler caught all the places where we were trying to share the printer between threads. It forced us to think about the design and make the necessary changes. + +## What Rust Teaches Us + + +This experience with `ast-grep` really highlights Rust's approach to concurrency. Rust forces you to _think deeply_ about your design and _encode_ it in the type system. + +You can't just haphazardly add threads and hope it works. Without clearly **designing the process architecture upfront**, you will soon find yourself trapped in a maze of the compiler's error messages. + +Rust then forces you to express the concurrency design in code via **type system enforcement**. +You need to use concurrency primitives, ownership rules, borrowing, and the `Send`/`Sync` traits to encode your design constraints. The compiler acts like a strict project manager, not allowing you to ship code if it doesn't meet the concurrency requirements. + +In other languages, concurrency is often treated as an afterthought. It is up to the programmer's discretion to design the architecture correctly. And it is also the programmer's responsibility to conscientiously and meticulously ensure the architecture is correctly implemented. + +## The Trade-off of Fearless Concurrency + +[And what, Rust, must we give in return?](https://knowyourmeme.com/memes/guldan-offer) Rust's approach comes with a trade-off: + +* **Upfront design investment:** You need to design your architecture thoroughly before you start writing actual production code. While the compiler could be helpful when you explore options or ambiguous design ideas, it can also be a hindrance when you need to iterate quickly. +* **Refactoring can be hard:** If you need to change your architectural design, it can be an invasive change across your codebase, because you need to change the type signatures, the concurrency primitives, and data flows. Other languages might be more flexible in this regard. + +Rust feels a bit like a mini theorem prover, like [Lean](https://lean-lang.org/). You are using the compiler to prove that your concurrent model is correct and safe. + +If you are still figuring out your product market fit and need rapid iteration, other languages might be [a better choice](https://x.com/charliermarsh/status/1867927883421032763). But if you need the safety and performance that Rust provides, it is definitely worth the effort! + +## The Fun to Play with Rust + +ast-grep is a hobby project. Even though it might be a bit more work to get started, this small project shows that building concurrent applications in Rust can be [fun and rewarding](https://x.com/charliermarsh/status/1873402334967173228). I hope this gave you a glimpse into Rust's fearless concurrency and maybe inspires you to take the plunge! \ No newline at end of file diff --git a/website/blog/interactive-demo.md b/website/blog/interactive-demo.md new file mode 100644 index 00000000..ad98425e --- /dev/null +++ b/website/blog/interactive-demo.md @@ -0,0 +1,67 @@ +--- +author: + - name: Herrington Darkholme +date: 2025-06-07 +head: + - - meta + - property: og:type + content: website + - - meta + - property: og:title + content: Interactive Code Fixes with Multiple Options! + - - meta + - property: og:url + content: https://ast-grep.github.io/blog/interactive-demo.html + - - meta + - property: og:description + content: Today, we're thrilled to showcase a game-changing feature, multi-option interactive code fixes! +--- + +# Interactive Code Fixes with Multiple Options! + +Today, we're thrilled to showcase a game-changing feature: **multi-option interactive code fixes!** + + + + +## Beyond Simple Search: Interactive Refactoring + +ast-grep's interactive mode, activated with `ast-grep scan --interactive`, transforms code analysis into a dynamic, actionable workflow. When a rule identifies a pattern match, you're presented with: + +1. **Clear Diff Views:** Instantly see the problematic code (red) and the proposed change (green), making it easy to understand the impact of the fix. +2. **Contextual Markdown Notes:** Rules can embed rich Markdown notes, providing instant documentation, best practices, and explanations directly in your terminal – no need to jump to external docs. + +## The Power of Choice: Multiple Fix Options + +This is where ast-grep truly stands out. Instead of a single, rigid fix, many rules now offer **multiple, intelligent remediation options**. + +**How it works:** + +* When a match is found, ast-grep displays a list of available fixes. +* Simply use the **`tab` key** to cycle through the different fix proposals. +* Once you've found the ideal solution, hit `Enter` to apply it. + +This flexibility allows you to choose the fix that best aligns with your project's coding standards or specific refactoring goals. + +## Real-World Example: Angular `@Input()` Optionality + +Consider a common TypeScript scenario in Angular: an `@Input()` decorator where the component property is typed as `string`, but it's optional by default (meaning it could be `undefined`). + +ast-grep's rule for this issue intelligently offers two distinct fixes: + +1. **Add `undefined` to Type:** Transforms `test: string;` to `test: string | undefined;`, explicitly acknowledging the optionality in the type system. +2. **Make Input Required:** Adds `{ required: true }` to the `@Input()` decorator, enforcing that the input must always be provided. + +You choose the solution that fits your use case, and ast-grep applies the transformation seamlessly. + +## Behind the Scenes: Configurable Fixes + +This powerful multi-fix capability is driven by the rule's YAML configuration. Rules can define an array of `fix` templates, each with a unique `title` and `template`, allowing rule authors to provide comprehensive repair options. + + +### Streamline Your Workflow Today! + +Ast-grep with its interactive multi-fix feature is a game-changer for maintaining code quality, enforcing standards, and accelerating large-scale code transformations. It puts the power of intelligent, context-aware refactoring directly into your hands. + +**Ready to refactor like a pro?** +Give ast-grep a try and experience the future of code analysis and transformation! diff --git a/website/blog/migrate-bevy.md b/website/blog/migrate-bevy.md new file mode 100644 index 00000000..a026ab70 --- /dev/null +++ b/website/blog/migrate-bevy.md @@ -0,0 +1,446 @@ +--- +author: + - name: Herrington Darkholme +date: 2023-05-17 +head: + - - meta + - property: og:type + content: website + - - meta + - property: og:title + content: Migrating Bevy can be easier with (semi-)automation + - - meta + - property: og:url + content: https://ast-grep.github.io/blog/migrate-bevy.html + - - meta + - property: og:description + content: In this article, we will show you how to make migration easier by using some command line tools. +--- + +# Migrating Bevy can be easier with (semi-)automation + +Using open source software can be a double-edged sword: We enjoy the latest features and innovations, but we hate frequent and sometimes tedious upgrades. + +Bevy is a fast and flexible game engine written in Rust. It aims to provide a modern and modular architecture, notably [Entity Component System(ECS)](https://www.wikiwand.com/en/Entity_component_system), that allows developers to craft rich and interactive experiences. +However, the shiny new engine is also an evolving project that periodically introduces breaking changes in its API. +Bevy's migration guide is comprehensive, but daunting. It is sometimes overwhelmingly long because it covers many topics and scenarios. + +In this article, we will show you how to make migration easier by using some command line tools such as [`git`](https://git-scm.com/), [`cargo`](https://doc.rust-lang.org/cargo/) and [`ast-grep`](https://ast-grep.github.io/). These tools can help you track the changes, search for specific patterns in your code, and automate API migration. Hope you can migrate your Bevy projects with less hassle and more confidence by following our tips. + +---- + +We will use the utility AI library [big-brain](https://github.com/zkat/big-brain), the second most starred Bevy project on GitHub, as an example to illustrate bumping Bevy version from 0.9 to 0.10. +Upgrading consists of four big steps: **make a clean git branch**, **updating the dependencies**, **running fix commands**, and **fixing failing tests**. And here is a list of commands used in the migration. + +* `git`: Manage code history, keep code snapshot, and help you revert changes if needed. +* `cargo check`: Quickly check code for errors and warnings without building it. +* `ast-grep`: Search for ASTs in source and automate code rewrite using patterns or expressions. +* `cargo fmt`: Format the rewritten code according to Rust style guidelines. +* `cargo test`: Run tests in the project and report the results to ensure the program still works. + +## Preparation + +Before we start, we need to make sure that we have the following tools installed: [Rust](https://rustup.rs/), [git](https://git-scm.com/) and [ast-grep](https://ast-grep.github.io/). + +Compared to the other two tools, ast-grep is lesser-known. In short it can do search and replace based on [abstract syntax trees](https://www.wikiwand.com/en/Abstract_syntax_tree). You can install it via [`cargo`](https://crates.io/crates/ast-grep) or [`brew`](https://formulae.brew.sh/formula/ast-grep). + +```shell +# install the binary `ast-grep` +cargo install ast-grep +# or use brew +brew install ast-grep +``` + +### Clone + +The first step is to clone your repository to your local machine. You can use the following command to clone the big-brain project: + +```sh +git clone git@github.com:HerringtonDarkholme/big-brain.git +``` + +Note that the big-brain project is not the official repository of the game, but a fork that has not updated its dependencies yet. We use this fork for illustration purposes only. + +### Check out a new branch + +Next, you need to create a new branch for the migration. This will allow you to keep track of your changes and revert them if something goes wrong. You can use the following command to create and switch to a new branch called `upgrade-bevy`: + +```sh +git checkout -b upgrade-bevy +``` + +> Key take away: make sure you have a clean git history and create a new branch for upgrading. + +## Update Dependency + +Now it's time for us to kick off the real migration! First big step is to update dependencies. It can be a little bit tricker than you think because of transitive dependencies. + +### Update dependencies + +Let's change the dependency file `Cargo.toml`. Luckily big-brain has clean dependencies. + +Here is the diff: +```diff +diff --git a/Cargo.toml b/Cargo.toml +index c495381..9e99a3b 100644 +--- a/Cargo.toml ++++ b/Cargo.toml +@@ -14,11 +14,11 @@ homepage = "https://github.com/zkat/big-brain" + [workspace] + + [dependencies] +-bevy = { version = "0.9.0", default-features = false } ++bevy = { version = "0.10.0", default-features = false } + big-brain-derive = { version = "=0.16.0", path = "./derive" } + + [dev-dependencies] +-bevy = { version = "0.9.0", default-features = true } ++bevy = { version = "0.10.0", default-features = true } + rand = { version = "0.8.5", features = ["small_rng"] } + + [features] +``` + +### Update lock-file + +After you have updated your dependencies, you need to build a new lock-file that reflects the changes. You can do this by running the following command: +```bash +cargo check +``` + +This will check your code for errors and generate a new Cargo.lock file that contains the exact versions of your dependencies. + +### Check Cargo.lock, return to step 3 if necessary + +You should inspect your Cargo.lock file to make sure that all your dependencies are compatible and use the same version of Bevy. Bevy is [more a bazaar than a cathedral](https://www.wikiwand.com/en/The_Cathedral_and_the_Bazaar). You may install third-party plugins and extensions from the ecosystem besides the core library. This means that some of these crates may not be updated or compatible with the latest version of Bevy or may have different dependencies themselves, causing errors or unexpected behavior in your code. +If you find any inconsistencies, you can go back to step 3 and modify your dependencies accordingly. Repeat this process until your Cargo.lock file is clean and consistent. + +A tip here is to search `bevy 0.9` in the lock file. `Cargo.lock` will list library with different version numbers. + +Fortunately, Bevy is the only dependency in big-brain. So we are good to go now! + +> Key take away: take advantage of Cargo.lock to find transitive dependencies that need updating. + +## (Semi-)Automate Migration + +### `cargo check` and `ast-grep --rewrite` + +We will use compiler to spot breaking changes and use AST rewrite tool to repeatedly fix these issues. +This is a semi-automated process because we need to manually check the results and fix the remaining errors. + +The mantra here is to use automation that maximize your productivity. Write codemod that is straightforward to you and fix remaining issues by hand. + +1. `CoreSet` + +The first error is quite easy. The compiler outputs the following error. + +```shell +error[E0432]: unresolved import `CoreStage` + --> src/lib.rs:226:13 + | +226 | use CoreStage::*; + | ^^^^^^^^^ use of undeclared type `CoreStage` +``` +From [migration guide](https://bevyengine.org/learn/migration-guides/0.9-0.10/): + +> The `CoreStage` (... more omitted) enums have been replaced with `CoreSet` (... more omitted). The same scheduling guarantees have been preserved. + +So we just need to change the import name. [Using ast-grep is trivial here](https://ast-grep.github.io/guide/introduction.html#introduction). +We need to provide a pattern, `-p`, for it to search as well as a rewrite string, `-r` to replace the old API with the new one. The command should be quite self-explanatory. + +``` +ast-grep -p 'CoreStage' -r CoreSet -i +``` + +We suggest to add `-i` flag for `--interactive` editing. ast-grep will display the changed code diff and ask your decision to accept or not. + +```diff +--- a/src/lib.rs ++++ b/src/lib.rs +@@ -223,7 +223,7 @@ pub struct BigBrainPlugin; + + impl Plugin for BigBrainPlugin { + fn build(&self, app: &mut App) { +- use CoreStage::*; ++ use CoreSet::*; +``` + + +2. `StageLabel` + +Our next error is also easy-peasy. + +``` +error: cannot find derive macro `StageLabel` in this scope + --> src/lib.rs:269:45 + | +269 | #[derive(Clone, Debug, Hash, Eq, PartialEq, StageLabel, Reflect)] + | +``` + +The [doc](https://bevyengine.org/learn/migration-guides/0.9-0.10/#label-types): +> System labels have been renamed to systems sets and unified with stage labels. The `StageLabel` trait should be replaced by a system set, using the `SystemSet` trait as dicussed immediately below. + +The command: +```bash +ast-grep -p 'StageLabel' -r SystemSet -i +``` + +3. `SystemStage` + +The next error is much harder. First, the error complains two breaking changes. + +``` +error[E0599]: no method named `add_stage_after` found for mutable reference `&mut bevy::prelude::App` in the current scope + --> src/lib.rs:228:13 + | ↓↓↓↓↓↓↓↓↓↓↓ use of undeclared type `SystemStage` +228 | app.add_stage_after(First, BigBrainStage::Scorers, SystemStage::parallel()); + | ^^^^^^^^^^^^^^^ help: there is a method with a similar name: `add_state` +``` + +Let's see what [migration guide](https://bevyengine.org/learn/migration-guides/0.9-0.10/#stages) said. This time we will give the code example. + +``` +// before +app.add_stage_after(CoreStage::Update, AfterUpdate, SystemStage::parallel()); + +// after +app.configure_set( + AfterUpdate + .after(CoreSet::UpdateFlush) + .before(CoreSet::PostUpdate), +); +``` + +`add_stage_after` is removed and `SystemStage` is renamed. We should use `configure_set` and `before`/`after` methods. + +Let's write a command for this code migration. + +```bash +ast-grep \ + -p '$APP.add_stage_after($STAGE, $OWN_STAGE, SystemStage::parallel())' \ + -r '$APP.configure_set($OWN_STAGE.after($STAGE))' -i +``` + +This pattern deserves some explanation. + +`$STAGE` and `$OWN_STAGE` are [meta-variables](https://ast-grep.github.io/guide/pattern-syntax.html#meta-variable). + +meta-variable is a wildcard expression that can match any single AST node. So we effectively find all `add_stage_after` call. We can also use meta-variables in the rewrite string and ast-grep will replace them with the captured AST nodes. ast-grep's meta-variables are very similar to regular expression's dot `.`, except they are not textual. + +However, I found some `add_stage_after`s are not replaced. Nah, ast-grep is [quite dumb](https://github.com/ast-grep/ast-grep/issues/374) that it cannot handle the optional comma after the last argument. So I used another query with a trailing comma. + +```shell +ast-grep \ + -p 'app.add_stage_after($STAGE, $OWN_STAGE, SystemStage::parallel(),)' \ + -r 'app.configure_set($OWN_STAGE.after($STAGE))' -i +``` + +Cool! Now it replaced all `add_stage_after` calls! + +```diff +--- a/src/lib.rs ++++ b/src/lib.rs +@@ -225,7 +225,7 @@ impl Plugin for BigBrainPlugin { +- app.add_stage_after(First, BigBrainStage::Scorers, SystemStage::parallel()); ++ app.configure_set(BigBrainStage::Scorers.after(First)); +@@ -245,7 +245,7 @@ impl Plugin for BigBrainPlugin { +- app.add_stage_after(PreUpdate, BigBrainStage::Actions, SystemStage::parallel()); ++ app.configure_set(BigBrainStage::Actions.after(PreUpdate)); +@@ -253,7 +253,7 @@ impl Plugin for BigBrainPlugin { +- app.add_stage_after(Last, BigBrainStage::Cleanup, SystemStage::parallel()); ++ app.configure_set(BigBrainStage::Cleanup.after(Last)); +``` + +4. `Stage` + +Our next error is about [`add_system_to_stage`](https://bevyengine.org/learn/migration-guides/0.9-0.10/#stages). The migration guide told us: + + +```rust +// Before: +app.add_system_to_stage(CoreStage::PostUpdate, my_system) +// After: +app.add_system(my_system.in_base_set(CoreSet::PostUpdate)) +``` + +Let's also write a pattern for it. + +```sh +ast-grep \ + -p '$APP.add_system_to_stage($STAGE, $SYS)' \ + -r '$APP.add_system($SYS.in_base_set($STAGE))' -i +``` + +Example diff: + +```diff +--- a/src/lib.rs ++++ b/src/lib.rs +@@ -243,7 +243,7 @@ impl Plugin for BigBrainPlugin { +- app.add_system_to_stage(BigBrainStage::Thinkers, thinker::thinker_system); ++ app.add_system(thinker::thinker_system.in_base_set(BigBrainStage::Thinkers)); +``` + +5. `system_sets` + +The next error corresponds to the system_sets in [migration guide](https://bevyengine.org/learn/migration-guides/0.9-0.10/#system-sets-bevy-0-9). + +``` +// Before: +app.add_system_set( + SystemSet::new() + .with_system(a) + .with_system(b) + .with_run_criteria(my_run_criteria) +); +// After: +app.add_systems((a, b).run_if(my_run_condition)); +``` + +We need to change `SystemSet::new().with_system(a).with_system(b)` to `(a, b)`. +Alas, I don't know how to write a pattern to fix that. Maybe ast-grep is not strong enough to support this. I just change `with_system` manually. +_It is still faster than me scratching my head about how to automate everything._ + +Another change is to use `add_systems` instead of `add_system_set`. This is a simple pattern! + +```sh +ast-grep \ + -p '$APP.add_system_set_to_stage($STAGE, $SYS,)' \ + -r '$APP.add_systems($SYS.in_set($STAGE))' -i +``` + +This should fix `system_sets`! + +6. Last error + +Our last error is about `in_base_set`'s type. + +```shell +error[E0277]: the trait bound `BigBrainStage: BaseSystemSet` is not satisfied + --> src/lib.rs:238:60 + | +238 | app.add_system(thinker::thinker_system.in_base_set(BigBrainStage::Thinkers)); + | ----------- ^^^^^^^^^^^^^^^^^^^^^^^ the trait `BaseSystemSet` is not implemented for `BigBrainStage` + | | + | required by a bound introduced by this call + | + = help: the following other types implement trait `BaseSystemSet`: + StartupSet + bevy::prelude::CoreSet +note: required by a bound in `bevy::prelude::IntoSystemConfig::in_base_set` +``` + +Okay, `BigBrainStage::Thinkers` is not a base set in Bevy, so we should change it to `in_set`. + +```diff +- .add_system(one_off_action_system.in_base_set(BigBrainStage::Actions)) ++ .add_system(one_off_action_system.in_set(BigBrainStage::Actions)) +``` + +**Hoooray! Finally the program compiles! ~~ship it!~~ Now let's test it.** + + +> Key take away: Automation saves your time! But you don't have to automate everything. + +## cargo fmt +Congrats! You have automated code refactoring! But ast-grep's rewrite can be messy and hard to read. Most code-rewriting tool does not support pretty-print, sadly. +A simple solution is to run `cargo fmt` and make the repository neat and tidy. + +``` +cargo fmt +``` + +A good practice is to run this command every time after a code rewrite. + +> Key take away: Format code rewrite as much as you want. + +## Test Our Refactor + +### `cargo test` + +Let's use Rust's standard test command to verify our changes: `cargo test`. + +Oops. we have one test error, not too bad! + +``` +running 1 test +test steps ... FAILED + +failures: + +---- steps stdout ---- +steps test +thread 'steps' panicked at '`"Update"` and `"Cleanup"` have a `before`-`after` relationship (which may be transitive) but share systems.' +note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace +``` + +Okay, it complains that `Update` and `Cleanup` have a conflicting running order. This is probably caused by `configure_set`. + +I should have caught the bug during diff review but I missed that. It is not too late to change it manually. + +```diff +--- a/src/lib.rs ++++ b/src/lib.rs +@@ -225,7 +225,7 @@ impl Plugin for BigBrainPlugin { +- app.configure_set(BigBrainStage::Scorers.after(First)); ++ app.configure_set(BigBrainStage::Scorers.in_base_set(First)); +@@ -242,12 +242,12 @@ impl Plugin for BigBrainPlugin { +- app.configure_set(BigBrainStage::Actions.after(PreUpdate)); ++ app.configure_set(BigBrainStage::Actions.in_base_set(PreUpdate)); +``` + +Run `cargo test` again? + +``` + + Doc-tests big-brain + +failures: + +---- src/lib.rs - (line 127) stdout ---- +error[E0599]: + no method named `add_system_to_stage` found for mutable reference + `&mut bevy::prelude::App` + in the current scope +``` + +We failed doc-test! + +Because our ast based tool does not process comments. Lame. :( +We need manually fix them. + +``` +--- a/src/lib.rs ++++ b/src/lib.rs +@@ -137,8 +137,8 @@ +-//! .add_system_to_stage(BigBrainStage::Actions, drink_action_system) +-//! .add_system_to_stage(BigBrainStage::Scorers, thirsty_scorer_system) ++//! .add_system(drink_action_system.in_set(BigBrainStage::Actions)) ++//! .add_system(thirsty_scorer_system.in_set(BigBrainStage::Scorers)) +``` + +**Finally we passed all tests!** + +``` +test result: ok. 21 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 4.68s +``` + +## Conclusion + +Now we can commit and push our version upgrade to the upstream. It is not a too long battle, is it? + +I have created a pull request for reference. https://github.com/HerringtonDarkholme/big-brain/pull/1/files + +Reading a long migration guide is not easy, and fixing compiler errors is even harder. + +It would be nice if the official guide can contain some automated command to ease the burden. For example, [yew.rs](https://yew.rs/docs/next/migration-guides/yew/from-0_20_0-to-next) did a great job by providing automation in every release note! + +To recap our semi-automated refactoring, this is our four steps: + +* Keep a clean git branch for upgrading +* Update all dependencies in the project and check lock files. +* Compile, Rewrite, Verify and Format. Repeat this process until the project compiles. +* Run Test and fix the remaining bugs. + +I hope this workflow will help you and other programming language developers in the future! diff --git a/website/blog/more-llm-support.md b/website/blog/more-llm-support.md new file mode 100644 index 00000000..daba8a37 --- /dev/null +++ b/website/blog/more-llm-support.md @@ -0,0 +1,84 @@ +--- +author: + - name: Herrington Darkholme +search: false +date: 2025-03-23 +head: + - - meta + - property: og:type + content: website + - - meta + - property: og:title + content: ast-grep gets more LLM support! + - - meta + - property: og:url + content: https://ast-grep.github.io/blog/more-llm-support.html + - - meta + - property: og:description + content: ast-grep is getting even better with enhanced Large Language Model (LLM) support. This exciting development opens up new possibilities for developers to analyze, understand, and transform code more efficiently. Let's dive into the details of these new features. +--- + +# ast-grep Gets More LLM Support! + +## Leveling Up Code Analysis with AI + +ast-grep, the powerful tool for structural code search, is getting even better with enhanced Large Language Model (LLM) support. This exciting development opens up new possibilities for developers to analyze, understand, and transform code more efficiently. Let's dive into the details of these new features. + +## `llms.txt` Support + +ast-grep now supports a new file format, [`llms.txt`](https://llmstxt.org/), designed to work seamlessly with LLMs. It is also on [llmstxthub](https://llmstxthub.com/websites/ast-grep) for easy access to the latest files. + +A key challenge for large language models is their limited ability to process extensive website content. They often struggle with the complexity of converting full HTML pages, which include navigation, advertisements, and JavaScript, into a simplified text format that LLMs can effectively use. + +On the other hand, ast-grep faces challenges due to the limited training data available for LLMs. Because of this, LLMs often confuse ast-grep with other similar tools, even when provided with accurate prompts. Furthermore, despite ast-grep's comprehensive online documentation, LLM search capabilities don't guarantee accurate retrieval of information on rule writing. This hinders ast-grep's widespread adoption in the AI era. + +`llms.txt` addresses this by providing models with comprehensive context, enhancing their [in-context learning](https://arxiv.org/abs/2301.00234) and improving the accuracy of their output. It is particularly effective with models that have large context windows, such as [Google’s Gemini](https://aistudio.google.com/). + +![Example Usage with $GOOG Gemini](/image/blog/gemini.jpeg) +

Example llms.txt usage with Gemini. The full doc reduces model hallucination.
+ + +The general usage of `llms.txt` is as follows: + +1. Visit [https://ast-grep.github.io/llms-full.txt](https://ast-grep.github.io/llms-full.txt) and copy the full documentation text +2. Paste these documents into your conversation with your preferred AI chatbot +3. Ask AI questions about ast-grep + + +## AI-powered Codemod Studio + +[Codemod.com](https://codemod.com/) is a long-time [contributor](https://go.codemod.com/ast-grep-contributions) [supporter](https://github.com/ast-grep/ast-grep?tab=readme-ov-file#sponsor) of ast-grep and has recently introduced a new feature called [Codemod Studio](https://app.codemod.com/studio). + +The studio introduces an AI assistant which is is a game-changer for writing ast-grep rules. This interactive environment allows you to use natural language to describe the code patterns you want to find, and then the AI will help you write the corresponding ast-grep rule. Here's how it works: + +* **Describe your goal**: In plain English, explain what you want to achieve with your ast-grep rule (e.g., "Find all instances of `console.log`"). +* **AI assistance**: The AI analyzes your description and suggests an appropriate ast-grep pattern. +* **Refine and test**: You can then refine the generated rule, test it against your codebase, and iterate until it meets your needs. + +This innovative approach democratizes ast-grep rule creation, making it accessible to developers of all skill levels, even without previous experience with ast-grep. + +## GenAI Script Support + +Microsoft’s GenAI Script supports [ast-grep](https://microsoft.github.io/genaiscript/reference/scripts/ast-grep/)! + +> [GenAIScript](https://microsoft.github.io/genaiscript/) is a scripting language that integrates LLMs into the scripting process using a simplified JavaScript syntax. Supported by our VS Code GenAIScript extension, it allows users to create, debug, and automate LLM-based scripts. + +Notably, GenAIScript provides a wrapper around `ast-grep` to search for patterns within a script's AST and transform that AST. This enables the creation of highly efficient scripts that modify source code by precisely targeting specific code elements. + + +## Upcoming MCP Support + +Looking ahead, ast-grep has a plan to support [Model Context Protocol](https://modelcontextprotocol.io) (MCP). This upcoming feature will further enhance the integration of LLMs with ast-grep, enabling even more sophisticated code analysis and transformation. + +MCP will provide a standardized interface for LLMs to interact with ast-grep, streamlining the process of analyzing and transforming code. Some of the key features of ast-grep MCP include: + +* List all ast-grep's [resources](https://modelcontextprotocol.io/docs/concepts/resources) in the project: rules, utils, and test cases. +* Orchestrate the LLM's actions by providing predefined [prompts](https://modelcontextprotocol.io/docs/concepts/prompts) and workflows. +* Provide [tools](https://modelcontextprotocol.io/docs/concepts/tools) to create/validate rules and search the codebase. + +See the tracking GitHub issue [here](https://github.com/ast-grep/ast-grep/issues/1895) + + +## Conclusion + +ast-grep's integration of LLMs, including `llms.txt`, Codemod Studio, and GenAI Script, represents a significant leap forward in code analysis. With the promise of MCP on the horizon, ast-grep is poised to become an indispensable tool for developers seeking to harness the power of AI to understand, transform, and elevate their code. The future of code analysis is here, and it's powered by ast-grep. diff --git a/website/blog/new-ver-38.md b/website/blog/new-ver-38.md new file mode 100644 index 00000000..da4a0f04 --- /dev/null +++ b/website/blog/new-ver-38.md @@ -0,0 +1,91 @@ +--- +author: + - name: Herrington Darkholme +search: false +date: 2025-05-18 +head: + - - meta + - property: og:type + content: website + - - meta + - property: og:title + content: ast-grep new release 0.38 + - - meta + - property: og:url + content: https://ast-grep.github.io/blog/new-ver-38.html + - - meta + - property: og:description + content: ast-grep 0.38 brings some fantastic new features to improve your code searching and linting experience, alongside a significant internal shift. +--- + +# ast-grep 0.38 is Here + +We're excited to announce the release of ast-grep 0.38! This version brings some fantastic new features to improve your code searching and linting experience, alongside a significant internal shift that paves the way for exciting future developments. + +For those new to ast-grep, it's a powerful command-line tool that lets you search and rewrite code based on its structure (Abstract Syntax Trees or ASTs), not just text. Think of it as `grep` or `sed`, but for code syntax! + +Let's dive into what's new: + +## New Features + +### Customizable Code Highlighting with `labels` + +One of the exciting new additions is the `labels` field for your rule configurations. Previously, ast-grep's highlighting was pre-programmed and could not provide much context. Now, you can customize the highlighting of your matches with labels that are more meaningful and relevant to your codebase. These clearer labels also contribute to a cleaner and more intuitive user interface when viewing diagnostics. + +![Example of Customizable Code Highlighting](/image/blog/labels-demo.png) + +But the benefits don't stop at individual understanding. The labels field offers a fantastic way to embed more guidance directly into your rules, and it allow you to share coding best practices, style guide reminders, or domain-specific knowledge across your entire team. This feature helps disseminate expertise and maintain consistency effortlessly. For example, [Sam Wight](https://github.com/samwightt), the labels feature's proposer, is using ast-grep to help his team to write better [Angular code](/catalog/typescript/missing-component-decorator.html)! + +![Example of VSCode](/image/blog/labels-vscode.jpeg) + +Furthermore, this improved diagnostic experience isn't confined to the command line. The ast-grep VSCode extension now fully respects these labels, bringing this enhanced highlighting via the Language Server Protocol (LSP). You can click on the label message in the VSCode diagnostic popup and jump to the relevant code point! + +### `--json` Output Gets More Informative + +The `--json` output option can now include rule `metadata` when you use the new `--include-metadata` flag. This is helpful for integrating ast-grep into other tools or for more detailed programmatic analysis, e.g. [SonarQube](https://github.com/ast-grep/ast-grep/issues/1987). + +## Tree-sitter Independence + +This is a significant architectural change! Previously, ast-grep was tightly coupled with tree-sitter, a fantastic parser generator tool. While tree-sitter has been foundational to ast-grep's ability to support many languages, this tight coupling had limitations. + +* **Introducing `SgNode`:** We've abstracted the core AST node representation with a new trait called `SgNode`. This makes ast-grep's core logic more flexible and less dependent on a single parsing technology. +* **WASM Power-Up:** The ast-grep WebAssembly (WASM) module, which powers our interactive playground, now directly uses `tree-sitter-web` instead of the wrapper library [`tree-sitter-facade`](https://github.com/ast-grep/tree-sitter-wasm/tree/main/crates/tree-sitter-facade). +* **Paving the Way for the Future:** This independence opens doors for exciting new possibilities: + * **Proof of Concept OXC Integration:** We're exploring [integration](https://github.com/ast-grep/ast-grep/pull/1970) with Oxc, a high-performance JavaScript/TypeScript toolchain written in Rust. Oxc boasts an extremely fast parser, which could bring significant performance benefits to ast-grep for JavaScript and TypeScript projects. + * **Future SWC Integration:** Similarly, we're looking into [leveraging SWC](https://github.com/swc-project/plugins/pull/435), another Rust-based platform for fast JavaScript/TypeScript compilation and transformation. + +This move is all about future-proofing ast-grep and allowing us to adopt the best parsing technologies for different languages and use cases, ultimately leading to a faster and more versatile tool for you. + +## Breaking Changes + +### Dropped Support for Older Linux Versions (glibc < 2.35) + +Due to [an upgrade in the GitHub Actions build images](https://github.com/actions/runner-images/issues/11101), ast-grep binaries are now built on Ubuntu 22.04. This means they rely on a newer version of glibc (GNU C Library). + +**Impact:** Pre-compiled ast-grep binaries will no longer support distributions with glibc versions older than 2.35. For example, Ubuntu 20.04, which has glibc 2.31, is no longer directly supported by our pre-built binaries. This change also impacts [@ast-grep/napi](https://www.npmjs.com/package/@ast-grep/napi). + +**Alternatives:** If you are on an older Linux distribution, you can still use ast-grep by: +* Building it from source. +* Using package managers that might compile it for your specific distribution if available (like AUR for Arch Linux). +* Consider upgrading your system to a more recent version of your Linux distribution. +* Keep using ast-grep 0.37 if you don't want to upgrade your system. + +### Rust Library API Breaking Changes + +For users of ast-grep as a Rust library, please note the following API adjustments: + +* `AstGrep` is now an alias for `Root`. +* Tree-sitter specific methods within the `Language` trait have been moved to a new `LanguageExt` trait. +* `StrDoc` and related types have been relocated to the `ast_grep_core::tree_sitter` module. + +These changes are part of the larger effort to decouple ast-grep from tree-sitter and provide a cleaner, more maintainable library interface. + +## Get Started with 0.38! + +We believe these changes, especially the move towards parser independence and the enhanced diagnostic labeling, will make ast-grep an even more powerful and user-friendly tool for your everyday development tasks. + +Head over to our [GitHub repo](https://github.com/ast-grep/ast-grep) to grab the latest version. Check out the [documentation](https://ast-grep.github.io/) for more details on how to use the new features. + +We're excited to see how you use ast-grep 0.38! As always, your feedback is invaluable, so please don't hesitate to open issues or discussions on our GitHub repository. + +Happy Grepping! diff --git a/website/blog/optimize-ast-grep.md b/website/blog/optimize-ast-grep.md new file mode 100644 index 00000000..562b5b66 --- /dev/null +++ b/website/blog/optimize-ast-grep.md @@ -0,0 +1,154 @@ +--- +author: + - name: Herrington Darkholme +date: 2023-01-23 +head: + - - meta + - property: og:type + content: website + - - meta + - property: og:title + content: Optimize ast-grep to get 10X faster + - - meta + - property: og:url + content: https://ast-grep.github.io/blog/optimize-ast-grep.html + - - meta + - property: og:description + content: How to optimize the Rust CLI tool ast-grep to become 10 times faster. +--- + +# Optimize ast-grep to get 10X faster + +In this post I will discuss how to optimize the Rust CLI tool [ast-grep](https://ast-grep.github.io/) to become 10 times faster. + +Rust itself usually runs fast enough, but it is not a silver bullet to all performance issues. + +In this case, I did not pay enough attention to runtime details or opted for naive implementation for a quick prototype. And these inadvertent mistakes and deliberate slacking off became ast-grep's bottleneck. + +# Context + +[ast-grep](https://ast-grep.github.io/) is [my](https://github.com/HerringtonDarkholme) hobby project to help you search and rewrite code using [abstract syntax tree](https://www.wikiwand.com/en/Abstract_syntax_tree). + +Conceptually, ast-grep takes a piece of pattern code (think it like a regular expression but for AST), matches the pattern against your codebase and gives a list of matched AST nodes back to you. See the [playground](https://ast-grep.github.io/playground) for a live demo. + +I designed ast-grep's architecture with performance in mind. Here are a few performance related highlights: + +* it is written in Rust, a native language compiled to machine code. +* it uses the venerable C library [tree-sitter](https://tree-sitter.github.io/) to parse code, which is the same library powering [GitHub's codesearch](https://github.com/features/code-search). +* its command line interface is built upon [ignore](https://docs.rs/ignore/latest/ignore/), the same crates used by the blazing fast [ripgrep](https://github.com/BurntSushi/ripgrep). + +Okay, enough self-promotion _BS_. If it is designed to be fast, how comes this blog? Let's dive into the performance bottleneck I found in my bad code. + + +> Spoiler. It's my bad to write slow Rust. + +# Profiling + +The first thing to optimize a program is to profile it. I am lazy this time and just uses the [flamegraph](https://github.com/flamegraph-rs/flamegraph) tool. + +Installing it is simple. + +```bash +cargo install flamegraph +``` + +Then run it against ast-grep! No other setup is needed, compared to other profiling tools! + +This time I'm using an ast-grep port of [es-lint](https://github.com/ast-grep/eslint) against [TypeScript](https://github.com/microsoft/TypeScript/)'s `src` folder. + +This is the profiling command I used. + +```bash +sudo flamegraph -- ast-grep scan -c eslint/sgconfig.yml TypeScript/src --json > /dev/null +``` + +The flamegraph looks like this. + +Before Optimzation + +Optimizing the program is a matter of finding the hotspots in the flamegraph and fix them. + +For a more intuitive feeling about performance, I used the old command `time` to measure the wall time to run the command. The result is not good. + +```bash +time ast-grep scan -c eslint/sgconfig.yml TypeScript/src +17.63s user, 0.46s system, 167% cpu, 10.823 total +``` + +The time before `user` is the actual CPU time spent on my program. The time before `total` represents the wall time. The ratio between them is the CPU utilization. In this case, it is 167%. It means my program is not fully utilizing the CPU. + +It only runs six rules against the codebase and it costs about 10 whole seconds! + + +In contrast, running one ast-grep pattern agasint the TypeScript source only costs 0.5 second and the CPU utilization is decent. + +```bash +time ast-grep run -p '$A && $A()' TypeScript/src --json > /dev/null + +1.96s user, 0.11s system, 329% cpu, 0.628 total +``` + +# Expensive Regex Cloning + +The first thing I noticed is that the `regex::Regex` type is cloned a lot. I do know it is expensive to compile a regex, but I did not expect cloning one will be the bottleneck. +Much to my limited understanding, `drop`ping Regex is also expensive! + +Fortunately the fix is simple: I can use a reference to the regex instead of cloning it. + +This optimzation alone shaves about 50% of execution time. + +```bash +time ast-grep scan -c eslint/sgconfig.yml TypeScript/src --json > /dev/null +13.89s user, 0.74s system, 274% cpu 5.320 total +``` + +The new flamegraph looks like this. + +Avoid Regex Cloning + + +# Matching Rule can be Avoided + +The second thing I noticed is that the `match_node` function is called a lot. It is the function that matches a pattern against an AST node. +ast-grep can match an AST node by rules, and those rules can be composed together into more complex rules. +For example, the rule `any: [rule1, rule2]` is a composite rule that consists of two sub-rules and the composite rule matches a node when either one of the sub-rules matches the node. +This can be expensive since multiple rules must be tried for every node to see if they actually make a match. + +I have already forsee it so every rule in ast-grep has an optimization called `potential_kinds`. AST node in tree-sitter has its own type encoded in a unsigned number called `kind`. +If a rule can only match nodes with specific kinds, then we can avoid calling `match_node` for nodes if its kind is not in the `potential_kinds` set. +I used a BitSet to encode the set of potential kinds. Naturally the `potential_kinds` of composite rules can be constructed by merging the `potential_kinds` of its sub-rules, according to their logic nature. +For example, `any`'s potential_kinds is the union of its sub-rules' potential_kinds, and `all`'s potential_kinds is the intersection of its sub-rules' potential_kinds. + +Using this optimization, I can avoid calling `match_node` for nodes that can never match a rule. This optimization shaves another 40% of execution time! + +```bash +ast-grep scan -c eslint/sgconfig.yml TypeScript/src --json > /dev/null +11.57s user, 0.48s system, 330% cpu, 3.644 total +``` + +The new flamegraph. + +potential_kinds trick + +# Duplicate Tree Traversal + +Finally, the function call `ts_tree_cursor_child_iterator_next` caught my eyes. It meant that a lot of time was spent on traversing the AST tree. + +Well, I dumbly iterating through all the six rules and matching the whole AST tree for each rule. This is a lot of duplicated work! + +So I used a data structure to combine these rules, according to their `potential_kinds`. When I'm traversing the AST tree, I will first retrieve the rules with potential_kinds containing the kind of the current node. Then I will only run these rules against the node. And nodes without any `potential_kinds` hit will be naturally skipped during the traversal. + +This is a huge optimization! The ending result is less than 1 second! And the CPU utilization is pretty good. + +```bash +ast-grep scan -c eslint/sgconfig.yml TypeScript/src --json > /dev/null +2.82s user, 0.12s system, 301% cpu, 0.975 total +``` + +# Conclusion + +The final flamegraph looks like this. I'm too lazy to optimize more. I'm happy with the sub-second result for now. + +Merging rules + +Optimizing ast-grep is a fun journey. I learned a lot about Rust and performance tuning. I hope you enjoyed this post as well. diff --git a/website/blog/stars-3000.md b/website/blog/stars-3000.md new file mode 100644 index 00000000..6ff270cf --- /dev/null +++ b/website/blog/stars-3000.md @@ -0,0 +1,78 @@ +--- +author: + - name: Herrington Darkholme +search: false +date: 2023-11-02 +head: + - - meta + - property: og:type + content: website + - - meta + - property: og:title + content: ast-grep got 3000 stars! + - - meta + - property: og:url + content: https://ast-grep.github.io/blog/stars-3000.html + - - meta + - property: og:description + content: ast-grep has recently reached 3000 stars on GitHub! This is a remarkable achievement for the project and I am deeply grateful for all the support and feedback that I have received from the open source community. +--- + +# ast-grep got 3000 stars! + +![3000 stars](/image/blog/star3k.png) + +I am very excited and thankful to share with you that ast-grep, a code search and transformation tool that I have been working on for the past year, has recently reached 3000 stars on GitHub! This is a remarkable achievement for the project and I am deeply grateful for all the support and feedback that I have received from the open source community. + +## What is ast-grep? + +[ast-grep](https://ast-grep.github.io) is a tool that allows you to search and transform code using abstract syntax trees (ASTs). ASTs are tree-like representations of the structure and meaning of source code. By using ASTs, ast-grep can perform more accurate and powerful operations than regular expressions or plain text search. + +ast-grep supports multiple programming languages, such as JavaScript, [TypeScript](/catalog/typescript/), Python, [Ruby](/catalog/ruby/), Java, C#, [Rust](/catalog/rust/), and more. You can write [patterns](/guide/pattern-syntax.html) and rules in [YAML](/guide/rule-config/atomic-rule.html) format to specify what you want to match and how you want to transform it. You can also use the command-line interface (CLI) or the web-based [playground](/playground.html) to run ast-grep on your code. + +## Why use ast-grep? + +ast-grep can help you with many tasks that involve code search and transformation, such as: + +* Finding and fixing bugs, vulnerabilities, or code smells +* Refactoring or migrating code to a new syntax or framework +* Enforcing or checking coding standards or best practices +* Analyzing various code using a uniform interface + +> ast-grep can save you time and effort by automating repetitive or tedious tasks that would otherwise require manual editing or complex scripting. + +## What’s new in ast-grep? + +ast-grep is constantly evolving and improving thanks to the feedback and contributions from the users and sponsors. Here are some of the recent changes and updates of ast-grep: + +* ast-grep’s YAML rule now has a new `transform` rule: `conversion`, which can change matches to different cases, such as upper, lower, or camelcase. +* ast-grep’s diff/rewriting now can fix multiple rules at once. See [commit](https://github.com/ast-grep/ast-grep/commit/2b301116996b7b010ed271672d35a3529fb36e56) +* `ast-grep test -f`now accepts regex to selectively run ast-grep’s test case. +* `ast-grep --json` supports multiple formats that powers [telescope-sg](https://github.com/Marskey/telescope-sg), a neovim plugin that integrates ast-grep with telescope. +* ast-grep now prints matches with context like `grep -A -B -C`. See [issue](https://github.com/ast-grep/ast-grep/issues/464) +* JSON schema is added for better YAML rule editing. See [folder](https://github.com/ast-grep/ast-grep/tree/main/schemas) +* ast-grep now has official github action setup! See [action](https://github.com/ast-grep/action) +* New documentation for [rewriting code](/guide/rewrite-code.html), [example catalogs](/catalog/), and [playground](/reference/playground.html). + +## What’s next for ast-grep? + +ast-grep has many plans and goals for the future to make it more useful and user-friendly. Here are some of the upcoming features and enhancements of ast-grep: + +* Add python api support to allow users to write custom scripts using ast-grep. See [issue](https://github.com/ast-grep/ast-grep/issues/389) +* Support global language config to let users specify default options for each language. See [issue](https://github.com/ast-grep/ast-grep/issues/658) +* Improve napi documentation to help users understand how to use the native node module of ast-grep. See [issue](https://github.com/ast-grep/ast-grep/issues/682) +* Add metavar filter to make ast-grep run more powerful by allowing users to filter matches based on metavariable values. See [issue](https://github.com/ast-grep/ast-grep/issues/379) +* Add ast-grep’s pattern/rule tutorial to teach users how to write effective and efficient patterns and rules for ast-grep. See [issue](https://github.com/ast-grep/ast-grep.github.io/issues/154) +* Add examples to ast-grep’s reference page to illustrate the usage and functionality of each option and feature. See [issue](https://github.com/ast-grep/ast-grep.github.io/issues/266) + +## How to get involved? + +If you are interested in ast-grep and want to try it out, you can install it from [npm](https://www.npmjs.com/package/@ast-grep/cli) or [GitHub](https://github.com/ast-grep/ast-grep). You can also visit the [website](https://ast-grep.github.io/) to learn more about the features, documentation, and examples of ast-grep. + +If you want to contribute to the code or documentation of ast-grep, we have prepared a thorough [contribution guide](/contributing/how-to.html) for you! You can also report issues, suggest features, or ask questions on the issue tracker. + +## Thank you! + +I hope you are as enthusiastic as I am about the progress and future of ast-grep. I sincerely value your feedback, suggestions, and contributions. Please do not hesitate to contact me if you have any questions or comments. + +Thank you for your wonderful support. You are making a difference in the open source community and in the lives of many developers who use ast-grep. \ No newline at end of file diff --git a/website/blog/stars-5000.md b/website/blog/stars-5000.md new file mode 100644 index 00000000..997d15a3 --- /dev/null +++ b/website/blog/stars-5000.md @@ -0,0 +1,122 @@ +--- +author: + - name: Herrington Darkholme +search: false +date: 2024-01-20 +head: + - - meta + - property: og:type + content: website + - - meta + - property: og:title + content: 'ast-grep: 5000 stars and beyond!' + - - meta + - property: og:url + content: https://ast-grep.github.io/blog/stars-5000.html + - - meta + - property: og:description + content: ast-grep has recently reached 5000 stars on GitHub! This is a remarkable achievement for the project and I am deeply grateful for all the support and feedback that I have received from the open source community. +--- + +# ast-grep: 5000 stars and beyond! + +We are thrilled to announce that ast-grep has reached 5000 stars on [GitHub](https://github.com/ast-grep/ast-grep)! This is a huge milestone for our project and we are very grateful for your feedback, contributions, and encouragement. + + +![ast-grep star history](/image/blog/stars-5k.png) + +## Why ast-grep? + +[ast-grep](https://ast-grep.github.io/) is a tool that allows you to search and transform code using abstract syntax trees (ASTs). ASTs are tree-like representations of the structure and meaning of source code. By using ASTs, ast-grep can perform more accurate and powerful operations than regular expressions or plain text search. + +We have introduced a lot of new features in the past few months, and we want to share them with you. We hope that you will find them useful and that they will help you write better code. + +## What's new in ast-grep? + +### Core +* We have redesigned and implemented a [new pattern engine](https://x.com/hd_nvim/status/1735850666235687241) inspired by [difftastic](https://github.com/Wilfred/difftastic). Now, patterns use Rust structures to represent the syntax of code, instead of tree-sitter objects. This improves performance by minimizing tree traversal and allows for more reliable and user-friendly pattern-matching. + +### CLI +* You can now use [`--inline-rules`](https://ast-grep.github.io/reference/cli/scan.html#inline-rules-rule-text) to run rules without creating any files on your disk! You can pass everything, pattern/rule/input, as a string. This is great for scripting! +* [`--stdin`](https://ast-grep.github.io/reference/cli/run.html#stdin) will always wait for your input so you can match some code written in your terminal. +* You can also select [custom languages](https://ast-grep.github.io/advanced/custom-language.html) in [`ast-grep new`](https://ast-grep.github.io/reference/cli/new.html). + +### Language Support +* We have added support for three new languages: bash, php and elixir. +* We have updated our language support to include golang's generic syntax and python's pattern matching syntax. +* You can try out kotlin on our [playground](https://ast-grep.github.io/playground.html#eyJtb2RlIjoiUGF0Y2giLCJsYW5nIjoia290bGluIiwicXVlcnkiOiJrb3RsaW4iLCJyZXdyaXRlIjoiJEEgPz89ICRCOyIsImNvbmZpZyI6IiIsInNvdXJjZSI6ImZ1biBtaW5hbWkoKSB7XG4gICAgdmFsIGtvdGxpbiA9IFwi5Y2X44GT44Go44KK44KTXCJcbn0ifQ==)! + +### Rule +* You can now use `expandStart` and `expandEnd` to [adjust the fix range](https://ast-grep.github.io/reference/yaml/fix.html#fixconfig) selection for more precise code transformations. +* You can also use [`languageGlob`](https://ast-grep.github.io/reference/sgconfig.html#languageglobs) to register alias languages for extension override, which gives you more flexibility in handling different file types. + +### Node/Python API +* We have added to napi a new function [parseAsync](https://github.com/ast-grep/ast-grep/blob/beb6f50e936809071e6bacae2c854aefa8e46d11/crates/napi/index.d.ts#L104-L111), which allows you to leverage multiple cores in Node.js for faster code parsing. +* We have also added [language globs](https://github.com/ast-grep/ast-grep/blob/beb6f50e936809071e6bacae2c854aefa8e46d11/crates/napi/index.d.ts#L45) to findInFiles in napi, which makes it easier to search for code patterns in non-standard files (like searching HTML in `.vue` file). +* You can now use [`getTransformed`](https://github.com/ast-grep/ast-grep/blob/beb6f50e936809071e6bacae2c854aefa8e46d11/crates/napi/index.d.ts#L75) in napi to get the transformed code as a string. + +### Doc +* We have improved our [napi](https://ast-grep.github.io/guide/api-usage/js-api.html)/[pyo3](https://ast-grep.github.io/guide/api-usage/py-api.html) documentation and added sandbox/colab links for you to try out ast-grep online! +* We have also updated our [transformation](https://ast-grep.github.io/reference/yaml/transformation.html) and [code fix](https://ast-grep.github.io/reference/yaml/fix.html) documentation with more examples and explanations. +* We have added new language examples for [go](https://ast-grep.github.io/catalog/go/) and [python](https://ast-grep.github.io/catalog/python/), which show you how to use ast-grep with these popular languages. +* We have created an ast-grep [bot](https://ast-grep.github.io/guide/introduction.html#check-out-discord-bot) on [discord](https://discord.com/invite/4YZjf6htSQ), which can answer your questions and provide tips and tricks on using ast-grep. + +### Community +* We are excited to see that some awesome projects are using ast-grep for their code transformations, such as: + - [vue-macro cli](https://github.com/vue-macros/vue-macros-cli) helps you migrate your Vue projects to the latest version of Vue + - a new [unocss engine](https://github.com/zhiyuanzmj/transformer-attributify-jsx-sg) transforms JSX attributes into CSS classes +* We are also happy to see that some innovative platforms are using ast-grep as one of their tools to help developers understand and improve their codebases, such as: + - [coderabbit](https://coderabbit.ai/) uses ast-grep to help AI analyzing your code and provide insights and recommendations + - [codemod](https://codemod.com/) is considering ast-grep as a new underlying tool in their code transformation studio + + +## What's next in ast-grep? + +### Applying sub-rules to sub-nodes + +Currently, ast-grep can only apply rules/transformations to the whole node that matches the pattern. This limits the flexibility and expressiveness of ast-grep, compared to other tools like [babel](https://babeljs.io/) or [libcst](https://libcst.readthedocs.io/en/latest/). + +_We want to make ast-grep more powerful by allowing it to apply sub-rules to the metavariable nodes within the matching node._ This will enable ast-grep to handle more complex and diverse use cases in code transformation. + +For example, we can merge multiple decorators into one mega decorator in Python. This is impossible without API in the current version of ast-grep. + + +![screenshot of transforming python code](/image/blog/subrule-demo.png) + + + +The basic workflow of ast-grep is _**"Find and Patch"**_: + +1. **Find** a target node based on rule/pattern. +2. **Generate** a new string based on the matched node. +3. **Replace** the node text with the generated fix. + +However, this workflow does not allow us to generate different text for different sub-nodes in a rule. (This is like not being able to write `if` statements.) +Nor does it allow us to apply a rule to multiple sub-nodes of a node. (This is like not being able to write `for` loops.) + +To overcome these limitations, we will add three new steps between step 1 and step 2: + + +a. **Find** a list of different sub-nodes that match different sub-rules. +b. **Generate** a different fix for each sub-node based on the matched sub-rule. +c. **Join** the fixes together and store the string in a new metavariable for later use. + +The new steps are similar to the existing **_"Find and Patch"_** workflow, but with more granularity and control. + +This is like doing syntax tree oriented programming. We can apply different rules to different sub-nodes, just like using conditional statements. We can also apply rules to multiple sub-nodes, just like using loops. _"Find and Patch" is kind of a specialized "Functional Programming" over the AST!_ + +That said, applying sub-rules is an advanced feature that requires a lot of learning and practice. When in doubt, you can always use the existing [N-API](https://ast-grep.github.io/guide/api-usage/js-api.html)/[PyO3](https://ast-grep.github.io/guide/api-usage/py-api.html) workflow! + +## Thank you! + +We want to thank all the ast-grep users and supporters for your feedback, contributions, and encouragement. + +And we want to especially thank ast-grep's sponsors! + +![ast-grep sponsors](/image/blog/sponsor1.png) + + + +We hope that you enjoy the new features and improvements in ast-grep. We are always working to make ast-grep better and we look forward to hearing from you. + +Happy coding! \ No newline at end of file diff --git a/website/blog/stars-6000.md b/website/blog/stars-6000.md new file mode 100644 index 00000000..84e60936 --- /dev/null +++ b/website/blog/stars-6000.md @@ -0,0 +1,96 @@ +--- +author: + - name: Herrington Darkholme +search: false +date: 2024-05-19 +head: + - - meta + - property: og:type + content: website + - - meta + - property: og:title + content: ast-grep got 6000 stars! + - - meta + - property: og:url + content: https://ast-grep.github.io/blog/stars-6000.html + - - meta + - property: og:description + content: ast-grep has recently reached 6000 stars on GitHub! This is a remarkable achievement for the project and I am deeply grateful for all the support and feedback that I have received from the open source community. +--- + +# ast-grep got 6000 stars! + +We are thrilled to announce that [ast-grep](https://ast-grep.github.io/), the powerful code search tool, has reached a stellar milestone of 6000 stars on GitHub! This is a testament to the community's trust in our tool and the continuous improvements we've made. Let's dive into the latest features and enhancements that make ast-grep the go-to tool for developers worldwide. + +![ast-grep 6k stars](/image/blog/stars-6k.png) + + +## Feature Enhancements + +- **Rewriters Addition**: We've added support for rewriters [#855](https://github.com/ast-grep/ast-grep/pull/855), enabling complex code transformations and refactoring with ease. The new feature unlocks a novel functional programming like code rewrite scheme: [find and patch](/advanced/find-n-patch.html). Check out our previous [blog post](https://dev.to/herrington_darkholme/find-patch-a-novel-functional-programming-like-code-rewrite-scheme-3964) for more details. + +![rewriter](/image/blog/rewriter.png) + + +- **Error/Warning Suppression Support**: The new feature [#446](https://github.com/ast-grep/ast-grep/pull/446) allows users to suppress specific errors or warnings via the [code comment](/guide/project/lint-rule.html#suppress-linting-error) `ast-grep-ignore`. ast-grep also [respects suppression comments](https://github.com/ast-grep/ast-grep/issues/1019) in Language Server Protocol (LSP), making it easier to manage warnings and errors in your codebase. + + +- **Enhanced Rule Constraints**: The ast-grep rule `constraints` previously only accepted `pattern`, `kind` and `regex`. +Now it accepts a full rule [#855](https://github.com/ast-grep/ast-grep/pull/855), providing more flexibility than ever before. + +## VSCode extension + +The [ast-grep VSCode extension](https://marketplace.visualstudio.com/items?itemName=ast-grep.ast-grep-vscode) is an official [VSCode integration](/guide/tools/editors.html) for this CLI tool. It unleashes the power of structural search and replace (SSR) directly into your editor. + +### Notable Features +- **Search**: Find code patterns with syntax tree. +- **Replace**: Refactor code with pattern. +- **Diagnose**: Identify issues via ast-grep rule. + +## Performance Boost + +- **Parallel Thread Output Fix**: A significant fix [#be230ca](https://github.com/ast-grep/ast-grep/commit/be230ca) ensures parallel thread outputs are now guaranteed, boosting overall performance. + +## Architectural Evolution + +- **Tree-Sitter Version Bump**: We've upgraded to the latest tree-sitter version, enhancing parsing accuracy and speed. In future releases, we plan to leverage tree-sitter's [new Web Assembly grammar](https://zed.dev/blog/language-extensions-part-1) to support even more languages. +- **Scan and Diff Merge**: The [refactor](https://github.com/ast-grep/ast-grep/commit/c78299d2902662cd98bda44f3faf3fbc88439078) combines `CombinedScan::scan` and `CombinedScan::diff` for a more streamlined process. +- **Input Stream Optimization**: Now, ast-grep avoids unnecessary input stream usage when updating all rules [#943](https://github.com/ast-grep/ast-grep/pull/943), making it possible to use `ast-grep scan --update-all`. + +## Usability Improvements + +- **Error Messaging for Rule File Parsing**: The VSCode extension now provides clearer error messages [#968](https://github.com/ast-grep/ast-grep/pull/968) when rule file parsing fails, making troubleshooting a breeze. + +- **Better Pattern Parsing**: Improved expando character replacement [#883](https://github.com/ast-grep/ast-grep/pull/883) to make pattern . +- **More Permissive Patterns**: Patterns have become more permissive [#1087](https://github.com/ast-grep/ast-grep/pull/1087) that allows matching `$METAVAR` with different syntax kind. + +## Enhanced Error Reporting + +We've introduced a suite of features to improve error reporting, making it easier to debug and refine your code: + +- Report undefined meta-variables, errors in fixes, unused rewriters, and undefined utility rules. +- Add field ID errors for relational rules and optimize test updates to avoid erroneous reports. +- Shift from reporting file counts to error counts for a more meaningful insight into code quality. + + +![error report](/image/blog/error-report.png) + + + +## Language Support Expansion + +- **Haskell Support**: Haskell enthusiasts rejoice! ast-grep now supports Haskell via tree-sitter-haskell [#1128](https://github.com/ast-grep/ast-grep/pull/1128), broadening our language coverage. + +## NAPI Advancements + +- **NAPI Linux x64 musl Support**: Our latest feat in NAPI [#c4d7902](https://github.com/ast-grep/ast-grep/commit/c4d7902) adds support for Linux x64 musl, ensuring wider compatibility and performance. + +## Thanks + +As ast-grep continues to grow, we remain committed to providing a tool that not only meets but exceeds the expectations of our diverse user base. + + +![sponsors](/image/blog/sponsor2.png) + + +We thank each and every one of you, especially ast-grep's sponsors, for your support, contributions, and feedback that have shaped ast-grep into what it is today. Here's to many more milestones ahead! \ No newline at end of file diff --git a/website/blog/stars-8000.md b/website/blog/stars-8000.md new file mode 100644 index 00000000..26cb5883 --- /dev/null +++ b/website/blog/stars-8000.md @@ -0,0 +1,145 @@ +--- +author: + - name: Herrington Darkholme +search: false +date: 2025-03-04 +head: + - - meta + - property: og:type + content: website + - - meta + - property: og:title + content: ast-grep Rockets to 8000 Stars! + - - meta + - property: og:url + content: https://ast-grep.github.io/blog/stars-8000.html + - - meta + - property: og:description + content: ast-grep has recently reached 6000 stars on GitHub! This is a remarkable achievement for the project and I am deeply grateful for all the support and feedback that I have received from the open source community. +--- + +# ast-grep Rockets to 8000 Stars! + +We are absolutely bursting with excitement to announce that ast-grep has soared past **8,000 stars** on GitHub! Every star represents a developer who sees the potential in ast-grep, and we're deeply grateful for your support. + +![stars-8000](/image/blog/stars-8k.jpeg) + +ast-grep's mission to make code searching, linting, and rewriting more accessible and powerful has truly resonated with the community. This blog post is your guide to all the fantastic updates, encompassing both the core ast-grep CLI tool and our ever-improving website. Buckle up, let's explore what's new! + + +## Expanding the Language Universe: YAML, PHP, and More! + +ast-grep is rapidly becoming a truly polyglot code analysis powerhouse! We've significantly expanded our language support to empower you to work with even more of your codebase: + +**YAML Support Arrives!** YAML is the backbone of configuration for countless projects. Now, ast-grep CLI officially speaks [YAML](/catalog/yaml/), allowing you to leverage the same powerful rule system to lint, search, and even rewrite your YAML configuration files. Imagine using ast-grep rules to enforce best practices in your Kubernetes manifests or streamline your CI/CD pipelines! And yes, you can even write ast-grep rules *using YAML* itself! + +**Enhanced PHP Analysis:** We've introduced a dedicated PHP language parser (`php-only-language`) for the CLI. This means more accurate and reliable analysis for your PHP code, helping you catch tricky bugs and enforce code quality standards with greater confidence. + +**Dynamic Languages in APIs:** Python and JavaScript API users, rejoice! You can now tap into dynamic language support within [PyO3](https://github.com/ast-grep/ast-grep/blob/main/crates/pyo3/tests/test_register_lang.py) and [napi](https://github.com/ast-grep/ast-grep/blob/main/crates/napi/__test__/custom.spec.ts). This unlocks exciting possibilities for extending ast-grep's reach and integrating it into even more diverse and dynamic environments. + +**Embedded Language in HTML:** We've refined support for registering [embedded languages](/advanced/language-injection.html) in the CLI, giving you even more flexibility when dealing with complex code structures like searching JavaScript/CSS in HTML. + +## More Powerful Rules & Patterns + +We've been laser-focused on making the ast-grep's rule system an even more powerful and precise tool for code manipulation: + +**CSS inspired `nthChild` Matcher:** [nthChild](/guide/rule-config/atomic-rule.html#nthchild) is a rule to find nodes based on their positions in the parent node's children list. It is heavily inspired by CSS's nth-child pseudo-class and helps you target specific nodes in a more granular way. + +**Pinpoint Precision with `range` Matchers:** Need to refine your rules to target a very specific section of code, even down to the character? ast-grep now supports [range](/guide/rule-config/atomic-rule.html#range) matchers! You can define rules that activate only within a particular line *and* character column range. This is useful for interacting with external tools like compilers. + +**Pattern with `--selector` and `--strictness` in `sg run`:** Need to fine tune your search pattern? The `--selector` and `--strictness` flag in [`sg run`](/reference/cli/run.html#run-specific-options) gives you fine-grained control over pattern matching. + +**Simplified Suppression with `ast-grep-ignore`:** [Suppressing rules](/guide/project/severity.html) just got simpler! You can now use the `ast-grep-ignore` comment directly on the same line as the code you want to exclude. Less clutter, more control. + +**More Robust Partial Pattern Snippet** The `ERROR` node in patterns can now match *anything*. This makes partial pattern snippet even more robust. + +## Sharpening the Code Search CLI + +**Glob Path Matching & Symbolic Link Traversal Unleashed:** CLI users can now leverage the power of [glob patterns](/reference/cli/run.html#globs-globs) to specify file paths and effortlessly traverse [symbolic links](/reference/cli/run.html#follow). Navigating and analyzing your projects is now more intuitive than ever. + +**Rule Entity Inspection & Overwrite: Deeper Insights, More Control:** Gain insights by `--inspect` CLI flag with [semi-structured tracing output](/reference/cli/scan.html#inspect-granularity). This feature empowers advanced users with deeper debugging and customization capabilities. + +**Contextual Code Scanning with Before/After Flags:** Enhance your CLI scan results with surrounding code context using the new `context`, `before`, and `after` flags. Understand the bigger picture around your matches at a glance. + +**Know Your Impact: Fixed Rules Count:** The CLI now prints a count of fixed rules, giving you immediate feedback on the scope of your code modifications. + +**Debugging Supercharged:** We've significantly improved debugging with prettified pattern output, Debug AST/CST visualization, and colorized output via the [`--debug-query`](/reference/cli/run.html#debug-query-format) flag. Troubleshooting and refining your rules is now a much smoother and more visual experience. + + +## Enhanced Tooling and API Experience + +We're committed to providing a seamless developer experience across all of ast-grep's interfaces: + +**Typed `SgNode` and `SgRoot` in NAPI:** For our NAPI users, we've introduced [typed `SgNode` and `SgRoot`](/blog/typed-napi.html), significantly improving type safety and code clarity when working with the API. This enhancement is [initiated](https://github.com/ast-grep/ast-grep/pull/1661) by [mohebifar](https://github.com/mohebifar) from [Codemod](https://codemod.com/). + +**Rule Config in `SgNode` Match Methods:** Flexibility at your fingertips! Rule configurations can now be [passed directly](https://github.com/ast-grep/ast-grep/pull/1730) to `SgNode` match methods like `matches`, `has`, `inside`, `follows`, and `precedes`. Configure your rules dynamically within your code. This feature is also contributed by [mohebifar](https://codemod.com/). + +**New `fieldChildren`:** The new `fieldChildren` method in NAPI and PyO3 provides easier access to named children nodes, simplifying AST traversal and manipulation in your API integrations. + +**Powerful Code Modification in PyO3/NAPI:** Unlock advanced code modification features with Fix Related Features and Modify Edit Range in [PyO3](/guide/api-usage/py-api.html#fix-code)/[NAPI](/guide/api-usage/js-api.html#fix-code). Refactoring and code transformation just got even more powerful from within your Python and JavaScript code. + +**Smaller, Faster NAPI Binaries:** We've reduced the NAPI binary size, resulting in smaller downloads and faster installations – get up and running with ast-grep even quicker! + +**Robust Python Integration:** Typings for PyO3 and strictness improvements in PyO3/YAML enhance the overall robustness and reliability of our Python integration. + +## Website: Documentation & Interactive Exploration + +The ast-grep website isn't just a static page; it's your interactive command center for learning, exploring, and mastering ast-grep! We've poured significant effort into expanding and refining the website to be your ultimate resource: + +**Documentation Deep Dive:** We've massively expanded and clarified the documentation, with deeper dives into crucial topics, clearer explanations of pattern objects, a comprehensive FAQ, and enhanced API documentation. Whether you're a beginner or an expert, you'll find valuable resources to level up your ast-grep skills. + +**Revamped Blog Section:** Dive into in-depth articles and latest news in the [brand-new blog section](/blog.html). Stay up-to-date with the latest ast-grep insights and learn from real-world examples. + +**Improved Sections & Navigation:** Finding what you need is now easier than ever with a reorganized and polished section and improved overall website navigation. + +**Website Stability & Polish:** We've squashed styling issues, resolved mobile responsiveness problems, fixed typing errors, and eliminated broken links to ensure a smooth and reliable browsing experience across all devices. + +### Interactive Example Catalog: Learn by Doing! + +The [example catalog](https://ast-grep.github.io/catalog) has received a major upgrade, transforming it into an interactive learning environment: + +**Interactive Rule Exploration:** Dive deep into rules with interactive features like Rule Display & Extraction, MetaVar Panel, Matched Labeling, Pattern Debugger, Selector Explorer, and Pattern Configuration & Icons. Dissect rules, understand their components, and visualize how they work – all in your browser! + +**Effortless Rule Discovery:** Finding the right rule is now a breeze with new filters for language and sorting options. + +**Enhanced Usability:** Small but mighty additions like Empty Filter and Rule Counting further enhance the catalog's ease of use. + +See [the youtube video](https://www.youtube.com/watch?v=oNbOoBhVL8o) for a live demo. + +### Playground Power-Ups: Your Online Rule Lab + +The online playground at [https://ast-grep.github.io/](https://ast-grep.github.io/) is now an even more powerful lab for experimenting and refining your rules: + +**Parser Version Visibility:** Small popups now display the tree-sitter version used in the playground, giving you valuable context for your rule testing. + +**Reset & Counter Enhancements:** Thanks to [@zhangmo8](https://github.com/zhangmo8), we've added a Reset Button and a match counter to further streamline your playground workflow. + +**CSS Support in Playground:** The online playground now speaks CSS! Test your ast-grep rules directly on CSS code snippets. + +## Performance Unleashed + +We're obsessed with speed and efficiency! Here are the performance enhancements we've delivered in the CLI: + +**Leaner Binaries, Faster Performance:** Optimized printer implementation has resulted in significant binary size reduction and improved overall performance. + +**Intelligent File Scanning:** ast-grep now only scans rule-sensitive files, dramatically improving performance for large projects. Less scanning, faster results! + +**`cargo binstall` for Instant Installs:** Faster installation is now a reality with `cargo binstall` support. Get pre-built binaries and get analyzing your code in record time. + +**Configurable Threads: Fine-Tune for Your Machine:** Fine-tune performance by configuring the number of threads ast-grep uses. Optimize ast-grep for your specific hardware and project needs. + +... and of course, numerous bug fixes under the hood to ensure a smoother, more reliable experience! + +## šŸŽ‰ Thank You - From the Bottom of Our Hearts! šŸŽ‰ + +Reaching over 8000 stars is an absolutely fantastic milestone, and it's all thanks to *you*, our incredible community! We are deeply grateful for your unwavering support, invaluable feedback, detailed bug reports, inspiring feature requests, and generous code contributions. You are the fuel that powers ast-grep's rocket! + +**Get Started & Get Involved Today!** + +* **Explore the Enhanced Website:** Dive into the wealth of resources at [https://ast-grep.github.io/](https://ast-grep.github.io/) +* **Star us on GitHub!** Show your support and help us reach the next milestone: [Star the GitHub Repo](https://github.com/ast-grep/ast-grep) +* **Try out the New Features & Give Feedback:** Join the conversation on [Discord](https://discord.com/invite/4YZjf6htSQ) and tell us what you think! +* **Contribute Rules to the Example Catalog:** Share your expertise and help others by [contributing rules](https://github.com/ast-grep/ast-grep.github.io/tree/main/website/catalog). +* **Report Bugs & Feature Requests:** Help us make ast-grep even better by reporting issues and suggesting new features on [GitHub Issues](https://github.com/ast-grep/ast-grep/issues) + +We're incredibly excited to continue this journey with you! Let's keep pushing the boundaries of code searching, linting, and rewriting, making it more powerful and accessible for everyone! šŸš€ ✨ \ No newline at end of file diff --git a/website/blog/typed-napi.md b/website/blog/typed-napi.md new file mode 100644 index 00000000..6ee8e6f5 --- /dev/null +++ b/website/blog/typed-napi.md @@ -0,0 +1,444 @@ +--- +author: + - name: Herrington Darkholme +search: false +date: 2024-12-22 +head: + - - meta + - property: og:type + content: website + - - meta + - property: og:title + content: ast-grep's Journey to Type Safety in Node API + - - meta + - property: og:url + content: https://ast-grep.github.io/blog/typed-napi.html + - - meta + - property: og:description + content: ast-grep/napi now supports typed AST manipulation which is correct, concise, robust, and performant. + - - meta + - property: og:image + content: https://ast-grep.github.io/image/blog/napi.jpg +--- + +# ast-grep's Journey to Type Safety in Node API + +> Recipe to Craft Balanced Types: _Design, Define, Refine, and Confine_ + +We're thrilled to introduce typed AST in [@ast-grep/napi](https://www.npmjs.com/package/@ast-grep/napi), addressing a [long-requested feature](https://github.com/ast-grep/ast-grep/issues/48) for AST manipulation from the early days of this project. + +In this blog post, we will delve into the challenges addressed by this feature and explore [the design](https://github.com/ast-grep/ast-grep/issues/1669) that shaped its implementation. _We also believe this post can serve as a general guide to crafting balanced TypeScript types._ + +![napi screenshot](/image/blog/napi.jpeg) + +## Type Safety in AST + +Working with Abstract Syntax Trees (ASTs) is complex. Even with AST [excellent](https://astexplorer.net/) [AST](https://ast-grep.github.io/playground.html) [tools](https://github.com/sxzz/ast-kit), handling all edge cases remains challenging. + +Type information serves as a crucial safety net when writing AST manipulation code. It guides developers toward handling all possible cases and enables exhaustive checking to ensure complete coverage. + +While `ast-grep/napi` has been a handy tool for programmatic AST processing, it previously lacked type information to help users write robust code. Thanks to [Mohebifar](https://github.com/mohebifar) from [Codemod](https://codemod.com/), we've now bridged this gap. Our solution generates types from parsers' metadata and employs TypeScript tricks to create an idiomatic API. + +## Qualities of Good Types + +Before diving into our implementation, let's explore what makes TypeScript definitions truly effective. In today's JavaScript ecosystem, creating a great library involves more than just intuitive APIs and thorough documentation – it requires thoughtful type definitions that enhance developer experience. + +A well-designed type system should balance four key qualities: + +* **Correct**: Types should act as reliable guardrails, rejecting invalid code while allowing all valid use cases. +* **Concise**: Types should be easy to understand, whether in IDE hovers or code completions. Clear, readable types help developers quickly grasp your API. +* **Robust**: In case type inference fails, the compiler should either graciously tolerate untyped code, or gracefully provide clear error messages. Cryptic type errors that span multiple screens is daunting and unhelpful. +* **Performant**: Both type checking and runtime code should be fast. Complex types can significantly slow down compilation while unnecessary API calls just conforming to type safety can hurt runtime performance. + +Balancing these qualities is demanding job because they often compete with each other, just like creating a type system that is both [sound and complete](https://logan.tw/posts/2014/11/12/soundness-and-completeness-of-the-type-system/#:~:text=A%20type%2Dsystem%20is%20sound,any%20false%20positive%20%5B2%5D.). Many TS libraries lean heavily toward strict correctness – for instance, implementing elaborate types to validate routing parameters. While powerful, [type gymnastics](https://www.octomind.dev/blog/navigating-the-typescript-gymnastics-on-developer-dogma-2) can come with significant trade-offs in complexity and compile-time performance. Sometimes, being slightly less strict can lead to a dramatically better developer experience. + +We will explore how ast-grep balances these qualities through _Design, Define, Refine, and Confine_. + +## Design Types + +Let's return to ast-grep's challenge and learn some background knowledge on how Tree-sitter, our underlying parser library, handles types. + +### TreeSitter's Core API + +At its heart, Tree-sitter provides a language-agnostic API for traversing syntax trees. Its base API is intentionally untyped, offering a consistent interface across all programming languages: + +```typescript +class Node { + kind(): string // Get the type of node, e.g., 'function_declaration' + field(name: string): Node // Get a specific child by its field name + parent(): Node // Navigate to the parent node + children(): Node[] // Get all child nodes + text(): string // Get the actual source code text +} +``` + +This API is elegantly simple, but its generality comes at the cost of type safety. + +In contrast, traditional language-specific parsers bake AST structures directly into their types. Consider [estree](https://github.com/estree/estree/blob/0362bbd130e926fed6293f04da57347a8b1e2325/es5.md). It encodes rich structural information about each node type in JavaScript. For instance, a `function_declaration` is a specific structure with the function's `name`, `parameters` list, and `body` fields. + +Fortunately, Tree-sitter hasn't left us entirely without type information. It provides detailed static type information in JSON format and leaves us an opportunity to enchant the flexible runtime API with the type safe magic. + +### Tree-sitter's `TypeMap` + +Tree-sitter provides [static node types](https://tree-sitter.github.io/tree-sitter/using-parsers#static-node-types) for library authors to consume. The type information has the following form, in TypeScript interface: + +```typescript +interface TypeMap { + [kind: string]: { + type: string + named: boolean + fields?: { + [field: string]: { + types: { type: string, named: boolean }[] + } + } + children?: { name: string, type: string }[] + subtypes?: { type: string, named: boolean }[] + } +} +``` + +`TypeMap` is a comprehensive catalog of all possible node types in a language's syntax tree. Let's break this down with a concrete example from TypeScript: + +```typescript +type TypeScript = { + // AST node type definition + function_declaration: { + type: "function_declaration", // kind + named: true, // is named + fields: { + body: { + types: [ { type: "statement_block", named: true } ] + }, + } + }, + ... +} +``` + +The structure contains the information about the node's kind, whether it is named, and its' fields and children. +`fields` is a map from field name to the type of the field, which encodes the AST structure like traditional parsers. + +Tree-sitter also has a special type called `subtypes`, an alias of a list of other kinds. + +```typescript +type TypeScript = { + // node type alias + declaration: { + type: "declaration", + subtypes: [ + { type: "class_declaration", named: true }, + { type: "function_declaration", named: true }, + ] + }, + ... +} +``` + +In this example, `declaration` is an alias of `function_declaration`, `class_declaration` and other kinds. The alias type is used to reduce the redundancy in the static type JSON and will NOT be a node's actual kind. + +Thanks to Tree-Sitter's design, we can leverage this rich type information to build our typed APIs! + +### Design Principles of ast-grep/napi + +Our new API follows a progressive enhancement approach to type safety: + +**Preserve untyped AST access**. The existing untyped API remains available by default, ensuring backward compatibility + +**Optional type safety on demand**. Users can opt into typed AST nodes either manually or automatically for enhanced type checking and autocompletion + +However, it is a bumpy ride to transition to a new typed API via the path of Tree-sitter's static type. + +First, type information JSON is hosted by Parser Library Repository. ast-grep/napi uses [a dedicated script](https://github.com/ast-grep/ast-grep/blob/main/crates/napi/scripts/generateTypes.ts) to fetch the JSON and generates the type. A [F# like type provider](https://learn.microsoft.com/en-us/dotnet/fsharp/tutorials/type-providers/) is on my TypeScript wishlist. + +Second, the JSON contains a lot of unnamed kinds, which are not useful to users. Including them in the union type is too noisy. We will address this in the next section. + +Finally, as mentioned earlier, the JSON contains alias types. We need to resolve the alias type to its concrete type, which is also covered in the next section. + +## Define Types + +New API's core involves several key new types and extensions to existing types. + +### Let `SgNode` Have Type + +`SgNode` class, the cornerstone of our new API, now accepts two new optional type parameters. + +```typescript +class SgNode = Kinds> { + kind: K + fields: M[K]['fields'] // demo definition, real one is more complex +} +``` + +It represents a node in a language with type map `M` that has a specific kind `K`. E.g. `SgNode` means a function declaration node in TypeScript. When used without a specific kind parameter, `SgNode` defaults to accepting any valid node kind in the language. + +`SgNode` provides a **correct** AST interface in a specific language. While at the same time, it is still **robust** enough to not trigger compiler error when no type information is available. + + +### `ResolveType` + +While Tree-sitter's type aliases help keep the JSON type definitions compact, they present a challenge: these aliases never appear as actual node kinds in ast-grep rules. + +To handle this, we created `ResolveType` to **correctly** map aliases to their concrete kinds: + +```typescript +type ResolveType = + M[T] extends {subtypes: infer S extends {type: string}[] } + ? ResolveType + : T +``` + +This type recursively resolves aliases until it reaches actual node types that developers work with. + +### `Kinds` + +Having access to all possible AST node types is powerful, but it is unwieldy to work with large string literal union types. It can be a huge UX improvement to use a type alias to **concisely** represent all possible kinds of nodes. + +Additionally, Tree-sitter's static type contains a bunch of noisy unnamed kinds. But excluding them from the union type can lead to a incomplete type signature. ast-grep instead bundle them into a plain `string` type, creating a more **robust** API. + +```typescript +type Kinds = ResolveType & LowPriorityString +type LowPriorityString = string & {} +``` + +The above type is a linient string type that is compatible with any string type. But it also uses a [well-known trick](https://stackoverflow.com/a/61048124/2198656) to take advantage of TypeScript's type priority to prefer the `ResolveType` in completion over the `string & {}` type. + + +We alias `string & {}` to `LowPriorityString` to make the code's intent clearer. This approach creates a more intuitive developer experience, though it does run into [some limitations](https://github.com/microsoft/TypeScript/issues/33471) with TypeScript's handling of [open-ended unions](https://github.com/microsoft/TypeScript/issues/26277). + +We need other tricks to address these limitations. Introducing `RefineNode` type. + +### Bridging general nodes and specific nodes via `RefineNode` + +A key challenge in our type system was handling two distinct categories of nodes: + +1. **General Nodes**: String-based typing (like our original API, but with enhanced completion), `SgNode>`. +2. **Specific Nodes**: Precisely typed nodes with known kinds, `SgNode`. + +When dealing with nodes that could be several specific kinds, we faced an interesting type system challenge. Consider these two approaches: + +```typescript +// Approach 1: Union in the type parameter +let single: SgNode<'expression' | 'type'> + +// Approach 2: Union of specific nodes +let union: SgNode<'expression'> | SgNode<'type'> +``` + +These approaches behave differently in TypeScript, for a [good reason](https://x.com/hd_nvim/status/1868706176281854151): + +```typescript +let single: SgNode<'expression' | 'type'> +if (single.kind === 'expression') { + single // Remains SgNode<'expression' | 'type'> - not narrowed! +} + +let union: SgNode<'expression'> | SgNode<'type'> +if (union.kind === 'expression') { + union // Successfully narrowed to SgNode<'expression'> +} +``` + +`SgNode` is technically covariant in its kind parameter, meaning it's safe to distribute the type constructor over unions. However TypeScript doesn't support this automatically. (We will not go down the rabbit hole of type constructor variance here. But interested readers can check out [this wiki](https://en.wikipedia.org/wiki/Covariance_and_contravariance_(computer_science)).) + +To bridge this gap, we introduced the `RefineNode` type: + +```typescript +type RefineNode = string extends K ? SgNode : // one SgNode + K extends keyof M ? SgNode : never // distribute over union +``` + +This utility type provides two key behaviors: +1. When `K` includes a string type, it preserves the general node behavior +2. Otherwise, it refines the node into a union of specific types, using TypeScripts' [distributive conditional types](https://www.typescriptlang.org/docs/handbook/2/conditional-types.html#distributive-conditional-types). + +This approach, inspired by [Biome's Rowan API](https://github.com/biomejs/biome/blob/09a04af727b3cdba33ac35837d112adb55726add/crates/biome_rowan/src/ast/mod.rs#L108-L120), achieves our dual goals: it remains **correct** by preserving proper type relationships and stays **robust** by gracefully handling both typed and untyped usage. + +This hybrid approach gives developers the best of both worlds: strict type checking when types are known, with the flexibility to fall back to string-based typing when needed. + +## Refine Types + +Now let's talk about how to refine the general node to a specific node in ast-grep/napi. +We've implemented two concise and idiomatic approaches in TypeScript: manual and automatic refinement. + +### Refine Node, Manually + +#### Runtime Type Checking + +The first manual approach uses runtime verification through the `is` method: + +```typescript +class SgNode { + is(kind: T): this is SgNode +} +``` + +This enables straightforward type narrowing: + +```typescript +if (sgNode.is("function_declaration")) { + sgNode.kind // narrow to 'function_declaration' +} +``` + +#### Type Parameter Specification + +Another manual approach lets you explicitly specify node types through type parameters. This is particularly useful when you're certain about a node's kind and want to skip runtime checks for better performance. + +This pattern may feel familiar if you've worked with the [DOM API](https://www.typescriptlang.org/docs/handbook/dom-manipulation.html#the-queryselector-and-queryselectorall-methods)'s `querySelector`. Just as `querySelector` can be refined from a general `Element` to a specific `HTMLDivElement`, we can refine our nodes: + +```typescript +sgNode.parent<"program">() // Returns SgNode +``` + + +The type parameter approach uses an interesting overloading signature + +```typescript +interface NodeMethod { + (): SgNode // Untyped version + (): RefineNode // Typed version +} +``` + +If no type is provided, it returns a general node, `SgNode`. If a type is provided, it returns a specific node, `SgNode`. + +This dual-signature typing avoids the limitations of a single generic signature, which would either always return `SgNode` or always produce a union of `SgNode`s. + +#### Choosing the Right Type + +When should you use each manual refinement method? Here are some guidelines: + +āœ“ Use `is()` when: +* You need runtime type check +* Node types might vary +* Type safety is crucial + +āœ“ Use type parameters when: + +* You're completely certain of the node type +* Performance is critical +* The node type is fixed + +:::tip Safety Tip + +Be cautious with type parameters as they bypass runtime checks. It can break type safety if misused. +You can audit their usage with the command: + +```bash +ast-grep -p '$NODE.$METHOD<$K>($$$)' +``` +::: + +### Refine Node, Automatically + +A standout feature of our new API is automatic type refinement based on contextual information. This happens seamlessly through the `field` method. + +When you access a node's field using `field("name")`, the system automatically examines the static type information and refines the node type accordingly: + +```typescript +let exportStmt: SgNode<'export_statement'> +exportStmt.field('declaration') // Automatically refines to union: + // SgNode<'function_declaration'> | + // SgNode<'variable_declaration'> | ... +``` + +The magic here is that you never need to specify the possible types explicitly - the system infers them automatically. This approach is both **concise** in usage and **correct** in type inference. + +### Exhaustive Pattern Matching with kindToRefine + +We've also introduced a new `kindToRefine` property for comprehensive type checking. You might wonder: why add this when we already have a `kind()` method? + +There are two key reasons: +1. Preserving backward compatibility with the existing `kind()` method +2. Enabling TypeScript's type narrowing, which works with properties but not method calls + +While `kindToRefine` is implemented as a getter that calls into Rust code (making it as computationally expensive as the `kind()` method), it enables powerful type checking capabilities. To ensure developers are aware of this **performance** characteristic, we deliberately chose a _distinct and longer_ property name. + +This property really shines when working in tandem union types returned by `RefineNode`, helping you write **correct** AST transformations through exhaustive pattern matching: + +```typescript +const func: SgNode<'function_declaration'> | SgNode<'arrow_function'> + +switch (func.kindToRefine) { + case 'function_declaration': + func.kindToRefine // Narrowed to function_declaration + break + case 'arrow_function': + func.kindToRefine // Narrowed to arrow_function + break + default: + func satisfies never // TypeScript ensures we handled all cases +} +``` + +The combination of automatic type refinement and exhaustive pattern matching makes it easier to write **correct** AST transformations while catching potential errors at compile time. + +## Confine Types + +Always bear in mind this mantra: _Be austere with type level programming._ + +Overdoing type level programming can overload the compiler as well as overwhelm users. +It is a good practice to confine the API type to a reasonable complexity level. + +### Prune unnamed kinds + +Tree-sitter's static type includes many unnamed kinds, which are not user-friendly. + +For instance, operators like `+`/`-`/`*`/`/` are too verbose for an AST library. We're building a compiler plugin, not solving elementary school math problems, right? + +This is why we exclude the unnamed kinds and include `string` in the `Kinds`. + +In the type generation step, ast-grep filters out these unnamed kinds to make the type more **concise**. + +### Opt-in refinement for better compile time performance + +The new API is designed to provide a better type checking and autocompletion experience for users. +However, this improvement comes at the cost of **performance**. A single type map for one language can span several thousand lines of code with hundreds of kinds. The more type information the user provides, the slower the compile time. + +To manage this, you need to explicitly opt into type information by passing type parameters to the `parse` method. + +```typescript +import { parse } from '@ast-grep/napi' +import TS from '@ast-grep/napi/lang/TypeScript' // import this can be slow +const untyped = parse(Lang.TypeScript, code) +const typed = parse(Lang.TypeScript, code) +``` + +### Typed Rule! + +The last notable feature is the typed rule. You can even type the `kind` in rule JSON! + +```typescript +interface Rule { + kind: Kinds + ... // other rules +} +``` + +Of course, this isn't about _confining_ the type but allowing type information to enhance rules, significantly improving UX and rule **correctness**. + +You can look up the available kinds in the static type via the completion popup in your editor. (btw I use nvim) + +```typescript +sgNode.find({ + rule: { + // kind: 'invalid_kind', // error! + kind: 'function_declaration', // typed! + } +}) +``` + +![napi screenshot](/image/blog/rule.jpeg) + +## Ending + +I'm incredibly excited about the future of AST manipulation in TypeScript. You can see the full type definition [here](https://github.com/ast-grep/ast-grep/tree/main/crates/napi/types). + +This feature empowers users to seamlessly switch between untyped and typed AST, offering flexibility and enhanced capabilities, an innovation that has not been seen in other AST libraries, especially not in native language based ones. + +As [Theo](https://x.com/theo) aptly puts it in [his video](https://www.youtube.com/clip/Ugkxn2oomDuyQjtaKXhYP1MU9TLEShf5m1nf): + +> There are very few devs that understand Rust deeply enough and compiler deeply enough that also care about TypeScript in web dev enough to build something for web devs in Rust + +ast-grep is determined to bridge that gap between Rust and TypeScript! \ No newline at end of file diff --git a/website/blog/yaml-vs-dsl.md b/website/blog/yaml-vs-dsl.md new file mode 100644 index 00000000..9a3b3187 --- /dev/null +++ b/website/blog/yaml-vs-dsl.md @@ -0,0 +1,223 @@ +--- +author: + - name: Herrington Darkholme +search: false +date: 2025-07-07 +head: + - - meta + - property: og:type + content: website + - - meta + - property: og:title + content: 'YAML vs DSL: comparison is subjective' + - - meta + - property: og:url + content: https://ast-grep.github.io/blog/yaml-vs-dsl.html + - - meta + - property: og:description + content: YAML and DSL are two different approaches to configure rule in structural search. The question "which is better" is largely subjective. + +--- + +# YAML vs DSL: comparison is subjective + +As stated in the [tool comparison](/advanced/tool-comparison.html#gritql), YAML configuration and DSL (Domain Specific Language) are two different approaches to configure rules in structural search. The question "which is better" is largely subjective. + +However, recently I have received some feedback that YAML is __objectively__ not as good as DSL, and I would like to clarify some points. + + +The original argument is quoted as follows: + +> While I see you're trying to dismiss it as a preference, I see it as a fundamental blocker. ast-grep has effectively built a DSL inside YAML. This becomes pretty apparent from your documentation, where you have to extensively explain how pattern syntax works, how metavariables work, etc.. You have to see that arguments such as "it's just YAML, no new syntax to learn" aren't entirely true either. Now IMO, if you're creating a DSL anyway, you're better off doing it properly than to go halfway. With GritQL we get syntax highlighting for all the aspects of the DSL, which I think is a significant boost. I think GritQL queries are significantly easier to read than ast-grep's mix of DSL of YAML. + +## Direct rebuttal to the argument + +### Abstraction does not necessitate DSL + +> effectively built a DSL inside YAML + +This is a misunderstanding of DSL and abstraction. To model Abstract Syntax Tree (AST) manipulation, you will have to have some form of concepts, such as pattern, metavariable, etc. This is true for both DSL and YAML. You cannot cut more concepts out of the tool even if you have [Occam's razor](https://en.wikipedia.org/wiki/Occam%27s_razor). + +ast-grep does support pattern. It is a concept to match a strcture that contains multiple AST nodes, which makes it easier to write a rule. You can use `kind`/`has`/`all` to simulate pattern matching. But it does not mean that ast-grep should cut the concept of pattern since it looks like a DSL. In fact, ast-grep's pattern is just one of its [atomic rules](/guide/rule-config/atomic-rule.html). It does not meant to be a special embedded DSL. + +### Pattern has its limitations + +> your documentation, where you have to extensively explain how pattern syntax works, how pattern syntax works, how metavariables work + +As stated before, pattern makes ast-grep users' life easier. Explaining how pattern works is necessary to help users understand how to write rules. This is not a sign of a DSL being necessary, but rather a sign of the limitation of pattern: it is not general enough to cover all cases, and you have to communicate to your users how pattern works in your system. For example, see this [tweet](https://x.com/hd_nvim/status/1941876968363798766) about how to write a pattern to match `function` declation in JavaScript. Another brain teaser, how to tell if `$a = $b` is an `assignment_expression` or `field_initializer`? + +(an [ad-hominen](https://en.wikipedia.org/wiki/Ad_hominem) note: it is ironic to see this argument from a tool without proper documentation. I cannot suspend my suspect whether the author has used pattern to write non-trivial rules at all.) + +### Slippery slope fallacy + +> if you're creating a DSL anyway, you're better off doing it properly than to go halfway + +This argument is a [slippery slope fallacy](https://en.wikipedia.org/wiki/Slippery_slope). Using pattern syntax does not mean that other rules should also be written in DSL. Again, pattern is just one of ast-grep's rules. ast-grep compose smaller rules to form more complex rules. ast-grep's rule system is not DSL based, but rather using YAML's key-value pair to represent rules. Using syntax in one rule does not imply that all rules should be combined in DSL. + +Using one concept in your library, framework or tool does not imply that you have to design a whole new syntax for it. The similar comparison will be frontend frameworks. Some frameworks like React and Vue use [hook](https://react.dev/reference/react/hooks) or [signals](https://dev.to/this-is-learning/the-evolution-of-signals-in-javascript-8ob) to represent state changes. But using these building blocks does not grant the verdict to design a new language for your frontend framework. Even the most avant-garde company only introduces [new syntax](https://flow.org/en/docs/react/hook-syntax/) in JavaScript, not inventing a new language. + + +### Subjective opinion is not objective fact + +> I think GritQL queries are significantly easier to read than ast-grep's mix of DSL of YAML. + +This is a subjective opinion, instead of an fundamental blocker. The author failed to capture the difference between _"pattern syntax"_ and _"rule system"_. ast-grep's rule system is YAML based, so it is easier to write a [well-formed](https://en.wikipedia.org/wiki/Well-formedness) rule. Instead, DSL based rule system using their own syntax and can be more difficult to write a valid rule, especially for beginners. + +We can also see using DSL is not subjectively better than YAML as well. + +## Subjective Comparison of DSL + +Let's review the DSL mentioned above. + +### Mix of several different paradigms + +Biome's DSL is a mix of several different paradigms: declarative, logic, and imperative. Let's see one example: + +```JavaScript +`$method($message)` where { + $method <: `console.log`, + if ($message <: r"Hello, .*!") { + $linter = "hello world" + } else { + $linter = "not hello" + }, + register_diagnostic( + span = $method, + message = $linter + ) +} +``` + +* `$method('$message')` is a declarative pattern matching syntax. +* `where` and `<:` are related to [logic programming](https://en.wikipedia.org/wiki/Logic_programming#:~:text=Logic%20programming%20is%20a%20programming,solve%20problems%20in%20the%20domain.) paradigm, say, [Prolog](https://en.wikipedia.org/wiki/Prolog) or SQL. +* `if` is a typical imperative programming paradigm + +The mixture of paradigms does not blend well. At least, in the eye of a programming language veteran, it is too messy for a DSL for linting or structural search. We are not designing a next-era programming language. + +### Easy to miss comma + +Did you notice there is two trailing commas in `$method <: console.log` and if block? + +```JavaScript{2,7} +`$method($message)` where { + $method <: `console.log`, // [!code focus] + if ($message <: r"Hello, .*!") { + $linter = "hello world" + } else { + $linter = "not hello" + }, // [!code focus] + register_diagnostic( + span = $method, + message = $linter + ) +} +``` + +Without them you will get a syntax error. This is a common problem for beginners to miss commas, a typical pitfall only in DSL. Alas, I can still remeber the old day when C compiler complained about missing semicolon. + +### Similar basic patterns have distinct syntax appearance + +There are several different basic patterns in the DSL. Though they are at the similar level of abstraction, their appearance is totally different. + +```JavaScript +`console.log($foo)` // pattern +augmented_assignment_expression(operator = $op, left = $x, right = $v) // syntax node +r"Hello, (.*)"($name) // regex +``` + +These patterns are corresponding to `pattern`, `kind` and `regex` in ast-grep. However, they look totally different. You need more learning to pick up these distinct syntax. + +### Similar syntax appearance have different meaning + +One common pitfall to design DSL is that similar syntax have different meaning. + +```JavaScript +// this is a syntax node call +augmented_assignment_expression(operator = $op) +pattern console_method_to_info($method) { + `console.$method($message)` => `console.info($message)` +} +// this is a pattern call +console_method_to_info(method = `log`) +predicate program_contains_logger() { + $program <: contains `logger` +} +// this is a predicate call +program_contains_logger() + +// define a lines function +function lines($string) { + return split($string, separator=`\n`) +} +// this is a function call +lines(string = $message) +``` + +They all look like function calls, but they are not. See explanation below for the differences. + +### Confusing Concepts of `pattern`, `predicate` and `function` + +These three concepts are very similar, but they have slightly different usage in the DSL. + +* `pattern` is used in `<:` or somewhere else, I dunno, the doc does not explain it well. +* `predicate` is used in `where` condition. +* `function` is used in `assignment`, `insertion` or `rewrite`. + +Example: + +```JavaScript +`console.log` => `logger.info` where { + $program <: contains_logger(), // pattern + program_contains_logger(), // equivalent predicate + $program => replace_logger(), // function +} +``` + +### Confusing Concepts of `condition`, `clause` and `modifier` + +The DSL also has three similar concepts: `condition`, `clause` and `modifier`. +Introduced in different places, [here](https://docs.grit.io/language/conditions) and [here](https://docs.grit.io/language/modifiers), without clear definition. + +### Similar patterns but applied in different places + +Tell the difference between [and](https://docs.grit.io/language/modifiers#and-clause), [any](https://docs.grit.io/language/modifiers#any-clause), [some](https://docs.grit.io/language/modifiers#some-clause) and [every](https://docs.grit.io/language/modifiers#every-clause). +Confusing? You should learn the difference between meta var in [list pattern](https://docs.grit.io/language/modifiers#list-patterns) and plain meta var. Also, don't confuse list meta var with [spread meta var](https://docs.grit.io/language/patterns#metavariables) + +### One more thing, variable scope. + +If you still have patience, you need one last thing to learn: variable scope. + +I have no better explanation for it since I don't understand it well, so I will quote the [official documentation](https://docs.grit.io/language/bubble): + +> Once a metavariable is bound to a value, it retains this value throughout the target code. Therefore, the scope of the metavariable spans the entire target file. + +To fully understand it, you also need to know `bubble`, `bubble($argument)` and pattern auto wrap. + +## What can go even more wrong? + +Integrating a custom linting rule from another parser ecosystem to your own has several more decisions to make. Making bad decisions can lead to even more confusion. + +* Your pattern syntax includes non standard syntax, like `$...`. You need to [change your parser](https://github.com/biomejs/biome/blob/31e439674493da76e0ce213e5660be3d903efbef/crates/biome_js_parser/src/syntax/jsx/mod.rs#L321) to support it. +* You need to make a decision if you want to support existing pattern libraries. But these patterns are built upon [tree-sitter](https://tree-sitter.github.io/tree-sitter/). So you have to [map tree-sitter AST to your own](https://github.com/biomejs/biome/blob/7bf9a608e1592fd595f658f5f800e12d51835d34/crates/biome_grit_patterns/src/grit_target_language/js_target_language.rs#L42-L55) +* Even worse, if you chose to support existing pattern libraries, you also need to work on a general algorithm to handle incompatibility between different ASTs. For example, different AST structures, different node names, different node properties, etc. +* If you only supports part of it, how can you teach your users what is supported and what is not, without [reading the source](https://github.com/biomejs/biome/blob/7bf9a608e1592fd595f658f5f800e12d51835d34/crates/biome_grit_patterns/src/grit_target_language/js_target_language.rs#L48-L50)? +* You also need to update your playground or editor plugins to support mapping between tree-sitter AST and your own AST. And teach users to use it. + +## Conclusion + +If you also feel confused, you are not alone. Again, the preference of DSL over YAMl is largely subjective. + +If you think DSL is better, you are right. [You are absolutely right](https://www.reddit.com/r/ClaudeAI/comments/152b51r/you_are_absolutely_right/). In fact, you are [not even wrong](https://en.wikipedia.org/wiki/Not_even_wrong). Since this is a subjective opinion, not an objective fact. + +If you are a library or framework author, you can make decision based on your own preference. However, mistakenly thinking your preference is objective will lead to confusion and misunderstanding. It may even reflect inferior tech taste and judgement. + +Consider these points when you want to have objective comparison: + +* Documentation? +* User Education? Howe you teach users to write your DSL? +* Tooling support like [playground](/playground.html). +* Editor support beyong syntax highlighting. Say LSP. +* Integration with API, how you bring type-safe DSL into your general purpose programming language, like [graphql](https://github.com/Quramy/ts-graphql-plugin) and [styled component](https://github.com/styled-components/typescript-styled-plugin). +* Broader ecosystem support, such as GitHub language detection, AI support, etc. + +If you are going to use native tooling in your JavaScript/TypeScript project, I recommend you to use [oxlint](https://oxc.rs/) and, if you need simple custom rules, [ast-grep](https://ast-grep.github.io/). diff --git a/website/catalog/c/index.md b/website/catalog/c/index.md index 87c6d7ea..3aef13f3 100644 --- a/website/catalog/c/index.md +++ b/website/catalog/c/index.md @@ -2,5 +2,10 @@ This page curates a list of example ast-grep rules to check and to rewrite C code. +:::tip C files can be parsed as Cpp +You can parse C code as Cpp to avoid rewriting similar rules. The [`languageGlobs`](/reference/sgconfig.html#languageglobs) option can force ast-grep to parse `.c` files as Cpp. +::: + + \ No newline at end of file diff --git a/website/catalog/c/match-function-call.md b/website/catalog/c/match-function-call.md new file mode 100644 index 00000000..87519d90 --- /dev/null +++ b/website/catalog/c/match-function-call.md @@ -0,0 +1,47 @@ +## Match Function Call in C + +* [Playground Link](/playground.html#eyJtb2RlIjoiQ29uZmlnIiwibGFuZyI6ImMiLCJxdWVyeSI6InRlc3QoJCQkKSIsInJld3JpdGUiOiIiLCJjb25maWciOiJydWxlOlxuICBwYXR0ZXJuOiBcbiAgICBjb250ZXh0OiAkTSgkJCQpO1xuICAgIHNlbGVjdG9yOiBjYWxsX2V4cHJlc3Npb24iLCJzb3VyY2UiOiIjZGVmaW5lIHRlc3QoeCkgKDIqeClcbmludCBhID0gdGVzdCgyKTtcbmludCBtYWluKCl7XG4gICAgaW50IGIgPSB0ZXN0KDIpO1xufSJ9) + +### Description + +One of the common questions of ast-grep is to match function calls in C. + +A plain pattern like `test($A)` will not work. This is because [tree-sitter-c](https://github.com/tree-sitter/tree-sitter-c) +parse the code snippet into `macro_type_specifier`, see the [pattern output](https://ast-grep.github.io/playground.html#eyJtb2RlIjoiUGF0Y2giLCJsYW5nIjoiYyIsInF1ZXJ5IjoidGVzdCgkJCQpIiwicmV3cml0ZSI6IiIsImNvbmZpZyI6InJ1bGU6XG4gIHBhdHRlcm46IFxuICAgIGNvbnRleHQ6ICRNKCQkJCk7XG4gICAgc2VsZWN0b3I6IGNhbGxfZXhwcmVzc2lvbiIsInNvdXJjZSI6IiNkZWZpbmUgdGVzdCh4KSAoMip4KVxuaW50IGEgPSB0ZXN0KDIpO1xuaW50IG1haW4oKXtcbiAgICBpbnQgYiA9IHRlc3QoMik7XG59In0=). + +To avoid this ambiguity, ast-grep lets us write a [contextual pattern](/guide/rule-config/atomic-rule.html#pattern), which is a pattern inside a larger code snippet. +We can use `context` to write a pattern like this: `test($A);`. Then, we can use the selector `call_expression` to match only function calls. + +### YAML + +```yaml +id: match-function-call +language: c +rule: + pattern: + context: $M($$$); + selector: call_expression +``` + +### Example + + +```c{2,4} +#define test(x) (2*x) +int a = test(2); +int main(){ + int b = test(2); +} +``` + +### Caveat + +Note, tree-sitter-c parses code differently when it receives code fragment. For example, + +* `test(a)` is parsed as `macro_type_specifier` +* `test(a);` is parsed as `expression_statement -> call_expression` +* `int b = test(a)` is parsed as `declaration -> init_declarator -> call_expression` + +The behavior is controlled by how the tree-sitter parser is written. And tree-sitter-c behaves differently from [tree-sitter-cpp](https://github.com/tree-sitter/tree-sitter-cpp). + +Please file issues on tree-sitter-c repo if you want to change the behavior. ast-grep will respect changes and decision from upstream authors. diff --git a/website/catalog/cpp/find-struct-inheritance.md b/website/catalog/cpp/find-struct-inheritance.md new file mode 100644 index 00000000..c6deabfd --- /dev/null +++ b/website/catalog/cpp/find-struct-inheritance.md @@ -0,0 +1,49 @@ +## Find Struct Inheritance + +* [Playground Link](/playground.html#eyJtb2RlIjoiUGF0Y2giLCJsYW5nIjoiY3BwIiwicXVlcnkiOiJzdHJ1Y3QgJFNPTUVUSElORzogICRJTkhFUklUU19GUk9NIHsgJCQkQk9EWTsgfSIsInJld3JpdGUiOiIiLCJzdHJpY3RuZXNzIjoic21hcnQiLCJzZWxlY3RvciI6IiIsImNvbmZpZyI6IiIsInNvdXJjZSI6InN0cnVjdCBGb286IEJhciB7fTtcblxuc3RydWN0IEJhcjogQmF6IHtcbiAgaW50IGEsIGI7XG59In0=) + +### Description + +ast-grep's pattern is AST based. A code snippet like `struct $SOMETHING: $INHERITS` will not work because it does not have a correct AST structure. The correct pattern should spell out the full syntax like `struct $SOMETHING: $INHERITS { $$$BODY; }`. + +Compare the ast structure below to see the difference, especially the `ERROR` node. You can also use the playground's pattern panel to debug. + +:::code-group +```shell [Wrong Pattern] +ERROR + $SOMETHING + base_class_clause + $INHERITS +``` + +```shell [Correct Pattern] +struct_specifier + $SOMETHING + base_class_clause + $INHERITS + field_declaration_list + field_declaration + $$$BODY +``` +::: + +If it is not possible to write a full pattern, [YAML rule](/guide/rule-config.html) is a better choice. + + +### Pattern +```shell +ast-grep --lang cpp --pattern ' +struct $SOMETHING: $INHERITS { $$$BODY; }' +``` + +### Example + + +```cpp {1-3} +struct Bar: Baz { + int a, b; +} +``` + +### Contributed by +Inspired by this [tweet](https://x.com/techno_bog/status/1885421768384331871) diff --git a/website/catalog/cpp/fix-format-vuln.md b/website/catalog/cpp/fix-format-vuln.md new file mode 100644 index 00000000..d06e9e98 --- /dev/null +++ b/website/catalog/cpp/fix-format-vuln.md @@ -0,0 +1,61 @@ + +## Fix Format String Vulnerability + +* [Playground Link](/playground.html#eyJtb2RlIjoiQ29uZmlnIiwibGFuZyI6ImNwcCIsInF1ZXJ5IjoiIiwicmV3cml0ZSI6IiIsInN0cmljdG5lc3MiOiJzbWFydCIsInNlbGVjdG9yIjoiIiwiY29uZmlnIjoiaWQ6IGZpeC1mb3JtYXQtc2VjdXJpdHktZXJyb3Jcbmxhbmd1YWdlOiBDcHBcbnJ1bGU6XG4gIHBhdHRlcm46ICRQUklOVEYoJFMsICRWQVIpXG5jb25zdHJhaW50czpcbiAgUFJJTlRGOiAjIGEgZm9ybWF0IHN0cmluZyBmdW5jdGlvblxuICAgIHsgcmVnZXg6IFwiXnNwcmludGZ8ZnByaW50ZiRcIiB9XG4gIFZBUjogIyBub3QgYSBsaXRlcmFsIHN0cmluZ1xuICAgIG5vdDpcbiAgICAgIGFueTpcbiAgICAgIC0geyBraW5kOiBzdHJpbmdfbGl0ZXJhbCB9XG4gICAgICAtIHsga2luZDogY29uY2F0ZW5hdGVkX3N0cmluZyB9XG5maXg6ICRQUklOVEYoJFMsIFwiJXNcIiwgJFZBUilcbiIsInNvdXJjZSI6Ii8vIEVycm9yXG5mcHJpbnRmKHN0ZGVyciwgb3V0KTtcbnNwcmludGYoJmJ1ZmZlclsyXSwgb2JqLT5UZXh0KTtcbnNwcmludGYoYnVmMSwgVGV4dF9TdHJpbmcoVFhUX1dBSVRJTkdfRk9SX0NPTk5FQ1RJT05TKSk7XG4vLyBPS1xuZnByaW50ZihzdGRlcnIsIFwiJXNcIiwgb3V0KTtcbnNwcmludGYoJmJ1ZmZlclsyXSwgXCIlc1wiLCBvYmotPlRleHQpO1xuc3ByaW50ZihidWYxLCBcIiVzXCIsIFRleHRfU3RyaW5nKFRYVF9XQUlUSU5HX0ZPUl9DT05ORUNUSU9OUykpOyJ9) + +### Description + +The [Format String exploit](https://owasp.org/www-community/attacks/Format_string_attack) occurs when the submitted data of an input string is evaluated as a command by the application. + +For example, using `sprintf(s, var)` can lead to format string vulnerabilities if `var` contains user-controlled data. This can be exploited to execute arbitrary code. By explicitly specifying the format string as `"%s"`, you ensure that `var` is treated as a string, mitigating this risk. + + +### YAML +```yaml +id: fix-format-security-error +language: Cpp +rule: + pattern: $PRINTF($S, $VAR) +constraints: + PRINTF: # a format string function + { regex: "^sprintf|fprintf$" } + VAR: # not a literal string + not: + any: + - { kind: string_literal } + - { kind: concatenated_string } +fix: $PRINTF($S, "%s", $VAR) +``` + +### Example + + +```cpp {2-4} +// Error +fprintf(stderr, out); +sprintf(&buffer[2], obj->Text); +sprintf(buf1, Text_String(TXT_WAITING_FOR_CONNECTIONS)); +// OK +fprintf(stderr, "%s", out); +sprintf(&buffer[2], "%s", obj->Text); +sprintf(buf1, "%s", Text_String(TXT_WAITING_FOR_CONNECTIONS)); +``` + +### Diff + +```js +// Error +fprintf(stderr, out); // [!code --] +fprintf(stderr, "%s", out); // [!code ++] +sprintf(&buffer[2], obj->Text); // [!code --] +sprintf(&buffer[2], "%s", obj->Text); // [!code ++] +sprintf(buf1, Text_String(TXT_WAITING_FOR_CONNECTIONS)); // [!code --] +sprintf(buf1, "%s", Text_String(TXT_WAITING_FOR_CONNECTIONS)); // [!code ++] +// OK +fprintf(stderr, "%s", out); +sprintf(&buffer[2], "%s", obj->Text); +sprintf(buf1, "%s", Text_String(TXT_WAITING_FOR_CONNECTIONS)); +``` + +### Contributed by +[xiaoxiangmoe](https://github.com/xiaoxiangmoe) \ No newline at end of file diff --git a/website/catalog/cpp/index.md b/website/catalog/cpp/index.md new file mode 100644 index 00000000..74094dcc --- /dev/null +++ b/website/catalog/cpp/index.md @@ -0,0 +1,10 @@ +# Cpp + +This page curates a list of example ast-grep rules to check and to rewrite Cpp code. + +:::tip Reuse Cpp rules with C +Cpp is a superset of C, so you can reuse Cpp rules with C code. The [`languageGlobs`](/reference/sgconfig.html#languageglobs) option can force ast-grep to parse `.c` files as Cpp. +::: + + + diff --git a/website/catalog/go/match-function-call.md b/website/catalog/go/match-function-call.md index 5284e7fa..bf42db80 100644 --- a/website/catalog/go/match-function-call.md +++ b/website/catalog/go/match-function-call.md @@ -6,11 +6,13 @@ One of the common questions of ast-grep is to match function calls in Golang. -A plain pattern like `fmt.Println($A)` will not work. This is because Golang syntax also allows type conversions, e.g. `int(3.14)`, that look like function calls. +A plain pattern like `fmt.Println($A)` will not work. This is because Golang syntax also allows type conversions, e.g. `int(3.14)`, that look like function calls. Tree-sitter, ast-grep's parser, will prefer parsing `func_call(arg)` as a type conversion instead of a call expression. To avoid this ambiguity, ast-grep lets us write a [contextual pattern](/guide/rule-config/atomic-rule.html#pattern), which is a pattern inside a larger code snippet. We can use `context` to write a pattern like this: `func t() { fmt.Println($A) }`. Then, we can use the selector `call_expression` to match only function calls. +Please also read the [deep dive](/advanced/pattern-parse.html) on [ambiguous pattern](/advanced/pattern-parse.html#ambiguous-pattern-code). + ### YAML ```yaml diff --git a/website/catalog/go/match-package-import.md b/website/catalog/go/match-package-import.md new file mode 100644 index 00000000..7880709d --- /dev/null +++ b/website/catalog/go/match-package-import.md @@ -0,0 +1,54 @@ +## Match package import in Golang + +* [Playground Link](/playground.html#eyJtb2RlIjoiQ29uZmlnIiwibGFuZyI6ImdvIiwicXVlcnkiOiIiLCJyZXdyaXRlIjoiIiwic3RyaWN0bmVzcyI6InNtYXJ0Iiwic2VsZWN0b3IiOiIiLCJjb25maWciOiJpZDogbWF0Y2gtcGFja2FnZS1pbXBvcnRcbmxhbmd1YWdlOiBnb1xucnVsZTpcbiAga2luZDogaW1wb3J0X3NwZWNcbiAgaGFzOlxuICAgIHJlZ2V4OiBnaXRodWIuY29tL2dvbGFuZy1qd3Qvand0Iiwic291cmNlIjoicGFja2FnZSBtYWluXG5cbmltcG9ydCAoXG5cdFwiZm10XCJcblx0XCJnaXRodWIuY29tL2dvbGFuZy1qd3Qvand0XCIgIC8vIFRoaXMgbWF0Y2hlcyB0aGUgQVNUIHJ1bGVcbilcblxuZnVuYyBtYWluKCkge1xuXHQvLyBDcmVhdGUgYSBuZXcgdG9rZW5cblx0dG9rZW4gOj0gand0Lk5ldyhqd3QuU2lnbmluZ01ldGhvZEhTMjU2KVxuXHRcblx0Ly8gQWRkIHNvbWUgY2xhaW1zXG5cdHRva2VuLkNsYWltcyA9IGp3dC5NYXBDbGFpbXN7XG5cdFx0XCJ1c2VyXCI6IFwiYWxpY2VcIixcblx0XHRcInJvbGVcIjogXCJhZG1pblwiLFxuXHR9XG5cdFxuXHQvLyBTaWduIHRoZSB0b2tlblxuXHR0b2tlblN0cmluZywgZXJyIDo9IHRva2VuLlNpZ25lZFN0cmluZyhbXWJ5dGUoXCJteS1zZWNyZXRcIikpXG5cdGlmIGVyciAhPSBuaWwge1xuXHRcdGZtdC5QcmludGYoXCJFcnJvciBzaWduaW5nIHRva2VuOiAldlxcblwiLCBlcnIpXG5cdFx0cmV0dXJuXG5cdH1cblx0XG5cdGZtdC5QcmludGYoXCJHZW5lcmF0ZWQgdG9rZW46ICVzXFxuXCIsIHRva2VuU3RyaW5nKVxufSJ9) + +### Description + +A generic rule template for detecting imports of specific packages in Go source code. This rule can be customized to match any package by modifying the regex pattern, making it useful for security auditing, dependency management, and compliance checking. + +This rule identifies Go import statements based on the configured regex pattern, including: + +Direct imports: `import "package/name"` +Versioned imports: `import "package/name/v4"` +Subpackage imports: `import "package/name/subpkg"` +Grouped imports within `import () blocks` + +### YAML + +```yaml +id: match-package-import +language: go +rule: + kind: import_spec + has: + regex: PACKAGE_PATTERN_HERE +``` + +### Example + +JWT Library Detection + +```go{5} +package main + +import ( + "fmt" + "github.com/golang-jwt/jwt" // This matches the AST rule +) + +func main() { + token := jwt.New(jwt.SigningMethodHS256) // Create a new token + // Add some claims + token.Claims = jwt.MapClaims{"user": "alice", "role": "admin"} + tokenString, err := token.SignedString([]byte("my-secret")) // Sign the token + if err != nil { + fmt.Printf("Error signing token: %v\n", err) + return + } + fmt.Printf("Generated token: %s\n", tokenString) +} +``` + +### Contributed by + +[Sudesh Gutta](https://github.com/sudeshgutta) diff --git a/website/catalog/html/extract-i18n-key.md b/website/catalog/html/extract-i18n-key.md new file mode 100644 index 00000000..63a7e2fe --- /dev/null +++ b/website/catalog/html/extract-i18n-key.md @@ -0,0 +1,44 @@ +## Extract i18n Keys + +* [Playground Link](/playground.html#eyJtb2RlIjoiQ29uZmlnIiwibGFuZyI6Imh0bWwiLCJxdWVyeSI6IiIsInJld3JpdGUiOiIiLCJzdHJpY3RuZXNzIjoicmVsYXhlZCIsInNlbGVjdG9yIjoiIiwiY29uZmlnIjoicnVsZTpcbiAga2luZDogdGV4dFxuICBwYXR0ZXJuOiAkVFxuICBub3Q6XG4gICAgcmVnZXg6ICdcXHtcXHsuKlxcfVxcfSdcbmZpeDogXCJ7eyAkKCckVCcpIH19XCIiLCJzb3VyY2UiOiI8dGVtcGxhdGU+XG4gIDxzcGFuPkhlbGxvPC9zcGFuPlxuICA8c3Bhbj57eyB0ZXh0IH19PC9zcGFuPlxuPC90ZW1wbGF0ZT4ifQ==) + +### Description + +It is tedious to manually find and replace all the text in the template with i18n keys. This rule helps to extract static text into i18n keys. Dynamic text, e.g. mustache syntax, will be skipped. + +In practice, you may want to map the extracted text to a key in a dictionary file. While this rule only demonstrates the extraction part, further mapping process can be done via a script reading the output of ast-grep's [`--json`](/guide/tools/json.html) mode, or using [`@ast-grep/napi`](/guide/api-usage/js-api.html). + +### YAML +```yaml +id: extract-i18n-key +language: html +rule: + kind: text + pattern: $T + # skip dynamic text in mustache syntax + not: { regex: '\{\{.*\}\}' } +fix: "{{ $('$T') }}" +``` + +### Example + + +```html {2} + +``` + +### Diff + +```html + +``` + +### Contributed by +Inspired by [Vue.js RFC](https://github.com/vuejs/rfcs/discussions/705#discussion-7255672) \ No newline at end of file diff --git a/website/catalog/html/index.md b/website/catalog/html/index.md new file mode 100644 index 00000000..77b3a249 --- /dev/null +++ b/website/catalog/html/index.md @@ -0,0 +1,12 @@ +# HTML + +This page curates a list of example ast-grep rules to check and to rewrite HTML code. + +:::tip Use HTML parser for frameworks +You can leverage the [`languageGlobs`](/reference/sgconfig.html#languageglobs) option to parse framework files as plain HTML, such as `vue`, `svelte`, and `astro`. + +**Caveat**: This approach may not parse framework-specific syntax, like Astro's [frontmatter script](https://docs.astro.build/en/basics/astro-components/#the-component-script) or [Svelte control flow](https://svelte.dev/docs/svelte/if). You will need to load [custom languages](/advanced/custom-language.html) for such cases. +::: + + + \ No newline at end of file diff --git a/website/catalog/html/upgrade-ant-design-vue.md b/website/catalog/html/upgrade-ant-design-vue.md new file mode 100644 index 00000000..c0617dbd --- /dev/null +++ b/website/catalog/html/upgrade-ant-design-vue.md @@ -0,0 +1,73 @@ + +## Upgrade Ant Design Vue + +* [Playground Link](/playground.html#eyJtb2RlIjoiQ29uZmlnIiwibGFuZyI6Imh0bWwiLCJxdWVyeSI6IiIsInJld3JpdGUiOiIiLCJzdHJpY3RuZXNzIjoicmVsYXhlZCIsInNlbGVjdG9yIjoiIiwiY29uZmlnIjoidXRpbHM6XG4gIGluc2lkZS10YWc6XG4gICAgaW5zaWRlOlxuICAgICAga2luZDogZWxlbWVudCBcbiAgICAgIHN0b3BCeTogeyBraW5kOiBlbGVtZW50IH1cbiAgICAgIGhhczpcbiAgICAgICAgc3RvcEJ5OiB7IGtpbmQ6IHRhZ19uYW1lIH1cbiAgICAgICAga2luZDogdGFnX25hbWVcbiAgICAgICAgcGF0dGVybjogJFRBR19OQU1FXG5ydWxlOlxuICBraW5kOiBhdHRyaWJ1dGVfbmFtZVxuICByZWdleDogOnZpc2libGVcbiAgbWF0Y2hlczogaW5zaWRlLXRhZyAgXG5maXg6IDpvcGVuXG5jb25zdHJhaW50czpcbiAgVEFHX05BTUU6XG4gICAgcmVnZXg6IGEtbW9kYWx8YS10b29sdGlwIiwic291cmNlIjoiPHRlbXBsYXRlPlxuICA8YS1tb2RhbCA6dmlzaWJsZT1cInZpc2libGVcIj5jb250ZW50PC9hLW1vZGFsPlxuICA8YS10b29sdGlwIDp2aXNpYmxlPVwidmlzaWJsZVwiIC8+XG4gIDxhLXRhZyA6dmlzaWJsZT1cInZpc2libGVcIj50YWc8L2EtdGFnPlxuPC90ZW1wbGF0ZT4ifQ==) + +### Description + + +ast-grep can be used to upgrade Vue template using the HTML parser. + +This rule is an example to upgrade [one breaking change](https://next.antdv.com/docs/vue/migration-v4#component-api-adjustment) in [Ant Design Vue](https://next.antdv.com/components/overview) from v3 to v4, unifying the controlled visible API of the component popup. + +It is designed to identify and replace the `visible` attribute with the `open` attribute for specific components like `a-modal` and `a-tooltip`. Note the rule should not replace other visible attributes that are not related to the component popup like `a-tag`. + +The rule can be broken down into the following steps: +1. Find the target attribute name by `kind` and `regex` +2. Find the attribute's enclosing element using `inside`, and get its tag name +3. Ensure the tag name is related to popup components, using constraints + + +### YAML +```yaml +id: upgrade-ant-design-vue +language: HTML +utils: + inside-tag: + # find the enclosing element of the attribute + inside: + kind: element + stopBy: { kind: element } # only the closest element + # find the tag name and store it in metavar + has: + stopBy: { kind: tag_name } + kind: tag_name + pattern: $TAG_NAME +rule: + # find the target attribute_name + kind: attribute_name + regex: :visible + # find the element + matches: inside-tag +# ensure it only matches modal/tooltip but not tag +constraints: + TAG_NAME: + regex: a-modal|a-tooltip +fix: :open +``` + +### Example + + +```html {2,3} + +``` + +### Diff + +```html + +``` + +### Contributed by +Inspired by [Vue.js RFC](https://github.com/vuejs/rfcs/discussions/705#discussion-7255672) diff --git a/website/catalog/index.md b/website/catalog/index.md index 53c72daa..938946ab 100644 --- a/website/catalog/index.md +++ b/website/catalog/index.md @@ -1,35 +1,13 @@ # Rule Catalog Get confused what ast-grep is? This is a list of rewriting rule to inspire you! +Explore the power of ast-grep with these rewriting rules that can transform your code in seconds. -Explore the power of ast-grep with these rewriting rules that can transform your code in seconds! +Feel free to join our [Discord](https://discord.gg/4YZjf6htSQ) channel or ask [Codemod AI](https://app.codemod.com/studio?ai_thread_id=new) to explain the rules for you line by line! -Feel free to join our [Discord](https://discord.gg/4YZjf6htSQ) channel and ask @ast-grep-bot to explain the rules for you line by line! -* [C](/catalog/c/) - * [Rewrite Method to Function Call](/catalog/c/#rewrite-method-to-function-call) - * [Rewrite Check to Yoda Condition](/catalog/c/#rewrite-check-to-yoda-condition) -* [Go](/catalog/go/) - * [Match Function Call](/catalog/go/#match-function-call) - * [Find function declarations with names of certain pattern](/catalog/go/#find-function-declarations-with-names-of-certain-pattern) -* [Python](/catalog/python/) - * [Migrate OpenAi SDK](/catalog/python/#migrate-openai-sdk) - * [Use Walrus Operator in `if` statement](/catalog/python/#use-walrus-operator-in-if-statement) - * [Prefer Generator Expressions](/catalog/python/#prefer-generator-expressions) -* [Ruby](/catalog/ruby/) - * [Prefer Symbol over Proc](/catalog/ruby/#prefer-symbol-over-proc) - * [Migrate action_filter in Ruby on Rails](/catalog/ruby/#migrate-action-filter-in-ruby-on-rails) -* [Rust](/catalog/rust/) - * [Avoid Duplicated Exports](/catalog/rust/#avoid-duplicated-exports) - * [Get number of digits in a `usize`](/catalog/rust/#get-number-of-digits-in-a-usize) - * [Beware of char offset when iterate over a string](/catalog/rust/#beware-of-char-offset-when-iterate-over-a-string) -* [TypeScript](/catalog/typescript/) - * [Repository of ESLint rules šŸ”—](https://github.com/ast-grep/eslint/) - * [Unnecessary `useState` Type](/catalog/typescript/#unnecessary-usestate-type) - * [No `await` in `Promise.all`](/catalog/typescript/#no-await-in-promise-all-array) - * [No `console` except in `catch` block](/catalog/typescript/#no-console-except-in-catch-block) - * [Find Import File without Extension](/catalog/typescript/#find-import-file-without-extension) - * [Migrate XState to V5 from V4](/catalog/typescript/#migrate-xstate-to-v5-from-v4) -* [TSX](/catalog/tsx/) - * [Avoid `&&` short circuit in JSX](/catalog/typescript/#avoid-short-circuit-in-jsx) - * [Rewrite MobX Component Style](/catalog/typescript/#rewrite-mobx-component-style) \ No newline at end of file + + + \ No newline at end of file diff --git a/website/catalog/java/index.md b/website/catalog/java/index.md new file mode 100644 index 00000000..d3e9212c --- /dev/null +++ b/website/catalog/java/index.md @@ -0,0 +1,5 @@ +# Java + +This page curates a list of example ast-grep rules to check and to rewrite Java code. + + \ No newline at end of file diff --git a/website/catalog/java/no-unused-vars.md b/website/catalog/java/no-unused-vars.md new file mode 100644 index 00000000..6ef1d8fb --- /dev/null +++ b/website/catalog/java/no-unused-vars.md @@ -0,0 +1,46 @@ +## No Unused Vars in Java + +* [Playground Link](/playground.html#eyJtb2RlIjoiQ29uZmlnIiwibGFuZyI6ImphdmEiLCJxdWVyeSI6ImlmKHRydWUpeyQkJEJPRFl9IiwicmV3cml0ZSI6IiRDOiBMaXN0WyRUXSA9IHJlbGF0aW9uc2hpcCgkJCRBLCB1c2VsaXN0PVRydWUsICQkJEIpIiwic3RyaWN0bmVzcyI6InNtYXJ0Iiwic2VsZWN0b3IiOiIiLCJjb25maWciOiJpZDogbm8tdW51c2VkLXZhcnNcbnJ1bGU6XG4gICAga2luZDogbG9jYWxfdmFyaWFibGVfZGVjbGFyYXRpb25cbiAgICBhbGw6XG4gICAgICAgIC0gaGFzOlxuICAgICAgICAgICAgaGFzOlxuICAgICAgICAgICAgICAgIGtpbmQ6IGlkZW50aWZpZXJcbiAgICAgICAgICAgICAgICBwYXR0ZXJuOiAkSURFTlRcbiAgICAgICAgLSBub3Q6XG4gICAgICAgICAgICBwcmVjZWRlczpcbiAgICAgICAgICAgICAgICBzdG9wQnk6IGVuZFxuICAgICAgICAgICAgICAgIGhhczpcbiAgICAgICAgICAgICAgICAgICAgc3RvcEJ5OiBlbmRcbiAgICAgICAgICAgICAgICAgICAgYW55OlxuICAgICAgICAgICAgICAgICAgICAgICAgLSB7IGtpbmQ6IGlkZW50aWZpZXIsIHBhdHRlcm46ICRJREVOVCB9XG4gICAgICAgICAgICAgICAgICAgICAgICAtIHsgaGFzOiB7a2luZDogaWRlbnRpZmllciwgcGF0dGVybjogJElERU5ULCBzdG9wQnk6IGVuZH19XG5maXg6ICcnXG4iLCJzb3VyY2UiOiJTdHJpbmcgdW51c2VkID0gXCJ1bnVzZWRcIjtcbk1hcDxTdHJpbmcsIFN0cmluZz4gZGVjbGFyZWRCdXROb3RJbnN0YW50aWF0ZWQ7XG5cblN0cmluZyB1c2VkMSA9IFwidXNlZFwiO1xuaW50IHVzZWQyID0gMztcbmJvb2xlYW4gdXNlZDMgPSBmYWxzZTtcbmludCB1c2VkNCA9IDQ7XG5TdHJpbmcgdXNlZDUgPSBcIjVcIjtcblxuXG5cbnVzZWQxO1xuU3lzdGVtLm91dC5wcmludGxuKHVzZWQyKTtcbmlmKHVzZWQzKXtcbiAgICBTeXN0ZW0ub3V0LnByaW50bG4oXCJzb21lIHZhcnMgYXJlIHVudXNlZFwiKTtcbiAgICBNYXA8U3RyaW5nLCBTdHJpbmc+IHVudXNlZE1hcCA9IG5ldyBIYXNoTWFwPD4oKSB7e1xuICAgICAgICBwdXQodXNlZDUsIFwidXNlZDVcIik7XG4gICAgfX07XG5cbiAgICAvLyBFdmVuIHRob3VnaCB3ZSBkb24ndCByZWFsbHkgZG8gYW55dGhpbmcgd2l0aCB0aGlzIG1hcCwgc2VwYXJhdGluZyB0aGUgZGVjbGFyYXRpb24gYW5kIGluc3RhbnRpYXRpb24gbWFrZXMgaXQgY291bnQgYXMgYmVpbmcgdXNlZFxuICAgIGRlY2xhcmVkQnV0Tm90SW5zdGFudGlhdGVkID0gbmV3IEhhc2hNYXA8PigpO1xuXG4gICAgcmV0dXJuIHVzZWQ0O1xufSJ9) + +### Description + +Identifying unused variables is a common task in code refactoring. You should rely on a Java linter or IDE for this task rather than writing a custom rule in ast-grep, but for educational purposes, this rule demonstrates how to find unused variables in Java. + +This approach makes some simplifying assumptions. We only consider local variable declarations and ignore the other many ways variables can be declared: Method Parameters, Fields, Class Variables, Constructor Parameters, Loop Variables, Exception Handler Parameters, Lambda Parameters, Annotation Parameters, Enum Constants, and Record Components. Now you may see why it is recommended to use a rule from an established linter or IDE rather than writing your own. + +### YAML + +```yaml +id: no-unused-vars +rule: + kind: local_variable_declaration + all: + - has: + has: + kind: identifier + pattern: $IDENT + - not: + precedes: + stopBy: end + has: + stopBy: end + any: + - { kind: identifier, pattern: $IDENT } + - { has: {kind: identifier, pattern: $IDENT, stopBy: end}} +fix: '' +``` + +First, we identify the local variable declaration and capture the pattern of the identifier inside of it. Then we use `not` and `precedes` to only match the local variable declaration if the identifier we captured does not appear later in the code. + +It is important to note that we use `all` here to force the ordering of the `has` rule to be before the `not` rule. This guarantees that the meta-variable `$IDENT` is captured by looking inside of the local variable declaration. + +Additionally, when looking ahead in the code, we can't just look for the identifier directly, but for any node that may contain the identifier. + + +### Example + +```java +String unused = "unused"; // [!code --] +String used = "used"; +System.out.println(used); +``` \ No newline at end of file diff --git a/website/catalog/kotlin/ensure-clean-architecture.md b/website/catalog/kotlin/ensure-clean-architecture.md new file mode 100644 index 00000000..967046a7 --- /dev/null +++ b/website/catalog/kotlin/ensure-clean-architecture.md @@ -0,0 +1,45 @@ + +## Ensure Clean Architecture + +* [Playground Link](/playground.html#eyJtb2RlIjoiQ29uZmlnIiwibGFuZyI6ImtvdGxpbiIsInF1ZXJ5IjoiIiwicmV3cml0ZSI6IiIsInN0cmljdG5lc3MiOiJyZWxheGVkIiwic2VsZWN0b3IiOiIiLCJjb25maWciOiJpZDogaW1wb3J0LWRlcGVuZGVuY3ktdmlvbGF0aW9uXG5tZXNzYWdlOiBJbXBvcnQgRGVwZW5kZW5jeSBWaW9sYXRpb24gXG5ub3RlczogRW5zdXJlcyB0aGF0IGltcG9ydHMgY29tcGx5IHdpdGggYXJjaGl0ZWN0dXJhbCBydWxlcy4gXG5zZXZlcml0eTogZXJyb3JcbnJ1bGU6XG4gIHBhdHRlcm46IGltcG9ydCAkUEFUSFxuY29uc3RyYWludHM6XG4gIFBBVEg6XG4gICAgYW55OlxuICAgIC0gcmVnZXg6IGNvbVxcLmV4YW1wbGUoXFwuXFx3KykqXFwuZGF0YVxuICAgIC0gcmVnZXg6IGNvbVxcLmV4YW1wbGUoXFwuXFx3KykqXFwucHJlc2VudGF0aW9uXG5maWxlczpcbi0gY29tL2V4YW1wbGUvZG9tYWluLyoqLyoua3QiLCJzb3VyY2UiOiJpbXBvcnQgYW5kcm9pZHgubGlmZWN5Y2xlLlZpZXdNb2RlbFxuaW1wb3J0IGFuZHJvaWR4LmxpZmVjeWNsZS5WaWV3TW9kZWxTY29wZVxuaW1wb3J0IGNvbS5leGFtcGxlLmN1c3RvbWxpbnRleGFtcGxlLmRhdGEubW9kZWxzLlVzZXJEdG9cbmltcG9ydCBjb20uZXhhbXBsZS5jdXN0b21saW50ZXhhbXBsZS5kb21haW4udXNlY2FzZXMuR2V0VXNlclVzZUNhc2VcbmltcG9ydCBjb20uZXhhbXBsZS5jdXN0b21saW50ZXhhbXBsZS5wcmVzZW50YXRpb24uc3RhdGVzLk1haW5TdGF0ZVxuaW1wb3J0IGRhZ2dlci5oaWx0LmFuZHJvaWQubGlmZWN5Y2xlLkhpbHRWaWV3TW9kZWwifQ==) + +### Description + +This ast-grep rule ensures that the **domain** package in a [Clean Architecture](https://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html) project does not import classes from the **data** or **presentation** packages. It enforces the separation of concerns by preventing the domain layer from depending on other layers, maintaining the integrity of the architecture. + +For example, the rule will trigger an error if an import statement like `import com.example.data.SomeClass` or `import com.example.presentation.AnotherClass` is found within the domain package. + +The rule uses the [`files`](/reference/yaml.html#files) field to apply only to the domain package. + +### YAML + +```yaml +id: import-dependency-violation +message: Import Dependency Violation +notes: Ensures that imports comply with architectural rules. +severity: error +rule: + pattern: import $PATH # capture the import statement +constraints: + PATH: # find specific package imports + any: + - regex: com\.example(\.\w+)*\.data + - regex: com\.example(\.\w+)*\.presentation +files: # apply only to domain package +- com/example/domain/**/*.kt +``` + +### Example + + +```kotlin {3,5} +import androidx.lifecycle.ViewModel +import androidx.lifecycle.ViewModelScope +import com.example.customlintexample.data.models.UserDto +import com.example.customlintexample.domain.usecases.GetUserUseCase +import com.example.customlintexample.presentation.states.MainState +import dagger.hilt.android.lifecycle.HiltViewModel +``` + +### Contributed by +Inspired by the post [Custom Lint Task Configuration in Gradle with Kotlin DSL](https://www.sngular.com/insights/320/custom-lint-task-configuration-in-gradle-with-kotlin-dsl) diff --git a/website/catalog/kotlin/index.md b/website/catalog/kotlin/index.md new file mode 100644 index 00000000..6a21da3a --- /dev/null +++ b/website/catalog/kotlin/index.md @@ -0,0 +1,5 @@ +# Kotlin + +This page curates a list of example ast-grep rules to check and to rewrite Kotlin code. + + diff --git a/website/catalog/python/index.md b/website/catalog/python/index.md index 3da5f532..665a7031 100644 --- a/website/catalog/python/index.md +++ b/website/catalog/python/index.md @@ -3,5 +3,9 @@ This page curates a list of example ast-grep rules to check and to rewrite Python code. - + + + + + \ No newline at end of file diff --git a/website/catalog/python/migrate-openai-sdk.md b/website/catalog/python/migrate-openai-sdk.md index bcd12284..72b14f5f 100644 --- a/website/catalog/python/migrate-openai-sdk.md +++ b/website/catalog/python/migrate-openai-sdk.md @@ -58,22 +58,22 @@ def index(): ``` ### Diff - + ```python import os -import openai // [!code --] -from openai import Client // [!code ++] +import openai # [!code --] +from openai import Client # [!code ++] from flask import Flask, jsonify app = Flask(__name__) -openai.api_key = os.getenv("OPENAI_API_KEY") // [!code --] -client = Client(os.getenv("OPENAI_API_KEY")) // [!code ++] +openai.api_key = os.getenv("OPENAI_API_KEY") # [!code --] +client = Client(os.getenv("OPENAI_API_KEY")) # [!code ++] @app.route("/chat", methods=("POST")) def index(): animal = request.form["animal"] - response = openai.Completion.create( // [!code --] - response = client.completions.create( // [!code ++] + response = openai.Completion.create( # [!code --] + response = client.completions.create( # [!code ++] model="text-davinci-003", prompt=generate_prompt(animal), temperature=0.6, diff --git a/website/catalog/python/optional-to-none-union.md b/website/catalog/python/optional-to-none-union.md new file mode 100644 index 00000000..ece130ae --- /dev/null +++ b/website/catalog/python/optional-to-none-union.md @@ -0,0 +1,39 @@ +## Rewrite `Optional[Type]` to `Type | None` + +* [Playground Link](/playground.html#eyJtb2RlIjoiQ29uZmlnIiwibGFuZyI6InB5dGhvbiIsInF1ZXJ5IjoiIiwicmV3cml0ZSI6IiIsInN0cmljdG5lc3MiOiJzaWduYXR1cmUiLCJzZWxlY3RvciI6IiIsImNvbmZpZyI6InJ1bGU6XG4gIHBhdHRlcm46IFxuICAgIGNvbnRleHQ6ICdhOiBPcHRpb25hbFskVF0nXG4gICAgc2VsZWN0b3I6IGdlbmVyaWNfdHlwZVxuZml4OiAkVCB8IE5vbmUiLCJzb3VyY2UiOiJkZWYgYShhcmc6IE9wdGlvbmFsW0ludF0pOiBwYXNzIn0=) + +### Description + +[PEP 604](https://peps.python.org/pep-0604/) recommends that `Type | None` is preferred over `Optional[Type]` for Python 3.10+. + +This rule performs such rewriting. Note `Optional[$T]` alone is interpreted as subscripting expression instead of generic type, we need to use [pattern object](/guide/rule-config/atomic-rule.html#pattern-object) to disambiguate it with more context code. + + +### YAML +```yaml +id: optional-to-none-union +language: python +rule: + pattern: + context: 'a: Optional[$T]' + selector: generic_type +fix: $T | None +``` + +### Example + + +```py {1} +def a(arg: Optional[int]): pass +``` + +### Diff + +```py +def a(arg: Optional[int]): pass # [!code --] +def a(arg: int | None): pass # [!code ++] +``` + +### Contributed by + +[Bede Carroll](https://github.com/ast-grep/ast-grep/discussions/1492) diff --git a/website/catalog/python/prefer-generator-expressions.md b/website/catalog/python/prefer-generator-expressions.md index af7b9a1f..abdc58d2 100644 --- a/website/catalog/python/prefer-generator-expressions.md +++ b/website/catalog/python/prefer-generator-expressions.md @@ -51,7 +51,7 @@ any([x for x in range(10)]) ``` ### Diff - + ```python any([x for x in range(10)]) # [!code --] any(x for x in range(10)) # [!code ++] diff --git a/website/catalog/python/recursive-rewrite-type.md b/website/catalog/python/recursive-rewrite-type.md new file mode 100644 index 00000000..a48246ef --- /dev/null +++ b/website/catalog/python/recursive-rewrite-type.md @@ -0,0 +1,95 @@ +## Recursive Rewrite Type + + +* [Playground Link](/playground.html#eyJtb2RlIjoiQ29uZmlnIiwibGFuZyI6InB5dGhvbiIsInF1ZXJ5IjoiIiwicmV3cml0ZSI6IiIsInN0cmljdG5lc3MiOiJzbWFydCIsInNlbGVjdG9yIjoiIiwiY29uZmlnIjoicmV3cml0ZXJzOlxyXG4tIGlkOiBvcHRpb25hbFxyXG4gIGxhbmd1YWdlOiBQeXRob25cclxuICBydWxlOlxyXG4gICAgYW55OlxyXG4gICAgLSBwYXR0ZXJuOlxyXG4gICAgICAgIGNvbnRleHQ6ICdhcmc6IE9wdGlvbmFsWyRUWVBFXSdcclxuICAgICAgICBzZWxlY3RvcjogZ2VuZXJpY190eXBlXHJcbiAgICAtIHBhdHRlcm46IE9wdGlvbmFsWyRUWVBFXVxyXG4gIHRyYW5zZm9ybTpcclxuICAgIE5UOlxyXG4gICAgICByZXdyaXRlOiBcclxuICAgICAgICByZXdyaXRlcnM6IFtvcHRpb25hbCwgdW5pb25zXVxyXG4gICAgICAgIHNvdXJjZTogJFRZUEVcclxuICBmaXg6ICROVCB8IE5vbmVcclxuLSBpZDogdW5pb25zXHJcbiAgbGFuZ3VhZ2U6IFB5dGhvblxyXG4gIHJ1bGU6XHJcbiAgICBwYXR0ZXJuOlxyXG4gICAgICBjb250ZXh0OiAnYTogVW5pb25bJCQkVFlQRVNdJ1xyXG4gICAgICBzZWxlY3RvcjogZ2VuZXJpY190eXBlXHJcbiAgdHJhbnNmb3JtOlxyXG4gICAgVU5JT05TOlxyXG4gICAgICByZXdyaXRlOlxyXG4gICAgICAgIHJld3JpdGVyczpcclxuICAgICAgICAgIC0gcmV3cml0ZS11bmlvbnNcclxuICAgICAgICBzb3VyY2U6ICQkJFRZUEVTXHJcbiAgICAgICAgam9pbkJ5OiBcIiB8IFwiXHJcbiAgZml4OiAkVU5JT05TXHJcbi0gaWQ6IHJld3JpdGUtdW5pb25zXHJcbiAgcnVsZTpcclxuICAgIHBhdHRlcm46ICRUWVBFXHJcbiAgICBraW5kOiB0eXBlXHJcbiAgdHJhbnNmb3JtOlxyXG4gICAgTlQ6XHJcbiAgICAgIHJld3JpdGU6IFxyXG4gICAgICAgIHJld3JpdGVyczogW29wdGlvbmFsLCB1bmlvbnNdXHJcbiAgICAgICAgc291cmNlOiAkVFlQRVxyXG4gIGZpeDogJE5UXHJcbnJ1bGU6XHJcbiAga2luZDogdHlwZVxyXG4gIHBhdHRlcm46ICRUUEVcclxudHJhbnNmb3JtOlxyXG4gIE5FV19UWVBFOlxyXG4gICAgcmV3cml0ZTogXHJcbiAgICAgIHJld3JpdGVyczogW29wdGlvbmFsLCB1bmlvbnNdXHJcbiAgICAgIHNvdXJjZTogJFRQRVxyXG5maXg6ICRORVdfVFlQRSIsInNvdXJjZSI6InJlc3VsdHM6ICBPcHRpb25hbFtVbmlvbltMaXN0W1VuaW9uW3N0ciwgZGljdF1dLCBzdHJdXVxuIn0=) + +### Description + +Suppose we want to transform Python's `Union[T1, T2]` to `T1 | T2` and `Optional[T]` to `T | None`. + +By default, ast-grep will only fix the outermost node that matches a pattern and will not rewrite the inner AST nodes inside a match. This avoids unexpected rewriting or infinite rewriting loop. + +So if you are using non-recursive rewriter like [this](https://github.com/ast-grep/ast-grep/discussions/1566#discussion-7401382), `Optional[Union[int, str]]` will only be converted to `Union[int, str] | None`. Note the inner `Union[int, str]` is not enabled. This is because the rewriter `optional` matches `Optional[$TYPE]` and rewrite it to `$TYPE | None`. The inner `$TYPE` is not processed. + +However, we can apply `rewriters` to inner types recursively. Take the `optional` rewriter as an example, we need to apply rewriters, `optional` and `unions`, **recursively** to `$TYPE` and get a new variable `$NT`. + +### YAML +```yml +id: recursive-rewrite-types +language: python +rewriters: +# rewrite Optional[T] to T | None +- id: optional + rule: + any: + - pattern: + context: 'arg: Optional[$TYPE]' + selector: generic_type + - pattern: Optional[$TYPE] + # recursively apply rewriters to $TYPE + transform: + NT: + rewrite: + rewriters: [optional, unions] + source: $TYPE + # use the new variable $NT + fix: $NT | None + +# similar to Optional, rewrite Union[T1, T2] to T1 | T2 +- id: unions + language: Python + rule: + pattern: + context: 'a: Union[$$$TYPES]' + selector: generic_type + transform: + UNIONS: + # rewrite all types inside $$$TYPES + rewrite: + rewriters: [ rewrite-unions ] + source: $$$TYPES + joinBy: " | " + fix: $UNIONS +- id: rewrite-unions + rule: + pattern: $TYPE + kind: type + # recursive part + transform: + NT: + rewrite: + rewriters: [optional, unions] + source: $TYPE + fix: $NT + +# find all types +rule: + kind: type + pattern: $TPE +# apply the recursive rewriters +transform: + NEW_TYPE: + rewrite: + rewriters: [optional, unions] + source: $TPE +# output +fix: $NEW_TYPE +``` + + +### Example + + +```python +results: Optional[Union[List[Union[str, dict]], str]] +``` + +### Diff + +```python +results: Optional[Union[List[Union[str, dict]], str]] # [!code --] +results: List[str | dict] | str | None #[!code ++] +``` + +### Contributed by +Inspired by [steinuil](https://github.com/ast-grep/ast-grep/discussions/1566) diff --git a/website/catalog/python/refactor-pytest-fixtures.md b/website/catalog/python/refactor-pytest-fixtures.md new file mode 100644 index 00000000..3006cd13 --- /dev/null +++ b/website/catalog/python/refactor-pytest-fixtures.md @@ -0,0 +1,162 @@ +## Refactor pytest fixtures + +* [Playground Link](/playground.html#eyJtb2RlIjoiQ29uZmlnIiwibGFuZyI6InB5dGhvbiIsInF1ZXJ5IjoiZGVmIGZvbygkWCk6XG4gICRTIiwicmV3cml0ZSI6ImxvZ2dlci5sb2coJE1BVENIKSIsImNvbmZpZyI6ImlkOiBweXRlc3QtdHlwZS1oaW50LWZpeHR1cmVcbmxhbmd1YWdlOiBQeXRob25cbnV0aWxzOlxuICBpcy1maXh0dXJlLWZ1bmN0aW9uOlxuICAgIGtpbmQ6IGZ1bmN0aW9uX2RlZmluaXRpb25cbiAgICBmb2xsb3dzOlxuICAgICAga2luZDogZGVjb3JhdG9yXG4gICAgICBoYXM6XG4gICAgICAgIGtpbmQ6IGlkZW50aWZpZXJcbiAgICAgICAgcmVnZXg6IF5maXh0dXJlJFxuICAgICAgICBzdG9wQnk6IGVuZFxuICBpcy10ZXN0LWZ1bmN0aW9uOlxuICAgIGtpbmQ6IGZ1bmN0aW9uX2RlZmluaXRpb25cbiAgICBoYXM6XG4gICAgICBmaWVsZDogbmFtZVxuICAgICAgcmVnZXg6IF50ZXN0X1xuICBpcy1weXRlc3QtY29udGV4dDpcbiAgICAjIFB5dGVzdCBjb250ZXh0IGlzIGEgbm9kZSBpbnNpZGUgYSBweXRlc3RcbiAgICAjIHRlc3QvZml4dHVyZVxuICAgIGluc2lkZTpcbiAgICAgIHN0b3BCeTogZW5kXG4gICAgICBhbnk6XG4gICAgICAgIC0gbWF0Y2hlczogaXMtZml4dHVyZS1mdW5jdGlvblxuICAgICAgICAtIG1hdGNoZXM6IGlzLXRlc3QtZnVuY3Rpb25cbiAgaXMtZml4dHVyZS1hcmc6XG4gICAgIyBGaXh0dXJlIGFyZ3VtZW50cyBhcmUgaWRlbnRpZmllcnMgaW5zaWRlIHRoZSBcbiAgICAjIHBhcmFtZXRlcnMgb2YgYSB0ZXN0L2ZpeHR1cmUgZnVuY3Rpb25cbiAgICBhbGw6XG4gICAgICAtIGtpbmQ6IGlkZW50aWZpZXJcbiAgICAgIC0gbWF0Y2hlczogaXMtcHl0ZXN0LWNvbnRleHRcbiAgICAgIC0gaW5zaWRlOlxuICAgICAgICAgIGtpbmQ6IHBhcmFtZXRlcnNcbnJ1bGU6XG4gIG1hdGNoZXM6IGlzLWZpeHR1cmUtYXJnXG4gIHJlZ2V4OiBeZm9vJFxuZml4OiAnZm9vOiBpbnQnXG4iLCJzb3VyY2UiOiJmcm9tIGNvbGxlY3Rpb25zLmFiYyBpbXBvcnQgSXRlcmFibGVcbmZyb20gdHlwaW5nIGltcG9ydCBBbnlcblxuaW1wb3J0IHB5dGVzdFxuZnJvbSBweXRlc3QgaW1wb3J0IGZpeHR1cmVcblxuQHB5dGVzdC5maXh0dXJlKHNjb3BlPVwic2Vzc2lvblwiKVxuZGVmIGZvbygpIC0+IEl0ZXJhYmxlW2ludF06XG4gICAgeWllbGQgNVxuXG5AZml4dHVyZVxuZGVmIGJhcihmb28pIC0+IHN0cjpcbiAgICByZXR1cm4gc3RyKGZvbylcblxuZGVmIHJlZ3VsYXJfZnVuY3Rpb24oZm9vKSAtPiBOb25lOlxuICAgICMgVGhpcyBmdW5jdGlvbiBkb2Vzbid0IHVzZSB0aGUgJ2ZvbycgZml4dHVyZVxuICAgIHByaW50KGZvbylcblxuZGVmIHRlc3RfMShmb28sIGJhcik6XG4gICAgcHJpbnQoZm9vLCBiYXIpXG5cbmRlZiB0ZXN0XzIoYmFyKTpcbiAgICAuLi4ifQ==) + +### Description + +One of the most commonly used testing framework in Python is [pytest](https://docs.pytest.org/en/8.2.x/). Among other things, it allows the use of [fixtures](https://docs.pytest.org/en/6.2.x/fixture.html). + +Fixtures are defined as functions that can be required in test code, or in other fixtures, as an argument. This means that all functions arguments with a given name in a pytest context (test function or fixture) are essentially the same entity. However, not every editor's LSP is able to keep track of this, making refactoring challenging. + +Using ast-grep, we can define some rules to match fixture definition and usage without catching similarly named entities in a non-test context. + +First, we define utils to select pytest test/fixture functions. + +```yaml +utils: + is-fixture-function: + kind: function_definition + follows: + kind: decorator + has: + kind: identifier + regex: ^fixture$ + stopBy: end + is-test-function: + kind: function_definition + has: + field: name + regex: ^test_ +``` + +Pytest fixtures are declared with a decorator `@pytest.fixture`. We match the `function_definition` node that directly follows a `decorator` node. That decorator node must have a `fixture` identifier somewhere. This accounts for different location of the `fixture` node depending on the type of imports and whether the decorator is used as is or called with parameters. + +Pytest functions are fairly straightforward to detect, as they always start with `test_` by convention. + +The next utils builds onto those two to incrementally: +- Find if a node is inside a pytest context (test/fixture) +- Find if a node is an argument in such a context + +```yaml +utils: + is-pytest-context: + # Pytest context is a node inside a pytest + # test/fixture + inside: + stopBy: end + any: + - matches: is-fixture-function + - matches: is-test-function + is-fixture-arg: + # Fixture arguments are identifiers inside the + # parameters of a test/fixture function + all: + - kind: identifier + - inside: + kind: parameters + - matches: is-pytest-context +``` + +Once those utils are declared, you can perform various refactoring on a specific fixture. + +The following rule adds a type-hint to a fixture. + +```yaml +rule: + matches: is-fixture-arg + regex: ^foo$ +fix: 'foo: int' +``` + +This one renames a fixture and all its references. + +```yaml +rule: + kind: identifier + matches: is-fixture-context + regex: ^foo$ +fix: 'five' +``` + +### Example + +#### Renaming Fixtures + +```python {2,6,7,12,13} +@pytest.fixture +def foo() -> int: + return 5 + +@pytest.fixture(scope="function") +def some_fixture(foo: int) -> str: + return str(foo) + +def regular_function(foo) -> None: + ... + +def test_code(foo: int) -> None: + assert foo == 5 +``` + +#### Diff + + +```python {2,6,7,12} +@pytest.fixture +def foo() -> int: # [!code --] +def five() -> int: # [!code ++] + return 5 + +@pytest.fixture(scope="function") +def some_fixture(foo: int) -> str: # [!code --] +def some_fixture(five: int) -> str: # [!code ++] + return str(foo) + +def regular_function(foo) -> None: + ... + +def test_code(foo: int) -> None: # [!code --] +def test_code(five: int) -> None: # [!code ++] + assert foo == 5 # [!code --] + assert five == 5 # [!code ++] +``` + +#### Type Hinting Fixtures + + +```python {6,12} +@pytest.fixture +def foo() -> int: + return 5 + +@pytest.fixture(scope="function") +def some_fixture(foo) -> str: + return str(foo) + +def regular_function(foo) -> None: + ... + +def test_code(foo) -> None: + assert foo == 5 +``` + +#### Diff + + +```python {2,6,7,12} +@pytest.fixture +def foo() -> int: + return 5 + +@pytest.fixture(scope="function") +def some_fixture(foo) -> str: # [!code --] +def some_fixture(foo: int) -> str: # [!code ++] + return str(foo) + +def regular_function(foo) -> None: + ... + +def test_code(foo) -> None: # [!code --] +def test_code(foo: int) -> None: # [!code ++] + assert foo == 5 +``` \ No newline at end of file diff --git a/website/catalog/python/remove-async-await.md b/website/catalog/python/remove-async-await.md new file mode 100644 index 00000000..f6300848 --- /dev/null +++ b/website/catalog/python/remove-async-await.md @@ -0,0 +1,59 @@ +## Remove `async` function + +* [Playground Link](/playground.html#eyJtb2RlIjoiQ29uZmlnIiwibGFuZyI6InB5dGhvbiIsInF1ZXJ5IjoiYXdhaXQgJCQkQ0FMTCIsInJld3JpdGUiOiIkJCRDQUxMICIsImNvbmZpZyI6ImlkOiByZW1vdmUtYXN5bmMtZGVmXG5sYW5ndWFnZTogcHl0aG9uXG5ydWxlOlxuICBwYXR0ZXJuOlxuICAgIGNvbnRleHQ6ICdhc3luYyBkZWYgJEZVTkMoJCQkQVJHUyk6ICQkJEJPRFknXG4gICAgc2VsZWN0b3I6IGZ1bmN0aW9uX2RlZmluaXRpb25cbnRyYW5zZm9ybTpcbiAgUkVNT1ZFRF9CT0RZOlxuICAgIHJld3JpdGU6XG4gICAgICByZXdyaXRlcnM6IFtyZW1vdmUtYXdhaXQtY2FsbF1cbiAgICAgIHNvdXJjZTogJCQkQk9EWVxuZml4OiB8LVxuICBkZWYgJEZVTkMoJCQkQVJHUyk6XG4gICAgJFJFTU9WRURfQk9EWVxucmV3cml0ZXJzOlxuLSBpZDogcmVtb3ZlLWF3YWl0LWNhbGxcbiAgcnVsZTpcbiAgICBwYXR0ZXJuOiAnYXdhaXQgJCQkQ0FMTCdcbiAgZml4OiAkJCRDQUxMXG4iLCJzb3VyY2UiOiJhc3luYyBkZWYgbWFpbjMoKTpcbiAgYXdhaXQgc29tZWNhbGwoMSwgNSkifQ==) + +### Description + +The `async` keyword in Python is used to define asynchronous functions that can be `await`ed. + +In this example, we want to remove the `async` keyword from a function definition and replace it with a synchronous version of the function. We also need to remove the `await` keyword from the function body. + +By default, ast-grep will not apply overlapping replacements. This means `await` keywords will not be modified because they are inside the async function body. + +However, we can use the [`rewriter`](https://ast-grep.github.io/reference/yaml/rewriter.html) to apply changes inside the matched function body. + +### YAML + +```yaml +id: remove-async-def +language: python +rule: + # match async function definition + pattern: + context: 'async def $FUNC($$$ARGS): $$$BODY' + selector: function_definition +rewriters: +# define a rewriter to remove the await keyword + remove-await-call: + pattern: 'await $$$CALL' + fix: $$$CALL # remove await keyword +# apply the rewriter to the function body +transform: + REMOVED_BODY: + rewrite: + rewriters: [remove-await-call] + source: $$$BODY +fix: |- + def $FUNC($$$ARGS): + $REMOVED_BODY +``` + +### Example + + +```python +async def main3(): + await somecall(1, 5) +``` + +### Diff + +```python +async def main3(): # [!code --] + await somecall(1, 5) # [!code --] +def main3(): # [!code ++] + somecall(1, 5) # [!code ++] +``` + +### Contributed by +Inspired by the ast-grep issue [#1185](https://github.com/ast-grep/ast-grep/issues/1185) diff --git a/website/catalog/python/use-walrus-operator-in-if.md b/website/catalog/python/use-walrus-operator-in-if.md index 3ceb9e72..809d750e 100644 --- a/website/catalog/python/use-walrus-operator-in-if.md +++ b/website/catalog/python/use-walrus-operator-in-if.md @@ -79,12 +79,12 @@ if a: ``` ### Diff - + ```python -a = foo() // [!code --] +a = foo() # [!code --] -if a: // [!code --] -if a := foo(): // [!code ++] +if a: # [!code --] +if a := foo(): # [!code ++] do_bar() ``` diff --git a/website/catalog/ruby/migrate-action-filter.md b/website/catalog/ruby/migrate-action-filter.md index d8d358ea..b085f6da 100644 --- a/website/catalog/ruby/migrate-action-filter.md +++ b/website/catalog/ruby/migrate-action-filter.md @@ -51,19 +51,19 @@ end ``` ### Diff - + ```rb class TodosController < ApplicationController - before_action :authenticate // [!code --] - before_filter :authenticate // [!code ++] - around_action :wrap_in_transaction, only: :show // [!code --] - around_filter :wrap_in_transaction, only: :show // [!code ++] - after_action do |controller| // [!code --] - flash[:error] = "You must be logged in" // [!code --] - end // [!code --] - after_filter do |controller| // [!code ++] - flash[:error] = "You must be logged in" // [!code ++] - end // [!code ++] + before_action :authenticate # [!code --] + before_filter :authenticate # [!code ++] + around_action :wrap_in_transaction, only: :show # [!code --] + around_filter :wrap_in_transaction, only: :show # [!code ++] + after_action do |controller| # [!code --] + flash[:error] = "You must be logged in" # [!code --] + end # [!code --] + after_filter do |controller| # [!code ++] + flash[:error] = "You must be logged in" # [!code ++] + end # [!code ++] def index @todos = Todo.all diff --git a/website/catalog/ruby/prefer-symbol-over-proc.md b/website/catalog/ruby/prefer-symbol-over-proc.md index 159e4708..adecc36c 100644 --- a/website/catalog/ruby/prefer-symbol-over-proc.md +++ b/website/catalog/ruby/prefer-symbol-over-proc.md @@ -16,7 +16,6 @@ id: prefer-symbol-over-proc language: ruby rule: pattern: $LIST.$ITER { |$V| $V.$METHOD } -language: Ruby constraints: ITER: regex: 'map|select|each' @@ -33,12 +32,12 @@ not_list.no_match { |v| v.even? } ``` ### Diff - + ```rb -[1, 2, 3].select { |v| v.even? } // [!code --] -[1, 2, 3].select(&:even?) // [!code ++] -(1..100).each { |i| i.to_s } // [!code --] -(1..100).each(&:to_s) // [!code ++] +[1, 2, 3].select { |v| v.even? } # [!code --] +[1, 2, 3].select(&:even?) # [!code ++] +(1..100).each { |i| i.to_s } # [!code --] +(1..100).each(&:to_s) # [!code ++] not_list.no_match { |v| v.even? } ``` diff --git a/website/catalog/rule-template.md b/website/catalog/rule-template.md index 0abe0196..418da36c 100644 --- a/website/catalog/rule-template.md +++ b/website/catalog/rule-template.md @@ -1,7 +1,7 @@ --- language: JavaScript # please fully spell the language playgroundLink: '[TODO]' -command: 'sg -p [TODO] -r [TODO]' +command: 'ast-grep -p [TODO] -r [TODO]' hasFix: true ruleType: 'pattern' # 'pattern' or 'yaml' --- @@ -20,9 +20,9 @@ Some Description for your rule! ### Pattern ```shell -sg -p pattern -r rewrite -l js +ast-grep -p pattern -r rewrite -l js # or without fixer -sg -p pattern -l js +ast-grep -p pattern -l js ``` diff --git a/website/catalog/rust/boshen-footgun.md b/website/catalog/rust/boshen-footgun.md index c9c4f6bc..879010c2 100644 --- a/website/catalog/rust/boshen-footgun.md +++ b/website/catalog/rust/boshen-footgun.md @@ -23,7 +23,7 @@ Depending on your use case, you may want to use `char_indices()` instead of `cha ### Pattern ```shell -sg -p '$A.chars().enumerate()' \ +ast-grep -p '$A.chars().enumerate()' \ -r '$A.char_indices()' \ -l rs ``` diff --git a/website/catalog/rust/get-digit-count-in-usize.md b/website/catalog/rust/get-digit-count-in-usize.md index 6c7d3eac..e64969cc 100644 --- a/website/catalog/rust/get-digit-count-in-usize.md +++ b/website/catalog/rust/get-digit-count-in-usize.md @@ -19,7 +19,7 @@ The snippet above computes the integer logarithm base 10 of the number and adds ### Pattern ```shell -sg -p '$NUM.to_string().chars().count()' \ +ast-grep -p '$NUM.to_string().chars().count()' \ -r '$NUM.checked_ilog10().unwrap_or(0) + 1' \ -l rs ``` diff --git a/website/catalog/rust/index.md b/website/catalog/rust/index.md index be8043e6..99bed43c 100644 --- a/website/catalog/rust/index.md +++ b/website/catalog/rust/index.md @@ -4,4 +4,5 @@ This page curates a list of example ast-grep rules to check and to rewrite Rust - \ No newline at end of file + + \ No newline at end of file diff --git a/website/catalog/rust/rewrite-indoc-macro.md b/website/catalog/rust/rewrite-indoc-macro.md new file mode 100644 index 00000000..43197194 --- /dev/null +++ b/website/catalog/rust/rewrite-indoc-macro.md @@ -0,0 +1,72 @@ +## Rewrite `indoc!` macro + + +* [Playground Link](/playground.html#eyJtb2RlIjoiUGF0Y2giLCJsYW5nIjoicnVzdCIsInF1ZXJ5IjoiaW5kb2MhIHsgciNcIiQkJEFcIiMgfSIsInJld3JpdGUiOiJgJCQkQWAiLCJzdHJpY3RuZXNzIjoicmVsYXhlZCIsInNlbGVjdG9yIjoiIiwiY29uZmlnIjoicnVsZTogXG4gYW55OlxuIC0gcGF0dGVybjogJFYgPT09ICRTRU5TRVRJVkVXT1JEXG4gLSBwYXR0ZXJuOiAkU0VOU0VUSVZFV09SRCA9PT0gJFZcbmNvbnN0cmFpbnRzOlxuICBTRU5TRVRJVkVXT1JEOlxuICAgIHJlZ2V4OiBwYXNzd29yZCIsInNvdXJjZSI6ImZuIG1haW4oKSB7XG4gICAgaW5kb2MhIHtyI1wiXG4gICAgICAgIC5mb28ge1xuICAgICAgICAgICAgb3JkZXI6IDE7XG4gICAgICAgIH1cbiAgICBcIiN9O1xufSJ9) + +### Description + +This example, created from [a Tweet](https://x.com/zack_overflow/status/1885065128590401551), shows a refactoring operation being performed on Rust source code. The changes involve removing `indoc!` macro declarations while preserving the CSS-like content within them. + +Previously, the same refactor is implemented by a _unreadable monster regex_ in vim syntax. + +:::details Click to see the original regex (neovim, btw) + +```vimscript +:%s/\v(indoc!|)(| )([|\{)r#"(([^#]+|\n+)+)"#/`\4` +``` +I have to confess that I don't understand this regex even if I use neovim, btw. + +Let Claude break it down piece by piece: + +- `:%s/` - Vim substitution command for all lines +- `\v` - Very magic mode in vim for simpler regex syntax +- `(indoc!|)` - First capture group: matches either "indoc!" or nothing +- `(| )` - Second capture group: matches either empty string or a space +- `([|\{)` - Third capture group: matches either `[` or `{` +- `r#"` - Matches literal `r#"` (Rust raw string delimiter) +- `(([^#]+|\n+)+)` - Fourth capture group (nested): + - `[^#]+` - One or more non-# characters + - `|\n+` - OR one or more newlines + - Outer `()+` makes this repeat one or more times +- `"#` - Matches the closing raw string delimiter +- \`\4\` - Replaces with the fourth capture group wrapped in backticks + +This regex is designed to find Rust raw string literals (possibly wrapped in `indoc!` macro), capture their content, and replace the entire match with just the content wrapped in backticks. It's more precise than my previous explanation and matches the pattern you're showing. + +::: + + +### Pattern + +```shell +ast-grep --pattern 'indoc! { r#"$$$A"# }' --rewrite '`$$$A`' sgtest.rs +``` + +### Example + + +```rs {2-6} +fn main() { + indoc! {r#" + .foo { + order: 1; + } + "#}; +} +``` + +### Diff + +```rs +fn main() { + indoc! {r#" // [!code --] + `.foo { // [!code ++] + order: 1; + } + "#}; // [!code --] + `; // [!code ++] +} +``` + +### Contributed by +[Zack in SF](https://x.com/zack_overflow) diff --git a/website/catalog/tsx/avoid-nested-links.md b/website/catalog/tsx/avoid-nested-links.md new file mode 100644 index 00000000..329845a1 --- /dev/null +++ b/website/catalog/tsx/avoid-nested-links.md @@ -0,0 +1,39 @@ +## Avoid nested links +* [Playground Link](https://ast-grep.github.io/playground.html#eyJtb2RlIjoiQ29uZmlnIiwibGFuZyI6InRzeCIsInF1ZXJ5IjoiaWYgKCRBKSB7ICQkJEIgfSIsInJld3JpdGUiOiJpZiAoISgkQSkpIHtcbiAgICByZXR1cm47XG59XG4kJCRCIiwic3RyaWN0bmVzcyI6InNtYXJ0Iiwic2VsZWN0b3IiOiIiLCJjb25maWciOiJpZDogbm8tbmVzdGVkLWxpbmtzXG5sYW5ndWFnZTogdHN4XG5zZXZlcml0eTogZXJyb3JcbnJ1bGU6XG4gIHBhdHRlcm46IDxhICQkJD4kJCRBPC9hPlxuICBoYXM6XG4gICAgcGF0dGVybjogPGEgJCQkPiQkJDwvYT5cbiAgICBzdG9wQnk6IGVuZCIsInNvdXJjZSI6ImZ1bmN0aW9uIENvbXBvbmVudCgpIHtcbiAgcmV0dXJuIDxhIGhyZWY9Jy9kZXN0aW5hdGlvbic+XG4gICAgPGEgaHJlZj0nL2Fub3RoZXJkZXN0aW5hdGlvbic+TmVzdGVkIGxpbmshPC9hPlxuICA8L2E+O1xufVxuZnVuY3Rpb24gT2theUNvbXBvbmVudCgpIHtcbiAgcmV0dXJuIDxhIGhyZWY9Jy9kZXN0aW5hdGlvbic+XG4gICAgSSBhbSBqdXN0IGEgbGluay5cbiAgPC9hPjtcbn0ifQ==) + +### Description + +React will produce a warning message if you nest a link element inside of another link element. This rule will catch this mistake! + + +### YAML + +```yaml +id: no-nested-links +language: tsx +severity: error +rule: + pattern: $$$A + has: + pattern: $$$ + stopBy: end +``` + +### Example + + +```tsx {1-5} +function Component() { + return + Nested link! + ; +} +function OkayComponent() { + return + I am just a link. + ; +} +``` + +### Contributed by +[Tom MacWright](https://macwright.com/) \ No newline at end of file diff --git a/website/catalog/tsx/index.md b/website/catalog/tsx/index.md index 92e043f1..da41d6ee 100644 --- a/website/catalog/tsx/index.md +++ b/website/catalog/tsx/index.md @@ -2,12 +2,17 @@ This page curates a list of example ast-grep rules to check and to rewrite TypeScript with JSX syntax. -:::danger TypeScript and TSX are different. -TypeScript is a typed JavaScript extension and TSX is a further extension that allows JSX elements. -They need different parsers because of [conflicting syntax](https://www.typescriptlang.org/docs/handbook/jsx.html#the-as-operator). +:::danger TSX and TypeScript are different. +TSX differs from TypeScript because it is an extension of the latter that supports JSX elements. +They need distinct parsers because of [conflicting syntax](https://www.typescriptlang.org/docs/handbook/jsx.html#the-as-operator). -TS allows both the `as` operator and angle brackets (`<>`) for type assertions. While TSX only allows the `as` operator because it interprets angle brackets as JSX elements. +In order to reduce rule duplication, you can use the [`languageGlobs`](/reference/sgconfig.html#languageglobs) option to force ast-grep to use parse `.ts` files as TSX. ::: + - \ No newline at end of file + + + + + \ No newline at end of file diff --git a/website/catalog/typescript/redundant-usestate-type.md b/website/catalog/tsx/redundant-usestate-type.md similarity index 91% rename from website/catalog/typescript/redundant-usestate-type.md rename to website/catalog/tsx/redundant-usestate-type.md index 318d26f2..d250b084 100644 --- a/website/catalog/typescript/redundant-usestate-type.md +++ b/website/catalog/tsx/redundant-usestate-type.md @@ -13,15 +13,15 @@ We can usually skip annotating if the generic type argument is a single primitiv ::: code-group ```bash [number] -sg -p 'useState($A)' -r 'useState($A)' -l ts +ast-grep -p 'useState($A)' -r 'useState($A)' -l tsx ``` ```bash [string] -sg -p 'useState($A)' -r 'useState($A)' +ast-grep -p 'useState($A)' -r 'useState($A)' ``` ```bash [boolean] -sg -p 'useState($A)' -r 'useState($A)' +ast-grep -p 'useState($A)' -r 'useState($A)' ``` ::: diff --git a/website/catalog/tsx/rename-svg-attribute.md b/website/catalog/tsx/rename-svg-attribute.md new file mode 100644 index 00000000..b86ea263 --- /dev/null +++ b/website/catalog/tsx/rename-svg-attribute.md @@ -0,0 +1,49 @@ +## Rename SVG Attribute + + +* [Playground Link](/playground.html#eyJtb2RlIjoiQ29uZmlnIiwibGFuZyI6InRzeCIsInF1ZXJ5IjoiIiwicmV3cml0ZSI6IiIsInN0cmljdG5lc3MiOiJyZWxheGVkIiwic2VsZWN0b3IiOiIiLCJjb25maWciOiJpZDogcmV3cml0ZS1zdmctYXR0cmlidXRlXG5sYW5ndWFnZTogdHN4XG5ydWxlOlxuICBwYXR0ZXJuOiAkUFJPUFxuICByZWdleDogKFthLXpdKyktKFthLXpdKVxuICBraW5kOiBwcm9wZXJ0eV9pZGVudGlmaWVyXG4gIGluc2lkZTpcbiAgICBraW5kOiBqc3hfYXR0cmlidXRlXG50cmFuc2Zvcm06XG4gIE5FV19QUk9QOlxuICAgIGNvbnZlcnQ6XG4gICAgICBzb3VyY2U6ICRQUk9QXG4gICAgICB0b0Nhc2U6IGNhbWVsQ2FzZVxuZml4OiAkTkVXX1BST1AiLCJzb3VyY2UiOiJjb25zdCBlbGVtZW50ID0gKFxuICA8c3ZnIHdpZHRoPVwiMTAwXCIgaGVpZ2h0PVwiMTAwXCIgdmlld0JveD1cIjAgMCAxMDAgMTAwXCI+XG4gICAgPHBhdGggZD1cIk0xMCAyMCBMMzAgNDBcIiBzdHJva2UtbGluZWNhcD1cInJvdW5kXCIgZmlsbC1vcGFjaXR5PVwiMC41XCIgLz5cbiAgPC9zdmc+XG4pIn0=) + +### Description + +[SVG](https://en.wikipedia.org/wiki/SVG)(Scalable Vector Graphics)s' hyphenated names are not compatible with JSX syntax in React. JSX requires [camelCase naming](https://react.dev/learn/writing-markup-with-jsx#3-camelcase-salls-most-of-the-things) for attributes. +For example, an SVG attribute like `stroke-linecap` needs to be renamed to `strokeLinecap` to work correctly in React. + +### YAML +```yaml +id: rewrite-svg-attribute +language: tsx +rule: + pattern: $PROP # capture in metavar + regex: ([a-z]+)-([a-z]) # hyphenated name + kind: property_identifier + inside: + kind: jsx_attribute # in JSX attribute +transform: + NEW_PROP: # new property name + convert: # use ast-grep's convert + source: $PROP + toCase: camelCase # to camelCase naming +fix: $NEW_PROP +``` + +### Example +```tsx {3} +const element = ( + + + +) +``` + +### Diff +```ts +const element = ( + + // [!code --] + // [!code ++] + +) +``` + +### Contributed by +Inspired by [SVG Renamer](https://admondtamang.medium.com/introducing-svg-renamer-your-solution-for-react-svg-attributes-26503382d5a8) \ No newline at end of file diff --git a/website/catalog/tsx/reverse-react-compiler.md b/website/catalog/tsx/reverse-react-compiler.md new file mode 100644 index 00000000..4c84b665 --- /dev/null +++ b/website/catalog/tsx/reverse-react-compiler.md @@ -0,0 +1,102 @@ +## Reverse React Compilerā„¢ + +* [Playground Link](/playground.html#eyJtb2RlIjoiQ29uZmlnIiwibGFuZyI6InRzeCIsInF1ZXJ5IjoiIiwicmV3cml0ZSI6IiIsInN0cmljdG5lc3MiOiJyZWxheGVkIiwic2VsZWN0b3IiOiIiLCJjb25maWciOiJpZDogcmV3cml0ZS1jYWNoZSBcbmxhbmd1YWdlOiB0c3hcbnJ1bGU6XG4gIGFueTpcbiAgLSBwYXR0ZXJuOiB1c2VDYWxsYmFjaygkRk4sICQkJClcbiAgLSBwYXR0ZXJuOiBtZW1vKCRGTiwgJCQkKVxuZml4OiAkRk5cblxuLS0tXG5cbmlkOiByZXdyaXRlLXVzZS1tZW1vXG5sYW5ndWFnZTogdHN4XG5ydWxlOiB7IHBhdHRlcm46ICd1c2VNZW1vKCRGTiwgJCQkKScgfVxuZml4OiAoJEZOKSgpIiwic291cmNlIjoiY29uc3QgQ29tcG9uZW50ID0gKCkgPT4ge1xuICBjb25zdCBbY291bnQsIHNldENvdW50XSA9IHVzZVN0YXRlKDApXG4gIGNvbnN0IGluY3JlbWVudCA9IHVzZUNhbGxiYWNrKCgpID0+IHtcbiAgICBzZXRDb3VudCgocHJldkNvdW50KSA9PiBwcmV2Q291bnQgKyAxKVxuICB9LCBbXSlcbiAgY29uc3QgZXhwZW5zaXZlQ2FsY3VsYXRpb24gPSB1c2VNZW1vKCgpID0+IHtcbiAgICAvLyBtb2NrIEV4cGVuc2l2ZSBjYWxjdWxhdGlvblxuICAgIHJldHVybiBjb3VudCAqIDJcbiAgfSwgW2NvdW50XSlcblxuICByZXR1cm4gKFxuICAgIDw+XG4gICAgICA8cD5FeHBlbnNpdmUgUmVzdWx0OiB7ZXhwZW5zaXZlQ2FsY3VsYXRpb259PC9wPlxuICAgICAgPGJ1dHRvbiBvbkNsaWNrPXtpbmNyZW1lbnR9Pntjb3VudH08L2J1dHRvbj5cbiAgICA8Lz5cbiAgKVxufSJ9) + +### Description + +React Compiler is a build-time only tool that automatically optimizes your React app, working with plain JavaScript and understanding the Rules of React without requiring a rewrite. It optimizes apps by automatically memoizing code, similar to `useMemo`, `useCallback`, and `React.memo`, reducing unnecessary recomputation due to incorrect or forgotten memoization. + + +Reverse React Compilerā„¢ is a [parody tweet](https://x.com/aidenybai/status/1881397529369034997) that works in the opposite direction. It takes React code and removes memoization, guaranteed to make your code slower. ([not](https://x.com/kentcdodds/status/1881404373646880997) [necessarily](https://dev.to/prathamisonline/are-you-over-using-usememo-and-usecallback-hooks-in-react-5lp)) + +It is originally written in Babel and this is an [ast-grep version](https://x.com/hd_nvim/status/1881402678493970620) of it. + +:::details The Original Babel Implementation +For comparison purposes only. Note the original code [does not correctly rewrite](https://x.com/hd_nvim/status/1881404893136896415) `useMemo`. +```js +const ReverseReactCompiler = ({ types: t }) => ({ + visitor: { + CallExpression(path) { + const callee = path.node.callee; + if ( + t.isIdentifier(callee, { name: "useMemo" }) || + t.isIdentifier(callee, { name: "useCallback" }) || + t.isIdentifier(callee, { name: "memo" }) + ) { + path.replaceWith(args[0]); + } + }, + }, +}); +``` +::: + +### YAML + +```yaml +id: rewrite-cache +language: tsx +rule: + any: + - pattern: useCallback($FN, $$$) + - pattern: memo($FN, $$$) +fix: $FN +--- +id: rewrite-use-memo +language: tsx +rule: { pattern: 'useMemo($FN, $$$)' } +fix: ($FN)() # need IIFE to wrap memo function +``` + +### Example + +```tsx {3-5,6-9} +const Component = () => { + const [count, setCount] = useState(0) + const increment = useCallback(() => { + setCount((prevCount) => prevCount + 1) + }, []) + const expensiveCalculation = useMemo(() => { + // mock Expensive calculation + return count * 2 + }, [count]) + + return ( + <> +

Expensive Result: {expensiveCalculation}

+ + + ) +} +``` + +### Diff +```tsx +const Component = () => { + const [count, setCount] = useState(0) + const increment = useCallback(() => { // [!code --] + setCount((prevCount) => prevCount + 1) // [!code --] + }, []) // [!code --] + const increment = () => { // [!code ++] + setCount((prevCount) => prevCount + 1) // [!code ++] + } // [!code ++] + const expensiveCalculation = useMemo(() => { // [!code --] + // mock Expensive calculation // [!code --] + return count * 2 // [!code --] + }, [count]) // [!code --] + const expensiveCalculation = (() => { // [!code ++] + // mock Expensive calculation // [!code ++] + return count * 2 // [!code ++] + })() // [!code ++] + return ( + <> +

Expensive Result: {expensiveCalculation}

+ + + ) +} +``` + +### Contributed by + +Inspired by [Aiden Bai](https://twitter.com/aidenybai) \ No newline at end of file diff --git a/website/catalog/tsx/unnecessary-react-hook.md b/website/catalog/tsx/unnecessary-react-hook.md new file mode 100644 index 00000000..0e9500e8 --- /dev/null +++ b/website/catalog/tsx/unnecessary-react-hook.md @@ -0,0 +1,58 @@ +## Avoid Unnecessary React Hook +* [Playground Link](/playground.html#eyJtb2RlIjoiQ29uZmlnIiwibGFuZyI6ImphdmFzY3JpcHQiLCJxdWVyeSI6IiIsInJld3JpdGUiOiIiLCJzdHJpY3RuZXNzIjoic21hcnQiLCJzZWxlY3RvciI6IiIsImNvbmZpZyI6InV0aWxzOlxuICBob29rX2NhbGw6XG4gICAgaGFzOlxuICAgICAga2luZDogY2FsbF9leHByZXNzaW9uXG4gICAgICByZWdleDogXnVzZVxuICAgICAgc3RvcEJ5OiBlbmRcbnJ1bGU6XG4gIGFueTpcbiAgLSBwYXR0ZXJuOiBmdW5jdGlvbiAkRlVOQygkJCQpIHsgJCQkIH1cbiAgLSBwYXR0ZXJuOiBsZXQgJEZVTkMgPSAoJCQkKSA9PiAkJCQgXG4gIC0gcGF0dGVybjogY29uc3QgJEZVTkMgPSAoJCQkKSA9PiAkJCRcbiAgaGFzOlxuICAgIHBhdHRlcm46ICRCT0RZXG4gICAga2luZDogc3RhdGVtZW50X2Jsb2NrXG4gICAgc3RvcEJ5OiBlbmQgXG5jb25zdHJhaW50czpcbiAgRlVOQzoge3JlZ2V4OiBedXNlIH1cbiAgQk9EWTogeyBub3Q6IHsgbWF0Y2hlczogaG9va19jYWxsIH0gfSBcbiIsInNvdXJjZSI6ImZ1bmN0aW9uIHVzZUlBbU5vdEhvb2tBY3R1YWxseShhcmdzKSB7XG4gICAgY29uc29sZS5sb2coJ0NhbGxlZCBpbiBSZWFjdCBidXQgSSBkb250IG5lZWQgdG8gYmUgYSBob29rJylcbiAgICByZXR1cm4gYXJncy5sZW5ndGhcbn1cbmNvbnN0IHVzZUlBbU5vdEhvb2tUb28gPSAoLi4uYXJncykgPT4ge1xuICAgIGNvbnNvbGUubG9nKCdDYWxsZWQgaW4gUmVhY3QgYnV0IEkgZG9udCBuZWVkIHRvIGJlIGEgaG9vaycpXG4gICAgcmV0dXJuIGFyZ3MubGVuZ3RoXG59XG5cbmZ1bmN0aW9uIHVzZUhvb2soKSB7XG4gICAgdXNlRWZmZWN0KCgpID0+IHtcbiAgICAgIGNvbnNvbGUubG9nKCdSZWFsIGhvb2snKSAgIFxuICAgIH0pXG59In0=) + +### Description + +React hook is a powerful feature in React that allows you to use state and other React features in a functional component. + +However, you should avoid using hooks when you don't need them. If the code does not contain using any other React hooks, +it can be rewritten to a plain function. This can help to separate your application logic from the React-specific UI logic. + + +### YAML + +```yaml +id: unnecessary-react-hook +language: Tsx +utils: + hook_call: + has: + kind: call_expression + regex: ^use + stopBy: end +rule: + any: + - pattern: function $FUNC($$$) { $$$ } + - pattern: let $FUNC = ($$$) => $$$ + - pattern: const $FUNC = ($$$) => $$$ + has: + pattern: $BODY + kind: statement_block + stopBy: end +constraints: + FUNC: {regex: ^use } + BODY: { not: { matches: hook_call } } +``` + +### Example + + +```tsx {1-8} +function useIAmNotHookActually(args) { + console.log('Called in React but I dont need to be a hook') + return args.length +} +const useIAmNotHookToo = (...args) => { + console.log('Called in React but I dont need to be a hook') + return args.length +} + +function useTrueHook() { + useEffect(() => { + console.log('Real hook') + }) +} +``` + +### Contributed by +[Herrington Darkholme](https://twitter.com/hd_nvim) diff --git a/website/catalog/typescript/find-import-identifiers.md b/website/catalog/typescript/find-import-identifiers.md new file mode 100644 index 00000000..8be3fd43 --- /dev/null +++ b/website/catalog/typescript/find-import-identifiers.md @@ -0,0 +1,300 @@ +## Find Import Identifiers + +* [Playground Link](https://ast-grep.github.io/playground.html#eyJtb2RlIjoiQ29uZmlnIiwibGFuZyI6InR5cGVzY3JpcHQiLCJxdWVyeSI6ImNvbnNvbGUubG9nKCRNQVRDSCkiLCJyZXdyaXRlIjoibG9nZ2VyLmxvZygkTUFUQ0gpIiwic3RyaWN0bmVzcyI6InNtYXJ0Iiwic2VsZWN0b3IiOiIiLCJjb25maWciOiIjIGZpbmQtYWxsLWltcG9ydHMtYW5kLXJlcXVpcmVzLnlhbWxcbmlkOiBmaW5kLWFsbC1pbXBvcnRzLWFuZC1yZXF1aXJlc1xubGFuZ3VhZ2U6IFR5cGVTY3JpcHRcbm1lc3NhZ2U6IEZvdW5kIG1vZHVsZSBpbXBvcnQgb3IgcmVxdWlyZS5cbnNldmVyaXR5OiBpbmZvXG5ydWxlOlxuICBhbnk6XG4gICAgIyBBTElBUyBJTVBPUlRTXG4gICAgIyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS1cbiAgICAjIGltcG9ydCB7IE9SSUdJTkFMIGFzIEFMSUFTIH0gZnJvbSAnU09VUkNFJ1xuICAgICMgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXG4gICAgLSBhbGw6XG4gICAgICAgICMgMS4gVGFyZ2V0IHRoZSBzcGVjaWZpYyBub2RlIHR5cGUgZm9yIG5hbWVkIGltcG9ydHNcbiAgICAgICAgLSBraW5kOiBpbXBvcnRfc3BlY2lmaWVyXG4gICAgICAgICMgMi4gRW5zdXJlIGl0ICpoYXMqIGFuICdhbGlhcycgZmllbGQsIGNhcHR1cmluZyB0aGUgYWxpYXMgaWRlbnRpZmllclxuICAgICAgICAtIGhhczpcbiAgICAgICAgICAgIGZpZWxkOiBhbGlhc1xuICAgICAgICAgICAgcGF0dGVybjogJEFMSUFTXG4gICAgICAgICMgMy4gQ2FwdHVyZSB0aGUgb3JpZ2luYWwgaWRlbnRpZmllciAod2hpY2ggaGFzIHRoZSAnbmFtZScgZmllbGQpXG4gICAgICAgIC0gaGFzOlxuICAgICAgICAgICAgZmllbGQ6IG5hbWVcbiAgICAgICAgICAgIHBhdHRlcm46ICRPUklHSU5BTFxuICAgICAgICAjIDQuIEZpbmQgYW4gQU5DRVNUT1IgaW1wb3J0X3N0YXRlbWVudCBhbmQgY2FwdHVyZSBpdHMgc291cmNlIHBhdGhcbiAgICAgICAgLSBpbnNpZGU6XG4gICAgICAgICAgICBzdG9wQnk6IGVuZCAjIDw8PC0tLSBUaGlzIGlzIHRoZSBrZXkgZml4ISBTZWFyY2ggYW5jZXN0b3JzLlxuICAgICAgICAgICAga2luZDogaW1wb3J0X3N0YXRlbWVudFxuICAgICAgICAgICAgaGFzOiAjIEVuc3VyZSB0aGUgZm91bmQgaW1wb3J0X3N0YXRlbWVudCBoYXMgdGhlIHNvdXJjZSBmaWVsZFxuICAgICAgICAgICAgICBmaWVsZDogc291cmNlXG4gICAgICAgICAgICAgIHBhdHRlcm46ICRTT1VSQ0VcblxuICAgICMgREVGQVVMVCBJTVBPUlRTXG4gICAgIyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS1cbiAgICAjIGltcG9ydCB7IE9SSUdJTkFMIH0gZnJvbSAnU09VUkNFJ1xuICAgICMgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXG4gICAgLSBhbGw6XG4gICAgICAgIC0ga2luZDogaW1wb3J0X3N0YXRlbWVudFxuICAgICAgICAtIGhhczpcbiAgICAgICAgICAgICMgRW5zdXJlIGl0IGhhcyBhbiBpbXBvcnRfY2xhdXNlLi4uXG4gICAgICAgICAgICBraW5kOiBpbXBvcnRfY2xhdXNlXG4gICAgICAgICAgICBoYXM6XG4gICAgICAgICAgICAgICMgLi4udGhhdCBkaXJlY3RseSBjb250YWlucyBhbiBpZGVudGlmaWVyICh0aGUgZGVmYXVsdCBpbXBvcnQgbmFtZSlcbiAgICAgICAgICAgICAgIyBUaGlzIGlkZW50aWZpZXIgaXMgTk9UIHVuZGVyIGEgJ25hbWVkX2ltcG9ydHMnIG9yICduYW1lc3BhY2VfaW1wb3J0JyBub2RlXG4gICAgICAgICAgICAgIGtpbmQ6IGlkZW50aWZpZXJcbiAgICAgICAgICAgICAgcGF0dGVybjogJERFRkFVTFRfTkFNRVxuICAgICAgICAtIGhhczpcbiAgICAgICAgICAgIGZpZWxkOiBzb3VyY2VcbiAgICAgICAgICAgIHBhdHRlcm46ICRTT1VSQ0VcbiAgICBcbiAgICAjIFJFR1VMQVIgSU1QT1JUU1xuICAgICMgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXG4gICAgIyBpbXBvcnQgeyBPUklHSU5BTCB9IGZyb20gJ1NPVVJDRSdcbiAgICAjIC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxuICAgIC0gYWxsOlxuICAgICAgICAjIDEuIFRhcmdldCB0aGUgc3BlY2lmaWMgbm9kZSB0eXBlIGZvciBuYW1lZCBpbXBvcnRzXG4gICAgICAgIC0ga2luZDogaW1wb3J0X3NwZWNpZmllclxuICAgICAgICAjIDIuIEVuc3VyZSBpdCAqaGFzKiBhbiAnYWxpYXMnIGZpZWxkLCBjYXB0dXJpbmcgdGhlIGFsaWFzIGlkZW50aWZpZXJcbiAgICAgICAgLSBoYXM6XG4gICAgICAgICAgICBmaWVsZDogbmFtZVxuICAgICAgICAgICAgcGF0dGVybjogJE9SSUdJTkFMXG4gICAgICAgICMgNC4gRmluZCBhbiBBTkNFU1RPUiBpbXBvcnRfc3RhdGVtZW50IGFuZCBjYXB0dXJlIGl0cyBzb3VyY2UgcGF0aFxuICAgICAgICAtIGluc2lkZTpcbiAgICAgICAgICAgIHN0b3BCeTogZW5kICMgPDw8LS0tIFRoaXMgaXMgdGhlIGtleSBmaXghIFNlYXJjaCBhbmNlc3RvcnMuXG4gICAgICAgICAgICBraW5kOiBpbXBvcnRfc3RhdGVtZW50XG4gICAgICAgICAgICBoYXM6ICMgRW5zdXJlIHRoZSBmb3VuZCBpbXBvcnRfc3RhdGVtZW50IGhhcyB0aGUgc291cmNlIGZpZWxkXG4gICAgICAgICAgICAgIGZpZWxkOiBzb3VyY2VcbiAgICAgICAgICAgICAgcGF0dGVybjogJFNPVVJDRVxuXG4gICAgIyBEWU5BTUlDIElNUE9SVFMgKFNpbmdsZSBWYXJpYWJsZSBBc3NpZ25tZW50KSBcbiAgICAjIC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxuICAgICMgZWc6IChjb25zdCBWQVJfTkFNRSA9IHJlcXVpcmUoJ1NPVVJDRScpKVxuICAgICMgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXG4gICAgLSBhbGw6XG4gICAgICAgIC0ga2luZDogdmFyaWFibGVfZGVjbGFyYXRvclxuICAgICAgICAtIGhhczpcbiAgICAgICAgICAgIGZpZWxkOiBuYW1lXG4gICAgICAgICAgICBraW5kOiBpZGVudGlmaWVyXG4gICAgICAgICAgICBwYXR0ZXJuOiAkVkFSX05BTUUgIyBDYXB0dXJlIHRoZSBzaW5nbGUgdmFyaWFibGUgbmFtZVxuICAgICAgICAtIGhhczpcbiAgICAgICAgICAgIGZpZWxkOiB2YWx1ZVxuICAgICAgICAgICAgYW55OlxuICAgICAgICAgICAgICAjIERpcmVjdCBjYWxsXG4gICAgICAgICAgICAgIC0gYWxsOiAjIFdyYXAgY29uZGl0aW9ucyBpbiBhbGxcbiAgICAgICAgICAgICAgICAgIC0ga2luZDogY2FsbF9leHByZXNzaW9uXG4gICAgICAgICAgICAgICAgICAtIGhhczogeyBmaWVsZDogZnVuY3Rpb24sIHJlZ2V4OiAnXihyZXF1aXJlfGltcG9ydCkkJyB9XG4gICAgICAgICAgICAgICAgICAtIGhhczogeyBmaWVsZDogYXJndW1lbnRzLCBoYXM6IHsga2luZDogc3RyaW5nLCBwYXR0ZXJuOiAkU09VUkNFIH0gfSAjIENhcHR1cmUgc291cmNlXG4gICAgICAgICAgICAgICMgQXdhaXRlZCBjYWxsXG4gICAgICAgICAgICAgIC0ga2luZDogYXdhaXRfZXhwcmVzc2lvblxuICAgICAgICAgICAgICAgIGhhczpcbiAgICAgICAgICAgICAgICAgIGFsbDogIyBXcmFwIGNvbmRpdGlvbnMgaW4gYWxsXG4gICAgICAgICAgICAgICAgICAgIC0ga2luZDogY2FsbF9leHByZXNzaW9uXG4gICAgICAgICAgICAgICAgICAgIC0gaGFzOiB7IGZpZWxkOiBmdW5jdGlvbiwgcmVnZXg6ICdeKHJlcXVpcmV8aW1wb3J0KSQnIH1cbiAgICAgICAgICAgICAgICAgICAgLSBoYXM6IHsgZmllbGQ6IGFyZ3VtZW50cywgaGFzOiB7IGtpbmQ6IHN0cmluZywgcGF0dGVybjogJFNPVVJDRSB9IH0gIyBDYXB0dXJlIHNvdXJjZVxuXG4gICAgIyBEWU5BTUlDIElNUE9SVFMgKERlc3RydWN0dXJlZCBTaG9ydGhhbmQgQXNzaWdubWVudCkgICAgIFxuICAgICMgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXG4gICAgIyBlZzogKGNvbnN0IHsgT1JJR0lOQUwgfSA9IHJlcXVpcmUoJ1NPVVJDRScpKVxuICAgICMgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXG4gICAgLSBhbGw6XG4gICAgICAgICMgMS4gVGFyZ2V0IHRoZSBzaG9ydGhhbmQgaWRlbnRpZmllciB3aXRoaW4gdGhlIHBhdHRlcm5cbiAgICAgICAgLSBraW5kOiBzaG9ydGhhbmRfcHJvcGVydHlfaWRlbnRpZmllcl9wYXR0ZXJuXG4gICAgICAgIC0gcGF0dGVybjogJE9SSUdJTkFMXG4gICAgICAgICMgMi4gRW5zdXJlIGl0J3MgaW5zaWRlIGFuIG9iamVjdF9wYXR0ZXJuIHRoYXQgaXMgdGhlIG5hbWUgb2YgYSB2YXJpYWJsZV9kZWNsYXJhdG9yXG4gICAgICAgIC0gaW5zaWRlOlxuICAgICAgICAgICAga2luZDogb2JqZWN0X3BhdHRlcm5cbiAgICAgICAgICAgIGluc2lkZTogIyBDaGVjayB0aGUgdmFyaWFibGVfZGVjbGFyYXRvciBpdCBiZWxvbmdzIHRvXG4gICAgICAgICAgICAgIGtpbmQ6IHZhcmlhYmxlX2RlY2xhcmF0b3JcbiAgICAgICAgICAgICAgIyAzLiBDaGVjayB0aGUgdmFsdWUgYXNzaWduZWQgYnkgdGhlIHZhcmlhYmxlX2RlY2xhcmF0b3JcbiAgICAgICAgICAgICAgaGFzOlxuICAgICAgICAgICAgICAgIGZpZWxkOiB2YWx1ZVxuICAgICAgICAgICAgICAgIGFueTpcbiAgICAgICAgICAgICAgICAgICMgRGlyZWN0IGNhbGxcbiAgICAgICAgICAgICAgICAgIC0gYWxsOlxuICAgICAgICAgICAgICAgICAgICAgIC0ga2luZDogY2FsbF9leHByZXNzaW9uXG4gICAgICAgICAgICAgICAgICAgICAgLSBoYXM6IHsgZmllbGQ6IGZ1bmN0aW9uLCByZWdleDogJ14ocmVxdWlyZXxpbXBvcnQpJCcgfVxuICAgICAgICAgICAgICAgICAgICAgIC0gaGFzOiB7IGZpZWxkOiBhcmd1bWVudHMsIGhhczogeyBraW5kOiBzdHJpbmcsIHBhdHRlcm46ICRTT1VSQ0UgfSB9ICMgQ2FwdHVyZSBzb3VyY2VcbiAgICAgICAgICAgICAgICAgICMgQXdhaXRlZCBjYWxsXG4gICAgICAgICAgICAgICAgICAtIGtpbmQ6IGF3YWl0X2V4cHJlc3Npb25cbiAgICAgICAgICAgICAgICAgICAgaGFzOlxuICAgICAgICAgICAgICAgICAgICAgIGFsbDpcbiAgICAgICAgICAgICAgICAgICAgICAgIC0ga2luZDogY2FsbF9leHByZXNzaW9uXG4gICAgICAgICAgICAgICAgICAgICAgICAtIGhhczogeyBmaWVsZDogZnVuY3Rpb24sIHJlZ2V4OiAnXihyZXF1aXJlfGltcG9ydCkkJyB9XG4gICAgICAgICAgICAgICAgICAgICAgICAtIGhhczogeyBmaWVsZDogYXJndW1lbnRzLCBoYXM6IHsga2luZDogc3RyaW5nLCBwYXR0ZXJuOiAkU09VUkNFIH0gfSAjIENhcHR1cmUgc291cmNlXG4gICAgICAgICAgICAgIHN0b3BCeTogZW5kICMgU2VhcmNoIGFuY2VzdG9ycyB0byBmaW5kIHRoZSBjb3JyZWN0IHZhcmlhYmxlX2RlY2xhcmF0b3JcblxuICAgICMgRFlOQU1JQyBJTVBPUlRTIChEZXN0cnVjdHVyZWQgQWxpYXMgQXNzaWdubWVudCkgXG4gICAgIyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS1cbiAgICAjIGVnOiAoY29uc3QgeyBPUklHSU5BTDogQUxJQVMgfSA9IHJlcXVpcmUoJ1NPVVJDRScpKVxuICAgICMgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXG4gICAgLSBhbGw6XG4gICAgICAgICMgMS4gVGFyZ2V0IHRoZSBwYWlyX3BhdHRlcm4gZm9yIGFsaWFzZWQgZGVzdHJ1Y3R1cmluZ1xuICAgICAgICAtIGtpbmQ6IHBhaXJfcGF0dGVyblxuICAgICAgICAjIDIuIENhcHR1cmUgdGhlIG9yaWdpbmFsIGlkZW50aWZpZXIgKGtleSlcbiAgICAgICAgLSBoYXM6XG4gICAgICAgICAgICBmaWVsZDoga2V5XG4gICAgICAgICAgICBraW5kOiBwcm9wZXJ0eV9pZGVudGlmaWVyICMgQ291bGQgYmUgc3RyaW5nL251bWJlciBsaXRlcmFsIHRvbywgYnV0IHByb3BlcnR5X2lkZW50aWZpZXIgaXMgY29tbW9uXG4gICAgICAgICAgICBwYXR0ZXJuOiAkT1JJR0lOQUxcbiAgICAgICAgIyAzLiBDYXB0dXJlIHRoZSBhbGlhcyBpZGVudGlmaWVyICh2YWx1ZSlcbiAgICAgICAgLSBoYXM6XG4gICAgICAgICAgICBmaWVsZDogdmFsdWVcbiAgICAgICAgICAgIGtpbmQ6IGlkZW50aWZpZXJcbiAgICAgICAgICAgIHBhdHRlcm46ICRBTElBU1xuICAgICAgICAjIDQuIEVuc3VyZSBpdCdzIGluc2lkZSBhbiBvYmplY3RfcGF0dGVybiB0aGF0IGlzIHRoZSBuYW1lIG9mIGEgdmFyaWFibGVfZGVjbGFyYXRvclxuICAgICAgICAtIGluc2lkZTpcbiAgICAgICAgICAgIGtpbmQ6IG9iamVjdF9wYXR0ZXJuXG4gICAgICAgICAgICBpbnNpZGU6ICMgQ2hlY2sgdGhlIHZhcmlhYmxlX2RlY2xhcmF0b3IgaXQgYmVsb25ncyB0b1xuICAgICAgICAgICAgICBraW5kOiB2YXJpYWJsZV9kZWNsYXJhdG9yXG4gICAgICAgICAgICAgICMgNS4gQ2hlY2sgdGhlIHZhbHVlIGFzc2lnbmVkIGJ5IHRoZSB2YXJpYWJsZV9kZWNsYXJhdG9yXG4gICAgICAgICAgICAgIGhhczpcbiAgICAgICAgICAgICAgICBmaWVsZDogdmFsdWVcbiAgICAgICAgICAgICAgICBhbnk6XG4gICAgICAgICAgICAgICAgICAjIERpcmVjdCBjYWxsXG4gICAgICAgICAgICAgICAgICAtIGFsbDpcbiAgICAgICAgICAgICAgICAgICAgICAtIGtpbmQ6IGNhbGxfZXhwcmVzc2lvblxuICAgICAgICAgICAgICAgICAgICAgIC0gaGFzOiB7IGZpZWxkOiBmdW5jdGlvbiwgcmVnZXg6ICdeKHJlcXVpcmV8aW1wb3J0KSQnIH1cbiAgICAgICAgICAgICAgICAgICAgICAtIGhhczogeyBmaWVsZDogYXJndW1lbnRzLCBoYXM6IHsga2luZDogc3RyaW5nLCBwYXR0ZXJuOiAkU09VUkNFIH0gfSAjIENhcHR1cmUgc291cmNlXG4gICAgICAgICAgICAgICAgICAjIEF3YWl0ZWQgY2FsbFxuICAgICAgICAgICAgICAgICAgLSBraW5kOiBhd2FpdF9leHByZXNzaW9uXG4gICAgICAgICAgICAgICAgICAgIGhhczpcbiAgICAgICAgICAgICAgICAgICAgICBhbGw6XG4gICAgICAgICAgICAgICAgICAgICAgICAtIGtpbmQ6IGNhbGxfZXhwcmVzc2lvblxuICAgICAgICAgICAgICAgICAgICAgICAgLSBoYXM6IHsgZmllbGQ6IGZ1bmN0aW9uLCByZWdleDogJ14ocmVxdWlyZXxpbXBvcnQpJCcgfVxuICAgICAgICAgICAgICAgICAgICAgICAgLSBoYXM6IHsgZmllbGQ6IGFyZ3VtZW50cywgaGFzOiB7IGtpbmQ6IHN0cmluZywgcGF0dGVybjogJFNPVVJDRSB9IH0gIyBDYXB0dXJlIHNvdXJjZVxuICAgICAgICAgICAgICBzdG9wQnk6IGVuZCAjIFNlYXJjaCBhbmNlc3RvcnMgdG8gZmluZCB0aGUgY29ycmVjdCB2YXJpYWJsZV9kZWNsYXJhdG9yXG4gICAgICAgICAgICBzdG9wQnk6IGVuZCAjIEVuc3VyZSB3ZSBjaGVjayBhbmNlc3RvcnMgZm9yIHRoZSB2YXJpYWJsZV9kZWNsYXJhdG9yXG5cbiAgICAjIERZTkFNSUMgSU1QT1JUUyAoU2lkZSBFZmZlY3QgLyBTb3VyY2UgT25seSkgXG4gICAgIyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS1cbiAgICAjIGVnOiAocmVxdWlyZSgnU09VUkNFJykpXG4gICAgIyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS1cbiAgICAtIGFsbDpcbiAgICAgICAgLSBraW5kOiBzdHJpbmcgIyBUYXJnZXQgdGhlIHNvdXJjZSBzdHJpbmcgbGl0ZXJhbCBkaXJlY3RseVxuICAgICAgICAtIHBhdHRlcm46ICRTT1VSQ0VcbiAgICAgICAgLSBpbnNpZGU6ICMgU3RyaW5nIG11c3QgYmUgdGhlIGFyZ3VtZW50IG9mIHJlcXVpcmUoKSBvciBpbXBvcnQoKVxuICAgICAgICAgICAga2luZDogYXJndW1lbnRzXG4gICAgICAgICAgICBwYXJlbnQ6XG4gICAgICAgICAgICAgIGtpbmQ6IGNhbGxfZXhwcmVzc2lvblxuICAgICAgICAgICAgICBoYXM6XG4gICAgICAgICAgICAgICAgZmllbGQ6IGZ1bmN0aW9uXG4gICAgICAgICAgICAgICAgIyBNYXRjaCAncmVxdWlyZScgaWRlbnRpZmllciBvciAnaW1wb3J0JyBrZXl3b3JkIHVzZWQgZHluYW1pY2FsbHlcbiAgICAgICAgICAgICAgICByZWdleDogJ14ocmVxdWlyZXxpbXBvcnQpJCdcbiAgICAgICAgICAgIHN0b3BCeTogZW5kICMgU2VhcmNoIGFuY2VzdG9ycyBpZiBuZWVkZWQgKGZvciB0aGUgYXJndW1lbnRzL2NhbGxfZXhwcmVzc2lvbilcbiAgICAgICAgLSBub3Q6XG4gICAgICAgICAgICBpbnNpZGU6XG4gICAgICAgICAgICAgIGtpbmQ6IGxleGljYWxfZGVjbGFyYXRpb25cbiAgICAgICAgICAgICAgc3RvcEJ5OiBlbmQgIyBTZWFyY2ggYWxsIGFuY2VzdG9ycyB1cCB0byB0aGUgcm9vdFxuXG4gICAgIyBOQU1FU1BBQ0UgSU1QT1JUUyBcbiAgICAjIC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxuICAgICMgZWc6IChpbXBvcnQgKiBhcyBucyBmcm9tICdtb2QnKVxuICAgICMgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXG4gICAgLSBhbGw6XG4gICAgICAgIC0ga2luZDogaW1wb3J0X3N0YXRlbWVudFxuICAgICAgICAtIGhhczpcbiAgICAgICAgICAgIGtpbmQ6IGltcG9ydF9jbGF1c2VcbiAgICAgICAgICAgIGhhczpcbiAgICAgICAgICAgICAga2luZDogbmFtZXNwYWNlX2ltcG9ydFxuICAgICAgICAgICAgICBoYXM6XG4gICAgICAgICAgICAgICAgIyBuYW1lc3BhY2VfaW1wb3J0J3MgY2hpbGQgaWRlbnRpZmllciBpcyB0aGUgYWxpYXNcbiAgICAgICAgICAgICAgICBraW5kOiBpZGVudGlmaWVyXG4gICAgICAgICAgICAgICAgcGF0dGVybjogJE5BTUVTUEFDRV9BTElBU1xuICAgICAgICAtIGhhczpcbiAgICAgICAgICAgIGZpZWxkOiBzb3VyY2VcbiAgICAgICAgICAgIHBhdHRlcm46ICRTT1VSQ0VcblxuICAgICMgU0lERSBFRkZFQ1QgSU1QT1JUUyBcbiAgICAjIC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxuICAgICMgZWc6IChpbXBvcnQgJ21vZCcpXG4gICAgIyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS1cbiAgICAtIGFsbDpcbiAgICAgICAgLSBraW5kOiBpbXBvcnRfc3RhdGVtZW50XG4gICAgICAgIC0gbm90OiAjIE11c3QgTk9UIGhhdmUgYW4gaW1wb3J0X2NsYXVzZVxuICAgICAgICAgICAgaGFzOiB7IGtpbmQ6IGltcG9ydF9jbGF1c2UgfVxuICAgICAgICAtIGhhczogIyBCdXQgbXVzdCBoYXZlIGEgc291cmNlXG4gICAgICAgICAgICBmaWVsZDogc291cmNlXG4gICAgICAgICAgICBwYXR0ZXJuOiAkU09VUkNFXG4iLCJzb3VyY2UiOiIvL0B0cy1ub2NoZWNrXG4vLyBOYW1lZCBpbXBvcnRcbmltcG9ydCB7IHRlc3RpbmcgfSBmcm9tICcuL3Rlc3RzJztcblxuLy8gQWxpYXNlZCBpbXBvcnRcbmltcG9ydCB7IHRlc3RpbmcgYXMgdGVzdCB9IGZyb20gJy4vdGVzdHMyJztcblxuLy8gRGVmYXVsdCBpbXBvcnRcbmltcG9ydCBoZWxsbyBmcm9tICdoZWxsb193b3JsZDEnO1xuXG4vLyBOYW1lc3BhY2UgaW1wb3J0XG5pbXBvcnQgKiBhcyBzb21ldGhpbmcgZnJvbSAnaGVsbG9fd29ybGQyJztcblxuLy8gU2lkZS1lZmZlY3QgaW1wb3J0XG5pbXBvcnQgJ0BmYXN0aWZ5L3N0YXRpYyc7XG5cbi8vIFR5cGUgaW1wb3J0XG5pbXBvcnQge3R5cGUgaGVsbG8xMjQzIGFzIHRlc3Rpbmd9IGZyb20gJ2hlbGxvJztcblxuLy8gUmVxdWlyZSBwYXR0ZXJuc1xuY29uc3QgbW9kID0gcmVxdWlyZSgnc29tZS1tb2R1bGUnKTtcbnJlcXVpcmUoJ3BvbHlmaWxsJyk7XG5cbi8vIERlc3RydWN0dXJlZCByZXF1aXJlXG5jb25zdCB7IHRlc3QxMjIsIHRlc3QyIH0gPSByZXF1aXJlKCcuL2Rlc3RydWN0dXJlZDEnKTtcbi8vIEFsaWFzZWQgcmVxdWlyZVxuY29uc3QgeyB0ZXN0MTIyOiB0ZXN0MTIzLCB0ZXN0MjogdGVzdDIzLCB0ZXN0MzogdGVzdDMzIH0gPSByZXF1aXJlKCcuL2Rlc3RydWN0dXJlZDInKTtcblxuLy8gTWl4ZWQgaW1wb3J0c1xuaW1wb3J0IGRlZmF1bHRFeHBvcnQsIHsgbmFtZWRFeHBvcnQgfSBmcm9tICcuL21peGVkJztcbmltcG9ydCBkZWZhdWx0RXhwb3J0MiwgKiBhcyBuYW1lc3BhY2UgZnJvbSAnLi9taXhlZDInO1xuXG5cbi8vIE11bHRpcGxlIGltcG9ydCBsaW5lcyBmcm9tIHRoZSBzYW1lIGZpbGVcbmltcG9ydCB7IG9uZSwgdHdvIGFzIGFsaWFzLCB0aHJlZSB9IGZyb20gJy4vbXVsdGlwbGUnO1xuaW1wb3J0IHsgbmV2ZXIsIGdvbm5hLCBnaXZlLCB5b3UsIHVwIH0gZnJvbSAnLi9tdWx0aXBsZSc7XG5cbi8vIFN0cmluZyBsaXRlcmFsIHZhcmlhdGlvbnNcbmltcG9ydCB7IHRlc3QxIH0gZnJvbSBcIi4vZG91YmxlLXF1b3RlZFwiO1xuaW1wb3J0IHsgdGVzdDIgfSBmcm9tICcuL3NpbmdsZS1xdW90ZWQnO1xuXG4vLyBNdWx0aWxpbmUgaW1wb3J0c1xuaW1wb3J0IHtcbiAgICBsb25nSW1wb3J0MSxcbiAgICBsb25nSW1wb3J0MiBhcyBhbGlhczIsXG4gICAgbG9uZ0ltcG9ydDNcbn0gZnJvbSAnLi9tdWx0aWxpbmUnO1xuXG4vLyBEeW5hbWljIGltcG9ydHNcbmNvbnN0IGR5bmFtaWNNb2R1bGUgPSBpbXBvcnQoJy4vZHluYW1pYzEnKTtcbmNvbnN0IHt0ZXN0aW5nLCB0ZXN0aW5nMTIzfSA9IGltcG9ydCgnLi9keW5hbWljMicpO1xuY29uc3QgYXN5bmNEeW5hbWljTW9kdWxlID0gYXdhaXQgaW1wb3J0KCcuL2FzeW5jX2R5bmFtaWMxJykudGhlbihtb2R1bGUgPT4gbW9kdWxlLmRlZmF1bHQpO1xuLy8gQWxpYXNlZCBkeW5hbWljIGltcG9ydFxuY29uc3QgeyBvcmlnaW5hbElkZW50aWZpZXI6IGFsaWFzZWREeW5hbWljSW1wb3J0fSA9IGF3YWl0IGltcG9ydCgnLi9hc3luY19keW5hbWljMicpO1xuXG4vLyBDb21tZW50cyBpbiBpbXBvcnRzXG5pbXBvcnQgLyogdGVzdCAqLyB7IFxuICAgIC8vIENvbW1lbnQgaW4gaW1wb3J0XG4gICAgY29tbWVudGVkSW1wb3J0IFxufSBmcm9tICcuL2NvbW1lbnRlZCc7IC8vIEVuZCBvZiBsaW5lIGNvbW1lbnQgXG5cblxuIn0=) + +### Description + +Finding import metadata can be useful. Below is a comprehensive snippet for extracting identifiers from various import statements: + +* Alias Imports (`import { hello as world } from './file'`) +* Default & Regular Imports (`import test from './my-test`') +* Dynamic Imports (`require(...)`, and `import(...)`) +* Side Effect & Namespace Imports (`import * as myCode from './code`') + + +### YAML +```yaml +# find-all-imports-and-identifiers.yaml +id: find-all-imports-and-identifiers +language: TypeScript +rule: + any: + # ALIAS IMPORTS + # ------------------------------------------------------------ + # import { ORIGINAL as ALIAS } from 'SOURCE' + # ------------------------------------------------------------ + - all: + # 1. Target the specific node type for named imports + - kind: import_specifier + # 2. Ensure it *has* an 'alias' field, capturing the alias identifier + - has: + field: alias + pattern: $ALIAS + # 3. Capture the original identifier (which has the 'name' field) + - has: + field: name + pattern: $ORIGINAL + # 4. Find an ANCESTOR import_statement and capture its source path + - inside: + stopBy: end # <<<--- Search ancestors. + kind: import_statement + has: # Ensure the found import_statement has the source field + field: source + pattern: $SOURCE + + # DEFAULT IMPORTS + # ------------------------------------------------------------ + # import { ORIGINAL } from 'SOURCE' + # ------------------------------------------------------------ + - all: + - kind: import_statement + - has: + # Ensure it has an import_clause... + kind: import_clause + has: + # ...that directly contains an identifier (the default import name) + # This identifier is NOT under a 'named_imports' or 'namespace_import' node + kind: identifier + pattern: $DEFAULT_NAME + - has: + field: source + pattern: $SOURCE + + # REGULAR IMPORTS + # ------------------------------------------------------------ + # import { ORIGINAL } from 'SOURCE' + # ------------------------------------------------------------ + - all: + # 1. Target the specific node type for named imports + - kind: import_specifier + # 2. Ensure it *has* an 'alias' field, capturing the alias identifier + - has: + field: name + pattern: $ORIGINAL + # 4. Find an ANCESTOR import_statement and capture its source path + - inside: + stopBy: end # <<<--- This is the key fix! Search ancestors. + kind: import_statement + has: # Ensure the found import_statement has the source field + field: source + pattern: $SOURCE + + # DYNAMIC IMPORTS (Single Variable Assignment) + # ------------------------------------------------------------ + # const VAR_NAME = require('SOURCE') + # ------------------------------------------------------------ + - all: + - kind: variable_declarator + - has: + field: name + kind: identifier + pattern: $VAR_NAME # Capture the single variable name + - has: + field: value + any: + # Direct call + - all: # Wrap conditions in all + - kind: call_expression + - has: { field: function, regex: '^(require|import)$' } + - has: { field: arguments, has: { kind: string, pattern: $SOURCE } } # Capture source + # Awaited call + - kind: await_expression + has: + all: # Wrap conditions in all + - kind: call_expression + - has: { field: function, regex: '^(require|import)$' } + - has: { field: arguments, has: { kind: string, pattern: $SOURCE } } # Capture source + + # DYNAMIC IMPORTS (Destructured Shorthand Assignment) + # ------------------------------------------------------------ + # const { ORIGINAL } = require('SOURCE') + # ------------------------------------------------------------ + - all: + # 1. Target the shorthand identifier within the pattern + - kind: shorthand_property_identifier_pattern + - pattern: $ORIGINAL + # 2. Ensure it's inside an object_pattern that is the name of a variable_declarator + - inside: + kind: object_pattern + inside: # Check the variable_declarator it belongs to + kind: variable_declarator + # 3. Check the value assigned by the variable_declarator + has: + field: value + any: + # Direct call + - all: + - kind: call_expression + - has: { field: function, regex: '^(require|import)$' } + - has: { field: arguments, has: { kind: string, pattern: $SOURCE } } # Capture source + # Awaited call + - kind: await_expression + has: + all: + - kind: call_expression + - has: { field: function, regex: '^(require|import)$' } + - has: { field: arguments, has: { kind: string, pattern: $SOURCE } } # Capture source + stopBy: end # Search ancestors to find the correct variable_declarator + + # DYNAMIC IMPORTS (Destructured Alias Assignment) + # ------------------------------------------------------------ + # const { ORIGINAL: ALIAS } = require('SOURCE') + # ------------------------------------------------------------ + - all: + # 1. Target the pair_pattern for aliased destructuring + - kind: pair_pattern + # 2. Capture the original identifier (key) + - has: + field: key + kind: property_identifier # Could be string/number literal too, but property_identifier is common + pattern: $ORIGINAL + # 3. Capture the alias identifier (value) + - has: + field: value + kind: identifier + pattern: $ALIAS + # 4. Ensure it's inside an object_pattern that is the name of a variable_declarator + - inside: + kind: object_pattern + inside: # Check the variable_declarator it belongs to + kind: variable_declarator + # 5. Check the value assigned by the variable_declarator + has: + field: value + any: + # Direct call + - all: + - kind: call_expression + - has: { field: function, regex: '^(require|import)$' } + - has: { field: arguments, has: { kind: string, pattern: $SOURCE } } # Capture source + # Awaited call + - kind: await_expression + has: + all: + - kind: call_expression + - has: { field: function, regex: '^(require|import)$' } + - has: { field: arguments, has: { kind: string, pattern: $SOURCE } } # Capture source + stopBy: end # Search ancestors to find the correct variable_declarator + stopBy: end # Ensure we check ancestors for the variable_declarator + + # DYNAMIC IMPORTS (Side Effect / Source Only) + # ------------------------------------------------------------ + # require('SOURCE') + # ------------------------------------------------------------ + - all: + - kind: string # Target the source string literal directly + - pattern: $SOURCE + - inside: # String must be the argument of require() or import() + kind: arguments + parent: + kind: call_expression + has: + field: function + # Match 'require' identifier or 'import' keyword used dynamically + regex: '^(require|import)$' + stopBy: end # Search ancestors if needed (for the arguments/call_expression) + - not: + inside: + kind: lexical_declaration + stopBy: end # Search all ancestors up to the root + + # NAMESPACE IMPORTS + # ------------------------------------------------------------ + # import * as ns from 'mod' + # ------------------------------------------------------------ + - all: + - kind: import_statement + - has: + kind: import_clause + has: + kind: namespace_import + has: + # namespace_import's child identifier is the alias + kind: identifier + pattern: $NAMESPACE_ALIAS + - has: + field: source + pattern: $SOURCE + + # SIDE EFFECT IMPORTS + # ------------------------------------------------------------ + # import 'mod' + # ------------------------------------------------------------ + - all: + - kind: import_statement + - not: # Must NOT have an import_clause + has: { kind: import_clause } + - has: # But must have a source + field: source + pattern: $SOURCE +``` + +### Example + + +```ts {60} +//@ts-nocheck +// Named import +import { testing } from './tests'; + +// Aliased import +import { testing as test } from './tests2'; + +// Default import +import hello from 'hello_world1'; + +// Namespace import +import * as something from 'hello_world2'; + +// Side-effect import +import '@fastify/static'; + +// Type import +import {type hello1243 as testing} from 'hello'; + +// Require patterns +const mod = require('some-module'); +require('polyfill'); + +// Destructured require +const { test122, test2 } = require('./destructured1'); +// Aliased require +const { test122: test123, test2: test23, test3: test33 } = require('./destructured2'); + +// Mixed imports +import defaultExport, { namedExport } from './mixed'; +import defaultExport2, * as namespace from './mixed2'; + + +// Multiple import lines from the same file +import { one, two as alias, three } from './multiple'; +import { never, gonna, give, you, up } from './multiple'; + +// String literal variations +import { test1 } from "./double-quoted"; +import { test2 } from './single-quoted'; + +// Multiline imports +import { + longImport1, + longImport2 as alias2, + longImport3 +} from './multiline'; + +// Dynamic imports +const dynamicModule = import('./dynamic1'); +const {testing, testing123} = import('./dynamic2'); +const asyncDynamicModule = await import('./async_dynamic1').then(module => module.default); +// Aliased dynamic import +const { originalIdentifier: aliasedDynamicImport} = await import('./async_dynamic2'); + +// Comments in imports +import /* test */ { + // Comment in import + commentedImport +} from './commented'; // End of line comment +``` + +### Contributed by +[Michael Angelo Rivera](https://github.com/michaelangeloio) + diff --git a/website/catalog/typescript/find-import-usage.md b/website/catalog/typescript/find-import-usage.md new file mode 100644 index 00000000..e8ebafca --- /dev/null +++ b/website/catalog/typescript/find-import-usage.md @@ -0,0 +1,46 @@ +## Find Import Usage + +* [Playground Link](/playground.html#eyJtb2RlIjoiQ29uZmlnIiwibGFuZyI6InR5cGVzY3JpcHQiLCJxdWVyeSI6IiIsInJld3JpdGUiOiIiLCJzdHJpY3RuZXNzIjoicmVsYXhlZCIsInNlbGVjdG9yIjoiIiwiY29uZmlnIjoicnVsZTpcbiAgIyB0aGUgdXNhZ2VcbiAga2luZDogaWRlbnRpZmllclxuICBwYXR0ZXJuOiAkTU9EXG4gICMgaXRzIHJlbGF0aW9uc2hpcCB0byB0aGUgcm9vdFxuICBpbnNpZGU6XG4gICAgc3RvcEJ5OiBlbmRcbiAgICBraW5kOiBwcm9ncmFtXG4gICAgIyBhbmQgYmFjayBkb3duIHRvIHRoZSBpbXBvcnQgc3RhdGVtZW50XG4gICAgaGFzOlxuICAgICAga2luZDogaW1wb3J0X3N0YXRlbWVudFxuICAgICAgIyBhbmQgZGVlcGVyIGludG8gdGhlIGltcG9ydCBzdGF0ZW1lbnQgbG9va2luZyBmb3IgdGhlIG1hdGNoaW5nIGlkZW50aWZpZXJcbiAgICAgIGhhczpcbiAgICAgICAgc3RvcEJ5OiBlbmRcbiAgICAgICAga2luZDogaW1wb3J0X3NwZWNpZmllclxuICAgICAgICBwYXR0ZXJuOiAkTU9EICMgc2FtZSBwYXR0ZXJuIGFzIHRoZSB1c2FnZSBpcyBlbmZvcmNlZCBoZXJlIiwic291cmNlIjoiaW1wb3J0IHsgTW9uZ29DbGllbnQgfSBmcm9tICdtb25nb2RiJztcbmNvbnN0IHVybCA9ICdtb25nb2RiOi8vbG9jYWxob3N0OjI3MDE3JztcbmFzeW5jIGZ1bmN0aW9uIHJ1bigpIHtcbiAgY29uc3QgY2xpZW50ID0gbmV3IE1vbmdvQ2xpZW50KHVybCk7XG59XG4ifQ==) + +### Description + +It is common to find the usage of an imported module in a codebase. This rule helps you to find the usage of an imported module in your codebase. +The idea of this rule can be broken into several parts: + +* Find the use of an identifier `$MOD` +* To find the import, we first need to find the root file of which `$MOD` is `inside` +* The `program` file `has` an `import` statement +* The `import` statement `has` the identifier `$MOD` + + +### YAML +```yaml +id: find-import-usage +language: typescript +rule: + kind: identifier # ast-grep requires a kind + pattern: $MOD # the identifier to find + inside: # find the root + stopBy: end + kind: program + has: # and has the import statement + kind: import_statement + has: # look for the matching identifier + stopBy: end + kind: import_specifier + pattern: $MOD # same pattern as the usage is enforced here +``` + +### Example + + +```ts {4} +import { MongoClient } from 'mongodb'; +const url = 'mongodb://localhost:27017'; +async function run() { + const client = new MongoClient(url); +} +``` + +### Contributed by +[Steven Love](https://github.com/StevenLove) diff --git a/website/catalog/typescript/index.md b/website/catalog/typescript/index.md index 256dbc0f..2fd6edf6 100644 --- a/website/catalog/typescript/index.md +++ b/website/catalog/typescript/index.md @@ -7,11 +7,15 @@ Check out the [Repository of ESLint rules](https://github.com/ast-grep/eslint/) TypeScript is a typed JavaScript extension and TSX is a further extension that allows JSX elements. They need different parsers because of [conflicting syntax](https://www.typescriptlang.org/docs/handbook/jsx.html#the-as-operator). -TS allows both the `as` operator and angle brackets (`<>`) for type assertions. While TSX only allows the `as` operator because it interprets angle brackets as JSX elements. +However, you can use the [`languageGlobs`](/reference/sgconfig.html#languageglobs) option to force ast-grep to use parse `.ts` files as TSX. ::: - \ No newline at end of file + + + + + \ No newline at end of file diff --git a/website/catalog/typescript/migrate-xstate-v5.md b/website/catalog/typescript/migrate-xstate-v5.md index bfc8f33c..6691cda0 100644 --- a/website/catalog/typescript/migrate-xstate-v5.md +++ b/website/catalog/typescript/migrate-xstate-v5.md @@ -20,7 +20,7 @@ The rules below correspond to XState v5's [`createMachine`](https://stately.ai/d The example shows how ast-grep can use various features like [utility rule](/guide/rule-config/utility-rule.html), [transformation](/reference/yaml/transformation.html) and [multiple rule in single file](/reference/playground.html#test-multiple-rules) to automate the migration. Each rule has a clear and descriptive `id` field that explains its purpose. -For more information, you can use [@ast-grep-bot](https://discord.gg/4YZjf6htSQ) to provide more detailed explanation for each rule. +For more information, you can use [Codemod AI](https://app.codemod.com/studio?ai_thread_id=new) to provide more detailed explanation for each rule. ```yaml id: migrate-import-name diff --git a/website/catalog/typescript/missing-component-decorator.md b/website/catalog/typescript/missing-component-decorator.md new file mode 100644 index 00000000..61ed3097 --- /dev/null +++ b/website/catalog/typescript/missing-component-decorator.md @@ -0,0 +1,59 @@ +## Missing Component Decorator + +* [Playground Link](/playground.html#eyJtb2RlIjoiQ29uZmlnIiwibGFuZyI6ImphdmFzY3JpcHQiLCJxdWVyeSI6ImltcG9ydCAkQSBmcm9tICdhbmltZWpzJyIsInJld3JpdGUiOiJpbXBvcnQgeyBhbmltZSBhcyAkQSB9IGZyb20gJ2FuaW1lJyIsInN0cmljdG5lc3MiOiJzbWFydCIsInNlbGVjdG9yIjoiIiwiY29uZmlnIjoiaWQ6IG1pc3NpbmctY29tcG9uZW50LWRlY29yYXRvclxubWVzc2FnZTogWW91J3JlIHVzaW5nIGFuIEFuZ3VsYXIgbGlmZWN5Y2xlIG1ldGhvZCwgYnV0IG1pc3NpbmcgYW4gQW5ndWxhciBAQ29tcG9uZW50KCkgZGVjb3JhdG9yLlxubGFuZ3VhZ2U6IFR5cGVTY3JpcHRcbnNldmVyaXR5OiB3YXJuaW5nXG5ydWxlOlxuICBwYXR0ZXJuOlxuICAgIGNvbnRleHQ6ICdjbGFzcyBIaSB7ICRNRVRIT0QoKSB7ICQkJF99IH0nXG4gICAgc2VsZWN0b3I6IG1ldGhvZF9kZWZpbml0aW9uXG4gIGluc2lkZTpcbiAgICBwYXR0ZXJuOiAnY2xhc3MgJEtMQVNTICQkJF8geyAkJCRfIH0nXG4gICAgc3RvcEJ5OiBlbmRcbiAgICBub3Q6XG4gICAgICBoYXM6XG4gICAgICAgIHBhdHRlcm46ICdAQ29tcG9uZW50KCQkJF8pJ1xuY29uc3RyYWludHM6XG4gIE1FVEhPRDpcbiAgICByZWdleDogbmdPbkluaXR8bmdPbkRlc3Ryb3lcbmxhYmVsczpcbiAgS0xBU1M6XG4gICAgc3R5bGU6IHByaW1hcnlcbiAgICBtZXNzYWdlOiBcIlRoaXMgY2xhc3MgaXMgbWlzc2luZyB0aGUgZGVjb3JhdG9yLlwiXG4gIE1FVEhPRDpcbiAgICBzdHlsZTogc2Vjb25kYXJ5XG4gICAgbWVzc2FnZTogXCJUaGlzIGlzIGFuIEFuZ3VsYXIgbGlmZWN5Y2xlIG1ldGhvZC5cIlxubWV0YWRhdGE6XG4gIGNvbnRyaWJ1dGVkQnk6IHNhbXdpZ2h0dCIsInNvdXJjZSI6ImNsYXNzIE5vdENvbXBvbmVudCB7XG4gICAgbmdPbkluaXQoKSB7fVxufVxuXG5AQ29tcG9uZW50KClcbmNsYXNzIEtsYXNzIHtcbiAgICBuZ09uSW5pdCgpIHt9XG59In0=) + +### Description + +Angular lifecycle methods are a set of methods that allow you to hook into the lifecycle of an Angular component or directive. +They must be used within a class that is decorated with the `@Component()` decorator. + +### YAML + +This rule illustrates how to use custom labels to highlight specific parts of the code. + + +```yaml +id: missing-component-decorator +message: You're using an Angular lifecycle method, but missing an Angular @Component() decorator. +language: TypeScript +severity: warning +rule: + pattern: + context: 'class Hi { $METHOD() { $$$_} }' + selector: method_definition + inside: + pattern: 'class $KLASS $$$_ { $$$_ }' + stopBy: end + not: + has: + pattern: '@Component($$$_)' +constraints: + METHOD: + regex: ngOnInit|ngOnDestroy +labels: + KLASS: + style: primary + message: "This class is missing the decorator." + METHOD: + style: secondary + message: "This is an Angular lifecycle method." +metadata: + contributedBy: samwightt +``` + +### Example + + +```ts {2} +class NotComponent { + ngOnInit() {} +} + +@Component() +class Klass { + ngOnInit() {} +} +``` + +### Contributed by +[Sam Wight](https://github.com/samwightt). diff --git a/website/catalog/typescript/speed-up-barrel-import.md b/website/catalog/typescript/speed-up-barrel-import.md new file mode 100644 index 00000000..5373d066 --- /dev/null +++ b/website/catalog/typescript/speed-up-barrel-import.md @@ -0,0 +1,56 @@ +## Speed up Barrel Import + +* [Playground Link](/playground.html#eyJtb2RlIjoiQ29uZmlnIiwibGFuZyI6ImphdmFzY3JpcHQiLCJxdWVyeSI6IiIsInJld3JpdGUiOiIiLCJjb25maWciOiJydWxlOlxuICBwYXR0ZXJuOiBpbXBvcnQgeyQkJElERU5UU30gZnJvbSAnLi9iYXJyZWwnXG5yZXdyaXRlcnM6XG4tIGlkOiByZXdyaXRlLWlkZW50aWZlclxuICBydWxlOlxuICAgIHBhdHRlcm46ICRJREVOVFxuICAgIGtpbmQ6IGlkZW50aWZpZXJcbiAgZml4OiBpbXBvcnQgJElERU5UIGZyb20gJy4vYmFycmVsLyRJREVOVCdcbnRyYW5zZm9ybTpcbiAgSU1QT1JUUzpcbiAgICByZXdyaXRlOlxuICAgICAgcmV3cml0ZXJzOiBbcmV3cml0ZS1pZGVudGlmZXJdXG4gICAgICBzb3VyY2U6ICQkJElERU5UU1xuICAgICAgam9pbkJ5OiBcIlxcblwiXG5maXg6ICRJTVBPUlRTIiwic291cmNlIjoiaW1wb3J0IHsgYSwgYiwgYyB9IGZyb20gJy4vYmFycmVsJzsifQ==) + +### Description + +A [barrel import](https://adrianfaciu.dev/posts/barrel-files/) is a way to consolidate the exports of multiple modules into a single convenient module that can be imported using a single import statement. For instance, `import {a, b, c} from './barrel'`. + +It has [some](https://vercel.com/blog/how-we-optimized-package-imports-in-next-js) [benefits](https://marvinh.dev/blog/speeding-up-javascript-ecosystem-part-7/) to import each module directly from its own file without going through the barrel file. +Such as reducing [bundle size](https://dev.to/tassiofront/barrel-files-and-why-you-should-stop-using-them-now-bc4), improving building time or avoiding [conflicting names](https://flaming.codes/posts/barrel-files-in-javascript/). + + +### YAML +```yaml +id: speed-up-barrel-import +language: typescript +# find the barrel import statement +rule: + pattern: import {$$$IDENTS} from './barrel' +# rewrite imported identifiers to direct imports +rewriters: +- id: rewrite-identifer + rule: + pattern: $IDENT + kind: identifier + fix: import $IDENT from './barrel/$IDENT' +# apply the rewriter to the import statement +transform: + IMPORTS: + rewrite: + rewriters: [rewrite-identifer] + # $$$IDENTS contains imported identifiers + source: $$$IDENTS + # join the rewritten imports by newline + joinBy: "\n" +fix: $IMPORTS +``` + +### Example + +```ts {1} +import {a, b, c} from './barrel' +``` + +### Diff + +```ts +import {a, b, c} from './barrel' // [!code --] +import a from './barrel/a' // [!code ++] +import b from './barrel/b' // [!code ++] +import c from './barrel/c' // [!code ++] +``` + + +### Contributed by +[Herrington Darkholme](https://x.com/hd_nvim) diff --git a/website/catalog/typescript/switch-from-should-to-expect.md b/website/catalog/typescript/switch-from-should-to-expect.md new file mode 100644 index 00000000..6d473493 --- /dev/null +++ b/website/catalog/typescript/switch-from-should-to-expect.md @@ -0,0 +1,76 @@ +## Switch Chai from `should` style to `expect` + + +* [Playground Link](/playground.html#eyJtb2RlIjoiQ29uZmlnIiwibGFuZyI6InJ1c3QiLCJxdWVyeSI6IiIsInJld3JpdGUiOiIiLCJzdHJpY3RuZXNzIjoicmVsYXhlZCIsInNlbGVjdG9yIjoiIiwiY29uZmlnIjoiaWQ6IHNob3VsZF90b19leHBlY3RfaW5zdGFuY2VvZlxubGFuZ3VhZ2U6IFR5cGVTY3JpcHRcbnJ1bGU6XG4gIGFueTpcbiAgLSBwYXR0ZXJuOiAkTkFNRS5zaG91bGQuYmUuYW4uaW5zdGFuY2VvZigkVFlQRSlcbiAgLSBwYXR0ZXJuOiAkTkFNRS5zaG91bGQuYmUuYW4uaW5zdGFuY2VPZigkVFlQRSlcbmZpeDogfC1cbiAgZXhwZWN0KCROQU1FKS5pbnN0YW5jZU9mKCRUWVBFKVxuLS0tXG5pZDogc2hvdWxkX3RvX2V4cGVjdF9nZW5lcmljU2hvdWxkQmVcbmxhbmd1YWdlOiBUeXBlU2NyaXB0XG5ydWxlOlxuICBwYXR0ZXJuOiAkTkFNRS5zaG91bGQuYmUuJFBST1BcbmZpeDogfC1cbiAgZXhwZWN0KCROQU1FKS50by5iZS4kUFJPUFxuIiwic291cmNlIjoiaXQoJ3Nob3VsZCBwcm9kdWNlIGFuIGluc3RhbmNlIG9mIGNob2tpZGFyLkZTV2F0Y2hlcicsICgpID0+IHtcbiAgd2F0Y2hlci5zaG91bGQuYmUuYW4uaW5zdGFuY2VvZihjaG9raWRhci5GU1dhdGNoZXIpO1xufSk7XG5pdCgnc2hvdWxkIGV4cG9zZSBwdWJsaWMgQVBJIG1ldGhvZHMnLCAoKSA9PiB7XG4gIHdhdGNoZXIub24uc2hvdWxkLmJlLmEoJ2Z1bmN0aW9uJyk7XG4gIHdhdGNoZXIuZW1pdC5zaG91bGQuYmUuYSgnZnVuY3Rpb24nKTtcbiAgd2F0Y2hlci5hZGQuc2hvdWxkLmJlLmEoJ2Z1bmN0aW9uJyk7XG4gIHdhdGNoZXIuY2xvc2Uuc2hvdWxkLmJlLmEoJ2Z1bmN0aW9uJyk7XG4gIHdhdGNoZXIuZ2V0V2F0Y2hlZC5zaG91bGQuYmUuYSgnZnVuY3Rpb24nKTtcbn0pOyJ9) + +### Description + +[Chai](https://www.chaijs.com) is a BDD / TDD assertion library for JavaScript. It comes with [two styles](https://www.chaijs.com/) of assertions: `should` and `expect`. + +The `expect` interface provides a function as a starting point for chaining your language assertions and works with `undefined` and `null` values. +The `should` style allows for the same chainable assertions as the expect interface, however it extends each object with a should property to start your chain and [does not work](https://www.chaijs.com/guide/styles/#should-extras) with `undefined` and `null` values. + +This rule migrates Chai `should` style assertions to `expect` style assertions. Note this is an example rule and a excerpt from [the original rules](https://github.com/43081j/codemods/blob/cddfe101e7f759e4da08b7e2f7bfe892c20f6f48/codemods/chai-should-to-expect.yml). + +### YAML +```yaml +id: should_to_expect_instanceof +language: TypeScript +rule: + any: + - pattern: $NAME.should.be.an.instanceof($TYPE) + - pattern: $NAME.should.be.an.instanceOf($TYPE) +fix: |- + expect($NAME).instanceOf($TYPE) +--- +id: should_to_expect_genericShouldBe +language: TypeScript +rule: + pattern: $NAME.should.be.$PROP +fix: |- + expect($NAME).to.be.$PROP +``` + +### Example + + +```js {2,5-9} +it('should produce an instance of chokidar.FSWatcher', () => { + watcher.should.be.an.instanceof(chokidar.FSWatcher); +}); +it('should expose public API methods', () => { + watcher.on.should.be.a('function'); + watcher.emit.should.be.a('function'); + watcher.add.should.be.a('function'); + watcher.close.should.be.a('function'); + watcher.getWatched.should.be.a('function'); +}); +``` + +### Diff + +```js +it('should produce an instance of chokidar.FSWatcher', () => { + watcher.should.be.an.instanceof(chokidar.FSWatcher); // [!code --] + expect(watcher).instanceOf(chokidar.FSWatcher); // [!code ++] +}); +it('should expose public API methods', () => { + watcher.on.should.be.a('function'); // [!code --] + watcher.emit.should.be.a('function'); // [!code --] + watcher.add.should.be.a('function'); // [!code --] + watcher.close.should.be.a('function'); // [!code --] + watcher.getWatched.should.be.a('function'); // [!code --] + expect(watcher.on).to.be.a('function'); // [!code ++] + expect(watcher.emit).to.be.a('function'); // [!code ++] + expect(watcher.add).to.be.a('function'); // [!code ++] + expect(watcher.close).to.be.a('function'); // [!code ++] + expect(watcher.getWatched).to.be.a('function'); // [!code ++] +}); +``` + +### Contributed by +[James](https://bsky.app/profile/43081j.com), by [this post](https://bsky.app/profile/43081j.com/post/3lgimzfxza22i) + +### Exercise + +Exercise left to the reader: can you write a rule to implement [this migration to `node:assert`](https://github.com/paulmillr/chokidar/pull/1409/files)? \ No newline at end of file diff --git a/website/catalog/yaml/find-key-value.md b/website/catalog/yaml/find-key-value.md new file mode 100644 index 00000000..9580cf4d --- /dev/null +++ b/website/catalog/yaml/find-key-value.md @@ -0,0 +1,36 @@ +## Find key/value and Show Message + +* [Playground Link](/playground.html#eyJtb2RlIjoiQ29uZmlnIiwibGFuZyI6InlhbWwiLCJxdWVyeSI6IiIsInJld3JpdGUiOiIiLCJzdHJpY3RuZXNzIjoic21hcnQiLCJzZWxlY3RvciI6IiIsImNvbmZpZyI6ImlkOiBkZXRlY3QtaG9zdC1wb3J0XG5tZXNzYWdlOiBZb3UgYXJlIHVzaW5nICRIT1NUIG9uIFBvcnQgJFBPUlQsIHBsZWFzZSBjaGFuZ2UgaXQgdG8gODAwMFxuc2V2ZXJpdHk6IGVycm9yXG5ydWxlOlxuICBhbnk6XG4gIC0gcGF0dGVybjogfFxuICAgICBwb3J0OiAkUE9SVFxuICAtIHBhdHRlcm46IHxcbiAgICAgaG9zdDogJEhPU1QiLCJzb3VyY2UiOiJkYjpcbiAgIHVzZXJuYW1lOiByb290XG4gICBwYXNzd29yZDogcm9vdFxuXG5zZXJ2ZXI6XG4gIGhvc3Q6IDEyNy4wLjAuMVxuICBwb3J0OiA4MDAxIn0=) + +### Description + +This YAML rule helps detecting specific host and port configurations in your code. For example, it checks if the port is set to something other than 8000 or if a particular host is used. It provides an error message prompting you to update the configuration. + +### YAML + +```yaml +id: detect-host-port +message: You are using $HOST on Port $PORT, please change it to 8000 +severity: error +rule: + any: + - pattern: | + port: $PORT + - pattern: | + host: $HOST +``` + +### Example + + +```yaml {5,6} +db: + username: root + password: root +server: + host: 127.0.0.1 + port: 8001 +``` + +### Contributed by +[rohitcoder](https://twitter.com/rohitcoder) on [Discord](https://discord.com/invite/4YZjf6htSQ). diff --git a/website/catalog/yaml/index.md b/website/catalog/yaml/index.md new file mode 100644 index 00000000..7acfb5bb --- /dev/null +++ b/website/catalog/yaml/index.md @@ -0,0 +1,5 @@ +# YAML + +This page curates a list of example ast-grep rules to check and to rewrite YAML code. + + diff --git a/website/cheatsheet/rule.md b/website/cheatsheet/rule.md new file mode 100644 index 00000000..fbc55d34 --- /dev/null +++ b/website/cheatsheet/rule.md @@ -0,0 +1,262 @@ +# Rule Cheat Sheet + +This cheat sheet provides a concise overview of ast-grep's rule object configuration, covering Atomic, Relational, and Composite rules, along with notes on Utility rules. It's designed as a handy reference for common usage. + + + + +## Atomic Rules Cheat Sheet + +These are your precision tools, matching individual AST nodes based on their inherent properties. + + + + + +```yaml +pattern: console.log($ARG) +``` + +🧩 Match a node by code structure. e.g. `console.log` call with a single `$ARG` + + + + + +```yaml +pattern: + context: '{ key: value }' + selector: pair +``` + +🧩 To parse ambiguous patterns, use `context` and specify `selector` AST to search. + + + + + +```yaml +kind: if_statement +``` +šŸ·ļø Match an AST node by its `kind` name + + + + +```yaml +regex: ^regex.+$ +``` + +šŸ” Matches node text content against a [Rust regular expression](https://docs.rs/regex/latest/regex/) + + + + + +```yaml +nthChild: 1 +``` + +šŸ”¢ Find a node by its **1-based index** among its _named siblings_ + + + + + +```yaml +nthChild: + position: 2 + reverse: true + ofRule: { kind: argument_list } +``` + +šŸ”¢ Advanced positional control: `position`, `reverse` (count from end), or filter siblings using `ofRule` + + + + + +```yaml +range: + start: { line: 0, column: 0 } + end: { line: 0, column: 13 } +``` + +šŸŽÆ Matches a node based on its character span: 0-based, inclusive start, exclusive end + + + + + +## Relational Rules Cheat Sheet + +These powerful rules define how nodes relate to each other structurally. Think of them as your AST GPS! + + + + + +```yaml +inside: + kind: function_declaration +``` + +šŸ  Target node must appear **inside** its _parent/ancestor_ node matching the sub-rule + + + + + +```yaml +has: + kind: method_definition +``` + +🌳 Target node must **have** a _child/descendant_ node matching the sub-rule + + + + + +```yaml +has: + kind: statement_block + field: body +``` + +🌳 `field` makes `has`/`inside` match nodes by their [semantic role](/advanced/core-concepts.html#kind-vs-field) + + + + + +```yaml +precedes: + pattern: function $FUNC() { $$ } +``` + +ā—€ļø Target node must appear _before_ another node matching the sub-rule + + + + + +```yaml +follows: + pattern: let x = 10; +``` + +ā–¶ļø Target node must appear _after_ another node matching the sub-rule. + + + + + +```yaml +inside: + kind: function_declaration + stopBy: end +``` + +šŸ  `stopBy` makes relational rules search all the way to the end, not just immediate neighbors. + + + + + +## Composite Rules Cheat Sheet + +Combine multiple rules using Boolean logic. Crucially, these operations apply to a single target node! + + + + + +```yaml +all: + - pattern: const $VAR = $VALUE + - has: { kind: string_literal } +``` + +āœ… Node must satisfy **ALL** the rules in the list. + + + + + +```yaml +any: + - pattern: let $X = $Y + - pattern: const $X = $Y +``` + +🧔 Node must satisfy **AT LEAST ONE** of the rules in the list. + + + + + +```yaml +not: + pattern: console.log($$) +``` + +🚫 Node must **NOT** satisfy the specified sub-rule. + + + + + +```yaml +matches: is-function-call +``` + +šŸ”„ Matches the node if that utility rule matches it. Your gateway to modularity! + + + + + +## Utility Rules Cheat Sheet + +Define reusable rule definitions to cut down on duplication and build complex, maintainable rule sets. + + + + + +```yaml +rules: + - id: find-my-pattern + rule: + matches: my-local-check +utils: + my-local-check: + kind: identifier + regex: '^my' +``` + +šŸ” Defined within the `utils` field of your current config file. Only accessible within that file. + + + + + +```yaml +# In utils/my-global-check.yml +id: my-global-check +language: javascript +rule: + kind: variable_declarator + has: + kind: number_literal +``` + +šŸŒ Defined in separate YAML files in global `utilsDirs` folders, accessible across your entire project. + + + + diff --git a/website/cheatsheet/yaml.md b/website/cheatsheet/yaml.md new file mode 100644 index 00000000..59403ddd --- /dev/null +++ b/website/cheatsheet/yaml.md @@ -0,0 +1,240 @@ +# Config Cheat Sheet + +This cheat sheet provides a concise overview of ast-grep's linter rule YAML configuration. It's designed as a handy reference for common usage. + + + + +## Basic Information + +Core details that identify and define your rule and miscellaneous keys for documentation and custom data. + + + + +```yaml +id: no-console-log +``` + +šŸ†” A unique, descriptive identifier for the rule. + + + + + +```yaml +language: JavaScript +``` + +🌐 The programming language the rule applies to. + + + + + + +```yaml +url: 'https://doc.link/' +``` +šŸ”— A URL to the rule's documentation. + + + + + + +```yaml +metadata: { author: 'John Doe' } +``` + +šŸ““ metadata A dictionary for custom data related to the rule. + + + + + +## Finding + +Keys for specifying what code to search for. + + + + + +```yaml +rule: + pattern: 'console.log($$$ARGS)' +``` + +šŸŽÆ The core `rule` to find matching AST nodes. + + + + + +```yaml +constraints: + ARG: { kind: 'string' } } +``` + +āš™ļø Additional `constraints` rules to filter meta-variable matches. + + + + + +```yaml +utils: + is-react: + kind: function_declaration + has: { kind: jsx_element } +``` + +šŸ› ļø A dictionary of reusable utility rules. Use them in `matches` to modularize your rules. + + + + + + +## Patching + +Keys for defining how to automatically fix the found code. + + + + + + +```yaml +transform: + NEW_VAR: + substring: {endChar: 1, source: $V} +``` + +šŸŽ© `transform` meta-variables before they are used in `fix`. + + + + + +```yaml +transform: + NEW_VAR: substring($V, endChar=1) +``` + +šŸŽ© `transform` also accepts string form. + + + + + +```yaml +fix: "logger.log($$$ARGS)" +``` + +šŸ”§ A `fix` string to auto-fix the matched code. + + + + + +```yaml +fix: + template: "logger.log($$$ARGS)" + expandEnd: rule +``` + +šŸ”§ Fix also accepts `FixConfig` object. + + + + + +```yaml +rewriters: +- id: remove-quotes + rule: { pattern: "'$A'" } + fix: "$A" +``` + +āœļø A list of `rewriters` for complex transformations. + + + + + +## Linting + +Keys for configuring the messages and severity of reported issues. + + + + + +```yaml +severity: warning +``` + +āš ļø The `severity` level of the linting message. + + + + + +```yaml +message: "Avoid using $MATCH in production." +``` + +šŸ’¬ A concise `message` explaining the rule. Matched $VAR can be used. + + + + + +```yaml +note: + Use a _logger_ instead of `console` +``` + +šŸ“Œ More detailed `note`. It supports Markdown format. + + + + + +```yaml +labels: + ARG: + style: 'primary' + message: 'The argument to log' +``` + +šŸŽØ Customized `labels` for highlighting parts of the matched code. + + + + + +```yaml +files: ['src/**/*.js'] +``` + +āœ… Glob `files` patterns to include files for the rule. + + + + + +```yaml +ignores: ['test/**/*.js'] +``` + +āŒ Glob patterns to exclude files from the rule. + + + + diff --git a/website/contributing/add-lang.md b/website/contributing/add-lang.md index 0a6ac8b7..2219d7dc 100644 --- a/website/contributing/add-lang.md +++ b/website/contributing/add-lang.md @@ -151,7 +151,7 @@ Then, in your parser repository, use this command to build a WASM file. ```bash tree-sitter generate # if grammar is not generated before -tree-sitter build-wasm +tree-sitter build --wasm ``` Note you may need to install [docker](https://www.docker.com/) when building WASM files. diff --git a/website/contributing/how-to.md b/website/contributing/how-to.md index 73e2d56d..f0f0db19 100644 --- a/website/contributing/how-to.md +++ b/website/contributing/how-to.md @@ -50,6 +50,8 @@ We encourage you to share your knowledge and experience with ast-grep with other - **Write introductions to ast-grep**: You can write blog posts, articles, or tutorials that introduce ast-grep to new users. You can explain what ast-grep is, how it works, what problems it solves, and how to install and use it. You can also share some examples of how you use ast-grep in your own projects or workflows. +- **Answer questions about ast-grep**: Help answering people's questions on [StackOverflow](https://stackoverflow.com/questions/tagged/ast-grep) or [Discord](https://discord.gg/4YZjf6htSQ). Your answers will be appreciated! + - **Write ast-grep's tutorial**: You can write more advanced tutorials that show how to use ast-grep for specific tasks or scenarios. You can demonstrate how to use ast-grep's features and options, how to write complex queries and transformations, how to integrate ast-grep with other tools or platforms, and how to optimize ast-grep's performance and efficiency. - **Translate documentation**: You can help us make ast-grep more accessible to users from different regions and languages by translating its documentation into other languages. Reach out [@Shenqingchuan](https://twitter.com/Shenqingchuan), translation team member of [Rollup](https://github.com/rollup/rollup-docs-cn), [Vite](https://github.com/vitejs/docs-cn) and ast-grep, for more ideas about translation! diff --git a/website/guide/api-usage.md b/website/guide/api-usage.md index 2f858f75..c52ffd36 100644 --- a/website/guide/api-usage.md +++ b/website/guide/api-usage.md @@ -11,7 +11,11 @@ For example, you may struggle to: * count the number or order of nodes that match a certain pattern * compute the replacement string based on the matched nodes -To solve these problems, you can use ast-grep's programmatic API! You can freely inspect and change syntax trees in popular programming languages! +To solve these problems, you can use ast-grep's programmatic API! You can freely inspect and generate text patches based on syntax trees, using popular programming languages! + +:::tip +Applying ast-grep's `fix` using JS/Python API is still experimental. See [this issue](https://github.com/ast-grep/ast-grep/issues/1172) for more information. +::: ## Language Bindings diff --git a/website/guide/api-usage/js-api.md b/website/guide/api-usage/js-api.md index 29d2c7c1..a2053ce0 100644 --- a/website/guide/api-usage/js-api.md +++ b/website/guide/api-usage/js-api.md @@ -11,6 +11,7 @@ To try out the JavaScript API, you can use the [code sandbox](https://codesandbo First, install ast-grep's napi package. ::: code-group + ```bash[npm] npm install --save @ast-grep/napi ``` @@ -18,6 +19,7 @@ npm install --save @ast-grep/napi ```bash[pnpm] pnpm add @ast-grep/napi ``` + ::: Now let's explore ast-grep's API! @@ -25,6 +27,7 @@ Now let's explore ast-grep's API! ## Core Concepts The core concepts in ast-grep's JavaScript API are: + * `SgRoot`: a class representing the whole syntax tree * `SgNode`: a node in the syntax tree @@ -44,10 +47,10 @@ A common workflow to use ast-grep's JavaScript API is: **Example:** ```js{4-7} -import { js } from '@ast-grep/napi'; +import { parse, Lang } from '@ast-grep/napi'; let source = `console.log("hello world")` -const ast = js.parse(source) // 1. parse the source +const ast = parse(Lang.JavaScript, source) // 1. parse the source const root = ast.root() // 2. get the root const node = root.find('console.log($A)') // 3. find the node node.getMatch('A').text() // 4. collect the info @@ -58,13 +61,13 @@ node.getMatch('A').text() // 4. collect the info `SgRoot` represents the syntax tree of a source string. -We can import a language object from the `@ast-grep/napi` package and call the `parse` to transform string. +We can import the `Lang` enum from the `@ast-grep/napi` package and call the `parse` function to transform string. ```js{4} -import { js } from '@ast-grep/napi'; +import { Lang, parse } from '@ast-grep/napi'; const source = `console.log("hello world")` -const ast = js.parse(source) +const ast = parse(Lang.JavaScript, source) ``` The `SgRoot` object has a `root` method that returns the root `SgNode` of the AST. @@ -82,7 +85,7 @@ It has several jQuery like methods for us to search, filter and inspect the AST ```js const log = root.find('console.log($A)') // search node const arg = log.getMatch('A') // get matched variable -log.text() // "hello world" +arg.text() // "hello world" ``` Let's see its details in the following sections! @@ -115,16 +118,17 @@ A `Matcher` can be one of the three types: `string`, `number` or `object`. * `string` is parsed as a [pattern](/guide/pattern-syntax.html). e.g. `'console.log($A)'` -* `number` is interpreted as the node's kind. In tree-sitter, an AST node's type is represented by a number called kind id. Different syntax node has different kind ids. You can convert a kind name like `function` to the numeric representation by calling the `kind` function on the language object. e.g. `js.kind('function')`. +* `number` is interpreted as the node's kind. In tree-sitter, an AST node's type is represented by a number called kind id. Different syntax node has different kind ids. You can convert a kind name like `function` to the numeric representation by calling the `kind` function. e.g. `kind('function', Lang.JavaScript)`. * A `NapiConfig` has a similar type of [config object](/reference/yaml.html). See details below. ```ts // basic find example -root.find('console.log($A)') // returns SgNode of call_expression -const kind = js.kind('string') // convert kind name to kind id number -root.find(kind) // returns SgNode of string -root.find('notExist') // returns null if not found +root.find('console.log($A)') // returns SgNode of call_expression +let l = Lang.JavaScript // calling kind function requires Lang +const kind = kind(l, 'string') // convert kind name to kind id number +root.find(kind) // returns SgNode of string +root.find('notExist') // returns null if not found // basic find all example const nodes = root.findAll('function $A($$$) {$$$}') @@ -132,6 +136,16 @@ Array.isArray(nodes) // true, findAll returns SgNode nodes.map(n => n.text()) // string array of function source const empty = root.findAll('not exist') // returns [] empty.length === 0 // true + +// find i.e. `console.log("hello world")` using a NapiConfig +const node = root.find({ + rule: { + pattern: "console.log($A)" + }, + constraints: { + A: { regex: "hello" } + } +}) ``` Note, `find` returns `null` if no node is found. `findAll` returns an empty array if nothing matches. @@ -159,7 +173,7 @@ const src = ` console.log('hello') logger('hello', 'world', '!') ` -const root = js.parse(src).root() +const root = parse(Lang.JavaScript, src).root() const node = root.find('console.log($A)') const arg = node.getMatch("A") // returns SgNode('hello') arg !== null // true, node is found @@ -168,9 +182,9 @@ arg.text() // returns 'hello' node.getMultipleMatches('A') const logs = root.find('logger($$$ARGS)') -// returns [SgNode('hello'), SgNode('world'), SgNode('!')] -node.getMultipleMatches("ARGS") -node.getMatch("A") // returns null +// returns [SgNode('hello'), SgNode(','), SgNode('world'), SgNode(','), SgNode('!')] +logs.getMultipleMatches("ARGS") +logs.getMatch("A") // returns null ``` ## Inspection @@ -190,7 +204,7 @@ export class SgNode { **Example:** ```ts{3} -const ast = js.parse("console.log('hello world')") +const ast = parse(Lang.JavaScript, "console.log('hello world')") root = ast.root() root.text() // will return "console.log('hello world')" ``` @@ -250,98 +264,49 @@ export class SgNode { } ``` -## `findInFiles` - -If you have a lot of files to parse and want to maximize your programs' performance, ast-grep's language object provides a `findInFiles` function that parses multiple files and searches relevant nodes in parallel Rust threads. +## Fix code -APIs we showed above all require parsing code in Rust and pass the `SgRoot` back to JavaScript. -This incurs foreign function communication overhead and only utilizes the single main JavaScript thread. -By avoiding Rust-JS communication overhead and utilizing multiple core computing, -`findInFiles` is much faster than finding files in JavaScript and then passing them to Rust as string. +`SgNode` is immutable so it is impossible to change the code directly. -The function signature of `findInFiles` is as follows: +However, `SgNode` has a `replace` method to generate an `Edit` object. You can then use the `commitEdits` method to apply the changes and generate new source string. ```ts -export function findInFiles( - /** specify the file path and matcher */ - config: FindConfig, - /** callback function for found nodes in a file */ - callback: (err: null | Error, result: SgNode[]) => void -): Promise -``` - -`findInFiles` accepts a `FindConfig` object and a callback function. - -`FindConfig` specifies both what file path to _parse_ and what nodes to _search_. - -`findInFiles` will parse all files matching paths and will call back the function with nodes matching the `matcher` found in the files as arguments. - -### `FindConfig` - -The `FindConfig` object specifies which paths to search code and what rule to match node against. - -The `FindConfig` object has the following type: +interface Edit { + /** The start position of the edit */ + startPos: number + /** The end position of the edit */ + endPos: number + /** The text to be inserted */ + insertedText: string +} -```ts -export interface FindConfig { - paths: Array - matcher: NapiConfig +class SgNode { + replace(text: string): Edit + commitEdits(edits: Edit[]): string } ``` -The `path` field is an array of strings. You can specify multiple paths to search code. Every path in the array can be a file path or a directory path. For a directory path, ast-grep will recursively find all files matching the language. - -The `matcher` is the same as `NapiConfig` stated above. +**Example** -### Callback Function and Termination - -The `callback` function is called for every file that have nodes that match the rule. The callback function is a standard node-style callback with the first argument as `Error` and second argument as an array of `SgNode` objects that match the rule. +```ts{3,4} +const root = parse(Lang.JavaScript, "console.log('hello world')").root() +const node = root.find('console.log($A)') +const edit = node.replace("console.error('bye world')") +const newSource = node.commitEdits([edit]) +// "console.error('bye world')" +``` -The return value of `findInFiles` is a `Promise` object. The promise resolves to the number of files that have nodes that match the rule. +Note, `console.error($A)` will not generate `console.error('hello world')` in JavaScript API unlike the CLI. This is because using the host language to generate the replacement string is more flexible. -:::danger -`findInFiles` can return before all file callbacks are called due to NodeJS limitation. -See https://github.com/ast-grep/ast-grep/issues/206. +:::warning +Metavariable will not be replaced in the `replace` method. You need to create a string using `getMatch(var_name)` by using JavaScript. ::: -If you have a lot of files and `findInFiles` prematurely returns, you can use the total files returned by `findInFiles` as a check point. Maintain a counter outside of `findInFiles` and increment it in callback. If the counter equals the total number, we can conclude all files are processed. The following code is an example, with core logic highlighted. - -```ts:line-numbers {11,16-18} -type Callback = (t: any, cb: any) => Promise -function countedPromise(func: F) { - type P = Parameters - return async (t: P[0], cb: P[1]) => { - let i = 0 - let fileCount: number | undefined = undefined - // resolve will be called after all files are processed - let resolve = () => {} - function wrapped(...args: any[]) { - let ret = cb(...args) - if (++i === fileCount) resolve() - return ret - } - fileCount = await func(t, wrapped as P[1]) - // not all files are processed, await `resolve` to be called - if (fileCount > i) { - await new Promise(r => resolve = r) - } - return fileCount - } -} -``` +See also [ast-grep#1172](https://github.com/ast-grep/ast-grep/issues/1172) -### Example -Example of using `findInFiles` +## Use Other Language -```ts -let fileCount = await js.findInFiles({ - paths: ['relative/path/to/code'], - matcher: { - rule: {kind: 'member_expression'} - }, -}, (err, n) => { - t.is(err, null) - t.assert(n.length > 0) - t.assert(n[0].text().includes('.')) -}) -``` +To access other languages, you will need to use `registerDynamicLanguage` function and probably `@ast-grep/lang-*` package. +This is an experimental feature and the doc is not ready yet. Please refer to the [repo](https://github.com/ast-grep/langs) for more information. + +If you are interested in using other languages, please let us know by creating an issue. \ No newline at end of file diff --git a/website/guide/api-usage/performance-tip.md b/website/guide/api-usage/performance-tip.md new file mode 100644 index 00000000..ce90d3a0 --- /dev/null +++ b/website/guide/api-usage/performance-tip.md @@ -0,0 +1,150 @@ +# Performance Tip for napi usage + +Using `napi` to parse code and search for nodes [isn't always faster](https://medium.com/@hchan_nvim/benchmark-typescript-parsers-demystify-rust-tooling-performance-025ebfd391a3) than pure JavaScript implementations. + +There are a lot of tricks to improve performance when using `napi`. The mantra is to _reduce FFI (Foreign Function Interface) calls between Rust and JavaScript_, and to _take advantage of parallel computing_. + +## Prefer `parseAsync` over `parse` + +`parseAsync` can take advantage of NodeJs' libuv thread pool to parse code in parallel threads. This can be faster than the sync version `parse` when handling a lot of code. + +```ts +import { js } from '@ast-grep/napi'; +// only one thread parsing +const root = js.parse('console.log("hello world")') +// better, can use multiple threads +const root = await js.parseAsync('console.log("hello world")') +``` + +This is especially useful when you are using ast-grep in bundlers where the main thread is busy with other CPU intensive tasks. + +## Prefer `findAll` over manual traversal + +One way to find all nodes that match a rule is to traverse the syntax tree manually and check each node against the rule. This is slow because it requires a lot of FFI calls between Rust and JavaScript during the traversal. + +For example, the following code snippet finds all `member_expression` nodes in the syntax tree. Unfortunately, there are as many FFI calls as the tree node number in the recursion. + +```ts +const root = sgroot.root() +function findMemberExpression(node: SgNode): SgNode[] { + let ret: SgNode[] = [] + // `node.kind()` is a FFI call + if (node.kind() === 'member_expression') { + ret.push(node) + } + // `node.children()` is a FFI call + for (let child of node.children()) { + // recursion makes more FFI calls + ret = ret.concat(findMemberExpression(child)) + } + return ret +} +const nodes = findMemberExpression(root) +``` + +The equivalent code using `findAll` is much faster: + +```ts +const root = sgroot.root() +// only call FFI `findAll` once +const nodes = root.findAll({kind: 'member_expression'}) +``` + +> _One [success](https://x.com/hd_nvim/status/1767971906786128316) [story](https://x.com/sonofmagic95/status/1768433654404104555) on Twitter, as an example._ + + +## Prefer `findInFiles` when possible + +If you have a lot of files to parse and want to maximize your programs' performance, ast-grep's language object provides a `findInFiles` function that parses multiple files and searches relevant nodes in parallel Rust threads. + +APIs we showed above all require parsing code in Rust and pass the `SgRoot` back to JavaScript. +This incurs foreign function communication overhead and only utilizes the single main JavaScript thread. +By avoiding Rust-JS communication overhead and utilizing multiple core computing, +`findInFiles` is much faster than finding files in JavaScript and then passing them to Rust as string. + +The function signature of `findInFiles` is as follows: + +```ts +export function findInFiles( + /** specify the file path and matcher */ + config: FindConfig, + /** callback function for found nodes in a file */ + callback: (err: null | Error, result: SgNode[]) => void +): Promise +``` + +`findInFiles` accepts a `FindConfig` object and a callback function. + +`FindConfig` specifies both what file path to _parse_ and what nodes to _search_. + +`findInFiles` will parse all files matching paths and will call back the function with nodes matching the `matcher` found in the files as arguments. + +### `FindConfig` + +The `FindConfig` object specifies which paths to search code and what rule to match node against. + +The `FindConfig` object has the following type: + +```ts +export interface FindConfig { + paths: Array + matcher: NapiConfig +} +``` + +The `path` field is an array of strings. You can specify multiple paths to search code. Every path in the array can be a file path or a directory path. For a directory path, ast-grep will recursively find all files matching the language. + +The `matcher` is the same as `NapiConfig` stated above. + +### Callback Function and Termination + +The `callback` function is called for every file that have nodes that match the rule. The callback function is a standard node-style callback with the first argument as `Error` and second argument as an array of `SgNode` objects that match the rule. + +The return value of `findInFiles` is a `Promise` object. The promise resolves to the number of files that have nodes that match the rule. + +:::danger +`findInFiles` can return before all file callbacks are called due to NodeJS limitation. +See https://github.com/ast-grep/ast-grep/issues/206. +::: + +If you have a lot of files and `findInFiles` prematurely returns, you can use the total files returned by `findInFiles` as a check point. Maintain a counter outside of `findInFiles` and increment it in callback. If the counter equals the total number, we can conclude all files are processed. The following code is an example, with core logic highlighted. + +```ts:line-numbers {11,16-18} +type Callback = (t: any, cb: any) => Promise +function countedPromise(func: F) { + type P = Parameters + return async (t: P[0], cb: P[1]) => { + let i = 0 + let fileCount: number | undefined = undefined + // resolve will be called after all files are processed + let resolve = () => {} + function wrapped(...args: any[]) { + let ret = cb(...args) + if (++i === fileCount) resolve() + return ret + } + fileCount = await func(t, wrapped as P[1]) + // not all files are processed, await `resolve` to be called + if (fileCount > i) { + await new Promise(r => resolve = r) + } + return fileCount + } +} +``` + +### Example +Example of using `findInFiles` + +```ts +let fileCount = await js.findInFiles({ + paths: ['relative/path/to/code'], + matcher: { + rule: {kind: 'member_expression'} + }, +}, (err, n) => { + t.is(err, null) + t.assert(n.length > 0) + t.assert(n[0].text().includes('.')) +}) +``` diff --git a/website/guide/api-usage/py-api.md b/website/guide/api-usage/py-api.md index 463c51c2..25c7c125 100644 --- a/website/guide/api-usage/py-api.md +++ b/website/guide/api-usage/py-api.md @@ -179,10 +179,10 @@ arg.text() # returns 'hello' # returns [] because $A and $$$A are different node.get_multiple_matches("A") -node = root.find(pattern="logger($$$ARGS)") -# returns [SgNode('hello'), SgNode('world'), SgNode('!')] -node.get_multiple_matches("ARGS") -node.get_match("A") # returns None +logs = root.find(pattern="logger($$$ARGS)") +# returns [SgNode('hello'), SgNode(','), SgNode('world'), SgNode(','), SgNode('!')] +logs.get_multiple_matches("ARGS") +logs.get_match("A") # returns None ``` `SgNode` also supports `__getitem__` to get the match of single meta variable. @@ -274,4 +274,43 @@ class SgNode: def next_all(self) -> List[SgNode]: ... def prev(self) -> Optional[SgNode]: ... def prev_all(self) -> List[SgNode]: ... -``` \ No newline at end of file +``` + +## Fix code + +`SgNode` is immutable so it is impossible to change the code directly. + +However, `SgNode` has a `replace` method to generate an `Edit` object. You can then use the `commitEdits` method to apply the changes and generate new source string. + +```python +class Edit: + # The start position of the edit + start_pos: int + # The end position of the edit + end_pos: int + # The text to be inserted + inserted_text: str + +class SgNode: + # Edit + def replace(self, new_text: str) -> Edit: ... + def commit_edits(self, edits: List[Edit]) -> str: ... +``` + +**Example** + +```python +root = SgRoot("print('hello world')", "python").root() +node = root.find(pattern="print($A)") +edit = node.replace("logger.log('bye world')") +new_src = node.commit_edits([edit]) +# "logger.log('bye world')" +``` + +Note, `logger.log($A)` will not generate `logger.log('hello world')` in Python API unlike the CLI. This is because using the host language to generate the replacement string is more flexible. + +:::warning +Metavariable will not be replaced in the `replace` method. You need to create a string using `get_match(var_name)` by using Python. +::: + +See also [ast-grep#1172](https://github.com/ast-grep/ast-grep/issues/1172) diff --git a/website/guide/introduction.md b/website/guide/introduction.md index f043673a..7faece42 100644 --- a/website/guide/introduction.md +++ b/website/guide/introduction.md @@ -10,12 +10,12 @@ head: ## Introduction -ast-grep is a new AST based tool for managing your code, at massive scale. +ast-grep is a new AST based tool to manage your code, at massive scale. Using ast-grep can be as simple as running a single command in your terminal: ```bash -sg --pattern 'var code = $PAT' --rewrite 'let code = $PAT' --lang js +ast-grep --pattern 'var code = $PAT' --rewrite 'let code = $PAT' --lang js ``` The command above will replace `var` statement with `let` for all JavaScript files. @@ -24,12 +24,14 @@ The command above will replace `var` statement with `let` for all -Still got questions? Join our [Discord](https://discord.gg/4YZjf6htSQ) and ask @ast-grep-bot anything! +Still got questions? Join our [Discord](https://discord.gg/4YZjf6htSQ) and discuss with other users! -@ast-grep-bot is fluent in many natural languages. It can help you to quickly find the relevant information for your task and even write a pattern or YAML rule for you! +You can also ask questions under the [ast-grep](https://stackoverflow.com/questions/tagged/ast-grep) tag on [StackOverflow](https://stackoverflow.com/questions/ask). \ No newline at end of file diff --git a/website/guide/pattern-syntax.md b/website/guide/pattern-syntax.md index 38718fe2..8c026a9b 100644 --- a/website/guide/pattern-syntax.md +++ b/website/guide/pattern-syntax.md @@ -10,7 +10,6 @@ through the full syntax tree, so pattern can also match nested expression. For e code. ```javascript - const b = a + 1 funcCall(a + 1) @@ -142,7 +141,7 @@ testFunc(1 + 1) testFunc(...args) ``` -Note in the example above, even if two meta variables have the same name `$_FUNC`, each occurrence of `$_FUNC` can match different content because the are not captured. +Note in the example above, even if two meta variables have the same name `$_FUNC`, each occurrence of `$_FUNC` can match different content because they are not captured. :::info Why use non-capturing match? This is a useful trick to micro-optimize pattern matching speed, since we don't need to create a [HashMap](https://doc.rust-lang.org/stable/std/collections/struct.HashMap.html) for bookkeeping. @@ -164,4 +163,6 @@ We will cover using rules in next chapter. Pattern can also be an object instead of string in YAML rule. It is very useful to avoid ambiguity in code snippet. See [here](/guide/rule-config/atomic-rule.html#pattern) for more details. -::: \ No newline at end of file + +Also see our FAQ for more [guidance](/advanced/faq.html) on writing patterns. +::: diff --git a/website/guide/project/lint-rule.md b/website/guide/project/lint-rule.md index 2069722d..fcee3cbb 100644 --- a/website/guide/project/lint-rule.md +++ b/website/guide/project/lint-rule.md @@ -73,6 +73,24 @@ So `console.log(name)` will match the above rule, but `console.log('Rem')` will See [playground](https://ast-grep.github.io/playground.html#eyJtb2RlIjoiQ29uZmlnIiwibGFuZyI6ImphdmFzY3JpcHQiLCJxdWVyeSI6ImNvbnNvbGUubG9nKCRNQVRDSCkiLCJjb25maWciOiIjIENvbmZpZ3VyZSBSdWxlIGluIFlBTUxcbnJ1bGU6XG4gIHBhdHRlcm46IGNvbnNvbGUubG9nKCRHUkVFVClcbmNvbnN0cmFpbnRzOlxuICBHUkVFVDpcbiAgICBraW5kOiBpZGVudGlmaWVyIiwic291cmNlIjoiY29uc29sZS5sb2coJ0hlbGxvIFdvcmxkJylcbmNvbnNvbGUubG9nKGdyZWV0aW5nKVxuIn0=) in action. +:::warning +Note, constraints only applies to the single meta variable like `$ARG`, not multiple meta variable like `$$$ARGS`. +::: + +:::details `constraints` is applied after `rule` and does not work inside `not` +`constraints` is a filter to further refine the matched nodes and is applied after the `rule` is matched. +So the `constraints` field cannot be used inside `not`, for example +```yml +rule: + pattern: console.log($GREET) + not: { pattern: console.log($STR) } +constraints: + STR: { kind: string} +``` +The intent of the above rule is to match all `console.log` call except the one with string argument. +But it will match nothing because `console.log($STR)` is exactly the same as `console.log($GREET)` before the `constraints` is applied. +The `not` and `pattern` will conflict with each other. +::: ### `transform` @@ -81,7 +99,7 @@ See [playground](https://ast-grep.github.io/playground.html#eyJtb2RlIjoiQ29uZmln It is useful when you combine `transform` and `fix` to rewrite the codebase. For example, you may want to capitalize the matched variable name, or extract a substring from the matched node. -See the [transform](/guide/rewrite-code.html#use-transform-in-rewrite) section in rewriting guide for more details. +See the [transform](/guide/rewrite/transform.html) section in rewriting guide for more details. ### `fix` ast-grep can perform automatic rewriting to the codebase. The `fix` field in the rule configuration specifies how to rewrite the code. We can also use meta variables specified in the `rule` in `fix`. ast-grep will replace the meta-variables with the content of actual matched AST nodes. @@ -107,9 +125,9 @@ An example will be like this. The meta variable `$GREET` will be replaced both i ## Other Linting Fields * `message` is a concise description when the issue is reported. -* `severity` is the issue's severity. +* `severity` is the issue's severity. See more in [severity](/guide/project/severity.html). * `note` is a detailed message to elaborate the message and preferably to provide actionable fix to end users. - +* `labels` is a dictionary of labels to customize error reporting's code highlighting. ### `files`/`ignores` @@ -117,55 +135,82 @@ Rules can be applied to only certain files in a codebase with `files`. `files` s ```yaml files: -- "./tests/**" -- "./integration_tests/test.py" +- "tests/**" +- "integration_tests/test.py" ``` Similarly, you can use `ignores` to ignore applying a rule to certain files. `ignores` supports a list of glob patterns: ```yaml ignores: -- "./tests/config/**" +- "tests/config/**" ``` -:::tip They work together! -`ignores` and `files` can be used together. -::: +`ignores` and `files` can be used together. `ignores` will be tested before `files`. See [reference](/reference/yaml.html#ignores) for more details. -:::warning Don't forget `./` +:::warning Don't add `./` -Be sure to add `./` to the beginning of your rules. ast-grep will not recognize the paths if you omit `./`. +Be sure to remove `./` to the beginning of your rules. ast-grep will not recognize the paths if you add `./`. ::: -## Suppress Linting Error +## Customize Code Highlighting -It is possible to ignore a single line of code in ast-grep's scanning. A developer can suppress ast-grep's error by adding `ast-grep-ignore` above the line that triggers the issue. +ast-grep will report linting issues with highlighted code span called label. A label describes an underlined region of code associated with an issue. _By default, the matched target code and its surrounding code captured by [relational rules](/guide/rule-config/relational-rule.html)_. -The suppression comment has the following format: +ast-grep further allows you to customize the highlighting style with the configuration `labels` in the rule to provide more context to the developer. **`labels` is a dictionary of which the keys are the meta-variable name without `$` and the values ares label config objects.** -```javascript -// ast-grep-ignore -// ast-grep-ignore: , +The label config object contains two fields: the required `style` and the optional `message`. +* `style` specifies the category of the label. Available choices are `primary` and `secondary`. + * `primary` describe the primary cause of an issue. + * `secondary` provides additional context for a diagnostic. +* `message` specifies the message to be displayed along with the label. + +Note, a `label` meta-variable must have a corresponding AST node in the matched code because highlighting requires a range in the code for label. That is, the **label meta-variables must be defined in `rule` or `constraints`**. Meta-variables in `transform` cannot be used in `labels` as they are not part of the matched AST node. + +--- + +Let's see an example. Suppose we have a [rule](/playground.html#eyJtb2RlIjoiQ29uZmlnIiwibGFuZyI6ImphdmFzY3JpcHQiLCJxdWVyeSI6IiIsInJld3JpdGUiOiIiLCJzdHJpY3RuZXNzIjoic21hcnQiLCJzZWxlY3RvciI6IiIsImNvbmZpZyI6InJ1bGU6XG4gIHBhdHRlcm46XG4gICAgY29udGV4dDogJ2NsYXNzIEggeyAkTUVUSE9EKCkgeyAkJCQgfSB9J1xuICAgIHNlbGVjdG9yOiBtZXRob2RfZGVmaW5pdGlvblxuICBpbnNpZGU6XG4gICAgcGF0dGVybjogY2xhc3MgJENMQVNTIHsgJCQkIH1cbiAgICBzdG9wQnk6IGVuZCIsInNvdXJjZSI6ImNsYXNzIE5vdENvbXBvbmVudCB7XG4gICAgbmdPbkluaXQoKSB7fVxufSJ9) that matches method declaration in a class. + +```yaml +rule: + pattern: + context: 'class H { $METHOD() { $$$ } }' + selector: method_definition + inside: + pattern: class $CLASS { $$$ } + stopBy: end +``` +Without label customization, ast-grep will highlight the method declaration (target), and the whole class declaration, captured by relational rule. We can customize the highlighting with `labels`: + +```yaml +labels: + METHOD: + style: primary + message: the method name + CLASS: + style: secondary + message: The class name ``` -* A comment with the content `ast-grep-ignore` will suppress the following line's diagnostic. -* The magic word `ast-grep-ignore` alone will suppress _all_ kinds of diagnostics. -* `ast-grep-ignore: ` can suppress specific rules. -* You can ignore multiple rules by providing a comma-separated list in the comment. e.g. `ast-grep-ignore: rule-1, rule-2` +Instead of highlighting the whole method declaration and class declaration, we are just highlighting the method name and class name. The `style` field specifies the highlighting style. The `message` field specifies the message to be displayed in the editor extension. See this post for a [demo](https://x.com/hd_nvim/status/1924120276939256154) and [the example](/catalog/typescript/missing-component-decorator.html) in catalog. + +:::tip VSCode Extension respects `labels` +ast-grep's LSP diagnostic reporting also respects the labels configuration. Labels with messages are displayed in the editor extension as [diagnostic related information](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#diagnosticRelatedInformation). Users can jump to the label by clicking the message in the editor. +::: + -See the [playground](https://ast-grep.github.io/playground.html#eyJtb2RlIjoiQ29uZmlnIiwibGFuZyI6ImphdmFzY3JpcHQiLCJxdWVyeSI6IiRDQUxMRVIgOj0gJmZvb3t9IiwicmV3cml0ZSI6IiIsImNvbmZpZyI6ImlkOiBuby1jb25zb2xlXG5sYW5ndWFnZTogSmF2YVNjcmlwdFxucnVsZTpcbiAgcGF0dGVybjogY29uc29sZS5sb2coJEEpIiwic291cmNlIjoiY29uc29sZS5sb2coJ2hlbGxvJykgIC8vIG1hdGNoXG4vLyBhc3QtZ3JlcC1pZ25vcmVcbmNvbnNvbGUubG9nKCdzdXBwcmVzc2VkJykgLy8gc3VwcHJlc3NlZFxuLy8gYXN0LWdyZXAtaWdub3JlOiBuby1jb25zb2xlXG5jb25zb2xlLmxvZygnc3VwcHJlc3NlZCcpIC8vIHN1cHByZXNzZWRcbi8vIGFzdC1ncmVwLWlnbm9yZTogb3RoZXItcnVsZVxuY29uc29sZS5sb2coJ3dvcmxkJykgLy8gbWF0Y2hcbiJ9) for example. +## Ignore Linting Error -```javascript [1,7] -console.log('hello') // match +It is possible to ignore a single line of code in ast-grep's scanning. A developer can suppress ast-grep's error by adding `ast-grep-ignore` comment. For example, in JavaScript: + +```javascript // ast-grep-ignore -console.log('suppressed') // suppressed -// ast-grep-ignore: no-console -console.log('suppressed') // suppressed -// ast-grep-ignore: other-rule -console.log('world') // match +// ast-grep-ignore: , ``` +The first comment will suppress the following line's diagnostic. The second comment will suppress one or more specific rules. +There are more options to configure ast-grep's linting behavior, please see [severity](/guide/project/severity.html) for more deep dive. ## Test and Debug Rules @@ -173,5 +218,5 @@ After you have written your rule, you can test it with ast-grep's builtin `test` Let's see it in [next section](/guide/test-rule). :::tip Pro Tip -You can write a standalone [rule file](/reference/rule.html) and the command `sg scan -r rule.yml` to perform an [ad-hoc search](/guide/tooling-overview.html#run-one-single-query-or-one-single-rule). +You can write a standalone [rule file](/reference/rule.html) and the command `ast-grep scan -r rule.yml` to perform an [ad-hoc search](/guide/tooling-overview.html#run-one-single-query-or-one-single-rule). ::: \ No newline at end of file diff --git a/website/guide/project/project-config.md b/website/guide/project/project-config.md index 35d228bb..941026e7 100644 --- a/website/guide/project/project-config.md +++ b/website/guide/project/project-config.md @@ -3,7 +3,7 @@ ## Root Configuration File ast-grep supports using [YAML](https://yaml.org/) to configure its linting rules to scan your code repository. -We need a root configuration file `sgconfig.yml` to specify directories where `sg` can find all rules. +We need a root configuration file `sgconfig.yml` to specify directories where `ast-grep` can find all rules. In your project root, add `sgconfig.yml` with content as below. @@ -28,10 +28,10 @@ my-awesome-project |- not-a-rule.yml ``` -All the YAML files under `rules` folder will be treated as rule files by `sg`, while`not-a-rule.yml` is ignored by ast-grep. +All the YAML files under `rules` folder will be treated as rule files by `ast-grep`, while`not-a-rule.yml` is ignored. -**Note, the [`sg scan`](/reference/cli.html#scan) command requires you have an `sgconfig.yml` in your project root.** +**Note, the [`ast-grep scan`](/reference/cli.html#scan) command requires you have an `sgconfig.yml` in your project root.** :::tip Pro tip We can also use directories in `node_modules` to reuse preconfigured rules published on npm! @@ -39,4 +39,32 @@ We can also use directories in `node_modules` to reuse preconfigured rules publi More broadly speaking, any git hosted projects can be imported as rule sets by using [`git submodule`](https://www.git-scm.com/book/en/v2/Git-Tools-Submodules). ::: -## \ No newline at end of file +## Project Discovery + +ast-grep will try to find the `sgconfig.yml` file in the current working directory. If it is not found, it will traverse up the directory tree until it finds one. You can also specify the path to the configuration file using the `--config` option. + +```bash +ast-grep scan --config path/to/config.yml +``` + +:::tip Global Configuration +You can put an `sgconfig.yml` in your home directory to set global configurations for `ast-grep`. XDG configuration directory is **NOT** supported yet. +::: + +Project file discovery and `--config` option are also effective in the `ast-grep run` command. So you can use configurations like [custom languages](/reference/sgconfig.html#customlanguages) and [language globs](/reference/sgconfig.html#languageglobs). Note that `run` command does not require a `sgconfig.yml` file and will stil search code without it, but `scan` command will report an error if project config is not found. + +## Project Inspection + +You can use the [`--inspect summary`](/reference/cli/scan.html#inspect-granularity) flag to see the project directory ast-grep is using. + +```bash +ast-grep scan --inspect summary +``` + +It will print the project directory and the configuration file path. + +```bash +sg: summary|project: isProject=true,projectDir=/path/to/project +``` + +Output format can be found in the [GitHub issue](https://github.com/ast-grep/ast-grep/issues/1574). \ No newline at end of file diff --git a/website/guide/project/severity.md b/website/guide/project/severity.md new file mode 100644 index 00000000..1f94c410 --- /dev/null +++ b/website/guide/project/severity.md @@ -0,0 +1,149 @@ +# Handle Error Reports + +## Severity Levels + +ast-grep supports these severity levels for rules: + +* `error`: The rule will report an error and fails a scan. +* `warning`: The rule will report a warning. +* `info`: The rule will report an informational message. +* `hint`: The rule will report a hint. This is the default severity level. +* `off`: The rule will disable the rule at all. + +If an `error` rule is triggered, `ast-grep scan` will exit with a non-zero status code. This is useful for CI/CD pipelines to fail the build when a rule is violated. + +You can configure the severity level of a rule in the rule file: + +```yaml +id: rule-id +severity: error +# ... more fields +``` + +## Override Severity on CLI + +You can override the severity level of a rule on the command line. This is useful when you want to change the severity level of a rule for a specific scan. + +```bash +ast-grep scan --error rule-id --warning other-rule-id +``` + +You can use multiple `--error`, `--warning`, `--info`, `--hint`, and `--off` flags to override multiple rules. + + +## Ignore Linting Error + +It is possible to ignore a single line of code in ast-grep's scanning. A developer can suppress ast-grep's error by adding `ast-grep-ignore` above the line that triggers the issue, or on the same line. + +The suppression comment has the following format, in JavaScript for example: + +```javascript {1,7} +console.log('hello') // match +// ast-grep-ignore +console.log('suppressed') // suppressed +// ast-grep-ignore: no-console +console.log('suppressed') // suppressed +// ast-grep-ignore: other-rule +console.log('world') // match + +// Same line suppression +console.log('suppressed') // ast-grep-ignore +console.log('suppressed') // ast-grep-ignore: no-console +``` + +See the [playground](https://ast-grep.github.io/playground.html#eyJtb2RlIjoiQ29uZmlnIiwibGFuZyI6ImphdmFzY3JpcHQiLCJxdWVyeSI6IiRDQUxMRVIgOj0gJmZvb3t9IiwicmV3cml0ZSI6IiIsImNvbmZpZyI6ImlkOiBuby1jb25zb2xlXG5sYW5ndWFnZTogSmF2YVNjcmlwdFxucnVsZTpcbiAgcGF0dGVybjogY29uc29sZS5sb2coJEEpIiwic291cmNlIjoiY29uc29sZS5sb2coJ2hlbGxvJykgIC8vIG1hdGNoXG4vLyBhc3QtZ3JlcC1pZ25vcmVcbmNvbnNvbGUubG9nKCdzdXBwcmVzc2VkJykgLy8gc3VwcHJlc3NlZFxuLy8gYXN0LWdyZXAtaWdub3JlOiBuby1jb25zb2xlXG5jb25zb2xlLmxvZygnc3VwcHJlc3NlZCcpIC8vIHN1cHByZXNzZWRcbi8vIGFzdC1ncmVwLWlnbm9yZTogb3RoZXItcnVsZVxuY29uc29sZS5sb2coJ3dvcmxkJykgLy8gbWF0Y2hcbiJ9) in action. + +These are the rules for suppression comments: + +* A comment with the content `ast-grep-ignore` will suppress the following line/the same line's diagnostic. +* The magic word `ast-grep-ignore` alone will suppress _all_ kinds of diagnostics. +* `ast-grep-ignore: ` can turn off specific rules. +* You can turn off multiple rules by providing a comma-separated list in the comment. e.g. `ast-grep-ignore: rule-1, rule-2` +* Suppression comments will suppress the next line diagnostic if and only if there is no preceding ASTs on the same line. + +## File Level Suppression + +You can also suppress all diagnostics in a file by adding a suppression comment at the top of the file followed by an empty line. This is useful when you want to ignore all diagnostics in a file. + +For example, in JavaScript: + +:::code-group +```javascript [Disable all rules] +// ast-grep-ignore + +// This file will not be scanned by ast-grep +// note the empty line after the suppression comment. +debugger // this line will not be scanned +console.debug('debugging') // this line will not be scanned +``` + +```javascript{6} [Disable sepcific rules] +// ast-grep-ignore: no-debugger + +// This file will not be scanned by ast-grep +// note the empty line after the suppression comment. +debugger // this line will not trigger error +console.debug('debugging') // this line will trigger error +``` + +::: + +To suppress the whole file, there must be [two conditions](https://github.com/ast-grep/ast-grep/issues/1541#issuecomment-2573212686) met: +* The suppression comment is on the very first line of the file. +* AND the next line (second line in file) is empty + +These conditions are designed for backward compatibility. + +## Report Unused Suppressions + +ast-grep can report unused suppression comments in your codebase. This is useful to keep your codebase clean and to avoid suppressing issues that are no longer relevant. An example report will look like this: + +```diff +help[unused-suppression]: Unused 'ast-grep-ignore' directive. +- // ast-grep-ignore ++ +``` + +`unused-suppression` itself behaves like a `hint` rule with auto-fix. +But it is enabled, by default, only **when all rules are enabled**. + +More specifically, [these conditions](https://github.com/ast-grep/ast-grep/blob/553f5e5ac577b6d2e0904c423bb5dbd27804328b/crates/cli/src/scan.rs#L68-L73) must be met: + +1. No rule is [disabled](/guide/project/severity.html#override-severity-on-cli) by the `--off` flag on the CLI. `severity: off` configured in the YAML rule file does not count. +2. The CLI [`--rule`](/reference/cli/scan.html#r-rule-rule-file) flag is not used. +3. The CLI [`--inline-rules`](/reference/cli/scan.html#inline-rules-rule-text) flag is not used. +4. The CLI [`--filter`](/reference/cli/scan.html#filter-regex) flag is not used. + +:::tip Unused suppression report only happens in `ast-grep scan` +If a rule is skipped during a scan, it is possible to mistakenly report a suppression comment as unused. +So running specific rules or disabling rules will not trigger the unused suppression report. +::: + +You can also override the severity level of the `unused-suppression` rule on the command line. This can change the default behavior or unused-suppression reporting. + +```bash +# treat unused directive as error, useful in CI/CD +ast-grep scan --error unused-suppression +# enable report even not all rules are enabled +ast-grep --rule rule.yml scan --hint unused-suppression +``` + +## Inspect Rule Severity + +Finally, ast-grep provides a CLI flag [`--inspect`](/reference/cli/scan.html#inspect-granularity) to debug what rules are enabled and their severity levels. This is useful to understand the rule configuration and to debug why a rule is not triggered. + +```bash +ast-grep scan --inspect entity + +``` + +Example standard error debugging output: +``` +sg: entity|rule|no-dupe-class-members: finalSeverity=Error +sg: entity|rule|no-new-symbol: finalSeverity=Error +sg: entity|rule|no-cond-assign: finalSeverity=Warning +sg: entity|rule|no-constant-condition: finalSeverity=Warning +sg: entity|rule|no-dupe-keys: finalSeverity=Error +sg: entity|rule|no-await-in-loop: finalSeverity=Warning + +``` \ No newline at end of file diff --git a/website/guide/quick-start.md b/website/guide/quick-start.md index 76f4d9c9..e04bee80 100644 --- a/website/guide/quick-start.md +++ b/website/guide/quick-start.md @@ -29,6 +29,11 @@ brew install ast-grep sudo port install ast-grep ``` +```shell [nix-shell] +# try ast-grep in nix-shell +nix-shell -p ast-grep +``` + ```shell [cargo] # install via cargo cargo install ast-grep --locked @@ -45,18 +50,18 @@ pip install ast-grep-cli ``` ::: -The binary command, `sg`, or `ast-grep`, should be available now. Let's try it with `--help`. +The binary command, `ast-grep` or `sg`, should be available now. Let's try it with `--help`. ```shell -sg --help -# if you are on Linux ast-grep --help +# if you are not on Linux +sg --help ``` :::danger Use `sg` on Linux Linux has a default command `sg` for `setgroups`. You can use the full command name `ast-grep` instead of `sg`. You can also use shorter alias if you want by `alias sg=ast-grep`. -We will use `sg` in the guide below. +We will use `ast-grep` in the guide below. ::: @@ -103,22 +108,22 @@ Optionally, we can use `lang` to tell ast-grep our target code language. :::code-group ```shell [Full Command] -sg --pattern '$PROP && $PROP()' --lang ts TypeScript/src +ast-grep --pattern '$PROP && $PROP()' --lang ts TypeScript/src ``` ```shell [Short Form] -sg -p '$PROP && $PROP()' -l ts TypeScript/src +ast-grep -p '$PROP && $PROP()' -l ts TypeScript/src ``` ```shell [Without Lang] # ast-grep will infer languages based on file extensions -sg -p '$PROP && $PROP()' TypeScript/src +ast-grep -p '$PROP && $PROP()' TypeScript/src ``` ::: :::tip Pro Tip Pattern must be quoted by single quote `'` to prevent shell from interpreting `$` sign. -`sg -p '$PROP && $PROP()'` is okay. +`ast-grep -p '$PROP && $PROP()'` is okay. -But `sg -p "$PROP && $PROP()"` will be interpreted as `sg -p " && ()"` after shell expansion. +But `ast-grep -p "$PROP && $PROP()"` will be interpreted as `ast-grep -p " && ()"` after shell expansion. ::: ## Rewrite @@ -127,7 +132,7 @@ Cool? Now we can use this pattern to refactor TypeScript source! ```shell # pattern and language argument support short form -sg -p '$PROP && $PROP()' \ +ast-grep -p '$PROP && $PROP()' \ --rewrite '$PROP?.()' \ --interactive \ -l ts \ @@ -142,6 +147,10 @@ That's it! You have refactored TypeScript's repository in minutes. Congratulatio Hope you enjoy the power of AST editing in plain programming language pattern. Our next step is to know more about the pattern code. +:::tip Pattern does not work? +See our FAQ for more [guidance](/advanced/faq.html) on writing patterns. +::: + +file_type_typescript \ No newline at end of file diff --git a/website/public/langs/yaml.svg b/website/public/langs/yaml.svg new file mode 100644 index 00000000..64511db4 --- /dev/null +++ b/website/public/langs/yaml.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/website/public/parsers/downloadParsers.mjs b/website/public/parsers/downloadParsers.mjs new file mode 100644 index 00000000..ad78010b --- /dev/null +++ b/website/public/parsers/downloadParsers.mjs @@ -0,0 +1,23 @@ +import { parserPaths, repos, versions } from '../../_data/parsers.ts' +import fs from 'node:fs' + +async function main() { + const dirname = import.meta.dirname + for (const [lang, path] of Object.entries(parserPaths)) { + const repo = repos[lang] + const version = versions[lang] + if (!repo || !version) { + console.error(`Missing repo or version for ${lang}`) + continue + } + const url = `${repo}@v${version}/${path}` + console.log(`Downloading ${lang} parser from ${url}`) + const res = await fetch(url) + const buffer = await res.arrayBuffer() + const wasm = new Uint8Array(buffer) + console.log(`Writing ${lang} parser to ${path}`) + fs.writeFileSync(`${dirname}/${path}`, wasm) + } +} + +main() \ No newline at end of file diff --git a/website/public/parsers/tree-sitter-bash.wasm b/website/public/parsers/tree-sitter-bash.wasm new file mode 100644 index 00000000..28e2869f Binary files /dev/null and b/website/public/parsers/tree-sitter-bash.wasm differ diff --git a/website/public/parsers/tree-sitter-c.wasm b/website/public/parsers/tree-sitter-c.wasm new file mode 100644 index 00000000..6e037eab Binary files /dev/null and b/website/public/parsers/tree-sitter-c.wasm differ diff --git a/website/public/parsers/tree-sitter-c_sharp.wasm b/website/public/parsers/tree-sitter-c_sharp.wasm new file mode 100644 index 00000000..ec79f5f2 Binary files /dev/null and b/website/public/parsers/tree-sitter-c_sharp.wasm differ diff --git a/website/public/parsers/tree-sitter-cpp.wasm b/website/public/parsers/tree-sitter-cpp.wasm new file mode 100644 index 00000000..2e8cf9b1 Binary files /dev/null and b/website/public/parsers/tree-sitter-cpp.wasm differ diff --git a/website/public/parsers/tree-sitter-css.wasm b/website/public/parsers/tree-sitter-css.wasm new file mode 100644 index 00000000..f9b9f1af Binary files /dev/null and b/website/public/parsers/tree-sitter-css.wasm differ diff --git a/website/public/tree-sitter-elixir.wasm b/website/public/parsers/tree-sitter-elixir.wasm old mode 100755 new mode 100644 similarity index 100% rename from website/public/tree-sitter-elixir.wasm rename to website/public/parsers/tree-sitter-elixir.wasm diff --git a/website/public/parsers/tree-sitter-go.wasm b/website/public/parsers/tree-sitter-go.wasm new file mode 100644 index 00000000..74cd9277 Binary files /dev/null and b/website/public/parsers/tree-sitter-go.wasm differ diff --git a/website/public/parsers/tree-sitter-html.wasm b/website/public/parsers/tree-sitter-html.wasm new file mode 100644 index 00000000..d0fca082 Binary files /dev/null and b/website/public/parsers/tree-sitter-html.wasm differ diff --git a/website/public/parsers/tree-sitter-java.wasm b/website/public/parsers/tree-sitter-java.wasm new file mode 100644 index 00000000..68a0c1f3 Binary files /dev/null and b/website/public/parsers/tree-sitter-java.wasm differ diff --git a/website/public/parsers/tree-sitter-javascript.wasm b/website/public/parsers/tree-sitter-javascript.wasm new file mode 100644 index 00000000..74c03541 Binary files /dev/null and b/website/public/parsers/tree-sitter-javascript.wasm differ diff --git a/website/public/parsers/tree-sitter-json.wasm b/website/public/parsers/tree-sitter-json.wasm new file mode 100644 index 00000000..a75bba7a Binary files /dev/null and b/website/public/parsers/tree-sitter-json.wasm differ diff --git a/website/public/parsers/tree-sitter-kotlin.wasm b/website/public/parsers/tree-sitter-kotlin.wasm new file mode 100644 index 00000000..1f3b1625 Binary files /dev/null and b/website/public/parsers/tree-sitter-kotlin.wasm differ diff --git a/website/public/parsers/tree-sitter-php.wasm b/website/public/parsers/tree-sitter-php.wasm new file mode 100644 index 00000000..9bec0b79 Binary files /dev/null and b/website/public/parsers/tree-sitter-php.wasm differ diff --git a/website/public/parsers/tree-sitter-php_only.wasm b/website/public/parsers/tree-sitter-php_only.wasm new file mode 100644 index 00000000..6911aef5 Binary files /dev/null and b/website/public/parsers/tree-sitter-php_only.wasm differ diff --git a/website/public/parsers/tree-sitter-python.wasm b/website/public/parsers/tree-sitter-python.wasm new file mode 100644 index 00000000..408d97a2 Binary files /dev/null and b/website/public/parsers/tree-sitter-python.wasm differ diff --git a/website/public/parsers/tree-sitter-ruby.wasm b/website/public/parsers/tree-sitter-ruby.wasm new file mode 100644 index 00000000..c7a619c9 Binary files /dev/null and b/website/public/parsers/tree-sitter-ruby.wasm differ diff --git a/website/public/parsers/tree-sitter-rust.wasm b/website/public/parsers/tree-sitter-rust.wasm new file mode 100644 index 00000000..7a5cf2c9 Binary files /dev/null and b/website/public/parsers/tree-sitter-rust.wasm differ diff --git a/website/public/parsers/tree-sitter-scala.wasm b/website/public/parsers/tree-sitter-scala.wasm new file mode 100644 index 00000000..6f76cbe0 Binary files /dev/null and b/website/public/parsers/tree-sitter-scala.wasm differ diff --git a/website/public/tree-sitter-swift.wasm b/website/public/parsers/tree-sitter-swift.wasm old mode 100755 new mode 100644 similarity index 100% rename from website/public/tree-sitter-swift.wasm rename to website/public/parsers/tree-sitter-swift.wasm diff --git a/website/public/tree-sitter-toml.wasm b/website/public/parsers/tree-sitter-toml.wasm similarity index 100% rename from website/public/tree-sitter-toml.wasm rename to website/public/parsers/tree-sitter-toml.wasm diff --git a/website/public/parsers/tree-sitter-tsx.wasm b/website/public/parsers/tree-sitter-tsx.wasm new file mode 100644 index 00000000..563fcf27 Binary files /dev/null and b/website/public/parsers/tree-sitter-tsx.wasm differ diff --git a/website/public/parsers/tree-sitter-typescript.wasm b/website/public/parsers/tree-sitter-typescript.wasm new file mode 100644 index 00000000..6a5d7f1b Binary files /dev/null and b/website/public/parsers/tree-sitter-typescript.wasm differ diff --git a/website/public/parsers/tree-sitter-yaml.wasm b/website/public/parsers/tree-sitter-yaml.wasm new file mode 100644 index 00000000..66d6506d Binary files /dev/null and b/website/public/parsers/tree-sitter-yaml.wasm differ diff --git a/website/public/schema.json b/website/public/schema.json index 43181ea2..534a4fed 100644 --- a/website/public/schema.json +++ b/website/public/schema.json @@ -164,8 +164,7 @@ { "type": "object", "required": [ - "context", - "selector" + "context" ], "properties": { "context": { @@ -175,11 +174,54 @@ "selector": { "description": "The sub-syntax node kind that is the actual matcher of the pattern.", "type": "string" + }, + "strictness": { + "description": "Strictness of the pattern. More strict pattern matches fewer nodes.", + "$ref": "#/definitions/Strictness" } } } ] }, + "Strictness": { + "oneOf": [ + { + "description": "all nodes are matched", + "type": "string", + "enum": [ + "cst" + ] + }, + { + "description": "all nodes except source trivial nodes are matched.", + "type": "string", + "enum": [ + "smart" + ] + }, + { + "description": "only ast nodes are matched", + "type": "string", + "enum": [ + "ast" + ] + }, + { + "description": "ast-nodes excluding comments are matched", + "type": "string", + "enum": [ + "relaxed" + ] + }, + { + "description": "ast-nodes excluding comments, without text", + "type": "string", + "enum": [ + "signature" + ] + } + ] + }, "Relation": { "type": "object", "properties": { @@ -227,6 +269,10 @@ "description": "A single sub-rule and matches a node if the sub rule does not match.", "$ref": "#/definitions/SerializableRule" }, + "nthChild": { + "description": "`nth_child` accepts number, string or object. It specifies the position in nodes' sibling list.", + "$ref": "#/definitions/SerializableNthChild" + }, "pattern": { "description": "A pattern string or a pattern object.", "$ref": "#/definitions/PatternStyle" @@ -352,6 +398,10 @@ "description": "A single sub-rule and matches a node if the sub rule does not match.", "$ref": "#/definitions/SerializableRule" }, + "nthChild": { + "description": "`nth_child` accepts number, string or object. It specifies the position in nodes' sibling list.", + "$ref": "#/definitions/SerializableNthChild" + }, "pattern": { "description": "A pattern string or a pattern object.", "$ref": "#/definitions/PatternStyle" @@ -381,6 +431,52 @@ } ] }, + "SerializableNthChild": { + "description": "`nthChild` accepts either a number, a string or an object.", + "anyOf": [ + { + "description": "Simple syntax", + "$ref": "#/definitions/NthChildSimple" + }, + { + "description": "Object style syntax", + "type": "object", + "required": [ + "position" + ], + "properties": { + "ofRule": { + "description": "select the nth node that matches the rule, like CSS's of syntax", + "$ref": "#/definitions/SerializableRule" + }, + "position": { + "description": "nth-child syntax", + "$ref": "#/definitions/NthChildSimple" + }, + "reverse": { + "description": "matches from the end instead like CSS's nth-last-child", + "default": false, + "type": "boolean" + } + } + } + ] + }, + "NthChildSimple": { + "description": "A string or number describing the indices of matching nodes in a list of siblings.", + "anyOf": [ + { + "description": "A number indicating the precise element index", + "type": "integer", + "format": "uint", + "minimum": 0.0 + }, + { + "description": "Functional notation like CSS's An + B", + "type": "string" + } + ] + }, "Severity": { "oneOf": [ { @@ -464,6 +560,9 @@ "Transformation": { "description": "Represents a transformation that can be applied to a matched AST node. Available transformations are `substring`, `replace` and `convert`.", "oneOf": [ + { + "type": "string" + }, { "type": "object", "required": [ @@ -539,4 +638,4 @@ } } } -} \ No newline at end of file +} diff --git a/website/public/tree-sitter-bash.wasm b/website/public/tree-sitter-bash.wasm deleted file mode 100644 index 4092a51e..00000000 Binary files a/website/public/tree-sitter-bash.wasm and /dev/null differ diff --git a/website/public/tree-sitter-c.wasm b/website/public/tree-sitter-c.wasm deleted file mode 100755 index 86320a7a..00000000 Binary files a/website/public/tree-sitter-c.wasm and /dev/null differ diff --git a/website/public/tree-sitter-c_sharp.wasm b/website/public/tree-sitter-c_sharp.wasm deleted file mode 100644 index a9fe5d3c..00000000 Binary files a/website/public/tree-sitter-c_sharp.wasm and /dev/null differ diff --git a/website/public/tree-sitter-cpp.wasm b/website/public/tree-sitter-cpp.wasm deleted file mode 100644 index e7b22905..00000000 Binary files a/website/public/tree-sitter-cpp.wasm and /dev/null differ diff --git a/website/public/tree-sitter-dart.wasm b/website/public/tree-sitter-dart.wasm deleted file mode 100644 index 17175a9b..00000000 Binary files a/website/public/tree-sitter-dart.wasm and /dev/null differ diff --git a/website/public/tree-sitter-go.wasm b/website/public/tree-sitter-go.wasm deleted file mode 100755 index 1d17adba..00000000 Binary files a/website/public/tree-sitter-go.wasm and /dev/null differ diff --git a/website/public/tree-sitter-html.wasm b/website/public/tree-sitter-html.wasm deleted file mode 100644 index ee61c315..00000000 Binary files a/website/public/tree-sitter-html.wasm and /dev/null differ diff --git a/website/public/tree-sitter-java.wasm b/website/public/tree-sitter-java.wasm deleted file mode 100644 index 382f2557..00000000 Binary files a/website/public/tree-sitter-java.wasm and /dev/null differ diff --git a/website/public/tree-sitter-javascript.wasm b/website/public/tree-sitter-javascript.wasm deleted file mode 100644 index dfff4727..00000000 Binary files a/website/public/tree-sitter-javascript.wasm and /dev/null differ diff --git a/website/public/tree-sitter-json.wasm b/website/public/tree-sitter-json.wasm deleted file mode 100755 index bbab6b1b..00000000 Binary files a/website/public/tree-sitter-json.wasm and /dev/null differ diff --git a/website/public/tree-sitter-kotlin.wasm b/website/public/tree-sitter-kotlin.wasm deleted file mode 100755 index 093d6b7e..00000000 Binary files a/website/public/tree-sitter-kotlin.wasm and /dev/null differ diff --git a/website/public/tree-sitter-php.wasm b/website/public/tree-sitter-php.wasm deleted file mode 100644 index a08d6f50..00000000 Binary files a/website/public/tree-sitter-php.wasm and /dev/null differ diff --git a/website/public/tree-sitter-python.wasm b/website/public/tree-sitter-python.wasm deleted file mode 100644 index f27f4fed..00000000 Binary files a/website/public/tree-sitter-python.wasm and /dev/null differ diff --git a/website/public/tree-sitter-ruby.wasm b/website/public/tree-sitter-ruby.wasm deleted file mode 100644 index e0dc693e..00000000 Binary files a/website/public/tree-sitter-ruby.wasm and /dev/null differ diff --git a/website/public/tree-sitter-rust.wasm b/website/public/tree-sitter-rust.wasm deleted file mode 100755 index afa7e632..00000000 Binary files a/website/public/tree-sitter-rust.wasm and /dev/null differ diff --git a/website/public/tree-sitter-scala.wasm b/website/public/tree-sitter-scala.wasm deleted file mode 100755 index e0d8952b..00000000 Binary files a/website/public/tree-sitter-scala.wasm and /dev/null differ diff --git a/website/public/tree-sitter-tsx.wasm b/website/public/tree-sitter-tsx.wasm deleted file mode 100755 index 6df4ccfa..00000000 Binary files a/website/public/tree-sitter-tsx.wasm and /dev/null differ diff --git a/website/public/tree-sitter-typescript.wasm b/website/public/tree-sitter-typescript.wasm deleted file mode 100644 index 6e7789d0..00000000 Binary files a/website/public/tree-sitter-typescript.wasm and /dev/null differ diff --git a/website/public/tree-sitter-yaml.wasm b/website/public/tree-sitter-yaml.wasm deleted file mode 100644 index ecbc7f53..00000000 Binary files a/website/public/tree-sitter-yaml.wasm and /dev/null differ diff --git a/website/public/tree-sitter.wasm b/website/public/tree-sitter.wasm old mode 100755 new mode 100644 diff --git a/website/reference/api.md b/website/reference/api.md index 2a2b504b..3eb0b8c4 100644 --- a/website/reference/api.md +++ b/website/reference/api.md @@ -14,45 +14,54 @@ https://github.com/ast-grep/ast-grep/blob/main/crates/napi/index.d.ts ### Supported Languages -`@ast-grep/napi` supports `html`, `js`, `jsx`, `ts` and `tsx`. +`@ast-grep/napi` supports JS ecosystem languages by default. +More custom languages can be loaded via [`registerDynamicLanguage`](https://github.com/search?q=repo%3Aast-grep%2Flangs%20registerDynamicLanguage&type=code). #### Type -A language object has following methods. - ```ts -export namespace js { - /** Parse a string to an ast-grep instance */ - export function parse(src: string): SgRoot - /** Get the `kind` number from its string name. */ - export function kind(kindName: string): number - /** Compile a string to ast-grep Pattern. */ - export function pattern(pattern: string): NapiConfig - /** - * Discover and parse multiple files in Rust. - * `config` specifies the file path and matcher. - * `callback` will receive matching nodes found in a file. - * returns the number of matched files. - */ - export function findInFiles( - config: FindConfig, - callback: (err: null | Error, result: SgNode[]) => void - ): Promise +export const enum Lang { + Html = 'Html', + JavaScript = 'JavaScript', + Tsx = 'Tsx', + Css = 'Css', + TypeScript = 'TypeScript', } + +// More custom languages can be loaded +// see https://github.com/ast-grep/langs +type CustomLang = string & {} +``` + +`CustomLang` is not widely used now. If you have use case and needs support, please file an issue in the [@ast-grep/langs](https://github.com/ast-grep/langs?tab=readme-ov-file#packages) repository. + +### Main functions + +You can use `parse` to transform a string to ast-grep's main object `SgRoot`. +ast-grep also provides other utility for parse kind string and construct pattern. + +```ts +/** Parse a string to an ast-grep instance */ +export function parse(lang: Lang, src: string): SgRoot +/** Get the `kind` number from its string name. */ +export function kind(lang: Lang, kindName: string): number +/** Compile a string to ast-grep Pattern. */ +export function pattern(lang: Lang, pattern: string): NapiConfig ``` #### Example ```ts -import { js } from '@ast-grep/napi' +import { parse, Lang } from '@ast-grep/napi' -const source = `console.log("hello world")` -const ast = js.parse(source) +const ast = parse(Lang.JavaScript, source) +const root = ast.root() +root.find("console.log") ``` ### SgRoot -You will get an `SgRoot` instance when you `lang.parse(string)`. +You will get an `SgRoot` instance when you `parse(lang, string)`. `SgRoot` can also be accessed in `lang.findInFiles`'s callback by calling `node.getRoot()`. @@ -67,7 +76,7 @@ class SgRoot { root(): SgNode /** * Returns the path of the file if it is discovered by ast-grep's `findInFiles`. - * Returns `"anonymous"` if the instance is created by `lang.parse(source)`. + * Returns `"anonymous"` if the instance is created by `parse(lang, source)`. */ filename(): string } @@ -76,7 +85,9 @@ class SgRoot { #### Example ```ts -const ast = js.parse(source) +import { parse, Lang } from '@ast-grep/napi' + +const ast = parse(Lang.JavaScript, source) const root = ast.root() root.find("console.log") ``` @@ -96,6 +107,10 @@ class SgNode { isNamed(): boolean isNamedLeaf(): boolean kind(): string + // check if node has kind + is(kind: string): boolean + // for TypeScript type narrow + kindToRefine: string text(): string // Check if node meets certain patterns matches(m: string): boolean @@ -120,9 +135,14 @@ class SgNode { nextAll(): Array prev(): SgNode | null prevAll(): Array + // Edit + replace(text: string): Edit + commitEdits(edits: Edit[]): string } ``` +Some methods have more sophisticated type signatures for the ease of use. See the [source code](https://github.com/ast-grep/ast-grep/blob/0999cdb542ff4431e3734dad38fcd648de972e6a/crates/napi/types/sgnode.d.ts#L38-L41) and our [tech blog](/blog/typed-napi.html) + ### NapiConfig `NapiConfig` is used in `find` or `findAll`. @@ -136,7 +156,6 @@ interface NapiConfig { rule: object constraints?: object language?: FrontEndLanguage - // @experimental transform?: object utils?: object } @@ -157,12 +176,128 @@ interface FindConfig { } ``` +### Edit + +`Edit` is used in `replace` and `commitEdits`. + +```ts +interface Edit { + startPos: number + endPos: number + insertedText: string +} +``` + ### Useful Examples * [Test Case Source](https://github.com/ast-grep/ast-grep/blob/main/crates/napi/__test__/index.spec.ts) for `@ast-grep/napi` * ast-grep usage in [vue-vine](https://github.com/vue-vine/vue-vine/blob/b661fd2dfb54f2945e7bf5f3691443e05a1ab8f8/packages/compiler/src/analyze.ts#L32) +### Language Object (deprecated) + +:::details language objects are deprecated + +`ast-grep/napi` also has special language objects for `html`, `js` and `css`. They are deprecated and will be removed in the next version. + +A language object has following methods. + +```ts +/** + * @deprecated language specific objects are deprecated + * use the equivalent functions like `parse` in @ast-grep/napi + */ +export declare namespace js { + /** @deprecated use `parse(Lang.JavaScript, src)` instead */ + export function parse(src: string): SgRoot + /** @deprecated use `parseAsync(Lang.JavaScript, src)` instead */ + export function parseAsync(src: string): Promise + /** @deprecated use `kind(Lang.JavaScript, kindName)` instead */ + export function kind(kindName: string): number + /** @deprecated use `pattern(Lang.JavaScript, p)` instead */ + export function pattern(pattern: string): NapiConfig + /** @deprecated use `findInFiles(Lang.JavaScript, config, callback)` instead */ + export function findInFiles( + config: FindConfig, + callback: (err: null | Error, result: SgNode[]) => void + ): Promise +} +``` + +#### Example + +```ts +import { js } from '@ast-grep/napi' + +const source = `console.log("hello world")` +const ast = js.parse(source) +``` + +::: + ## Python API +### SgRoot + +The entry point object of ast-grep. You can use SgRoot to parse a string into a syntax tree. + +```python +class SgRoot: + def __init__(self, src: str, language: str) -> None: ... + def root(self) -> SgNode: ... +``` + + +### SgNode + +Most methods are self-explanatory. Please submit a new [issue](https://github.com/ast-grep/ast-grep/issues/new/choose) if you find something confusing. + +```python +class SgNode: + # Node Inspection + def range(self) -> Range: ... + def is_leaf(self) -> bool: ... + def is_named(self) -> bool: ... + def is_named_leaf(self) -> bool: ... + def kind(self) -> str: ... + def text(self) -> str: ... + + # Refinement + def matches(self, **rule: Unpack[Rule]) -> bool: ... + def inside(self, **rule: Unpack[Rule]) -> bool: ... + def has(self, **rule: Unpack[Rule]) -> bool: ... + def precedes(self, **rule: Unpack[Rule]) -> bool: ... + def follows(self, **rule: Unpack[Rule]) -> bool: ... + def get_match(self, meta_var: str) -> Optional[SgNode]: ... + def get_multiple_matches(self, meta_var: str) -> List[SgNode]: ... + def get_transformed(self, meta_var: str) -> Optional[str]: ... + def __getitem__(self, meta_var: str) -> SgNode: ... + + # Search + @overload + def find(self, config: Config) -> Optional[SgNode]: ... + @overload + def find(self, **kwargs: Unpack[Rule]) -> Optional[SgNode]: ... + @overload + def find_all(self, config: Config) -> List[SgNode]: ... + @overload + def find_all(self, **kwargs: Unpack[Rule]) -> List[SgNode]: ... + + # Tree Traversal + def get_root(self) -> SgRoot: ... + def field(self, name: str) -> Optional[SgNode]: ... + def parent(self) -> Optional[SgNode]: ... + def child(self, nth: int) -> Optional[SgNode]: ... + def children(self) -> List[SgNode]: ... + def ancestors(self) -> List[SgNode]: ... + def next(self) -> Optional[SgNode]: ... + def next_all(self) -> List[SgNode]: ... + def prev(self) -> Optional[SgNode]: ... + def prev_all(self) -> List[SgNode]: ... + + # Edit + def replace(self, new_text: str) -> Edit: ... + def commit_edits(self, edits: List[Edit]) -> str: ... +``` + ### Rule The `Rule` object is a Python representation of the [YAML rule object](/guide/rule-config/atomic-rule.html) in the CLI. See the [reference](/reference/rule.html). @@ -210,6 +345,20 @@ class Config(TypedDict, total=False): transform: Dict[str, Mapping] ``` +### Edit + +`Edit` is used in `replace` and `commitEdits`. + +```python +class Edit: + # The start position of the edit + start_pos: int + # The end position of the edit + end_pos: int + # The text to be inserted + inserted_text: str +``` + ## Rust API Rust API is not stable yet. The following link is only for those who are interested in modifying ast-grep's source. diff --git a/website/reference/cli.md b/website/reference/cli.md index d851c842..5aaf9dfe 100644 --- a/website/reference/cli.md +++ b/website/reference/cli.md @@ -1,15 +1,15 @@ # Command Line Reference -You can always see up-to-date command line options using `sg --help`. +You can always see up-to-date command line options using `ast-grep --help`. ast-grep has several subcommands as listed below. -## `sg run` -Run one time search or rewrite in command line. This is the default command when you run `sg` so `sg -p 'foo()'` is equivalent to `sg run -p 'foo()'`. [View detailed reference.](/reference/cli/run.html) +## `ast-grep run` +Run one time search or rewrite in command line. This is the default command when you run the CLI, so `ast-grep -p 'foo()'` is equivalent to `ast-grep run -p 'foo()'`. [View detailed reference.](/reference/cli/run.html) ### Usage ```shell -sg run [OPTIONS] --pattern [PATHS]... +ast-grep run [OPTIONS] --pattern [PATHS]... ``` ### Arguments @@ -21,28 +21,34 @@ sg run [OPTIONS] --pattern [PATHS]... | Short | Long | Description | |-------|------|-------------| | -p| --pattern `` | AST pattern to match. | +| | --selector `` | AST kind to extract sub-part of pattern to match. | | -r| --rewrite `` | String to replace the matched AST node. | | -l| --lang `` | The language of the pattern query. ast-grep will infer the language based on file extension if this option is omitted. | -| | --debug-query | Print query pattern's tree-sitter AST. Requires lang be set explicitly. | +| | --debug-query`[=]` | Print query pattern's tree-sitter AST. Requires lang be set explicitly. | +| | --strictness `` | The strictness of the pattern [possible values: cst, smart, ast, relaxed, signature] | +| | --follow | Follow symbolic links | +| | --no-ignore `` | Do not respect hidden file system or ignore files (.gitignore, .ignore, etc.) [possible values: hidden, dot, exclude, global, parent, vcs] | +| | --stdin | Enable search code from StdIn. See [link](/guide/tooling-overview.html#enable-stdin-mode) | +| | --globs `` | Include or exclude file paths +| -j| --threads `` | Set the approximate number of threads to use [default: heuristic] | -i| --interactive | Start interactive edit session. Code rewrite only happens inside a session. | | -U| --update-all | Apply all rewrite without confirmation if true. | -| | --json`[= + \ No newline at end of file diff --git a/website/src/BlogIndex.vue b/website/src/BlogIndex.vue new file mode 100644 index 00000000..0be52b89 --- /dev/null +++ b/website/src/BlogIndex.vue @@ -0,0 +1,56 @@ + + + + + \ No newline at end of file diff --git a/website/src/Homepage.vue b/website/src/Homepage.vue index 3d32fbc0..ceef84a2 100644 --- a/website/src/Homepage.vue +++ b/website/src/Homepage.vue @@ -21,7 +21,6 @@ provide('toggle-appearance', async () => { if (!enableTransitions()) { return toggle() } - // @ts-expect-error return document.startViewTransition(toggle).ready }) diff --git a/website/src/catalog/Option.vue b/website/src/catalog/Option.vue new file mode 100644 index 00000000..49a8a89e --- /dev/null +++ b/website/src/catalog/Option.vue @@ -0,0 +1,32 @@ + + + + + \ No newline at end of file diff --git a/website/src/catalog/RuleFilter.vue b/website/src/catalog/RuleFilter.vue new file mode 100644 index 00000000..d491fe3e --- /dev/null +++ b/website/src/catalog/RuleFilter.vue @@ -0,0 +1,160 @@ + + + + \ No newline at end of file diff --git a/website/src/catalog/RuleItem.vue b/website/src/catalog/RuleItem.vue new file mode 100644 index 00000000..36ab2a99 --- /dev/null +++ b/website/src/catalog/RuleItem.vue @@ -0,0 +1,288 @@ + + + + + \ No newline at end of file diff --git a/website/src/catalog/RuleList.vue b/website/src/catalog/RuleList.vue new file mode 100644 index 00000000..41f8d287 --- /dev/null +++ b/website/src/catalog/RuleList.vue @@ -0,0 +1,177 @@ + + + + + \ No newline at end of file diff --git a/website/src/catalog/data.ts b/website/src/catalog/data.ts new file mode 100644 index 00000000..c8e3175b --- /dev/null +++ b/website/src/catalog/data.ts @@ -0,0 +1,111 @@ +import { data as allRules } from '../../_data/catalog.data' +export type { RuleMeta } from '../../_data/catalog.data' +import { atou, utoa } from '../utils' + +export function intersect(a: string[], b: string[]) { + return a.some(x => b.includes(x)) +} + +export function getRuleMetaData(filter: Filter, sortBy = 'name') { + const { + selectedLanguages, + selectedRules, + } = filter + return allRules.filter(meta => { + const langFilter = !selectedLanguages.length || selectedLanguages.includes(meta.language) + const ruleFilter = !selectedRules.length || intersect(selectedRules, meta.rules) + const featureFilter = !filter.selectedFeatures.length || + intersect(filter.selectedFeatures, meta.features) + const typeFilter = !filter.selectedTypes.length || filter.selectedTypes.includes(meta.type) + return langFilter && ruleFilter && featureFilter && typeFilter + }).toSorted((a, b) => { + if (sortBy === 'name') { + return a.name.localeCompare(b.name) + } else if (sortBy === 'lang') { + return a.language.localeCompare(b.language) + } else if (sortBy === 'complexity') { + const complexityA = a.rules.length + a.features.length + const complexityB = b.rules.length + b.features.length + return complexityA - complexityB + } else { + return 0 + } + }) +} + +export type Filter = { + selectedLanguages: ExampleLangs[] + selectedRules: string[] + selectedFeatures: string[] + selectedTypes: string[] +} + +export type ExampleLangs = keyof typeof languages +export const languages = { + c: 'C', + cpp: 'C++', + go: 'Go', + html: 'HTML', + java: 'Java', + kotlin: 'Kotlin', + python: 'Python', + ruby: 'Ruby', + rust: 'Rust', + tsx: 'TSX', + typescript: 'TypeScript', + yaml: 'YAML', +} + +export const ruleTypes = [ + 'Pattern', + 'YAML', +] + +export const ruleFilters = { + atomic: [ + 'pattern', + 'kind', + 'regex', + ], + relational: [ + 'inside', + 'has', + 'follows', + 'precedes', + ], + composite: [ + 'all', + 'any', + 'not', + 'matches', + ], +} + +export const features = [ + 'rewriters', + 'transform', + 'constraints', + 'utils', + 'labels', +] + +export function serialize(data: Filter): string { + const allEmpty = Object.values(data).every(x => !x.length) + if (allEmpty) { + return '' + } + return utoa(JSON.stringify(data)) +} + +export function deserialize(str: string): Filter { + try { + return JSON.parse(atou(str)) + } catch { + return { + selectedLanguages: [], + selectedRules: [], + selectedFeatures: [], + selectedTypes: [], + } + } +} diff --git a/website/src/catalog/index.vue b/website/src/catalog/index.vue new file mode 100644 index 00000000..2d4a3411 --- /dev/null +++ b/website/src/catalog/index.vue @@ -0,0 +1,45 @@ + + + + + \ No newline at end of file diff --git a/website/src/cheatsheet/Item.vue b/website/src/cheatsheet/Item.vue new file mode 100644 index 00000000..1887e4aa --- /dev/null +++ b/website/src/cheatsheet/Item.vue @@ -0,0 +1,45 @@ + + + + + + \ No newline at end of file diff --git a/website/src/cheatsheet/SheetTable.vue b/website/src/cheatsheet/SheetTable.vue new file mode 100644 index 00000000..93268476 --- /dev/null +++ b/website/src/cheatsheet/SheetTable.vue @@ -0,0 +1,49 @@ + + + + + \ No newline at end of file diff --git a/website/src/components/EnvDisplay.vue b/website/src/components/EnvDisplay.vue index ef4790de..fad08965 100644 --- a/website/src/components/EnvDisplay.vue +++ b/website/src/components/EnvDisplay.vue @@ -1,8 +1,13 @@ \ No newline at end of file diff --git a/website/src/components/PatternConfig.vue b/website/src/components/PatternConfig.vue new file mode 100644 index 00000000..e56ddce7 --- /dev/null +++ b/website/src/components/PatternConfig.vue @@ -0,0 +1,64 @@ + + + + + \ No newline at end of file diff --git a/website/src/components/PatternEditor.vue b/website/src/components/PatternEditor.vue new file mode 100644 index 00000000..0a410598 --- /dev/null +++ b/website/src/components/PatternEditor.vue @@ -0,0 +1,127 @@ + + + + + \ No newline at end of file diff --git a/website/src/components/Playground.vue b/website/src/components/Playground.vue index e3ed5c86..80f65a8d 100644 --- a/website/src/components/Playground.vue +++ b/website/src/components/Playground.vue @@ -1,107 +1,43 @@ @@ -194,6 +126,17 @@ provide(langLoadedKey, langLoaded) filter: drop-shadow(0 0 16px #00000020); } +.match-result { + display: flex; + justify-content: space-between; + align-items: center; +} + +.action-bar { + display: flex; + align-items: center; +} + @media only screen and (max-width: 780px) { .half.inactive { position: absolute; @@ -206,11 +149,67 @@ provide(langLoadedKey, langLoaded) pointer-events: none; } } -.pattern-separator { - border-top: 1px solid var(--vp-c-bg-soft); - box-shadow: 0 0 5px rgba(0, 0, 0, 0.1); - font-size: 12px; - border-radius: 10px 10px 0 0; - padding: 0 1em 0; + + + \ No newline at end of file diff --git a/website/src/components/QueryEditor.vue b/website/src/components/QueryEditor.vue index 315c6503..f9fd616e 100644 --- a/website/src/components/QueryEditor.vue +++ b/website/src/components/QueryEditor.vue @@ -1,49 +1,43 @@ + + + + \ No newline at end of file diff --git a/website/src/components/SelectLang.vue b/website/src/components/SelectLang.vue index 50746f87..716ea77f 100644 --- a/website/src/components/SelectLang.vue +++ b/website/src/components/SelectLang.vue @@ -1,30 +1,40 @@ + +.parser-icon { + cursor: help; +} + \ No newline at end of file diff --git a/website/src/components/Toolbars.vue b/website/src/components/Toolbars.vue index abd1f3c9..1ffc1e84 100644 --- a/website/src/components/Toolbars.vue +++ b/website/src/components/Toolbars.vue @@ -1,11 +1,14 @@ diff --git a/website/src/components/Toast.vue b/website/src/components/utils/Toast.vue similarity index 99% rename from website/src/components/Toast.vue rename to website/src/components/utils/Toast.vue index c6d353da..71b2f160 100644 --- a/website/src/components/Toast.vue +++ b/website/src/components/utils/Toast.vue @@ -59,4 +59,4 @@ watch(text, (newVal) => { opacity: 0; transform: translate(50%, -10%); } - + \ No newline at end of file diff --git a/website/src/homepage/Ecosystem.vue b/website/src/homepage/Ecosystem.vue index ea86dc9e..aee68938 100644 --- a/website/src/homepage/Ecosystem.vue +++ b/website/src/homepage/Ecosystem.vue @@ -4,9 +4,6 @@

Who is using ast-grep

- @@ -25,9 +22,12 @@ - + + +
diff --git a/website/src/homepage/Features.vue b/website/src/homepage/Features.vue index 7c075c03..d65c4647 100644 --- a/website/src/homepage/Features.vue +++ b/website/src/homepage/Features.vue @@ -1,43 +1,53 @@ -