diff --git a/.eslintrc.js b/.eslintrc.js index f0fa339..7320f33 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -1,6 +1,6 @@ module.exports = { parser: '@typescript-eslint/parser', - plugins: ['@typescript-eslint', 'node', 'prettier'], + plugins: ['@typescript-eslint', 'node', 'prettier', '@codeque'], extends: [ 'eslint:recommended', 'plugin:@typescript-eslint/recommended', @@ -58,5 +58,13 @@ module.exports = { next: '*', }, // padding after multiline-expression ], + '@codeque/error': [ + 'error', + [ + { + query: 'console.log()', + }, + ], + ], }, } diff --git a/.gitignore b/.gitignore index 4c66499..c98c8a0 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,12 @@ editor-log.txt cliQuery devQuery devFile - +*.snapshot.json dist -*.temp \ No newline at end of file +*.temp +.vscode/settings.json +*.tgz +*.tar.gz +**/getAstJson/*.json +**/getAstJson/*.txt +dist-tree-sitter \ No newline at end of file diff --git a/Readme.md b/Readme.md index 5a315dd..69217f2 100644 --- a/Readme.md +++ b/Readme.md @@ -1,112 +1,193 @@

- - + +

- Home Page | - Docs | - Roadmap | - Mission | - Playground + Website  •   + Docs   •   + Roadmap  •   + Mission  •   + Playground

-

- Supercharged structural code search and replace tool -

+

Find and lint complex code patterns effortlessly

--- -## Overview 📣 +# What is CodeQue? -**CodeQue** is structural code search tool for TypeScript and JavaScript projects. +CodeQue is semantic code search engine that understands the code syntax. -CodeQue can be used to search for any code, from simple symbol search to complex multiline patterns. +It matches code structurally which makes it excellent for more complex queries. -It reduces struggle by providing accurate results regardless the formatting noise. +Query language offers wildcards, partial matching and ignores code formatting. -It makes it easy to get familiar with codebase and helps make better decisions as a result. +Structural code search is available for JavaScript, TypesScript, HTML, CSS, Python, Lua, C# and more soon. -You can also use it as a linter. +Text code search with handy wildcards is available for **every language** and covers common regex search use cases. -Find out more about the project on [codeque.co](https://codeque.co) +

Give it a try in + playground

-## ESLint plugin 💅 -Use CodeQue ESLint plugin to create custom rules in zero time. +

Just paste code snippet to start searching, no installation needed!

-Mark errors or warnings tailored to your codebase. +**Integrations** -Installation 👇 +CodeQue is available as: -```sh -yarn add --dev @codeque/eslint-plugin -``` +- [VSCode extension](https://marketplace.visualstudio.com/items?itemName=CodeQue.codeque) for delightful code search and navigation experience. +- [ESLint integration](https://www.npmjs.com/package/@codeque/eslint-plugin) for creating custom linting rules in zero time. +- [CLI tool](https://www.npmjs.com/package/@codeque/cli) for searching code and more including headless environments. + +

All CodeQue tools work offline hence code never leaves your local environment.

-> `@codeque/eslint-plugin` is currently in `beta` +**Coming soon** -Usage ✨ +CodeQue will be soon available as: -[See docs on npm](https://www.npmjs.com/package/@codeque/eslint-plugin) +- Duplicated code identification +- Batch code refactoring +- Advanced ESLint rule creator +

🔔 Get notified about updates 🔔

-```ts -{ - plugins: ['@codeque'], - rules: [ - "@codeque/error": ["error", [ - { - "query": "throw new Error()", - mode: "include", - message: "Use only project defined error classes.", - }, - ]] - ] -} -``` +
-

- -

+ + ## Visual Studio Code Extension 🔮 -VScode extension aims to make you workflow more efficient. +VScode extension aims to make your workflow more efficient. -[Read about features](https://github.com/codeque-co/codeque/tree/master/packages/vscode#readme) +It addresses the problems of standard search by providing multiline support and offers an easy way to add gaps or use wildcards in the query. -[Get VScode Extension from Marketplace](https://marketplace.visualstudio.com/items?itemName=CodeQue.codeque) +You don't need to have any Regex knowledge to query complex code patterns. +With CodeQue, you can easily navigate and modify your codebase, making your development process faster and more efficient. -

Watch extension in action (external link) 👇

+It will help you with code refactoring, speed up project discovery, and make it easy to find duplicated or similar code patterns. - - - +Advanced code search options and todo-like list of accurate search results will streamline your workflow. + +
+ +

Watch extension in action in 1 minute (external link) 👇

+ + + + + +
+ + + +
+ +

Get extension from marketplace

+ + +## ESLint integration 💅 + +Using CodeQue ESLint plugin you can create your own custom linting rules in zero time. + +Custom ESLint rules can help execute on long-term refactors or prevent introducing codebase specific bugs or bad patterns. + +Rules can replace your decision log and help standardizing coding conventions across the project or organization. + +CodeQue ESLint integration is a no-brainier for any team willing to improve their codebase quality. + + +**Installation 👇** + +```sh +yarn add --dev @codeque/eslint-plugin +``` + +**Usage ✨** + +Enhance your `.eslintrc` with following setup: + +```json +{ + "plugins": ["@codeque"], + "rules": { + "@codeque/error": [ + "error", + [ + { + "query": "fetchData()", + "mode": "exact", + "message": "Using fetchData() without parameters causes app crash!" + } + ] + ], + "@codeque/warning": [ + "warn", + [ + { + "query": "import $$$ from 'lodash';", + "mode": "include", + "message": "Prefer to import lodash functions from separate packages like 'lodash.debounce'" + } + ] + ] + } +} +``` + +Find more information in [`@codeque/eslint-plugin` package readme](https://www.npmjs.com/package/@codeque/eslint-plugin) + +
+ +

+ +

+ +
+ + ## CLI tool 🔥 -CodeQue can be used as a CLI tool +CodeQue CLI is a complementary tool that can be used for + +- Searching code patterns right from terminal including headless environments +- Building scripts to assert that some code patterns exist or not exist +- Enhancing git hooks to avoid committing or pushing unwanted code -Installation 👇 + + +**Installation 👇** ```sh yarn global add @codeque/cli ``` -Usage 🕵️ +**Usage ✨** ```sh codeque ``` -codeque cli demo +
-Find more info in [`@codeque/cli` package docs](./packages/cli/README.md) +

codeque cli demo

+
-## Other Platforms 🌶️ +Find more information in [`@codeque/cli` package readme](https://www.npmjs.com/package/@codeque/cli) -CodeQue will be soon available as: -- Cloud service and integrations for teams + + +## Support and feedback + +Feel free to use [Github Issues](https://github.com/codeque-co/codeque/issues) +to + +- ask for help with writing a query +- report a bug or doubt +- suggest feature or improvement diff --git a/fix-framer-motion.js b/fix-framer-motion.js index 89d4b68..49afffa 100644 --- a/fix-framer-motion.js +++ b/fix-framer-motion.js @@ -5,7 +5,7 @@ const pkgPath = path.join( __dirname, 'node_modules', 'framer-motion', - 'package.json' + 'package.json', ) const framerMotionPkg = JSON.parse(fs.readFileSync(pkgPath).toString()) diff --git a/package.json b/package.json index 568da87..b763d8e 100644 --- a/package.json +++ b/package.json @@ -5,6 +5,9 @@ "workspaces": [ "packages/*" ], + "engines": { + "node": ">=14.0.0" + }, "devDependencies": { "@babel/cli": "^7.16.0", "@babel/core": "^7.16.0", @@ -20,11 +23,17 @@ "eslint-plugin-jest": "^24.1.0", "eslint-plugin-node": "^11.1.0", "eslint-plugin-prettier": "^4.0.0", - "typescript": "^4.5.2" + "typescript": "5.9.3" }, "scripts": { "lint": "yarn workspaces run lint", "typecheck": "yarn workspaces run typecheck", - "postinstall": "node fix-framer-motion.js" + "test": "yarn workspaces run test", + "checks": "yarn typecheck && yarn lint && yarn test", + "postinstall": "node fix-framer-motion.js", + "syncReadmes": "node ./syncReadmes.js" + }, + "dependencies": { + "@codeque/eslint-plugin": "^0.1.1" } } \ No newline at end of file diff --git a/packages/cli/README.md b/packages/cli/README.md index e936de2..8227db1 100644 --- a/packages/cli/README.md +++ b/packages/cli/README.md @@ -1,30 +1,75 @@ -

- CodeQue -

+

- Supercharged structural code search and replace tool + + + +
+

+

+ Website  •   + Docs   •   + Roadmap  •   + Mission  •   + Playground

+

Find and lint complex code patterns effortlessly

+ --- -## Overview 📣 +# What is CodeQue? + +CodeQue is semantic code search engine that understands the code syntax. + +It matches code structurally which makes it excellent for more complex queries. + +Query language offers wildcards, partial matching and ignores code formatting. + +Structural code search is available for JavaScript, TypesScript, HTML, CSS, Python, Lua, C# and more soon. + +Text code search with handy wildcards is available for **every language** and covers common regex search use cases. + +

Give it a try in + playground

+ +

Just paste code snippet to start searching, no installation needed!

+ +**Integrations** + +CodeQue is available as: -CodeQue is structural code search tool for TypeScript and JavaScript projects. +- [VSCode extension](https://marketplace.visualstudio.com/items?itemName=CodeQue.codeque) for delightful code search and navigation experience. +- [ESLint integration](https://www.npmjs.com/package/@codeque/eslint-plugin) for creating custom linting rules in zero time. +- [CLI tool](https://www.npmjs.com/package/@codeque/cli) for searching code and more including headless environments. -CodeQue can be used to search for any code, from simple symbol search to complex multiline patterns. +

All CodeQue tools work offline hence code never leaves your local environment.

-It reduces struggle by providing accurate results regardless the formatting noise. +**Coming soon** -It makes it easy to get familiar with codebase and helps make better decisions as a result. +CodeQue will be soon available as: -You can also use it as a linter. +- Duplicated code identification +- Batch code refactoring +- Advanced ESLint rule creator -Find out more about the project on [codeque.co](https://codeque.co) +

🔔 Get notified about updates 🔔

-[Try CodeQue Visual Studio Code Extension](https://marketplace.visualstudio.com/items?itemName=CodeQue.codeque) +
-[Get CodeQue ESLint plugin](https://www.npmjs.com/package/@codeque/eslint-plugin) and create custom linting rules in zero time. + + + + +## CLI tool 🔥 + +CodeQue CLI is a complementary tool that can be used for + +- Searching code patterns right from terminal including headless environments +- Building scripts to assert that some code patterns exist or not exist +- Enhancing git hooks to avoid committing or pushing unwanted code + + ## Installation 👇 @@ -42,7 +87,9 @@ codeque Type query and hit `ctrl+s` to run your first search! -codeque cli demo +

+codeque cli demo +

### CodeQue CLI features: @@ -74,7 +121,7 @@ I love using CodeQue to look for specific function or React hook usage. It's fas This a typical query that you can use to find usage of some React hook. ```ts -const $$$ = useMyHook(); +const $$$ = useMyHook() ``` ### Assertions ☔ @@ -142,3 +189,14 @@ codeque [options] - `-ogi, --omitGitIgnore` - Search files regardless .gitignore settings (_optional_) - `-ae, --allExtensions` - Search in all file extensions. Useful for text search mode. (_optional_) + + + +## Support and feedback + +Feel free to use [Github Issues](https://github.com/codeque-co/codeque/issues) +to + +- ask for help with writing a query +- report a bug or doubt +- suggest feature or improvement diff --git a/packages/cli/package.json b/packages/cli/package.json index 19cf52f..79bbade 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,7 +1,7 @@ { "name": "@codeque/cli", - "version": "0.4.0", - "description": "Supercharged multiline code search and replace tool", + "version": "0.5.1", + "description": "Multiline code search for every language. Structural code search for JavaScript, TypeScript, HTML and CSS", "bin": { "codeque": "./bin.js" }, @@ -31,7 +31,7 @@ }, "dependencies": { "@babel/code-frame": "^7.18.6", - "@codeque/core": "^0.4.0", + "@codeque/core": "^0.6.1", "colorette": "^2.0.16", "commander": "^8.3.0", "ora": "^5.0", @@ -45,6 +45,7 @@ "build:code": "tsc --project tsconfig.json", "docs-gen": "node tools/addDocsToReadme.js", "typecheck": "tsc --noEmit --project tsconfig.json", + "test": "echo 0", "test:circular": "dpdm --exit-code circular:1 --tree=false --warning=false './src/**'", "lint": "eslint --ext .js,.ts src", "lint:fix": "yarn lint --fix", diff --git a/packages/cli/src/dev.ts b/packages/cli/src/dev.ts index 218380b..4f9556e 100644 --- a/packages/cli/src/dev.ts +++ b/packages/cli/src/dev.ts @@ -1,3 +1,4 @@ +/* eslint-disable @codeque/error */ import { getMode, Mode, diff --git a/packages/cli/tsconfig.json b/packages/cli/tsconfig.json index d9f52d7..94f01be 100644 --- a/packages/cli/tsconfig.json +++ b/packages/cli/tsconfig.json @@ -1,14 +1,16 @@ { "extends": "../../tsconfig.json", - "include": ["src"], + "include": [ + "src" + ], "compilerOptions": { - "baseUrl": "./src", - "paths": {}, - "outDir": "./dist", + "baseUrl": "./src", + "paths": {}, + "outDir": "./dist" }, "exclude": [ "dist", "node_modules", "tools/*.js" ] -} +} \ No newline at end of file diff --git a/packages/core/README.md b/packages/core/README.md index cc18155..df42b08 100644 --- a/packages/core/README.md +++ b/packages/core/README.md @@ -1,5 +1,79 @@ -It's internal CodeQue core package. + -CodeQue is structural code search tool for TypeScript and JavaScript projects. +

+ + + +
+

+

+ Website  •   + Docs   •   + Roadmap  •   + Mission  •   + Playground +

-Check [`@codeque/cli`](https://www.npmjs.com/package/@codeque/cli) \ No newline at end of file +

Find and lint complex code patterns effortlessly

+ +--- + +# What is CodeQue? + +CodeQue is semantic code search engine that understands the code syntax. + +It matches code structurally which makes it excellent for more complex queries. + +Query language offers wildcards, partial matching and ignores code formatting. + +Structural code search is available for JavaScript, TypesScript, HTML, CSS, Python, Lua, C# and more soon. + +Text code search with handy wildcards is available for **every language** and covers common regex search use cases. + +

Give it a try in + playground

+ +

Just paste code snippet to start searching, no installation needed!

+ +**Integrations** + +CodeQue is available as: + +- [VSCode extension](https://marketplace.visualstudio.com/items?itemName=CodeQue.codeque) for delightful code search and navigation experience. +- [ESLint integration](https://www.npmjs.com/package/@codeque/eslint-plugin) for creating custom linting rules in zero time. +- [CLI tool](https://www.npmjs.com/package/@codeque/cli) for searching code and more including headless environments. + +

All CodeQue tools work offline hence code never leaves your local environment.

+ +**Coming soon** + +CodeQue will be soon available as: + +- Duplicated code identification +- Batch code refactoring +- Advanced ESLint rule creator + +

🔔 Get notified about updates 🔔

+ +
+ + + +## `@codeque/core` + +It's internal package and it's not meant to be used directly. + +The exposed API might have breaking changes between releases. + +To use CodeQue check available platforms listed above. + + + +## Support and feedback + +Feel free to use [Github Issues](https://github.com/codeque-co/codeque/issues) +to + +- ask for help with writing a query +- report a bug or doubt +- suggest feature or improvement diff --git a/packages/core/__tests__/CSS/matchCodePatterns/basic.test.ts b/packages/core/__tests__/CSS/matchCodePatterns/basic.test.ts new file mode 100644 index 0000000..fe9fdd5 --- /dev/null +++ b/packages/core/__tests__/CSS/matchCodePatterns/basic.test.ts @@ -0,0 +1,361 @@ +import { searchInStrings } from '../../../src/searchInStrings' + +describe('Basic queries', () => { + it('Should exact match rule with selector', () => { + const fileContent = ` + p { + background-color: red; + } + ` + const queries = [fileContent] + + const { matches, errors } = searchInStrings({ + mode: 'exact', + caseInsensitive: true, + queryCodes: queries, + files: [ + { + path: 'mock', + content: fileContent, + }, + ], + }) + + expect(errors).toHaveLength(0) + expect(matches.length).toBe(1) + }) + + it('Should exact match simple declaration without wrapping block', () => { + const fileContent = ` + p { + background-color: red; + } + ` + + const queries = [`background-color: red;`] + + const { matches, errors } = searchInStrings({ + mode: 'exact', + caseInsensitive: true, + queryCodes: queries, + files: [ + { + path: 'mock', + content: fileContent, + }, + ], + }) + + expect(errors).toHaveLength(0) + expect(matches.length).toBe(1) + }) + + it('Should exact match simple rule without selector', () => { + const fileContent = ` + p { + background-color: red; + } + ` + + const queries = [`{ background-color: red; }`] + + const { matches, errors } = searchInStrings({ + mode: 'exact', + caseInsensitive: true, + queryCodes: queries, + files: [ + { + path: 'mock', + content: fileContent, + }, + ], + }) + + expect(errors).toHaveLength(0) + expect(matches.length).toBe(1) + }) + + it('Should match rule with function property', () => { + const fileContent = ` + .class { + background-color: linear-gradient(#e66465, #9198e5); + display:flex + } + ` + + const queries = [ + ` + .class { + background-color: linear-gradient(#e66465, #9198e5); + } + `, + ] + + const { matches, errors } = searchInStrings({ + mode: 'include', + caseInsensitive: true, + queryCodes: queries, + files: [ + { + path: 'mock', + content: fileContent, + }, + ], + }) + + expect(errors).toHaveLength(0) + expect(matches.length).toBe(1) + }) + + it('Should match atrule', () => { + const fileContent = ` + @media (min-width: 300px) { + #main { + font-weight: bold + } + } + ` + + const queries = [ + ` + @media (min-width: 300px) { + #main { + font-weight: bold + } + } + `, + ] + + const { matches, errors } = searchInStrings({ + mode: 'exact', + caseInsensitive: true, + queryCodes: queries, + files: [ + { + path: 'mock', + content: fileContent, + }, + ], + }) + + expect(errors).toHaveLength(0) + expect(matches.length).toBe(1) + }) + + it('Should match rule inside atrule', () => { + const fileContent = ` + @media (min-width: 300px) { + #main { + font-weight: bold + } + } + ` + + const queries = [ + ` + { + font-weight: bold + } + `, + ] + + const { matches, errors } = searchInStrings({ + mode: 'include', + caseInsensitive: true, + queryCodes: queries, + files: [ + { + path: 'mock', + content: fileContent, + }, + ], + }) + + expect(errors).toHaveLength(0) + expect(matches.length).toBe(1) + }) + + it('Should match selector with combinator', () => { + const fileContent = ` + ul > li { + margin-left: 10px; + } + ` + + const queries = [ + ` + ul > li { + + } + `, + ] + + const { matches, errors } = searchInStrings({ + mode: 'include', + caseInsensitive: true, + queryCodes: queries, + files: [ + { + path: 'mock', + content: fileContent, + }, + ], + }) + + expect(errors).toHaveLength(0) + expect(matches.length).toBe(1) + }) + + it('Should match declaration with dimension', () => { + const fileContent = ` + ul > li { + margin-left: 10vw; + } + ` + + const queries = [ + ` + margin-left: 10vw; + `, + ] + + const { matches, errors } = searchInStrings({ + mode: 'include', + caseInsensitive: true, + queryCodes: queries, + files: [ + { + path: 'mock', + content: fileContent, + }, + ], + }) + + expect(errors).toHaveLength(0) + expect(matches.length).toBe(1) + }) + + it('Should match calc function and partial selector', () => { + const fileContent = ` + div, p, span { + margin-left: calc(100vw - 90%); + } + ` + + const queries = [ + ` + p { + margin-left: calc(100vw - 90%); + } + `, + ] + + const { matches, errors } = searchInStrings({ + mode: 'include', + caseInsensitive: true, + queryCodes: queries, + files: [ + { + path: 'mock', + content: fileContent, + }, + ], + }) + + expect(errors).toHaveLength(0) + expect(matches.length).toBe(1) + }) + + it('Should not match partial selector in exact mode', () => { + const fileContent = ` + div, p, span { + margin-left: calc(100vw - 90%); + } + ` + + const queries = [ + ` + p { + margin-left: calc(100vw - 90%); + } + `, + ] + + const { matches, errors } = searchInStrings({ + mode: 'exact', + caseInsensitive: true, + queryCodes: queries, + files: [ + { + path: 'mock', + content: fileContent, + }, + ], + }) + + expect(errors).toHaveLength(0) + expect(matches.length).toBe(0) + }) + + it('Should match url', () => { + const fileContent = ` + div { + background: url("http://image.com") + } + ` + + const queries = [ + ` + div { + background: url("http://image.com") + } + `, + ] + + const { matches, errors } = searchInStrings({ + mode: 'exact', + caseInsensitive: true, + queryCodes: queries, + files: [ + { + path: 'mock', + content: fileContent, + }, + ], + }) + + expect(errors).toHaveLength(0) + expect(matches.length).toBe(1) + }) + + it('Should match subset of multi value declaration ', () => { + const fileContent = ` + div { + padding: 10px 8px 4px + } + ` + + const queries = [ + ` + div { + padding: 10px 4px + } + `, + ] + + const { matches, errors } = searchInStrings({ + mode: 'include', + caseInsensitive: true, + queryCodes: queries, + files: [ + { + path: 'mock', + content: fileContent, + }, + ], + }) + + expect(errors).toHaveLength(0) + expect(matches.length).toBe(1) + }) +}) diff --git a/packages/core/__tests__/CSS/matchCodePatterns/wildcards.test.ts b/packages/core/__tests__/CSS/matchCodePatterns/wildcards.test.ts new file mode 100644 index 0000000..756a990 --- /dev/null +++ b/packages/core/__tests__/CSS/matchCodePatterns/wildcards.test.ts @@ -0,0 +1,651 @@ +import { searchInStrings } from '../../../src/searchInStrings' + +describe('Wildcard queries', () => { + it('Should match wildcard in type selector', () => { + const fileContent = ` + p { + background-color: red; + } + ` + const queries = [ + ` + $$ { + background-color: red; + } + `, + ] + + const { matches, errors } = searchInStrings({ + mode: 'exact', + caseInsensitive: true, + queryCodes: queries, + files: [ + { + path: 'mock', + content: fileContent, + }, + ], + }) + + expect(errors).toHaveLength(0) + expect(matches.length).toBe(1) + }) + + it('Should match wildcard in class name', () => { + const fileContent = ` + .someClass { + background-color: red; + } + ` + const queries = [ + ` + .some$$ { + background-color: red; + } + `, + ] + + const { matches, errors } = searchInStrings({ + mode: 'exact', + caseInsensitive: true, + queryCodes: queries, + files: [ + { + path: 'mock', + content: fileContent, + }, + ], + }) + + expect(errors).toHaveLength(0) + expect(matches.length).toBe(1) + }) + + it('Should match wildcard in id selector name', () => { + const fileContent = ` + #someId { + background-color: red; + } + ` + const queries = [ + ` + #some$$ { + background-color: red; + } + `, + ] + + const { matches, errors } = searchInStrings({ + mode: 'exact', + caseInsensitive: true, + queryCodes: queries, + files: [ + { + path: 'mock', + content: fileContent, + }, + ], + }) + + expect(errors).toHaveLength(0) + expect(matches.length).toBe(1) + }) + + it('Should match wildcard in declaration property name', () => { + const fileContent = ` + p { + background-color: red; + } + ` + const queries = [ + ` + p { + background-$$: red; + } + `, + ] + + const { matches, errors } = searchInStrings({ + mode: 'exact', + caseInsensitive: true, + queryCodes: queries, + files: [ + { + path: 'mock', + content: fileContent, + }, + ], + }) + + expect(errors).toHaveLength(0) + expect(matches.length).toBe(1) + }) + + it('Should match wildcard in property color value', () => { + const fileContent = ` + p { + background-color: red; + } + ` + const queries = [ + ` + p { + background-color: $$; + } + `, + ] + + const { matches, errors } = searchInStrings({ + mode: 'exact', + caseInsensitive: true, + queryCodes: queries, + files: [ + { + path: 'mock', + content: fileContent, + }, + ], + }) + + expect(errors).toHaveLength(0) + expect(matches.length).toBe(1) + }) + + it('Should match wildcard in property dimension unit', () => { + const fileContent = ` + p { + margin-left: 10em; + } + ` + const queries = [ + ` + p { + margin-left: 10$$; + } + `, + ] + + const { matches, errors } = searchInStrings({ + mode: 'exact', + caseInsensitive: true, + queryCodes: queries, + files: [ + { + path: 'mock', + content: fileContent, + }, + ], + }) + + expect(errors).toHaveLength(0) + expect(matches.length).toBe(1) + }) + + it('Should match wildcard in property dimension value', () => { + const fileContent = ` + p { + margin-left: 10em; + } + ` + const queries = [ + ` + p { + margin-left: 0x0em; + } + `, + ] + + const { matches, errors } = searchInStrings({ + mode: 'exact', + caseInsensitive: true, + queryCodes: queries, + files: [ + { + path: 'mock', + content: fileContent, + }, + ], + }) + + expect(errors).toHaveLength(0) + expect(matches.length).toBe(1) + }) + + it('Should match wildcard in property dimension unit and value', () => { + const fileContent = ` + p { + margin-left: 10em; + } + ` + const queries = [ + ` + p { + margin-left: 0x0$$ + } + `, + ] + + const { matches, errors } = searchInStrings({ + mode: 'exact', + caseInsensitive: true, + queryCodes: queries, + files: [ + { + path: 'mock', + content: fileContent, + }, + ], + }) + + expect(errors).toHaveLength(0) + expect(matches.length).toBe(1) + }) + + it('Should match wildcard in media query identifier', () => { + const fileContent = ` + @media screen and (min-width: 480px) { + body { + background-color: lightgreen; + } + } + ` + const queries = [ + ` + @media $$ and (min-width: 480px) { + body { + background-color: lightgreen; + } + } + `, + ] + + const { matches, errors } = searchInStrings({ + mode: 'exact', + caseInsensitive: true, + queryCodes: queries, + files: [ + { + path: 'mock', + content: fileContent, + }, + ], + }) + + expect(errors).toHaveLength(0) + expect(matches.length).toBe(1) + }) + + it('Should match wildcard in media query media feature name', () => { + const fileContent = ` + @media screen and (min-width: 480px) { + body { + background-color: lightgreen; + } + } + ` + const queries = [ + ` + @media screen and ($$: 480px) { + body { + background-color: lightgreen; + } + } + `, + ] + + const { matches, errors } = searchInStrings({ + mode: 'exact', + caseInsensitive: true, + queryCodes: queries, + files: [ + { + path: 'mock', + content: fileContent, + }, + ], + }) + + expect(errors).toHaveLength(0) + expect(matches.length).toBe(1) + }) + + it('Should match numeric wildcard in media query media feature dimension value', () => { + const fileContent = ` + @media screen and (min-width: 480px) { + body { + background-color: lightgreen; + } + } + ` + const queries = [ + ` + @media screen and (min-width: 0x0px) { + body { + background-color: lightgreen; + } + } + `, + ] + + const { matches, errors } = searchInStrings({ + mode: 'exact', + caseInsensitive: true, + queryCodes: queries, + files: [ + { + path: 'mock', + content: fileContent, + }, + ], + }) + + expect(errors).toHaveLength(0) + expect(matches.length).toBe(1) + }) + + it('Should match wildcard in keyframes name', () => { + const fileContent = ` + @keyframes slidein { + from { + transform: translateX(0%); + } + + to { + transform: translateX(100%); + } + } + ` + const queries = [ + ` + @keyframes $$ { + from { + transform: translateX(0%); + } + + to { + transform: translateX(100%); + } + } + `, + ] + + const { matches, errors } = searchInStrings({ + mode: 'exact', + caseInsensitive: true, + queryCodes: queries, + files: [ + { + path: 'mock', + content: fileContent, + }, + ], + }) + + expect(errors).toHaveLength(0) + expect(matches.length).toBe(1) + }) + + it('Should match wildcard in keyframes type selector', () => { + const fileContent = ` + @keyframes slidein { + from { + transform: translateX(0%); + } + + to { + transform: translateX(100%); + } + } + ` + const queries = [ + ` + @keyframes slidein { + $$ { + transform: translateX(0%); + } + + to { + transform: translateX(100%); + } + } + `, + ] + + const { matches, errors } = searchInStrings({ + mode: 'exact', + caseInsensitive: true, + queryCodes: queries, + files: [ + { + path: 'mock', + content: fileContent, + }, + ], + }) + + expect(errors).toHaveLength(0) + expect(matches.length).toBe(1) + }) + + it('Should match wildcard in keyframes percentage selector', () => { + const fileContent = ` + @keyframes slidein { + 0% { + transform: translateX(0%); + } + + 100% { + transform: translateX(100%); + } + } + ` + const queries = [ + ` + @keyframes slidein { + 0% { + transform: translateX(0%); + } + + 0x0% { + transform: translateX(100%); + } + } + `, + ] + + const { matches, errors } = searchInStrings({ + mode: 'exact', + caseInsensitive: true, + queryCodes: queries, + files: [ + { + path: 'mock', + content: fileContent, + }, + ], + }) + + expect(errors).toHaveLength(0) + expect(matches.length).toBe(1) + }) + + it('Should match wildcard in url function value 1', () => { + const fileContent = ` + p { + background: url("some-path") + } + ` + const queries = [ + ` + p { + background: url($$) + } + `, + ] + + const { matches, errors } = searchInStrings({ + mode: 'exact', + caseInsensitive: true, + queryCodes: queries, + files: [ + { + path: 'mock', + content: fileContent, + }, + ], + }) + + expect(errors).toHaveLength(0) + expect(matches.length).toBe(1) + }) + + it('Should match wildcard in url function value 2', () => { + const fileContent = ` + p { + background: url("some-path") + } + ` + const queries = [ + ` + p { + background: url("$$") + } + `, + ] + + const { matches, errors } = searchInStrings({ + mode: 'exact', + caseInsensitive: true, + queryCodes: queries, + files: [ + { + path: 'mock', + content: fileContent, + }, + ], + }) + + expect(errors).toHaveLength(0) + expect(matches.length).toBe(1) + }) + + it('Should match wildcard in function name', () => { + const fileContent = ` + p { + background: linear-gradient(0px) + } + ` + const queries = [ + ` + p { + background: $$(0px) + } + `, + ] + + const { matches, errors } = searchInStrings({ + mode: 'exact', + caseInsensitive: true, + queryCodes: queries, + files: [ + { + path: 'mock', + content: fileContent, + }, + ], + }) + + expect(errors).toHaveLength(0) + expect(matches.length).toBe(1) + }) + + it('Should match color hash with node wildcard', () => { + const fileContent = ` + p { + background: #000 + } + ` + const queries = [ + ` + p { + background: $$ + } + `, + ] + + const { matches, errors } = searchInStrings({ + mode: 'exact', + caseInsensitive: true, + queryCodes: queries, + files: [ + { + path: 'mock', + content: fileContent, + }, + ], + }) + + expect(errors).toHaveLength(0) + expect(matches.length).toBe(1) + }) + + it('Should match color hash with numeric wildcard', () => { + const fileContent = ` + p { + background: #000 + } + ` + const queries = [ + ` + p { + background: #0x0 + } + `, + ] + + const { matches, errors } = searchInStrings({ + mode: 'exact', + caseInsensitive: true, + queryCodes: queries, + files: [ + { + path: 'mock', + content: fileContent, + }, + ], + }) + + expect(errors).toHaveLength(0) + expect(matches.length).toBe(1) + }) + + it('Should match dimension with node wildcard', () => { + const fileContent = ` + p { + width: 100px + } + ` + const queries = [ + ` + p { + width: $$$ + } + `, + ] + + const { matches, errors } = searchInStrings({ + mode: 'exact', + caseInsensitive: true, + queryCodes: queries, + files: [ + { + path: 'mock', + content: fileContent, + }, + ], + }) + + expect(errors).toHaveLength(0) + expect(matches.length).toBe(1) + }) +}) diff --git a/packages/core/__tests__/CSS/other/parseQuery.test.ts b/packages/core/__tests__/CSS/other/parseQuery.test.ts new file mode 100644 index 0000000..ff95069 --- /dev/null +++ b/packages/core/__tests__/CSS/other/parseQuery.test.ts @@ -0,0 +1,41 @@ +import { getUniqueTokens, extractQueryNode } from '../../../src/parseQuery' +import { getParserSettings } from '../../utils' +import { PoorNodeType } from '../../../src/types' + +describe('parse query', () => { + const parserSettings = getParserSettings() + + const { parseCode } = parserSettings + + const preprocessQueryCode = parserSettings.preprocessQueryCode as ( + s: string, + ) => string + + const postprocessQueryNode = parserSettings.postprocessQueryNode as ( + node: PoorNodeType, + ) => PoorNodeType + + it('should get unique tokens', () => { + const queryCode = ` + p, .class { + width: 120px; + background-color: $$; + height: 0x0%; + } + ` + + const { queryNode } = extractQueryNode( + postprocessQueryNode(parseCode(preprocessQueryCode(queryCode))), + parserSettings, + ) + + const uniqueTokens = [...getUniqueTokens(queryNode, false, parserSettings)] + + expect(uniqueTokens).toMatchObject([ + 'class', + 'width', + 'background-color', + 'height', + ]) + }) +}) diff --git a/packages/core/__tests__/CSS/other/searchWithContext.test.ts b/packages/core/__tests__/CSS/other/searchWithContext.test.ts new file mode 100644 index 0000000..4d8e7ba --- /dev/null +++ b/packages/core/__tests__/CSS/other/searchWithContext.test.ts @@ -0,0 +1,139 @@ +import { searchInStrings } from '../../../src/searchInStrings' + +describe('Wildcard queries', () => { + it('Should match wildcard ref between class rules', () => { + const fileContent = ` + .firstClass { + background-color: red; + } + + .otherClassName { + display: none; + } + ` + const queries = [ + ` + .first$$_classRef {} + + .other$$_classRef_Name {} + `, + ] + + const { matches, errors } = searchInStrings({ + mode: 'include', + caseInsensitive: true, + queryCodes: queries, + files: [ + { + path: 'mock', + content: fileContent, + }, + ], + }) + + expect(errors).toHaveLength(0) + expect(matches.length).toBe(1) + + expect(matches[0].aliases.stringAliasesMap['classRef'].aliasValue).toBe( + 'Class', + ) + }) + + it('Should not match wildcard ref between class rules when they are different', () => { + const fileContent = ` + .firstClass { + background-color: red; + } + + .otherXXXXName { + display: none; + } + ` + const queries = [ + ` + .first$$_classRef {} + + .other$$_classRef_Name {} + `, + ] + + const { matches, errors } = searchInStrings({ + mode: 'include', + caseInsensitive: true, + queryCodes: queries, + files: [ + { + path: 'mock', + content: fileContent, + }, + ], + }) + + expect(errors).toHaveLength(0) + expect(matches.length).toBe(0) + }) + + it('Should match wildcard ref between class name and color', () => { + const fileContent = ` + .bgRed { + background-color: red; + } + ` + const queries = [ + ` + .bg$$_colorRef { + background-color: $$_colorRef; + } + `, + ] + + const { matches, errors } = searchInStrings({ + mode: 'include', + caseInsensitive: true, + queryCodes: queries, + files: [ + { + path: 'mock', + content: fileContent, + }, + ], + }) + + expect(errors).toHaveLength(0) + expect(matches.length).toBe(1) + + expect(matches[0].aliases.stringAliasesMap['colorRef'].aliasValue).toBe( + 'Red', + ) + }) + + it('Should not match wildcard ref between class name and color for case sensitive search', () => { + const fileContent = ` + .bgRed { + background-color: red; + } + ` + const queries = [ + ` + .bg$$_colorRef { + background-color: $$_colorRef; + } + `, + ] + + const { matches, errors } = searchInStrings({ + mode: 'include', + caseInsensitive: false, + queryCodes: queries, + files: [ + { + path: 'mock', + content: fileContent, + }, + ], + }) + + expect(errors).toHaveLength(0) + expect(matches.length).toBe(0) + }) +}) diff --git a/packages/core/__tests__/CSharp/matchCodePatterns/basic.test.ts b/packages/core/__tests__/CSharp/matchCodePatterns/basic.test.ts new file mode 100644 index 0000000..c7c25dd --- /dev/null +++ b/packages/core/__tests__/CSharp/matchCodePatterns/basic.test.ts @@ -0,0 +1,179 @@ +import { searchInStrings } from '../../../src/searchInStrings' +import { getParserSettings } from '../../utils' + +describe('Basic queries', () => { + beforeAll(async () => { + await getParserSettings().init?.() + }) + + it('Should exact match identifier', async () => { + const fileContent = ` + using System; + + Console.WriteLine("Hello"); + ` + const queries = [fileContent] + + const { matches, errors } = searchInStrings({ + mode: 'exact', + caseInsensitive: true, + queryCodes: queries, + files: [ + { + path: 'mock', + content: fileContent, + }, + ], + }) + + expect(errors).toHaveLength(0) + expect(matches.length).toBe(1) + }) + + it('Should exact match function definition', () => { + const fileContent = ` + using System; + + class TestClass + { + public static void Main(){} + + static void Fib(int n) + { + var init = (0, 1); + var (a, b) = init; + while (a < n) + { + Console.Write($"{a} "); + (a, b) = (b, a + b); + } + Console.WriteLine(); + } + } + ` + const queries = [fileContent] + + const { matches, errors } = searchInStrings({ + mode: 'exact', + caseInsensitive: true, + queryCodes: queries, + files: [ + { + path: 'mock', + content: fileContent, + }, + ], + }) + + expect(errors).toHaveLength(0) + expect(matches.length).toBe(1) + }) + + it('Should partial match function definition', () => { + const fileContent = ` + using System; + + class TestClass + { + public static void Main(){} + + static void Fib(int n) + { + var init = (0, 1); + var (a, b) = init; + while (a < n) + { + Console.Write($"{a} "); + (a, b) = (b, a + b); + } + Console.WriteLine(); + } + } + ` + + const queries = [ + ` + class TestClass + { + void Fib(int n) + { + Console.WriteLine(); + } + } + `, + ] + + const { matches, errors } = searchInStrings({ + mode: 'include', + caseInsensitive: true, + queryCodes: queries, + files: [ + { + path: 'mock', + content: fileContent, + }, + ], + }) + + expect(errors).toHaveLength(0) + expect(matches.length).toBe(1) + }) + + it('Should match multiline', () => { + const fileContent = ` + using System; + + class TestClass + { + public static void Main(){} + + static void Fib(int n) + { + var init = (0, 1); + var (a, b) = init; + while (a < n) + { + Console.Write($"{a} "); + (a, b) = (b, a + b); + } + Console.WriteLine(); + } + } + ` + + const queries = [ + ` + class TestClass + { + void Fib(int n) + { + var init = (0, 1); + var (a, b) = init; + } + } + `, + ] + + const { matches, errors } = searchInStrings({ + mode: 'include', + caseInsensitive: true, + queryCodes: queries, + files: [ + { + path: 'mock', + content: fileContent, + }, + ], + }) + + expect(errors).toHaveLength(0) + expect(matches.length).toBe(1) + + const match = matches[0] + + expect(match.loc).toStrictEqual({ + start: { line: 4, column: 8, index: 32 }, + end: { line: 19, column: 9, index: 429 }, + }) + }) +}) diff --git a/packages/core/__tests__/CSharp/matchCodePatterns/literals.test.ts b/packages/core/__tests__/CSharp/matchCodePatterns/literals.test.ts new file mode 100644 index 0000000..baea649 --- /dev/null +++ b/packages/core/__tests__/CSharp/matchCodePatterns/literals.test.ts @@ -0,0 +1,165 @@ +import { searchInStrings } from '../../../src/searchInStrings' +import { getParserSettings } from '../../utils' + +describe('Literals', () => { + beforeAll(async () => { + await getParserSettings().init?.() + }) + + it('Should match string literal', () => { + const fileContent = ` + using System; + + Console.WriteLine("Csharp"); + ` + + const queries = [ + ` + "Csharp" + `, + ] + + const { matches, errors } = searchInStrings({ + mode: 'include', + caseInsensitive: true, + queryCodes: queries, + files: [ + { + path: 'mock', + content: fileContent, + }, + ], + }) + + expect(errors).toHaveLength(0) + expect(matches.length).toBe(1) + }) + + it('Should exact match template string literal', () => { + const fileContent = ` + using System; + + Console.WriteLine($"Machine {machineId} has {status}."); + ` + + const queries = [ + ` + $"Machine {machineId} has {status}." + `, + ] + + const { matches, errors } = searchInStrings({ + mode: 'include', + caseInsensitive: true, + queryCodes: queries, + files: [ + { + path: 'mock', + content: fileContent, + }, + ], + }) + + expect(errors).toHaveLength(0) + expect(matches.length).toBe(1) + }) + + it('Should match Integer literal', () => { + const fileContent = ` + class TestClass + { + static void TestPrint() + { + var simpleInt = 32; + } + } + ` + + const queries = [ + ` + 32 + `, + ] + + const { matches, errors } = searchInStrings({ + mode: 'include', + caseInsensitive: true, + queryCodes: queries, + files: [ + { + path: 'mock', + content: fileContent, + }, + ], + }) + + expect(errors).toHaveLength(0) + expect(matches.length).toBe(1) + }) + + it('Should match Float literal', () => { + const fileContent = ` + class TestClass + { + static void TestPrint() + { + var simpleFloat = 32.0; + } + } + ` + + const queries = [ + ` + 32.0 + `, + ] + + const { matches, errors } = searchInStrings({ + mode: 'include', + caseInsensitive: true, + queryCodes: queries, + files: [ + { + path: 'mock', + content: fileContent, + }, + ], + }) + + expect(errors).toHaveLength(0) + expect(matches.length).toBe(1) + }) + + it('Should match Hex literal', () => { + const fileContent = ` + class TestClass + { + static void TestPrint() + { + var simpleHex = 0x1A2F; + } + } + ` + + const queries = [ + ` + 0x1A2F + `, + ] + + const { matches, errors } = searchInStrings({ + mode: 'include', + caseInsensitive: true, + queryCodes: queries, + files: [ + { + path: 'mock', + content: fileContent, + }, + ], + }) + + expect(errors).toHaveLength(0) + expect(matches.length).toBe(1) + }) +}) diff --git a/packages/core/__tests__/CSharp/matchCodePatterns/wildcards.test.ts b/packages/core/__tests__/CSharp/matchCodePatterns/wildcards.test.ts new file mode 100644 index 0000000..623d786 --- /dev/null +++ b/packages/core/__tests__/CSharp/matchCodePatterns/wildcards.test.ts @@ -0,0 +1,156 @@ +import { searchInStrings } from '../../../src/searchInStrings' +import { getParserSettings } from '../../utils' + +describe('Wildcards', () => { + beforeAll(async () => { + await getParserSettings().init?.() + }) + + it('Should match string wildcard', () => { + const fileContent = ` + using System; + + var langLabel = "Csharp"; + Console.WriteLine("Csharp"); + ` + + const queries = [ + ` + "Cs$$" + `, + ] + + const { matches, errors } = searchInStrings({ + mode: 'include', + caseInsensitive: true, + queryCodes: queries, + files: [ + { + path: 'mock', + content: fileContent, + }, + ], + }) + + expect(errors).toHaveLength(0) + expect(matches.length).toBe(2) + }) + + it('Should match node wildcard', () => { + const fileContent = ` + using System; + + var langLabel = "Csharp"; + Console.WriteLine("Csharp"); + ` + + const queries = [ + ` + var langLabel = $$$; + `, + ] + + const { matches, errors } = searchInStrings({ + mode: 'include', + caseInsensitive: true, + queryCodes: queries, + files: [ + { + path: 'mock', + content: fileContent, + }, + ], + }) + + expect(errors).toHaveLength(0) + expect(matches.length).toBe(1) + }) + + it('Should match identifier wildcard', () => { + const fileContent = ` + using System; + + var langLabel = "Csharp"; + Console.WriteLine("Csharp"); + ` + + const queries = [ + ` + Console.$$(); + `, + ] + + const { matches, errors } = searchInStrings({ + mode: 'include', + caseInsensitive: true, + queryCodes: queries, + files: [ + { + path: 'mock', + content: fileContent, + }, + ], + }) + + expect(errors).toHaveLength(0) + expect(matches.length).toBe(1) + }) + + it('Should match integer wildcard', () => { + const fileContent = ` + using System; + + var duration = 3600; + ` + + const queries = [ + ` + 0x0 + `, + ] + + const { matches, errors } = searchInStrings({ + mode: 'include', + caseInsensitive: true, + queryCodes: queries, + files: [ + { + path: 'mock', + content: fileContent, + }, + ], + }) + + expect(errors).toHaveLength(0) + expect(matches.length).toBe(1) + }) + + it('Should match float wildcard', () => { + const fileContent = ` + using System; + + var ratio = 2.33; + ` + + const queries = [ + ` + 0x0 + `, + ] + + const { matches, errors } = searchInStrings({ + mode: 'include', + caseInsensitive: true, + queryCodes: queries, + files: [ + { + path: 'mock', + content: fileContent, + }, + ], + }) + + expect(errors).toHaveLength(0) + expect(matches.length).toBe(1) + }) +}) diff --git a/packages/core/__tests__/HTML/matchCodePatterns/all.test.ts b/packages/core/__tests__/HTML/matchCodePatterns/all.test.ts new file mode 100644 index 0000000..7467ddb --- /dev/null +++ b/packages/core/__tests__/HTML/matchCodePatterns/all.test.ts @@ -0,0 +1,280 @@ +import { searchInStrings } from '../../../src/searchInStrings' +import dedent from 'dedent' + +describe('Basic queries', () => { + const fileContent = ` +
+
+

Some text

+
+
+ +
+
+ +

Some other text

+ ` + + it('Should match paragraph node', () => { + const queries = [`

`] + + const { matches, errors } = searchInStrings({ + mode: 'include', + caseInsensitive: true, + queryCodes: queries, + files: [ + { + path: 'mock', + content: fileContent, + }, + ], + }) + + expect(errors).toHaveLength(0) + expect(matches.length).toBe(2) + }) + + it('Should match div node with some params', () => { + const queries = [ + `
`, + ] + + const { matches, errors } = searchInStrings({ + mode: 'include', + caseInsensitive: true, + queryCodes: queries, + files: [ + { + path: 'mock', + content: fileContent, + }, + ], + }) + + expect(errors).toHaveLength(0) + expect(matches.length).toBe(1) + }) + + it('Should match div with text node', () => { + const queries = [`

Some text

`] + + const { matches, errors } = searchInStrings({ + mode: 'include', + caseInsensitive: true, + queryCodes: queries, + files: [ + { + path: 'mock', + content: fileContent, + }, + ], + }) + + expect(errors).toHaveLength(0) + expect(matches.length).toBe(1) + }) + + it('Should not match img if params does not match', () => { + const queries = [``] + + const { matches, errors } = searchInStrings({ + mode: 'include', + caseInsensitive: true, + queryCodes: queries, + files: [ + { + path: 'mock', + content: fileContent, + }, + ], + }) + + expect(errors).toHaveLength(0) + expect(matches.length).toBe(0) + }) + + it('Should match div node with p node using wildcard', () => { + const queries = [`<$$>

Some text

`] + + const { matches, errors } = searchInStrings({ + mode: 'include', + caseInsensitive: true, + queryCodes: queries, + files: [ + { + path: 'mock', + content: fileContent, + }, + ], + }) + + expect(errors).toHaveLength(0) + expect(matches.length).toBe(1) + }) + + it('Should match div node with p node using wildcard', () => { + const queries = [`<$$>

Some text

`] + + const { matches, errors } = searchInStrings({ + mode: 'include', + caseInsensitive: true, + queryCodes: queries, + files: [ + { + path: 'mock', + content: fileContent, + }, + ], + }) + + expect(errors).toHaveLength(0) + expect(matches.length).toBe(1) + }) + + it('Should not match div with children using element wildcard without children in exact mode', () => { + const queries = [`<$$>`] + + const { matches, errors } = searchInStrings({ + mode: 'exact', + caseInsensitive: true, + queryCodes: queries, + files: [ + { + path: 'mock', + content: fileContent, + }, + ], + }) + + expect(errors).toHaveLength(0) + expect(matches.length).toBe(0) + }) + + it('Should match div with property using string wildcard', () => { + const queries = [`
`] + + const { matches, errors } = searchInStrings({ + mode: 'include', + caseInsensitive: true, + queryCodes: queries, + files: [ + { + path: 'mock', + content: fileContent, + }, + ], + }) + + expect(errors).toHaveLength(0) + expect(matches.length).toBe(1) + }) + + it('Should not match div with property using string wildcard on non-existing prop', () => { + const queries = [`
`] + + const { matches, errors } = searchInStrings({ + mode: 'include', + caseInsensitive: true, + queryCodes: queries, + files: [ + { + path: 'mock', + content: fileContent, + }, + ], + }) + + expect(errors).toHaveLength(0) + expect(matches.length).toBe(0) + }) + + it('Should match p with string content using string wildcard', () => { + const queries = [`

$$text

`] + + const { matches, errors } = searchInStrings({ + mode: 'include', + caseInsensitive: true, + queryCodes: queries, + files: [ + { + path: 'mock', + content: fileContent, + }, + ], + }) + + expect(errors).toHaveLength(0) + expect(matches.length).toBe(2) + }) + + it('Should find multiple nodes 1', () => { + const fileContent = ` +
+

Some text

+
+

+
+ +
+ ` + + const queries = [ + ` +
+
+ `, + ] + + const { matches, errors } = searchInStrings({ + mode: 'include', + caseInsensitive: true, + queryCodes: queries, + files: [ + { + path: 'mock', + content: fileContent, + }, + ], + }) + + expect(errors).toHaveLength(0) + expect(matches.length).toBe(1) + }) + + it('Should find multiple nodes 2', () => { + const queries = [ + ` +
+
+ `, + ] + + const { matches, errors } = searchInStrings({ + mode: 'include', + caseInsensitive: true, + queryCodes: queries, + files: [ + { + path: 'mock', + content: fileContent, + }, + ], + }) + + expect(errors).toHaveLength(0) + expect(matches.length).toBe(1) + + expect( + dedent` +
+

Some text

+
+
+ +
+ `, + ).toBe(matches[0].code) + }) +}) diff --git a/packages/core/__tests__/search/assignmentPattern.test.ts b/packages/core/__tests__/JavaScript/matchCodePatterns/assignmentPattern.test.ts similarity index 64% rename from packages/core/__tests__/search/assignmentPattern.test.ts rename to packages/core/__tests__/JavaScript/matchCodePatterns/assignmentPattern.test.ts index 058eba5..f1bc72a 100644 --- a/packages/core/__tests__/search/assignmentPattern.test.ts +++ b/packages/core/__tests__/JavaScript/matchCodePatterns/assignmentPattern.test.ts @@ -1,17 +1,6 @@ -import path from 'path' -import { getFilesList } from '/getFilesList' -import { searchInStrings } from '../../src/searchInStrings' +import { searchInStrings } from '../../../src/searchInStrings' describe('AssignmentPattern improvements in include mode', () => { - let filesList = [] as string[] - - beforeAll(async () => { - filesList = await getFilesList({ - searchRoot: path.resolve(__dirname, '__fixtures__'), - omitGitIgnore: true, - }) - }) - it('should match assignment pattern in function arguments', () => { const fileContent = ` function some(param = null) { @@ -39,7 +28,7 @@ describe('AssignmentPattern improvements in include mode', () => { ], }) - expect(errors.length).toBe(0) + expect(errors).toHaveLength(0) expect(matches.length).toBe(1) }) @@ -70,72 +59,10 @@ describe('AssignmentPattern improvements in include mode', () => { ], }) - expect(errors.length).toBe(0) - expect(matches.length).toBe(1) - }) - - it('should match assignment pattern in function arguments type annotation', () => { - const fileContent = ` - function some(param: [] | null = null) { - - } - ` - - const queries = [ - ` - function some(param: [] | null) { - - } - `, - ] - - const { matches, errors } = searchInStrings({ - mode: 'include', - caseInsensitive: true, - queryCodes: queries, - files: [ - { - path: 'mock', - content: fileContent, - }, - ], - }) - - expect(errors.length).toBe(0) + expect(errors).toHaveLength(0) expect(matches.length).toBe(1) }) - it('should not match assignment pattern in function arguments with wildcard but without types', () => { - const fileContent = ` - function some(param = null) { - - } - ` - - const queries = [ - ` - function some($$: [] | null = null) { - - } - `, - ] - - const { matches, errors } = searchInStrings({ - mode: 'include', - caseInsensitive: true, - queryCodes: queries, - files: [ - { - path: 'mock', - content: fileContent, - }, - ], - }) - - expect(errors.length).toBe(0) - expect(matches.length).toBe(0) - }) - it('should match assignment pattern in array destructuring', () => { const fileContent = ` const [data = []] = query @@ -160,7 +87,7 @@ describe('AssignmentPattern improvements in include mode', () => { ], }) - expect(errors.length).toBe(0) + expect(errors).toHaveLength(0) expect(matches.length).toBe(1) }) @@ -187,7 +114,7 @@ describe('AssignmentPattern improvements in include mode', () => { ], }) - expect(errors.length).toBe(0) + expect(errors).toHaveLength(0) expect(matches.length).toBe(1) }) @@ -214,7 +141,7 @@ describe('AssignmentPattern improvements in include mode', () => { ], }) - expect(errors.length).toBe(0) + expect(errors).toHaveLength(0) expect(matches.length).toBe(1) }) @@ -241,7 +168,7 @@ describe('AssignmentPattern improvements in include mode', () => { ], }) - expect(errors.length).toBe(0) + expect(errors).toHaveLength(0) expect(matches.length).toBe(1) }) }) diff --git a/packages/core/__tests__/search/basic.test.ts b/packages/core/__tests__/JavaScript/matchCodePatterns/basic.test.ts similarity index 81% rename from packages/core/__tests__/search/basic.test.ts rename to packages/core/__tests__/JavaScript/matchCodePatterns/basic.test.ts index bcadf0d..f64e173 100644 --- a/packages/core/__tests__/search/basic.test.ts +++ b/packages/core/__tests__/JavaScript/matchCodePatterns/basic.test.ts @@ -1,4 +1,4 @@ -import { searchInStrings } from '../../src/searchInStrings' +import { searchInStrings } from '../../../src/searchInStrings' describe('Basic queries', () => { it('Should match identifier in file', () => { @@ -18,7 +18,7 @@ describe('Basic queries', () => { ], }) - expect(errors.length).toBe(0) + expect(errors).toHaveLength(0) expect(matches.length).toBe(1) }) }) diff --git a/packages/core/__tests__/search/blocks.test.ts b/packages/core/__tests__/JavaScript/matchCodePatterns/blocks.test.ts similarity index 55% rename from packages/core/__tests__/search/blocks.test.ts rename to packages/core/__tests__/JavaScript/matchCodePatterns/blocks.test.ts index 778e27b..9956a56 100644 --- a/packages/core/__tests__/search/blocks.test.ts +++ b/packages/core/__tests__/JavaScript/matchCodePatterns/blocks.test.ts @@ -1,21 +1,17 @@ -import { searchInFileSystem } from '/searchInFs' -import { compareCode } from '../utils' +import { compareCode } from '../../utils' -import path from 'path' -import { getFilesList } from '/getFilesList' -import { searchInStrings } from '../../src/searchInStrings' +import { searchInStrings } from '../../../src/searchInStrings' describe('blocks', () => { - let filesList = [] as string[] - - beforeAll(async () => { - filesList = await getFilesList({ - searchRoot: path.resolve(__dirname, '__fixtures__'), - omitGitIgnore: true, - }) - }) - it('should match exact whole block', () => { + const fileContent = ` + const fn = () => { + toggleRTL(); + I18nManager.forceRTL(!isRTL); + Updates.reloadAsync(); + } + ` + const queries = [ ` () => { @@ -26,19 +22,32 @@ describe('blocks', () => { `, ] - const { matches, errors } = searchInFileSystem({ + const { matches, errors } = searchInStrings({ mode: 'exact', - filePaths: filesList, + files: [ + { + path: 'mock', + content: fileContent, + }, + ], queryCodes: queries, }) - expect(errors.length).toBe(0) + expect(errors).toHaveLength(0) expect(matches.length).toBe(1) expect(compareCode(matches[0].code, queries[0])).toBeTruthy() }) it('should match block using query without all statements and different order', () => { + const fileContent = ` + const fn = () => { + toggleRTL(); + I18nManager.forceRTL(!isRTL); + Updates.reloadAsync(); + } + ` + const queries = [ ` () => { @@ -48,17 +57,30 @@ describe('blocks', () => { `, ] - const { matches, errors } = searchInFileSystem({ + const { matches, errors } = searchInStrings({ mode: 'include', - filePaths: filesList, + files: [ + { + path: 'mock', + content: fileContent, + }, + ], queryCodes: queries, }) - expect(errors.length).toBe(0) + expect(errors).toHaveLength(0) expect(matches.length).toBe(1) }) it('should match block using query without all statements, but with order', () => { + const fileContent = ` + const fn = () => { + toggleRTL(); + I18nManager.forceRTL(!isRTL); + Updates.reloadAsync(); + } + ` + const queries = [ ` () => { @@ -68,13 +90,18 @@ describe('blocks', () => { `, ] - const { matches, errors } = searchInFileSystem({ + const { matches, errors } = searchInStrings({ mode: 'include-with-order', - filePaths: filesList, + files: [ + { + path: 'mock', + content: fileContent, + }, + ], queryCodes: queries, }) - expect(errors.length).toBe(0) + expect(errors).toHaveLength(0) expect(matches.length).toBe(1) }) @@ -117,7 +144,7 @@ describe('blocks', () => { ], }) - expect(errors.length).toBe(0) + expect(errors).toHaveLength(0) expect(matches.length).toBe(2) }) @@ -146,7 +173,40 @@ describe('blocks', () => { ], }) - expect(errors.length).toBe(0) + expect(errors).toHaveLength(0) + expect(matches.length).toBe(1) + }) + + it('should match whole program using block wildcard', () => { + const fileContent = ` + import a from 'b' + import c from 'd' + + module.exports = { + fn: () => a + b + } + ` + + const queries = [ + ` + {} + `, + ] + + const { matches, errors } = searchInStrings({ + mode: 'include', + caseInsensitive: true, + queryCodes: queries, + files: [ + { + path: 'mock', + content: fileContent, + }, + ], + }) + + expect(errors).toHaveLength(0) expect(matches.length).toBe(1) + expect(compareCode(matches[0].code, fileContent)).toBe(true) }) }) diff --git a/packages/core/__tests__/JavaScript/matchCodePatterns/functions.test.ts b/packages/core/__tests__/JavaScript/matchCodePatterns/functions.test.ts new file mode 100644 index 0000000..58c78d6 --- /dev/null +++ b/packages/core/__tests__/JavaScript/matchCodePatterns/functions.test.ts @@ -0,0 +1,141 @@ +import { parserType } from '../../utils' +import { searchInStrings } from '../../../src/searchInStrings' + +describe('functions', () => { + it('should match function with 2 arguments', () => { + const testFileContent = ` + (a,b,c) => {}; + (a,d) => {}; + (a, { b}) => {}; + ` + + const mockedFilesList = [ + { + path: 'mock', + content: testFileContent, + }, + ] + const queries = [ + ` + ($$_ref1, $$_ref2) => {} + `, + ] + + const { matches, errors } = searchInStrings({ + mode: 'include', + files: mockedFilesList, + queryCodes: queries, + }) + + expect(errors).toHaveLength(0) + expect(matches.length).toBe(2) + }) + + it('should match function with 2 arguments using double wildcard', () => { + const testFileContent = ` + (a,b,c) => {}; + (a,d) => {}; + (a, { b}) => {}; + ` + + const mockedFilesList = [ + { + path: 'mock', + content: testFileContent, + }, + ] + const queries = [ + ` + ($$_ref1, $$$_ref2) => {} + `, + ] + + const { matches, errors } = searchInStrings({ + mode: 'include', + files: mockedFilesList, + queryCodes: queries, + }) + + expect(errors).toHaveLength(0) + expect(matches.length).toBe(3) + }) + + it('should match function with 3 arguments', () => { + const testFileContent = ` + (a,b,c) => {}; + (a,d) => {}; + (a, { b}) => {}; + ` + + const mockedFilesList = [ + { + path: 'mock', + content: testFileContent, + }, + ] + const queries = [ + ` + ($$_ref1, $$_ref2, $$_ref3) => {} + `, + ] + + const { matches, errors } = searchInStrings({ + mode: 'include', + files: mockedFilesList, + queryCodes: queries, + }) + + expect(errors).toHaveLength(0) + expect(matches.length).toBe(1) + }) + + // TODO add support for matching this in babel (works out-of-the-box in EStree parsers) + if (parserType !== 'babel') { + it('should match function expression inside object', () => { + const fileContent = ` + const obj = { + someFn() {} + } + ` + const queries = [ + ` + function() {} + `, + ] + + const { matches, errors } = searchInStrings({ + mode: 'include', + files: [{ content: fileContent, path: 'mockPath' }], + queryCodes: queries, + }) + + expect(errors).toHaveLength(0) + expect(matches.length).toBe(1) + }) + + it('should match function expression inside class', () => { + const fileContent = ` + class Cl { + someFn() {} + } + ` + const queries = [ + ` + function() {} + `, + ] + + const { matches, errors } = searchInStrings({ + mode: 'include', + files: [{ content: fileContent, path: 'mockPath' }], + queryCodes: queries, + }) + + expect(errors).toHaveLength(0) + expect(matches.length).toBe(1) + }) + } else { + it.todo('should match function expression inside object') + it.todo('should match function expression inside class') + } +}) diff --git a/packages/core/__tests__/JavaScript/matchCodePatterns/import.test.ts b/packages/core/__tests__/JavaScript/matchCodePatterns/import.test.ts new file mode 100644 index 0000000..290a265 --- /dev/null +++ b/packages/core/__tests__/JavaScript/matchCodePatterns/import.test.ts @@ -0,0 +1,172 @@ +import { searchInStrings } from '../../../src/searchInStrings' + +describe('Import', () => { + it('Should find imports including some keys with persisted order', () => { + const fileContent = ` + import { + Button, + Wrapper, + IconButton + } from 'react-native-paper' + ` + const query = ` + import { + Button, + IconButton + } from 'react-native-paper' + ` + const { matches, errors } = searchInStrings({ + mode: 'include-with-order', + files: [{ content: fileContent, path: '' }], + queryCodes: [query], + }) + + expect(errors).toHaveLength(0) + expect(matches.length).toBe(1) + }) + + it('Should not find any imports including some keys when order changed', () => { + const fileContent = ` + import { + Button, + Wrapper, + IconButton + } from 'react-native-paper' + ` + + const query = ` + import { + IconButton, + Button, + } from 'react-native-paper' + ` + const { matches, errors } = searchInStrings({ + mode: 'include-with-order', + files: [{ content: fileContent, path: '' }], + queryCodes: [query], + }) + + expect(errors).toHaveLength(0) + expect(matches.length).toBe(0) + }) + + it('Should find different imports of library', () => { + const fileContent = ` + import rn from 'react-native' + import * as rn2 from 'react-native' + import { View } from 'react-native' + import { View as RNView } from 'react-native' + ` + + const query = ` + import $$$ from 'react-native'; + ` + const { matches, errors } = searchInStrings({ + mode: 'exact', + files: [{ content: fileContent, path: '' }], + queryCodes: [query], + }) + + expect(errors).toHaveLength(0) + expect(matches.length).toBe(4) + }) + + it('Should find default imports of a dependency', () => { + const fileContent = ` + import ScreenWrapper from '../ScreenWrapper'; + import { ScreenWrapper2 }from '../ScreenWrapper'; + ` + + const query = ` + import $$ from '../ScreenWrapper'; + ` + const { matches, errors } = searchInStrings({ + mode: 'include', + files: [{ content: fileContent, path: '' }], + queryCodes: [query], + }) + + expect(errors).toHaveLength(0) + expect(matches.length).toBe(1) + }) + + it('Should find default imports with case insensitive query', () => { + const fileContent = ` + import ScreenWrapper from '../ScreenWrapper'; + ` + const query = ` + import $$screenwrapper from '../screenwrapper'; + ` + const { matches, errors } = searchInStrings({ + mode: 'include', + files: [{ content: fileContent, path: '' }], + caseInsensitive: true, + queryCodes: [query], + }) + + expect(errors).toHaveLength(0) + expect(matches.length).toBe(1) + }) + + // TODO fix it, it should not match the second import + it('Should find aliased imports of a dependency', () => { + const fileContent = ` + import { Provider as PaperProvider } from 'react-native-paper'; + import { Provider } from 'react-native-paper'; + + ` + + const query = ` + import { Provider as $$ } from 'react-native-paper'; + ` + const { matches, errors } = searchInStrings({ + mode: 'include', + files: [{ content: fileContent, path: '' }], + queryCodes: [query], + }) + + expect(errors).toHaveLength(0) + expect(matches.length).toBe(2) + }) + + it('Should find imports with both default and named', () => { + const fileContent = ` + import Default, { Provider as PaperProvider } from 'react-native-paper'; + import { Provider as PaperProvider2 } from 'react-native-paper'; + import Default2 from 'react-native-paper'; + + ` + const query = ` + import $$, { $$$ } from '$$'; + ` + const { matches, errors } = searchInStrings({ + mode: 'include', + files: [{ content: fileContent, path: '' }], + queryCodes: [query], + }) + + expect(errors).toHaveLength(0) + expect(matches.length).toBe(1) + }) + + it('Should find aliased reexports ', () => { + // TODO: fix - it matches "export {default} from " but it shouldn't + const fileContent = ` + export { default } from 'lib' + export { one as another } from 'lib' + + ` + + const query = ` + export { $$ as $$$ } from '$$'; + ` + const { matches, errors } = searchInStrings({ + mode: 'include', + files: [{ content: fileContent, path: '' }], + queryCodes: [query], + }) + + expect(errors).toHaveLength(0) + expect(matches.length).toBe(2) + }) +}) diff --git a/packages/core/__tests__/search/literals.test.ts b/packages/core/__tests__/JavaScript/matchCodePatterns/literals.test.ts similarity index 83% rename from packages/core/__tests__/search/literals.test.ts rename to packages/core/__tests__/JavaScript/matchCodePatterns/literals.test.ts index 7ad2543..e955d41 100644 --- a/packages/core/__tests__/search/literals.test.ts +++ b/packages/core/__tests__/JavaScript/matchCodePatterns/literals.test.ts @@ -1,4 +1,4 @@ -import { searchInStrings } from '/searchInStrings' +import { searchInStrings } from '../../../src/searchInStrings' describe('Literals', () => { const testFileContent = ` @@ -40,7 +40,7 @@ describe('Literals', () => { queryCodes: queries, }) - expect(errors.length).toBe(0) + expect(errors).toHaveLength(0) expect(matches.length).toBe(2) }) @@ -57,7 +57,7 @@ describe('Literals', () => { queryCodes: queries, }) - expect(errors.length).toBe(0) + expect(errors).toHaveLength(0) expect(matches.length).toBe(2) }) @@ -75,7 +75,7 @@ describe('Literals', () => { caseInsensitive: true, }) - expect(errors.length).toBe(0) + expect(errors).toHaveLength(0) expect(matches.length).toBe(2) }) @@ -112,7 +112,7 @@ describe('Literals', () => { queryCodes: queries, }) - expect(errors.length).toBe(0) + expect(errors).toHaveLength(0) expect(matches.length).toBe(2) }) @@ -125,7 +125,7 @@ describe('Literals', () => { queryCodes: queries, }) - expect(errors.length).toBe(0) + expect(errors).toHaveLength(0) expect(matches.length).toBe(5) }) @@ -138,7 +138,7 @@ describe('Literals', () => { queryCodes: queries, }) - expect(errors.length).toBe(0) + expect(errors).toHaveLength(0) expect(matches.length).toBe(1) }) @@ -151,7 +151,7 @@ describe('Literals', () => { queryCodes: queries, }) - expect(errors.length).toBe(0) + expect(errors).toHaveLength(0) expect(matches.length).toBe(3) }) @@ -164,7 +164,7 @@ describe('Literals', () => { queryCodes: queries, }) - expect(errors.length).toBe(0) + expect(errors).toHaveLength(0) expect(matches.length).toBe(1) }) @@ -177,7 +177,7 @@ describe('Literals', () => { queryCodes: queries, }) - expect(errors.length).toBe(0) + expect(errors).toHaveLength(0) expect(matches.length).toBe(1) }) @@ -190,7 +190,7 @@ describe('Literals', () => { queryCodes: queries, }) - expect(errors.length).toBe(0) + expect(errors).toHaveLength(0) expect(matches.length).toBe(0) }) @@ -203,7 +203,7 @@ describe('Literals', () => { queryCodes: queries, }) - expect(errors.length).toBe(0) + expect(errors).toHaveLength(0) expect(matches.length).toBe(1) }) @@ -216,7 +216,7 @@ describe('Literals', () => { queryCodes: queries, }) - expect(errors.length).toBe(0) + expect(errors).toHaveLength(0) expect(matches.length).toBe(1) }) @@ -229,12 +229,12 @@ describe('Literals', () => { queryCodes: queries, }) - expect(errors.length).toBe(0) + expect(errors).toHaveLength(0) expect(matches.length).toBe(1) }) it('should match multiple template literals with include mode', () => { - const queries = ['`{$$}`'] + const queries = ['`${$$}`'] const { matches, errors } = searchInStrings({ mode: 'include', @@ -242,8 +242,34 @@ describe('Literals', () => { queryCodes: queries, }) - expect(errors.length).toBe(0) - expect(matches.length).toBe(0) + expect(errors).toHaveLength(0) + expect(matches.length).toBe(3) + }) + + it('should match template literal using partial query with include mode 1', () => { + const queries = ['`val ${$$}`'] + + const { matches, errors } = searchInStrings({ + mode: 'include', + files: filesList, + queryCodes: queries, + }) + + expect(errors).toHaveLength(0) + expect(matches.length).toBe(1) + }) + + it('should match template literal using partial query with include mode 2', () => { + const queries = ['`text`'] + + const { matches, errors } = searchInStrings({ + mode: 'include', + files: filesList, + queryCodes: queries, + }) + + expect(errors).toHaveLength(0) + expect(matches.length).toBe(1) }) it('should match different parts of template literals with include mode', () => { @@ -321,7 +347,7 @@ describe('Literals', () => { ], }) - expect(errors.length).toBe(0) + expect(errors).toHaveLength(0) expect(matches.length).toBe(1) }) }) diff --git a/packages/core/__tests__/search/multiStatement.test.ts b/packages/core/__tests__/JavaScript/matchCodePatterns/multiStatement.test.ts similarity index 69% rename from packages/core/__tests__/search/multiStatement.test.ts rename to packages/core/__tests__/JavaScript/matchCodePatterns/multiStatement.test.ts index d3d8989..c9177fb 100644 --- a/packages/core/__tests__/search/multiStatement.test.ts +++ b/packages/core/__tests__/JavaScript/matchCodePatterns/multiStatement.test.ts @@ -1,19 +1,106 @@ -import { searchInFileSystem } from '/searchInFs' -import { compareCode } from '../utils' +import { compareCode } from '../../utils' -import path from 'path' -import { getFilesList } from '/getFilesList' -import searchInStrings from '/searchInStrings' +import searchInStrings from '../../../src/searchInStrings' describe('multi statements', () => { - let filesList = [] as string[] + it('should find proper position in file if one of the multiline query statements is matched more than once in file node', () => { + const fileContent = ` + const result = db.model.findUnique(); + + if (!result) { + throw new Error(); + } + + const results = db.model2.find(); + ` + + const queries = [ + ` + const $$ = db.$$.$$(); + + if (!$$) { + throw new Error(); + } + `, + ] + + const files = [ + { + content: fileContent, + path: 'test-path', + }, + ] + + const { matches, errors } = searchInStrings({ + mode: 'include-with-order', + files, + queryCodes: queries, + }) + + expect(errors).toHaveLength(0) + expect(matches.length).toBe(1) + + const expectedMatch = ` + const result = db.model.findUnique(); + + if (!result) { + throw new Error(); + } + ` + expect(compareCode(matches[0].code, expectedMatch)).toBe(true) + }) + + it('should find proper position in file for several exact same statements in query and file', () => { + const fileContent = ` + export * from 'fileA' + export * from 'fileB' + export * from 'fileC' + export * from 'fileD' + + ` + + const queries = [ + ` + export * from '$$' + export * from '$$' + export * from '$$' + `, + ] - beforeAll(async () => { - filesList = await getFilesList({ - searchRoot: path.resolve(__dirname, '__fixtures__'), - omitGitIgnore: true, + const files = [ + { + content: fileContent, + path: 'test-path', + }, + ] + + const { matches, errors } = searchInStrings({ + mode: 'include', + files, + queryCodes: queries, }) + + expect(errors).toHaveLength(0) + expect(matches.length).toBe(1) + + const expectedMatch1 = ` + export * from 'fileA' + export * from 'fileB' + export * from 'fileC' + ` + + expect(compareCode(matches[0].code, expectedMatch1)).toBe(true) }) +}) + +describe('multi statements 2', () => { + const fileContent = ` + () => { + toggleRTL(); + I18nManager.forceRTL(!isRTL); + Updates.reloadAsync(); + } + ` it('should match three expressions without block wrapper in function body', () => { const queries = [ @@ -24,13 +111,13 @@ describe('multi statements', () => { `, ] - const { matches, errors } = searchInFileSystem({ + const { matches, errors } = searchInStrings({ mode: 'include', - filePaths: filesList, + files: [{ content: fileContent, path: '' }], queryCodes: queries, }) - expect(errors.length).toBe(0) + expect(errors).toHaveLength(0) expect(matches.length).toBe(1) }) @@ -43,13 +130,13 @@ describe('multi statements', () => { `, ] - const { matches, errors } = searchInFileSystem({ + const { matches, errors } = searchInStrings({ mode: 'exact', - filePaths: filesList, + files: [{ content: fileContent, path: '' }], queryCodes: queries, }) - expect(errors.length).toBe(0) + expect(errors).toHaveLength(0) expect(matches.length).toBe(1) }) @@ -62,13 +149,13 @@ describe('multi statements', () => { `, ] - const { matches, errors } = searchInFileSystem({ + const { matches, errors } = searchInStrings({ mode: 'include', - filePaths: filesList, + files: [{ content: fileContent, path: '' }], queryCodes: queries, }) - expect(errors.length).toBe(0) + expect(errors).toHaveLength(0) expect(matches.length).toBe(1) }) @@ -83,16 +170,30 @@ describe('multi statements', () => { `, ] - const { matches, errors } = searchInFileSystem({ + const { matches, errors } = searchInStrings({ mode: 'include', - filePaths: filesList, + files: [{ content: fileContent, path: '' }], queryCodes: queries, }) - expect(errors.length).toBe(0) + expect(errors).toHaveLength(0) expect(matches.length).toBe(1) }) + const fileContent2 = ` + import * as React from 'react' + import { Appbar } from 'react-native-paper' + import { createStackNavigator } from '@react-navigation/stack' + import ExampleList, { examples } from './ExampleList' + const Stack = createStackNavigator() + + export default function Root() { + return ( + + ) + } + ` + it('should match two expressions in program body and include middle line in result', () => { const queries = [ ` @@ -101,13 +202,13 @@ describe('multi statements', () => { `, ] - const { matches, errors } = searchInFileSystem({ + const { matches, errors } = searchInStrings({ mode: 'include', - filePaths: filesList, + files: [{ content: fileContent2, path: '' }], queryCodes: queries, }) - expect(errors.length).toBe(0) + expect(errors).toHaveLength(0) expect(matches.length).toBe(1) expect(matches[0].code.split('\n').filter(Boolean)).toHaveLength(3) @@ -122,13 +223,13 @@ describe('multi statements', () => { `, ] - const { matches, errors } = searchInFileSystem({ + const { matches, errors } = searchInStrings({ mode: 'include', - filePaths: filesList, + files: [{ content: fileContent2, path: '' }], queryCodes: queries, }) - expect(errors.length).toBe(0) + expect(errors).toHaveLength(0) expect(matches.length).toBe(1) expect(compareCode(matches[0].code, queries[0])).toBeTruthy() @@ -142,102 +243,13 @@ describe('multi statements', () => { `, ] - const { matches, errors } = searchInFileSystem({ - mode: 'include-with-order', - filePaths: filesList, - queryCodes: queries, - }) - - expect(errors.length).toBe(0) - expect(matches.length).toBe(0) - }) - - it('should find proper position in file if one of the multiline query statements is matched more than once in file node', () => { - const fileContent = ` - const result = await db.model.findUnique(); - - if (!result) { - throw new Error(); - } - - const results = await db.model2.find(); - ` - - const queries = [ - ` - const $$ = await db.$$.$$(); - - if (!$$) { - throw new Error(); - } - `, - ] - - const files = [ - { - content: fileContent, - path: 'test-path', - }, - ] - const { matches, errors } = searchInStrings({ mode: 'include-with-order', - files, + files: [{ content: fileContent2, path: '' }], queryCodes: queries, }) - expect(errors.length).toBe(0) - expect(matches.length).toBe(1) - - const expectedMatch = ` - const result = await db.model.findUnique(); - - if (!result) { - throw new Error(); - } - ` - expect(compareCode(matches[0].code, expectedMatch)).toBe(true) - }) - - it('should find proper position in file for several exact same statements in query and file', () => { - const fileContent = ` - export * from 'fileA' - export * from 'fileB' - export * from 'fileC' - export * from 'fileD' - - ` - - const queries = [ - ` - export * from '$$' - export * from '$$' - export * from '$$' - `, - ] - - const files = [ - { - content: fileContent, - path: 'test-path', - }, - ] - - const { matches, errors } = searchInStrings({ - mode: 'include', - files, - queryCodes: queries, - }) - - expect(errors.length).toBe(0) - expect(matches.length).toBe(1) - - const expectedMatch1 = ` - export * from 'fileA' - export * from 'fileB' - export * from 'fileC' - ` - - expect(compareCode(matches[0].code, expectedMatch1)).toBe(true) + expect(errors).toHaveLength(0) + expect(matches.length).toBe(0) }) }) diff --git a/packages/core/__tests__/search/objects.test.ts b/packages/core/__tests__/JavaScript/matchCodePatterns/objects.test.ts similarity index 52% rename from packages/core/__tests__/search/objects.test.ts rename to packages/core/__tests__/JavaScript/matchCodePatterns/objects.test.ts index 708304e..48ebc24 100644 --- a/packages/core/__tests__/search/objects.test.ts +++ b/packages/core/__tests__/JavaScript/matchCodePatterns/objects.test.ts @@ -1,8 +1,16 @@ -import { searchInFileSystem } from '/searchInFs' -import { compareCode } from '../utils' -import path from 'path' -import { getFilesList } from '/getFilesList' -import { searchInStrings } from '../../src/searchInStrings' +import { createLogger } from '../../../src/logger' +import { parseQueries } from '../../../src/parseQuery' +import { searchInStrings } from '../../../src/searchInStrings' +import { searchAst } from '../../../src/searchStages/searchAst' +import { NotNullParsedQuery } from '../../../src/types' +import { + compareCode, + getParserSettings, + parserType, + fixturesPath, +} from '../../utils' +import { getFilesList } from '../../../src/getFilesList' +import { searchInFileSystem } from '../../../src/searchInFs' describe('Objects', () => { const testFile = ` @@ -80,7 +88,7 @@ describe('Objects', () => { queryCodes: queries, }) - expect(errors.length).toBe(0) + expect(errors).toHaveLength(0) expect(matches.length).toBe(1) }) @@ -109,7 +117,7 @@ describe('Objects', () => { queryCodes: queries, }) - expect(errors.length).toBe(0) + expect(errors).toHaveLength(0) expect(matches.length).toBe(1) }) @@ -135,7 +143,7 @@ describe('Objects', () => { queryCodes: queries, }) - expect(errors.length).toBe(0) + expect(errors).toHaveLength(0) expect(matches.length).toBe(1) }) @@ -159,7 +167,7 @@ describe('Objects', () => { queryCodes: queries, }) - expect(errors.length).toBe(0) + expect(errors).toHaveLength(0) expect(matches.length).toBe(1) }) @@ -201,7 +209,7 @@ describe('Objects', () => { }); ` - expect(errors.length).toBe(0) + expect(errors).toHaveLength(0) expect(matches.length).toBe(4) expect(compareCode(`(${matches[0].code})`, firstMatch)).toBeTruthy() @@ -225,7 +233,7 @@ describe('Objects', () => { queryCodes: queries, }) - expect(errors.length).toBe(0) + expect(errors).toHaveLength(0) expect(matches.length).toBe(1) // It used to be 0, but now `{}` matches program as a block matches program }) @@ -250,38 +258,10 @@ describe('Objects', () => { queryCodes: queries, }) - expect(errors.length).toBe(0) + expect(errors).toHaveLength(0) expect(matches.length).toBe(1) }) - it('should match possibly repeated object properties', async () => { - const filesList = await getFilesList({ - searchRoot: path.resolve(__dirname, '__fixtures__'), - omitGitIgnore: true, - }) - - const queries = [ - ` - StyleSheet.create({ - $$: { - flex: 1, - alignItems: 'center', - justifyContent: 'center', - }, - }); - `, - ] - - const { matches, errors } = searchInFileSystem({ - mode: 'include', - filePaths: filesList, - queryCodes: queries, - }) - - expect(errors.length).toBe(0) - expect(matches.length).toBe(2) - }) - it('should match equivalent object keys', () => { const queries = [ ` @@ -307,7 +287,7 @@ describe('Objects', () => { queryCodes: queries, }) - expect(errors.length).toBe(0) + expect(errors).toHaveLength(0) expect(matches.length).toBe(5) }) @@ -332,26 +312,113 @@ describe('Objects', () => { queryCodes: queries, }) - expect(errors.length).toBe(0) + expect(errors).toHaveLength(0) expect(matches.length).toBe(1) }) - it('should match OptionalMemberExpression and non-optional MemberExpression interchangeably in include mode object property before rename', () => { - const testFile = ` + /** + * Esprima does not support optional chaining + */ + if (parserType !== 'esprima') { + it('should match OptionalMemberExpression and non-optional MemberExpression interchangeably in include mode', () => { + const testFile = ` obj?.field?.field obj.field.field obj?.field.field obj.field?.field ` - const query1 = ` + const query1 = ` obj.field.field ` - const query2 = ` + const query2 = ` obj?.field?.field ` - const { matches: matches1, errors: errors1 } = searchInStrings({ + const { matches: matches1, errors: errors1 } = searchInStrings({ + mode: 'include', + files: [ + { + path: 'mock', + content: testFile, + }, + ], + queryCodes: [query1], + }) + + const { matches: matches2, errors: errors2 } = searchInStrings({ + mode: 'include', + files: [ + { + path: 'mock', + content: testFile, + }, + ], + queryCodes: [query2], + }) + + expect(errors1.length).toBe(0) + expect(matches1.length).toBe(4) + + expect(errors2.length).toBe(0) + expect(matches2.length).toBe(4) + }) + + it('should match OptionalMemberExpression and non-optional MemberExpression interchangeably in include mode when assigned to variables', () => { + const testFile = ` + const a = obj?.field?.field + const b = obj.field.field + const c = obj?.field.field + const d = obj.field?.field + ` + + const query1 = ` + obj.field.field + ` + const query2 = ` + obj?.field?.field + ` + + const { matches: matches1, errors: errors1 } = searchInStrings({ + mode: 'include', + files: [ + { + path: 'mock', + content: testFile, + }, + ], + queryCodes: [query1], + }) + + const { matches: matches2, errors: errors2 } = searchInStrings({ + mode: 'include', + files: [ + { + path: 'mock', + content: testFile, + }, + ], + queryCodes: [query2], + }) + + expect(errors1.length).toBe(0) + expect(matches1.length).toBe(4) + + expect(errors2.length).toBe(0) + expect(matches2.length).toBe(4) + }) + } + + it('should match chain expression start with nodes tree wildcard', () => { + const testFile = ` + z.string().optional().nullable() + ` + + const query = ` + $$$.optional().nullable() + ` + + const { matches, errors } = searchInStrings({ mode: 'include', files: [ { @@ -359,10 +426,24 @@ describe('Objects', () => { content: testFile, }, ], - queryCodes: [query1], + queryCodes: [query], }) - const { matches: matches2, errors: errors2 } = searchInStrings({ + expect(errors).toHaveLength(0) + expect(matches.length).toBe(1) + }) + + // TODO support this + it.skip('should match chain expression using optional chain query with call expression at the end', () => { + const testFile = ` + z.string.nullable() + ` + + const query = ` + z?.sting?.nullable() + ` + + const { matches, errors } = searchInStrings({ mode: 'include', files: [ { @@ -370,13 +451,89 @@ describe('Objects', () => { content: testFile, }, ], - queryCodes: [query2], + queryCodes: [query], }) - expect(errors1.length).toBe(0) - expect(matches1.length).toBe(4) + expect(errors).toHaveLength(0) + expect(matches.length).toBe(1) + }) + + it('should match chain expression start with nodes tree wildcard in AST search inside raw AST node', () => { + const parserSettings = getParserSettings() + const searchSettings = { + mode: 'include' as const, + caseInsensitive: true, + parserSettings, + logger: createLogger(), + } + const testFile = ` + z.string().optional().nullable() + ` + + const query = ` + $$$.optional().nullable() + ` + + const fileNode = parserSettings.parseCode(testFile, '') + + // 👈 unwrap program node to mimic behavior of query builder search + const extractedFileNode = parserSettings.unwrapExpressionStatement( + parserSettings.getProgramBodyFromRootNode(fileNode)[0], + ) + + const [queries, parseOk] = parseQueries( + [query], + searchSettings.caseInsensitive, + searchSettings.parserSettings, + ) + + const { matches } = searchAst( + extractedFileNode, + { + queries: queries as NotNullParsedQuery[], + ...searchSettings, + getCodeForFileNode: () => '', + }, + false, // 👈 not unwrap program node inside searchAst + )[0] + + expect(extractedFileNode.type).toBe('CallExpression') + expect(queries?.[0]?.queryNode?.type).toBe('CallExpression') + expect(matches.length).toBe(1) + }) + + it('should match possibly repeated object properties', async () => { + const fileContent = ` + StyleSheet.create({ + a: { + flex: 1, + alignItems: 'center', + justifyContent: 'center', + }, + b: { + flex: 1, + alignItems: 'center', + justifyContent: 'center', + }, + }); + ` + + const queries = [ + ` + StyleSheet.create({ + $$: $$$_one, + $$: $$$_one, + }); + `, + ] - expect(errors2.length).toBe(0) - expect(matches2.length).toBe(4) + const { matches, errors } = searchInStrings({ + mode: 'include', + files: [{ content: fileContent, path: '' }], + queryCodes: queries, + }) + + expect(errors).toHaveLength(0) + expect(matches.length).toBe(1) }) }) diff --git a/packages/core/__tests__/JavaScript/matchCodePatterns/other.test.ts b/packages/core/__tests__/JavaScript/matchCodePatterns/other.test.ts new file mode 100644 index 0000000..f13619c --- /dev/null +++ b/packages/core/__tests__/JavaScript/matchCodePatterns/other.test.ts @@ -0,0 +1,308 @@ +import { searchInStrings } from '../../../src/searchInStrings' +import { compareCode } from '../../utils' +import { searchInFileSystem } from '../../../src/searchInFs' + +describe('Other', () => { + it('Should properly match identifiers with multiple wildcard sections', () => { + const fileContent = ` + varOne; + var_two; + ` + + const queries = [`$$_$$`] + + const { matches, errors } = searchInStrings({ + mode: 'exact', + caseInsensitive: true, + queryCodes: queries, + files: [ + { + path: 'mock', + content: fileContent, + }, + ], + }) + + expect(errors).toHaveLength(0) + expect(matches.length).toBe(1) + expect(Object.keys(matches[0].aliases.identifierAliasesMap)).toHaveLength(0) + }) + + it('Should properly match identifiers with content before and after wildcard', () => { + const fileContent = ` + preContentPost; + ` + + const queries = [`pre$$Post`] + + const { matches, errors } = searchInStrings({ + mode: 'exact', + caseInsensitive: true, + queryCodes: queries, + files: [ + { + path: 'mock', + content: fileContent, + }, + ], + }) + + expect(errors).toHaveLength(0) + expect(matches.length).toBe(1) + }) + + it('Should match identifier with aliased wildcard', () => { + const fileContent = ` + const preContent = 5 + ` + + const queries = [`pre$$_ref1`] + + const { matches, errors } = searchInStrings({ + mode: 'include', + caseInsensitive: true, + queryCodes: queries, + files: [ + { + path: 'mock', + content: fileContent, + }, + ], + }) + + expect(errors).toHaveLength(0) + expect(matches.length).toBe(1) + }) + + it('Should match identifier with wildcard with ref and content before and after it', () => { + const fileContent = ` + const preContentPost = 5 + ` + + const queries = [ + ` + pre$$_ref1_Post + `, + ] + + const { matches, errors } = searchInStrings({ + mode: 'include', + caseInsensitive: true, + queryCodes: queries, + files: [ + { + path: 'mock', + content: fileContent, + }, + ], + }) + + expect(errors).toHaveLength(0) + expect(matches.length).toBe(1) + }) + + it('Should match try-catch block with statement and re-throw of error', () => { + const fileContent = ` + try { + bla.bla.bla + } catch (error) { + logger.error(error); + throw error; + } + ` + + const queries = [`try {} catch($$) { Logger.error($$); throw $$ }`] + + const { matches, errors } = searchInStrings({ + mode: 'include', + caseInsensitive: true, + queryCodes: queries, + files: [ + { + path: 'mock', + content: fileContent, + }, + ], + }) + + expect(errors).toHaveLength(0) + expect(matches.length).toBe(1) + }) + + it('Should match class with static method', () => { + const fileContent = ` + class Test { + method() { return 0 } + static getInstance(){ + return new Test() + } + } + ` + + const queries = [`class $$ {static getInstance(){}}`] + + const { matches, errors } = searchInStrings({ + mode: 'include', + caseInsensitive: true, + queryCodes: queries, + files: [ + { + path: 'mock', + content: fileContent, + }, + ], + }) + + expect(errors).toHaveLength(0) + expect(matches.length).toBe(1) + }) + + it('Should match function with redundant block statement', () => { + const fileContent = ` + const DrawerContent = () => { + return + {preferences => } + ; + }; + + const add5 = (val) => { + return val + 5 + } + ` + + const queries = [ + ` + const $$ = () => { + return $$$ + }; + `, + `const $$ = ($$$) => { + return $$$ + }; + `, + ] + + const { matches, errors } = searchInStrings({ + mode: 'exact', + files: [{ content: fileContent, path: '' }], + queryCodes: queries, + }) + + expect(errors).toHaveLength(0) + expect(matches.length).toBe(2) + + expect( + compareCode(matches[1].code, `const add5 = (val) => { return val + 5 }`), + ).toBeTruthy() + }) + + it('should match nested ternary operator', () => { + const fileContent = ` + a ? b : a.b ? c.d : fn() + ` + + const queries = [ + ` + $$$ ? $$$ : $$$ ? $$$ : $$$ + `, + ] + + const { matches, errors } = searchInStrings({ + mode: 'include', + files: [{ content: fileContent, path: '' }], + queryCodes: queries, + }) + expect(errors).toHaveLength(0) + expect(matches.length).toBe(1) + }) + + it('should find all console logs', () => { + const fileContent = ` + console.log('test') + console.log() + console.error() + ` + + const query = ` + console.log() + ` + const { matches, errors } = searchInStrings({ + mode: 'include', + files: [{ content: fileContent, path: '' }], + queryCodes: [query], + }) + + expect(errors).toHaveLength(0) + expect(matches.length).toBe(2) + }) + + it('Should find all requires of jpg assets', () => { + const fileContent = ` + const asset = require('public/assets/image.jpg'); + const asset2 = require('image.jpg'); + + ` + + const queries = [ + ` + require("$$assets$$.jpg") + `, + ] + + const { matches, errors } = searchInStrings({ + mode: 'exact', + files: [{ content: fileContent, path: '' }], + queryCodes: queries, + }) + + expect(errors).toHaveLength(0) + expect(matches.length).toBe(1) + }) + + it('Should find all string concatenations using + operator', () => { + const fileContent = ` + () => {return "asd" + "ASD"} + ` + + const queries = [ + ` + "$$" + "$$" + `, + ] + + const { matches, errors } = searchInStrings({ + mode: 'include', + files: [{ content: fileContent, path: '' }], + queryCodes: queries, + }) + + expect(errors).toHaveLength(0) + expect(matches.length).toBe(1) + }) + + it('should not include the same result twice 2', () => { + const fileContent = ` + fn(() => { + return 5 + }) + ` + + const queries = [ + ` + $$(() => {}) + `, + ` + $$(() => $$$) + `, + ] + + const { matches, errors } = searchInStrings({ + mode: 'include', + files: [{ content: fileContent, path: '' }], + queryCodes: queries, + }) + + expect(errors).toHaveLength(0) + expect(matches.length).toBe(1) + }) +}) diff --git a/packages/core/__tests__/JavaScript/other/astUtils.test.ts b/packages/core/__tests__/JavaScript/other/astUtils.test.ts new file mode 100644 index 0000000..b7103ee --- /dev/null +++ b/packages/core/__tests__/JavaScript/other/astUtils.test.ts @@ -0,0 +1,51 @@ +import { createWildcardUtils } from '../../../src/wildcardUtilsFactory' +import { PoorNodeType } from '../../../src/types' +import { sortByLeastIdentifierStrength } from '../../../src/astUtils' + +describe('AST utils', () => { + const getIdentifierNodeName = (node: PoorNodeType) => node.name as string + const getNodeType = (node: PoorNodeType) => node.type as string + + const identifierTypes: string[] = ['Identifier'] + const numericWildcard = '0x0' + const wildcardChar = '$' + const wildcardUtils = createWildcardUtils( + identifierTypes, + numericWildcard, + wildcardChar, + getIdentifierNodeName, + getNodeType, + ) + + const idNode = (name: string) => ({ type: 'Identifier', name }) + + it('should sort identifiers by least strength', () => { + const identifiersList: PoorNodeType[] = [ + idNode('$$'), + idNode('oooo'), + idNode('foo'), + idNode('$$$'), + idNode('sdf$$_ref1_asd'), + { type: 'literal', value: 4 }, + idNode('$$_ref1'), + idNode('$$_ref1_asd'), + { type: 'literal', value: 'asd' }, + ] + + const sortedArr = [...identifiersList].sort((a, b) => + sortByLeastIdentifierStrength(a, b, wildcardUtils, getIdentifierNodeName), + ) + + expect(sortedArr.map(({ name, value }) => name || value)).toMatchObject([ + 'oooo', + 'foo', + 4, + 'asd', + 'sdf$$_ref1_asd', + '$$_ref1_asd', + '$$', + '$$_ref1', + '$$$', + ]) + }) +}) diff --git a/packages/core/__tests__/JavaScript/other/includeWithOrder.test.ts b/packages/core/__tests__/JavaScript/other/includeWithOrder.test.ts new file mode 100644 index 0000000..054cb88 --- /dev/null +++ b/packages/core/__tests__/JavaScript/other/includeWithOrder.test.ts @@ -0,0 +1,84 @@ +import { searchInStrings } from '../../../src/searchInStrings' + +describe('include-with-order wildcards', () => { + it('Should properly match several function arguments using node tree wildcards', () => { + const fileContent = ` + useQuery(API.path, { param : 'value' }, { suspense: true }) + ` + + const queries = [ + ` + useQuery($$$, $$$, { suspense: true }) + `, + ] + + const { matches, errors } = searchInStrings({ + mode: 'include-with-order', + caseInsensitive: true, + queryCodes: queries, + files: [ + { + path: 'mock', + content: fileContent, + }, + ], + }) + + expect(errors).toHaveLength(0) + expect(matches.length).toBe(1) + }) + + it('Should properly match several function parameters ', () => { + const fileContent = ` + function func(arg1, arg2 = "test") {} + ` + + const queries = [ + ` + function func($$, arg2 = "test") {} + `, + ] + + const { matches, errors } = searchInStrings({ + mode: 'include-with-order', + caseInsensitive: true, + queryCodes: queries, + files: [ + { + path: 'mock', + content: fileContent, + }, + ], + }) + + expect(errors).toHaveLength(0) + expect(matches.length).toBe(1) + }) + + it('Should properly match several function parameters 2', () => { + const fileContent = ` + function func(arg1, arg2 = "test") {} + ` + + const queries = [ + ` + function func(arg1, $$$) {} + `, + ] + + const { matches, errors } = searchInStrings({ + mode: 'include-with-order', + caseInsensitive: true, + queryCodes: queries, + files: [ + { + path: 'mock', + content: fileContent, + }, + ], + }) + + expect(errors).toHaveLength(0) + expect(matches.length).toBe(1) + }) +}) diff --git a/packages/core/__tests__/JavaScript/other/parseQuery.test.ts b/packages/core/__tests__/JavaScript/other/parseQuery.test.ts new file mode 100644 index 0000000..bff8303 --- /dev/null +++ b/packages/core/__tests__/JavaScript/other/parseQuery.test.ts @@ -0,0 +1,53 @@ +import { getUniqueTokens, extractQueryNode } from '../../../src/parseQuery' +import { getParserSettings } from '../../utils' + +describe('parse query', () => { + const parserSettings = getParserSettings() + + it('should get unique tokens', () => { + const queryCode = ` + require('some$$wildcard$$string') + const a = 'b' + const alpha = 'beta' + 123; + pre$$_ref1_post + $$_ref1_content$$_ref2 + asd$$_invalidRef_$$ + asd$$_invalidRef$$ + { + { + () => { + 0x0; + return 'test' + } + } + } + f('some$$_ref1_tttt$$_ref2') + ` + const { queryNode } = extractQueryNode( + parserSettings.parseCode(queryCode), + parserSettings, + ) + + const uniqueTokens = [...getUniqueTokens(queryNode, false, parserSettings)] + + expect(uniqueTokens).toMatchObject([ + 'require', + 'some', + 'wildcard', + 'string', + 'alpha', + 'beta', + '123', + 'pre', + 'post', + 'content', + 'asd', + '_invalidRef_', + '_invalidRef', + 'test', + // 'some', // only 'tttt' is new + 'tttt', + ]) + }) +}) diff --git a/packages/core/__tests__/searchInStrings.test.ts b/packages/core/__tests__/JavaScript/other/searchInStrings.test.ts similarity index 92% rename from packages/core/__tests__/searchInStrings.test.ts rename to packages/core/__tests__/JavaScript/other/searchInStrings.test.ts index 2ab6415..9ecd6a2 100644 --- a/packages/core/__tests__/searchInStrings.test.ts +++ b/packages/core/__tests__/JavaScript/other/searchInStrings.test.ts @@ -1,4 +1,4 @@ -import { searchInStrings } from '/searchInStrings' +import { searchInStrings } from '../../../src/searchInStrings' describe('Search in strings', () => { jest.mock('fs', () => { @@ -9,7 +9,7 @@ describe('Search in strings', () => { return { ...jest.requireActual('fs'), - readFileSync: throwOnUsage, + readFileSync: jest.fn(), } }) diff --git a/packages/core/__tests__/JavaScript/other/searchWithContext.test.ts b/packages/core/__tests__/JavaScript/other/searchWithContext.test.ts new file mode 100644 index 0000000..280f0ea --- /dev/null +++ b/packages/core/__tests__/JavaScript/other/searchWithContext.test.ts @@ -0,0 +1,1166 @@ +import { searchInStrings } from '../../../src/searchInStrings' +import { compareAstToCode, compareCode, parserType } from '../../utils' +import { useTraverseApproachTestOnly } from '../../../src/testOnlyConfig' + +describe('Search with context', () => { + it('Should match code with wildcard alias in search query', () => { + const fileContent = ` + const someVar = 5 + + someFunction(someVar); + + ` + + const queries = [ + ` + const $$_ref1 = 5 + + someFunction($$_ref1); + `, + ] + + const { matches, errors } = searchInStrings({ + mode: 'include', + caseInsensitive: true, + queryCodes: queries, + files: [ + { + path: 'mock', + content: fileContent, + }, + ], + }) + + expect(errors).toHaveLength(0) + expect(matches.length).toBe(1) + + expect(matches[0].aliases.identifierAliasesMap['ref1'].aliasValue).toBe( + 'someVar', + ) + }) + + it('Should match code with wildcard alias in the middle of identifier', () => { + const fileContent = ` + const preMatchedPost = 5 + + someFunction(preMatchedPost); + + ` + + const queries = [ + ` + const pre$$_ref1_Post = 5 + + someFunction(pre$$_ref1_Post); + `, + ] + + const { matches, errors } = searchInStrings({ + mode: 'include', + caseInsensitive: true, + queryCodes: queries, + files: [ + { + path: 'mock', + content: fileContent, + }, + ], + }) + + expect(errors).toHaveLength(0) + expect(matches.length).toBe(1) + + expect(matches[0].aliases.identifierAliasesMap['ref1'].aliasValue).toBe( + 'Matched', + ) + }) + + it('Should match code with wildcard alias in the middle of identifier when only the wildcard is used in subsequent statement', () => { + const fileContent = ` + const preMatchedPost = 5 + + someFunction(Matched); + + ` + + const queries = [ + ` + const pre$$_ref1_Post = 5 + + someFunction($$_ref1); + `, + ] + + const { matches, errors } = searchInStrings({ + mode: 'include', + caseInsensitive: true, + queryCodes: queries, + files: [ + { + path: 'mock', + content: fileContent, + }, + ], + }) + + expect(errors).toHaveLength(0) + expect(matches.length).toBe(1) + + expect(matches[0].aliases.identifierAliasesMap['ref1'].aliasValue).toBe( + 'Matched', + ) + }) + + it('Should match code with 2 wildcard aliases in the middle of identifier when wildcard aliases and new chars creates an id in subsequent statement where wildcards are separated by other chars', () => { + const fileContent = ` + const preMatchedPostNext = 5 + + someFunction(NewMatchedValueNext); + + ` + + const queries = [ + ` + const pre$$_ref1_Post$$_ref2 = 5 + + someFunction(New$$_ref1_Value$$_ref2_); + `, + ] + + const { matches, errors } = searchInStrings({ + mode: 'include', + caseInsensitive: true, + queryCodes: queries, + files: [ + { + path: 'mock', + content: fileContent, + }, + ], + }) + + expect(errors).toHaveLength(0) + expect(matches.length).toBe(1) + + expect(matches[0].aliases.identifierAliasesMap['ref1'].aliasValue).toBe( + 'Matched', + ) + + expect(matches[0].aliases.identifierAliasesMap['ref2'].aliasValue).toBe( + 'Next', + ) + }) + + it('Should match code with 1 partial wildcard alias in identifier with subsequent statement that uses that alias in other variable name', () => { + const fileContent = ` + const preMatched = 5 + + someFunction(NewMatched); + + ` + + const queries = [ + ` + const pre$$_ref1 = 5 + + someFunction(New$$_ref1); + `, + ] + + const { matches, errors } = searchInStrings({ + mode: 'include', + caseInsensitive: true, + queryCodes: queries, + files: [ + { + path: 'mock', + content: fileContent, + }, + ], + }) + + expect(errors).toHaveLength(0) + expect(matches.length).toBe(1) + + expect(matches[0].aliases.identifierAliasesMap['ref1'].aliasValue).toBe( + 'Matched', + ) + }) + + it('Should match code with 1 partial wildcard alias in identifier with subsequent statement inside new block that uses that alias in other variable name', () => { + const fileContent = ` + const preMatched = 5; + + { + const a = b; + someFunction(NewMatched); + class A {} + } + + ` + + const queries = [ + ` + const pre$$_ref1 = 5 + + { + someFunction(New$$_ref1); + } + `, + ] + + const { matches, errors } = searchInStrings({ + mode: 'include', + caseInsensitive: true, + queryCodes: queries, + files: [ + { + path: 'mock', + content: fileContent, + }, + ], + }) + + expect(errors).toHaveLength(0) + expect(matches.length).toBe(1) + + expect(matches[0].aliases.identifierAliasesMap['ref1'].aliasValue).toBe( + 'Matched', + ) + }) + + it('Should not match code with 2 wildcard aliases in the middle of identifier when wildcard aliases and new chars creates an id in subsequent statement where wildcards are next to each other', () => { + const fileContent = ` + const preMatchedPostNext = 5 + + someFunction(NewMatchedNextValue); + + ` + + const queries = [ + ` + const pre$$_ref1_Post$$_ref2 = 5 + + someFunction(New$$_ref1_$$_ref2_Value); // 👈 this is invalid wildcard + `, + ] + + const { matches, errors } = searchInStrings({ + mode: 'include', + caseInsensitive: true, + queryCodes: queries, + files: [ + { + path: 'mock', + content: fileContent, + }, + ], + }) + + expect(errors).toHaveLength(0) + expect(matches.length).toBe(0) + }) + + it('Should match code with wildcard alias case insensitive', () => { + const fileContent = ` + const preMatchedPost = 5 + + someFunction(prematchedPost); + + ` + + const queries = [ + ` + const pre$$_ref1_Post = 5 + + someFunction(pre$$_ref1_Post); + `, + ] + + const { matches, errors } = searchInStrings({ + mode: 'include', + caseInsensitive: true, + queryCodes: queries, + files: [ + { + path: 'mock', + content: fileContent, + }, + ], + }) + + expect(errors).toHaveLength(0) + expect(matches.length).toBe(1) + + expect(matches[0].aliases.identifierAliasesMap['ref1'].aliasValue).toBe( + 'Matched', + ) + }) + + it('Should match code with wildcard alias case sensitive', () => { + const fileContent = ` + const preMatchedPost = 5 + + someFunction(preMatchedPost); + + ` + + const queries = [ + ` + const pre$$_ref1_Post = 5 + + someFunction(pre$$_ref1_Post); + `, + ] + + const { matches, errors } = searchInStrings({ + mode: 'include', + caseInsensitive: false, + queryCodes: queries, + files: [ + { + path: 'mock', + content: fileContent, + }, + ], + }) + + expect(errors).toHaveLength(0) + expect(matches.length).toBe(1) + + expect(matches[0].aliases.identifierAliasesMap['ref1'].aliasValue).toBe( + 'Matched', + ) + }) + + it('Should not match code with wildcard alias case sensitive', () => { + const fileContent = ` + const preMatchedPost = 5 + + someFunction(prematchedPost); + + ` + + const queries = [ + ` + const pre$$_ref1_Post = 5 + + someFunction(pre$$_ref1_Post); + `, + ] + + const { matches, errors } = searchInStrings({ + mode: 'include', + caseInsensitive: false, + queryCodes: queries, + files: [ + { + path: 'mock', + content: fileContent, + }, + ], + }) + + expect(errors).toHaveLength(0) + expect(matches.length).toBe(0) + }) + + it('Should alias nodes tree', () => { + const fileContent = ` + someFunction((param) => { + const a = 6 + return a + param + }); + ` + + const queries = [ + ` + someFunction($$$_ref1); + `, + ] + + const { matches, errors } = searchInStrings({ + mode: 'include', + caseInsensitive: true, + queryCodes: queries, + files: [ + { + path: 'mock', + content: fileContent, + }, + ], + }) + + expect(errors).toHaveLength(0) + expect(matches.length).toBe(1) + + expect( + compareAstToCode( + matches[0].aliases.nodesTreeAliasesMap['ref1'].aliasNode, + `(param) => { + const a = 6 + return a + param + }`, + ), + ).toBe(true) + + expect( + compareCode( + matches[0].aliases.nodesTreeAliasesMap['ref1'].aliasValue, + `(param) => { + const a = 6 + return a + param + }`, + ), + ).toBe(true) + }) + + it('Should alias nodes tree with camelCase alias name', () => { + const fileContent = ` + someFunction((param) => { + const a = 6 + return a + param + }); + ` + + const queries = [ + ` + someFunction($$$_someRef); + `, + ] + + const { matches, errors } = searchInStrings({ + mode: 'include', + caseInsensitive: true, + queryCodes: queries, + files: [ + { + path: 'mock', + content: fileContent, + }, + ], + }) + + expect(errors).toHaveLength(0) + expect(matches.length).toBe(1) + + expect( + compareAstToCode( + matches[0].aliases.nodesTreeAliasesMap['someRef'].aliasNode, + `(param) => { + const a = 6 + return a + param + }`, + ), + ).toBe(true) + + expect( + compareCode( + matches[0].aliases.nodesTreeAliasesMap['someRef'].aliasValue, + `(param) => { + const a = 6 + return a + param + }`, + ), + ).toBe(true) + }) + + it('Should match aliased nodes tree in subsequent statements', () => { + const fileContent = ` + someFunction(() => 5); + + someOtherFunction(() => 5); + ` + + const queries = [ + ` + someFunction($$$_ref1); + + someOtherFunction($$$_ref1); + + `, + ] + + const { matches, errors } = searchInStrings({ + mode: 'include', + caseInsensitive: true, + queryCodes: queries, + files: [ + { + path: 'mock', + content: fileContent, + }, + ], + }) + + expect(errors).toHaveLength(0) + expect(matches.length).toBe(1) + + expect( + compareAstToCode( + matches[0].aliases.nodesTreeAliasesMap['ref1'].aliasNode, + '() => 5', + ), + ).toBe(true) + + expect( + compareCode( + matches[0].aliases.nodesTreeAliasesMap['ref1'].aliasValue, + '() => 5', + ), + ).toBe(true) + }) + + it('Should not match aliased nodes tree in subsequent statements if they are different', () => { + const fileContent = ` + someFunction(() => 5); + + someOtherFunction(() => 555); + ` + + const queries = [ + ` + someFunction($$$_ref1); + + someOtherFunction($$$_ref1); + + `, + ] + + const { matches, errors } = searchInStrings({ + mode: 'include', + caseInsensitive: true, + queryCodes: queries, + files: [ + { + path: 'mock', + content: fileContent, + }, + ], + }) + + expect(errors).toHaveLength(0) + expect(matches.length).toBe(0) + }) + + it('Should not match code with wildcard alias in search query if wildcard alias does not match', () => { + const fileContent = ` + const someVar = 5 + + someFunction(someOtherVar); + + ` + + const queries = [ + ` + const $$_ref1 = 5 + + someFunction($$_ref1); + `, + ] + + const { matches, errors } = searchInStrings({ + mode: 'include', + caseInsensitive: true, + queryCodes: queries, + files: [ + { + path: 'mock', + content: fileContent, + }, + ], + }) + + expect(errors).toHaveLength(0) + expect(matches.length).toBe(0) + }) + + it('aliased matches should not interferer in different code paths', () => { + const fileContent = ` + const someVar = 5 + + someFunction(someVar); + + function Sth() { + const someOtherVar = 5 + + someFunction(someOtherVar); + } + + ` + + const queries = [ + ` + const $$_ref1 = 5 + + someFunction($$_ref1); + `, + ] + + const { matches, errors } = searchInStrings({ + mode: 'include', + caseInsensitive: true, + queryCodes: queries, + files: [ + { + path: 'mock', + content: fileContent, + }, + ], + }) + + expect(errors).toHaveLength(0) + expect(matches.length).toBe(2) + + expect(matches[0].aliases.identifierAliasesMap['ref1'].aliasValue).toBe( + 'someVar', + ) + + expect(matches[1].aliases.identifierAliasesMap['ref1'].aliasValue).toBe( + 'someOtherVar', + ) + }) + + it('Should match aliased id wildcard in nested function calls when aliased id is the same', () => { + const fileContent = ` + someFn( + param, + someOtherFn(param), + ) + ` + + const queries = [ + ` + someFn( + $$_ref1, + someOtherFn($$_ref1), + ) + `, + ] + + const { matches, errors } = searchInStrings({ + mode: 'include', + caseInsensitive: true, + queryCodes: queries, + files: [ + { + path: 'mock', + content: fileContent, + }, + ], + }) + + expect(errors).toHaveLength(0) + expect(matches.length).toBe(1) + + expect(matches[0].aliases.identifierAliasesMap['ref1'].aliasValue).toBe( + 'param', + ) + }) + + it('Should not match aliased id wildcard in nested function calls when aliased id is not the same', () => { + const fileContent = ` + someFn( + param, + someOtherFn(param2), + ) + ` + + const queries = [ + ` + someFn( + $$_ref1, + someOtherFn($$_ref1), + ) + `, + ] + + const { matches, errors } = searchInStrings({ + mode: 'include', + caseInsensitive: true, + queryCodes: queries, + files: [ + { + path: 'mock', + content: fileContent, + }, + ], + }) + + expect(errors).toHaveLength(0) + expect(matches.length).toBe(0) + }) + + // This is not always desired, but can be worked around with query builder + // Test to assert that behavior + it('Should match first alias of two possible identifiers for given level', () => { + const fileContent = ` + import { FlexProps, BoxProps} from '@ui/layout' + ` + + const queries = [ + ` + import { $$_prefix_Props } from '@ui/layout' + `, + ] + + const { matches, errors } = searchInStrings({ + mode: 'include', + caseInsensitive: true, + queryCodes: queries, + files: [ + { + path: 'mock', + content: fileContent, + }, + ], + }) + + expect(errors).toHaveLength(0) + expect(matches.length).toBe(1) + + expect(matches[0].aliases.identifierAliasesMap['prefix'].aliasValue).toBe( + 'Flex', + ) + }) + + it('should get pre and post aliased wildcards while pre is empty', () => { + const fileContent = ` + ConsultantsSearchService + ` + + const queries = [`$$_pre_Consultant$$_post`] + + const { matches, errors } = searchInStrings({ + mode: 'include', + caseInsensitive: true, + queryCodes: queries, + files: [ + { + path: 'mock', + content: fileContent, + }, + ], + }) + + expect(errors).toHaveLength(0) + expect(matches.length).toBe(1) + expect(matches[0].aliases.identifierAliasesMap['pre'].aliasValue).toBe('') + + expect(matches[0].aliases.identifierAliasesMap['post'].aliasValue).toBe( + 'sSearchService', + ) + }) + + it('should get pre and post aliased wildcards with case insensitive mode', () => { + const fileContent = ` + CustomConsultantsSearchService + ` + + const queries = [`$$_pre_consultant$$_post`] + + const { matches, errors } = searchInStrings({ + mode: 'include', + caseInsensitive: true, + queryCodes: queries, + files: [ + { + path: 'mock', + content: fileContent, + }, + ], + }) + + expect(errors).toHaveLength(0) + expect(matches.length).toBe(1) + + expect(matches[0].aliases.identifierAliasesMap['pre'].aliasValue).toBe( + 'Custom', + ) + + expect(matches[0].aliases.identifierAliasesMap['post'].aliasValue).toBe( + 'sSearchService', + ) + }) + + it('should match aliased string wildcard', () => { + const fileContent = ` + const someString = 'pre-content-post' + + someFunction('content'); + ` + + const queries = [ + ` + const someString = 'pre-$$_ref1_-post' + + someFunction('$$_ref1_'); + `, + ] + + const { matches, errors } = searchInStrings({ + mode: 'include', + caseInsensitive: true, + queryCodes: queries, + files: [ + { + path: 'mock', + content: fileContent, + }, + ], + }) + + expect(errors).toHaveLength(0) + expect(matches.length).toBe(1) + + expect(matches[0].aliases.stringAliasesMap['ref1'].aliasValue).toBe( + 'content', + ) + }) + + it('should match aliased string wildcard between string literal and template string', () => { + const fileContent = ` + const someString = 'pre-content-post' + + someFunction(\`content\`); + ` + + const queries = [ + ` + const someString = 'pre-$$_ref1_-post' + + someFunction(\`$$_ref1_\`); + `, + ] + + const { matches, errors } = searchInStrings({ + mode: 'include', + caseInsensitive: true, + queryCodes: queries, + files: [ + { + path: 'mock', + content: fileContent, + }, + ], + }) + + expect(errors).toHaveLength(0) + expect(matches.length).toBe(1) + + expect(matches[0].aliases.stringAliasesMap['ref1'].aliasValue).toBe( + 'content', + ) + }) + + it('should match aliased string wildcard between string literal and identifier', () => { + const fileContent = ` + const someString = 'pre-content-post' + + someFunction(content); + ` + + const queries = [ + ` + const someString = 'pre-$$_ref1_-post' + + someFunction($$_ref1); + `, + ] + + const { matches, errors } = searchInStrings({ + mode: 'include', + caseInsensitive: true, + queryCodes: queries, + files: [ + { + path: 'mock', + content: fileContent, + }, + ], + }) + + expect(errors).toHaveLength(0) + expect(matches.length).toBe(1) + + expect(matches[0].aliases.stringAliasesMap['ref1'].aliasValue).toBe( + 'content', + ) + }) + + it('should match aliased identifier wildcard between identifier and string literal', () => { + const fileContent = ` + const preContentPost = 5 + + someFunction('content'); + ` + + const queries = [ + ` + const pre$$_ref1_Post = 5 + + someFunction('$$_ref1'); + `, + ] + + const { matches, errors } = searchInStrings({ + mode: 'include', + caseInsensitive: true, + queryCodes: queries, + files: [ + { + path: 'mock', + content: fileContent, + }, + ], + }) + + expect(errors).toHaveLength(0) + expect(matches.length).toBe(1) + + expect(matches[0].aliases.identifierAliasesMap['ref1'].aliasValue).toBe( + 'Content', + ) + }) + + if (global.testSettings?.isTraversal) { + // In traversal we only project any node wildcard query to identifiers (for now) + it('should match any node tree many times and produce many aliases', () => { + const fileContent = ` + someFunction('content'); + ` + + const queries = [ + ` + $$$_ref + `, + ] + + const { matches, errors } = searchInStrings({ + mode: 'include', + caseInsensitive: true, + queryCodes: queries, + files: [ + { + path: 'mock', + content: fileContent, + }, + ], + }) + + expect(errors).toHaveLength(0) + expect(matches.length).toBe(1) + + expect( + matches[0].aliases.nodesTreeAliasesMap['ref'].aliasValue.trim(), + ).toBe(`someFunction`) + }) + } else if (parserType === 'esprima' || parserType === 'espree') { + // Esprima and espree do not have top level File node, hence it matches one node less + + it('should match any node tree many times and produce many aliases', () => { + const fileContent = ` + someFunction('content'); + ` + + const queries = [ + ` + $$$_ref + `, + ] + + const { matches, errors } = searchInStrings({ + mode: 'include', + caseInsensitive: true, + queryCodes: queries, + files: [ + { + path: 'mock', + content: fileContent, + }, + ], + }) + + expect(errors).toHaveLength(0) + expect(matches.length).toBe(4) + + expect( + matches[0].aliases.nodesTreeAliasesMap['ref'].aliasValue.trim(), + ).toBe(`someFunction('content');`) + + expect( + matches[1].aliases.nodesTreeAliasesMap['ref'].aliasValue.trim(), + ).toBe(`someFunction('content')`) + + expect( + matches[2].aliases.nodesTreeAliasesMap['ref'].aliasValue.trim(), + ).toBe(`someFunction`) + + expect( + matches[3].aliases.nodesTreeAliasesMap['ref'].aliasValue.trim(), + ).toBe(`'content'`) + }) + } else { + it('should match any node tree many times and produce many aliases', () => { + const fileContent = ` + someFunction('content'); + ` + + const queries = [ + ` + $$$_ref + `, + ] + + const { matches, errors } = searchInStrings({ + mode: 'include', + caseInsensitive: true, + queryCodes: queries, + files: [ + { + path: 'mock', + content: fileContent, + }, + ], + }) + + expect(errors).toHaveLength(0) + expect(matches.length).toBe(5) + + expect( + matches[0].aliases.nodesTreeAliasesMap['ref'].aliasValue.trim(), + ).toBe(`someFunction('content');`) + + expect( + matches[1].aliases.nodesTreeAliasesMap['ref'].aliasValue.trim(), + ).toBe(`someFunction('content');`) + + expect( + matches[2].aliases.nodesTreeAliasesMap['ref'].aliasValue.trim(), + ).toBe(`someFunction('content')`) + + expect( + matches[3].aliases.nodesTreeAliasesMap['ref'].aliasValue.trim(), + ).toBe(`someFunction`) + + expect( + matches[4].aliases.nodesTreeAliasesMap['ref'].aliasValue.trim(), + ).toBe(`'content'`) + }) + } + + it('should not bind wildcard alias to first encounter partially matching pattern, if rest of the node is not matched', () => { + const fileContent = ` + (() => { + function func1() { + 1 + } + function func2() { + 2 + } + })() + ` + + const queries = [ + ` + (() => { + function $$_ref1() { + 2 + } + })(); + `, + ] + + const { matches, errors } = searchInStrings({ + mode: 'include', + caseInsensitive: true, + queryCodes: queries, + files: [ + { + path: 'mock', + content: fileContent, + }, + ], + }) + + expect(errors).toHaveLength(0) + expect(matches.length).toBe(1) + + expect(matches[0].aliases.identifierAliasesMap['ref1'].aliasValue).toBe( + 'func2', + ) + }) + + it('should not bind wildcard alias to first encounter partially matching pattern, if rest of the node is not matched 2', () => { + const fileContent = ` + { + function func1() { + 1 + } + function func2() { + 2 + } + } + ` + + const queries = [ + ` + { + function $$_ref1() { + 2 + } + } + `, + ] + + const { matches, errors } = searchInStrings({ + mode: 'include', + caseInsensitive: true, + queryCodes: queries, + files: [ + { + path: 'mock', + content: fileContent, + }, + ], + }) + + expect(errors).toHaveLength(0) + expect(matches.length).toBe(1) + + expect(matches[0].aliases.identifierAliasesMap['ref1'].aliasValue).toBe( + 'func2', + ) + }) + + it('Should match identifier in two subsequent nested code paths', () => { + const fileContent = ` + useQuery({ key: value }, { key: value2}) + ` + + const queries = [ + ` + useQuery({ $$_ref1: value }, { $$_ref1: value2}) + `, + ] + + const { matches, errors } = searchInStrings({ + mode: 'include', + caseInsensitive: true, + queryCodes: queries, + files: [ + { + path: 'mock', + content: fileContent, + }, + ], + }) + + expect(errors).toHaveLength(0) + expect(matches.length).toBe(1) + + expect(matches[0].aliases.identifierAliasesMap['ref1'].aliasValue).toBe( + 'key', + ) + }) +}) diff --git a/packages/core/__tests__/JavaScript/other/wildcardUtils.test.ts b/packages/core/__tests__/JavaScript/other/wildcardUtils.test.ts new file mode 100644 index 0000000..b06d729 --- /dev/null +++ b/packages/core/__tests__/JavaScript/other/wildcardUtils.test.ts @@ -0,0 +1,437 @@ +import { PoorNodeType, WildcardMeta } from '../../../src/types' +import { createWildcardUtils } from '../../../src/wildcardUtilsFactory' +import { regExpTest } from '../../../src/utils' + +describe('AST utils', () => { + const getIdentifierNodeName = (node: PoorNodeType) => node.name as string + const getNodeType = (node: PoorNodeType) => node.type as string + + const identifierTypes: string[] = ['Identifier'] + const numericWildcard = '0x0' + const wildcardChar = '$' + const wildcardUtils = createWildcardUtils( + identifierTypes, + numericWildcard, + wildcardChar, + getIdentifierNodeName, + getNodeType, + ) + + it('should remove identifier ref from wildcard', () => { + expect( + wildcardUtils.removeWildcardAliasesFromIdentifierName('$$$_ref1'), + ).toBe('$$$') + + expect( + wildcardUtils.removeWildcardAliasesFromIdentifierName('$$_ref1'), + ).toBe('$$') + + expect( + wildcardUtils.removeWildcardAliasesFromIdentifierName('$$something'), + ).toBe('$$something') + + expect( + wildcardUtils.removeWildcardAliasesFromIdentifierName('$$_something'), + ).toBe('$$') + + expect( + wildcardUtils.removeWildcardAliasesFromIdentifierName('asd$$_ref'), + ).toBe('asd$$') + + expect( + wildcardUtils.removeWildcardAliasesFromIdentifierName('asd$$_ref_bcd'), + ).toBe('asd$$bcd') + + expect( + wildcardUtils.removeWildcardAliasesFromIdentifierName('$$_ref_bcd'), + ).toBe('$$bcd') + + // should remove aliases for multiple wildcards + expect( + wildcardUtils.removeWildcardAliasesFromIdentifierName( + 'asd$$_ref_bcd$$_efg_ed$$_ac', + ), + ).toBe('asd$$bcd$$ed$$') + + // should not remove if ref is another wildcard + expect( + wildcardUtils.removeWildcardAliasesFromIdentifierName('$$$_$$'), + ).toBe('$$$_$$') + + // should not remove ref from invalid wildcard (one wildcard after another) + expect( + wildcardUtils.removeWildcardAliasesFromIdentifierName( + 'New$$_ref1_$$_ref2_Value', + ), + ).toBe('New$$_ref1_$$_ref2_Value') + }) + + it('should not remove if ref is in the middle of string', () => { + expect( + wildcardUtils.removeWildcardAliasesFromIdentifierName('$$_notRef$$'), + ).toBe('$$_notRef$$') + + expect( + wildcardUtils.removeWildcardAliasesFromIdentifierName('$$$_notRef$$'), + ).toBe('$$$_notRef$$') + + expect( + wildcardUtils.removeWildcardAliasesFromIdentifierName('$$notRef_$$'), + ).toBe('$$notRef_$$') + + expect( + wildcardUtils.removeWildcardAliasesFromIdentifierName('$$$notRef_$$'), + ).toBe('$$$notRef_$$') + + expect( + wildcardUtils.removeWildcardAliasesFromIdentifierName('$$_notRef_$$'), + ).toBe('$$_notRef_$$') + + expect( + wildcardUtils.removeWildcardAliasesFromIdentifierName('$$$_notRef_$$'), + ).toBe('$$$_notRef_$$') + }) + + describe('Classify string and string with wildcard', () => { + const valid: { value: string; wildcard: string }[] = [ + { value: '$$', wildcard: '$$' }, + { value: '$$$', wildcard: '$$$' }, + { value: '$$_ref', wildcard: '$$' }, + { value: '$$_ref_', wildcard: '$$' }, + { value: '$$$_ref', wildcard: '$$$' }, + { value: 'pre-$$-post', wildcard: 'pre-$$-post' }, + { value: 'pre-$$$-post', wildcard: 'pre-$$$-post' }, + { value: 'pre-$$$_alias_-post', wildcard: 'pre-$$$-post' }, + ] + const invalid = ['_ref', 'id', '', '$'] + + it.each(valid)( + 'Should match string wildcard in %s', + ({ value, wildcard }) => { + const valueWithRemovedWildcard = + wildcardUtils.removeWildcardAliasesFromStringLiteral(value) + + expect(regExpTest(wildcardUtils.anyStringWildcardRegExp, value)).toBe( + true, + ) + + expect(valueWithRemovedWildcard).toBe(wildcard) + }, + ) + + it.each(invalid)( + 'Should not recognise string wildcard in %s', + (str: string) => { + expect(regExpTest(wildcardUtils.anyStringWildcardRegExp, str)).toBe( + false, + ) + }, + ) + }) + + describe('Get wildcard info from identifier node name', () => { + it('should get not aliased wildcard info in identifier without additional string parts', () => { + const identifierName = '$$' + const wildcardInfo = + wildcardUtils.getIdentifierWildcardsFromString(identifierName) + + expect(wildcardInfo).toMatchObject([ + { + wildcardType: 'identifier', + wildcardWithAlias: '$$', + wildcardWithoutAlias: '$$', + wildcardAlias: null, + }, + ]) + }) + + it('should get not aliased wildcard info in identifier with additional string parts', () => { + const identifierName = 'pre$$post' + const wildcardInfo = + wildcardUtils.getIdentifierWildcardsFromString(identifierName) + + expect(wildcardInfo).toMatchObject([ + { + wildcardType: 'identifier', + wildcardWithAlias: '$$', + wildcardWithoutAlias: '$$', + wildcardAlias: null, + }, + ]) + }) + + it('should get aliased wildcard info in identifier without additional string parts', () => { + const identifierName = '$$_ref' + const identifierName2 = '$$_ref_' + + const wildcardInfo1 = + wildcardUtils.getIdentifierWildcardsFromString(identifierName) + const wildcardInfo2 = + wildcardUtils.getIdentifierWildcardsFromString(identifierName2) + + expect(wildcardInfo1).toMatchObject([ + { + wildcardType: 'identifier', + wildcardWithAlias: '$$_ref', + wildcardWithoutAlias: '$$', + wildcardAlias: 'ref', + }, + ]) + + expect(wildcardInfo2).toMatchObject([ + { + wildcardType: 'identifier', + wildcardWithAlias: '$$_ref_', + wildcardWithoutAlias: '$$', + wildcardAlias: 'ref', + }, + ]) + }) + + it('should get aliased wildcard info in identifier with additional string parts', () => { + const identifierName = 'pre$$_refpost' + const identifierName2 = 'pre$$_ref_post' + + const wildcardInfo1 = + wildcardUtils.getIdentifierWildcardsFromString(identifierName) + const wildcardInfo2 = + wildcardUtils.getIdentifierWildcardsFromString(identifierName2) + + expect(wildcardInfo1).toMatchObject([ + { + wildcardType: 'identifier', + wildcardWithAlias: '$$_refpost', + wildcardWithoutAlias: '$$', + wildcardAlias: 'refpost', + }, + ]) + + expect(wildcardInfo2).toMatchObject([ + { + wildcardType: 'identifier', + wildcardWithAlias: '$$_ref_', + wildcardWithoutAlias: '$$', + wildcardAlias: 'ref', + }, + ]) + }) + + it('should get multiple aliased wildcards info in identifier with additional string parts', () => { + const identifierName = 'pre$$_ref1_post$$_ref2' + + const wildcardInfo = + wildcardUtils.getIdentifierWildcardsFromString(identifierName) + + expect(wildcardInfo).toMatchObject([ + { + wildcardType: 'identifier', + wildcardWithAlias: '$$_ref1_', + wildcardWithoutAlias: '$$', + wildcardAlias: 'ref1', + }, + { + wildcardType: 'identifier', + wildcardWithAlias: '$$_ref2', + wildcardWithoutAlias: '$$', + wildcardAlias: 'ref2', + }, + ]) + }) + + it('should get identifier wildcard alias with camelCase name', () => { + const identifierName = '$$_someRef' + + const wildcardInfo = + wildcardUtils.getIdentifierWildcardsFromString(identifierName) + + expect(wildcardInfo).toMatchObject([ + { + wildcardType: 'identifier', + wildcardWithAlias: '$$_someRef', + wildcardWithoutAlias: '$$', + wildcardAlias: 'someRef', + }, + ]) + }) + + it('should get nodes tree wildcard alias with camelCase name', () => { + const identifierName = '$$$_someRef' + + const wildcardInfo = + wildcardUtils.getIdentifierWildcardsFromString(identifierName) + + expect(wildcardInfo).toMatchObject([ + { + wildcardType: 'nodeTree', + wildcardWithAlias: '$$$_someRef', + wildcardWithoutAlias: '$$$', + wildcardAlias: 'someRef', + }, + ]) + }) + }) + + describe('Get wildcard info from string literal', () => { + it('should get not aliased optional string wildcard info in string without additional parts', () => { + const stringContent = '$$' + const wildcardInfo = + wildcardUtils.getStringWildcardsFromString(stringContent) + + expect(wildcardInfo).toMatchObject([ + { + wildcardType: 'string', + wildcardWithAlias: '$$', + wildcardWithoutAlias: '$$', + wildcardAlias: null, + }, + ]) + }) + + it('should get not aliased string wildcard info in string with additional parts', () => { + const stringContent = 'pre$$post' + const wildcardInfo = + wildcardUtils.getStringWildcardsFromString(stringContent) + + expect(wildcardInfo).toMatchObject([ + { + wildcardType: 'string', + wildcardWithAlias: '$$', + wildcardWithoutAlias: '$$', + wildcardAlias: null, + }, + ]) + }) + + it('should get aliased optional string wildcard info in string without additional parts', () => { + const stringContent = '$$_ref' + const stringContent2 = '$$_ref_' + + const wildcardInfo1 = + wildcardUtils.getStringWildcardsFromString(stringContent) + const wildcardInfo2 = + wildcardUtils.getStringWildcardsFromString(stringContent2) + + expect(wildcardInfo1).toMatchObject([ + { + wildcardType: 'string', + wildcardWithAlias: '$$_ref', + wildcardWithoutAlias: '$$', + wildcardAlias: 'ref', + }, + ]) + + expect(wildcardInfo2).toMatchObject([ + { + wildcardType: 'string', + wildcardWithAlias: '$$_ref_', + wildcardWithoutAlias: '$$', + wildcardAlias: 'ref', + }, + ]) + }) + + it('should get aliased optional string wildcard info in string with additional parts', () => { + const stringContent = 'pre$$_refpost' + const stringContent2 = 'pre$$_ref_post' + + const wildcardInfo1 = + wildcardUtils.getStringWildcardsFromString(stringContent) + const wildcardInfo2 = + wildcardUtils.getStringWildcardsFromString(stringContent2) + + expect(wildcardInfo1).toMatchObject([ + { + wildcardType: 'string', + wildcardWithAlias: '$$_refpost', + wildcardWithoutAlias: '$$', + wildcardAlias: 'refpost', + }, + ]) + + expect(wildcardInfo2).toMatchObject([ + { + wildcardType: 'string', + wildcardWithAlias: '$$_ref_', + wildcardWithoutAlias: '$$', + wildcardAlias: 'ref', + }, + ]) + }) + + it('should get multiple optional aliased wildcards info in string with additional parts', () => { + const stringContent = 'pre$$_ref1_post$$_ref2' + + const wildcardInfo = + wildcardUtils.getStringWildcardsFromString(stringContent) + + expect(wildcardInfo).toMatchObject([ + { + wildcardType: 'string', + wildcardWithAlias: '$$_ref1_', + wildcardWithoutAlias: '$$', + wildcardAlias: 'ref1', + }, + { + wildcardType: 'string', + wildcardWithAlias: '$$_ref2', + wildcardWithoutAlias: '$$', + wildcardAlias: 'ref2', + }, + ]) + }) + + it('should get optional string wildcard alias with camelCase name', () => { + const stringContent = '$$_someRef' + + const wildcardInfo = + wildcardUtils.getStringWildcardsFromString(stringContent) + + expect(wildcardInfo).toMatchObject([ + { + wildcardType: 'string', + wildcardWithAlias: '$$_someRef', + wildcardWithoutAlias: '$$', + wildcardAlias: 'someRef', + }, + ]) + }) + + it('should get required string wildcard alias with camelCase name', () => { + const stringContent = '$$$_someRef' + + const wildcardInfo = + wildcardUtils.getStringWildcardsFromString(stringContent) + + expect(wildcardInfo).toMatchObject([ + { + wildcardType: 'string', + wildcardWithAlias: '$$$_someRef', + wildcardWithoutAlias: '$$$', + wildcardAlias: 'someRef', + }, + ]) + }) + + it('should get optional and required aliased wildcards info in string with additional parts', () => { + const stringContent = 'pre$$_ref1_post$$$_ref2' + + const wildcardInfo = + wildcardUtils.getStringWildcardsFromString(stringContent) + + expect(wildcardInfo).toMatchObject([ + { + wildcardType: 'string', + wildcardWithAlias: '$$_ref1_', + wildcardWithoutAlias: '$$', + wildcardAlias: 'ref1', + }, + { + wildcardType: 'string', + wildcardWithAlias: '$$$_ref2', + wildcardWithoutAlias: '$$$', + wildcardAlias: 'ref2', + }, + ]) + }) + }) +}) diff --git a/packages/core/__tests__/JavaScriptWithJSX/matchCodePatterns/JSX.test.ts b/packages/core/__tests__/JavaScriptWithJSX/matchCodePatterns/JSX.test.ts new file mode 100644 index 0000000..b9b1091 --- /dev/null +++ b/packages/core/__tests__/JavaScriptWithJSX/matchCodePatterns/JSX.test.ts @@ -0,0 +1,352 @@ +import { compareCode, parserType } from '../../utils' + +import { searchInStrings } from '../../../src/searchInStrings' + +describe('JSX', () => { + const testFileContent = ` + + + + + + + + + + + + ` + + const mockedFilesList = [ + { + path: 'mock', + content: testFileContent, + }, + ] + + it('Should find JSX by text content regardless formatting', () => { + const query = ` + + ` + const { matches, errors } = searchInStrings({ + mode: 'include', + files: mockedFilesList, + queryCodes: [query], + }) + expect(errors).toHaveLength(0) + expect(matches.length).toBe(2) + }) + + it('Should ignore all empty JSXText in search', () => { + const queries = [ + ` + <$$> + $$ + ; + `, + ] + + const { matches, errors } = searchInStrings({ + mode: 'include', + files: mockedFilesList, + queryCodes: queries, + }) + + expect(errors).toHaveLength(0) + expect(matches.length).toBe(3) + + expect( + compareCode( + matches[0].code, + ` + `, + ), + ).toBeTruthy() + }) + + it('Should match code with nested JSX when using wildcard on text content', () => { + const queries = [ + ` + + `, + ] + + const { matches, errors } = searchInStrings({ + mode: 'include', + caseInsensitive: true, + files: mockedFilesList, + queryCodes: queries, + }) + + expect(errors).toHaveLength(0) + expect(matches.length).toBe(1) + + expect( + compareCode( + matches[0].code, + ` + `, + ), + ).toBeTruthy() + }) + + describe('Self and not self closing JSX tags in include mode', () => { + it('Self-closing JSX tag in query should match also not self-closing tags', () => { + const fileContent = ` + asd; + bbc; + + ; + + + ` + + const queries = [ + ` + + `, + ] + + const { matches, errors } = searchInStrings({ + mode: 'include', + caseInsensitive: true, + queryCodes: queries, + files: [ + { + path: 'mock', + content: fileContent, + }, + ], + }) + + expect(errors).toHaveLength(0) + expect(matches.length).toBe(4) + }) + + it('Not self-closing JSX tag in query should match also self-closing tags', () => { + const fileContent = ` + asd; + bbc; + + ; + + + ` + + const queries = [ + ` + + `, + ] + + const { matches, errors } = searchInStrings({ + mode: 'include', + caseInsensitive: true, + queryCodes: queries, + files: [ + { + path: 'mock', + content: fileContent, + }, + ], + }) + + expect(errors).toHaveLength(0) + expect(matches.length).toBe(4) + }) + + it('Not self-closing JSX tag with children in query should not match self-closing tags', () => { + const fileContent = ` + ; + + + ` + + const queries = [ + ` + asd + `, + ] + + const { matches, errors } = searchInStrings({ + mode: 'include', + caseInsensitive: true, + queryCodes: queries, + files: [ + { + path: 'mock', + content: fileContent, + }, + ], + }) + + expect(errors).toHaveLength(0) + expect(matches.length).toBe(0) + }) + + it('Self-closing JSX tag with prop in query should match also not self-closing tag with prop', () => { + const fileContent = ` + asd; + bbc; + + ; + + + ` + + const queries = [ + ` + + `, + ] + + const { matches, errors } = searchInStrings({ + mode: 'include', + caseInsensitive: true, + queryCodes: queries, + files: [ + { + path: 'mock', + content: fileContent, + }, + ], + }) + + expect(errors).toHaveLength(0) + expect(matches.length).toBe(2) + }) + }) + + describe('JSXIdentifiers like Identifiers', () => { + it('Should match JSXIdentifier when looking for Identifier', () => { + const fileContent = ` + asd; + ` + + const queries = [ + ` + Comp; + `, + ] + + const { matches, errors } = searchInStrings({ + mode: 'include', + caseInsensitive: true, + queryCodes: queries, + files: [ + { + path: 'mock', + content: fileContent, + }, + ], + }) + + expect(errors).toHaveLength(0) + expect(matches.length).toBe(2) + }) + + it('Should match JSXIdentifier when looking for Identifier wildcard', () => { + const fileContent = ` + asd; + ` + + const queries = [ + ` + Co$$; + `, + ] + + const { matches, errors } = searchInStrings({ + mode: 'include', + caseInsensitive: true, + queryCodes: queries, + files: [ + { + path: 'mock', + content: fileContent, + }, + ], + }) + + expect(errors).toHaveLength(0) + expect(matches.length).toBe(2) + }) + }) + + it('Should not match too much values using wildcards in JSXText', () => { + const fileContent = ` + Edit User - App + ` + + const queries = [ + ` + $$| App |$$; + `, + ] + + const { matches, errors } = searchInStrings({ + mode: 'include', + caseInsensitive: true, + queryCodes: queries, + files: [ + { + path: 'mock', + content: fileContent, + }, + ], + }) + + expect(errors).toHaveLength(0) + expect(matches.length).toBe(0) + }) + + if (parserType !== 'esprima') { + it('Should match empty fragment tag', () => { + const fileContent = ` + <> + ` + + const queries = [ + ` + <> + `, + ] + + const { matches, errors } = searchInStrings({ + mode: 'include', + caseInsensitive: true, + queryCodes: queries, + files: [ + { + path: 'mock', + content: fileContent, + }, + ], + }) + + expect(errors).toHaveLength(0) + expect(matches.length).toBe(1) + }) + } +}) diff --git a/packages/core/__tests__/JavaScriptWithJSX/matchCodePatterns/JSX2.test.ts b/packages/core/__tests__/JavaScriptWithJSX/matchCodePatterns/JSX2.test.ts new file mode 100644 index 0000000..a2a3c04 --- /dev/null +++ b/packages/core/__tests__/JavaScriptWithJSX/matchCodePatterns/JSX2.test.ts @@ -0,0 +1,340 @@ +import { compareCode } from '../../utils' +import { searchInFileSystem } from '../../../src/searchInFs' + +import { fixturesPath } from '../../utils' +import { getFilesList } from '../../../src/getFilesList' + +import { searchInStrings } from '../../../src/searchInStrings' + +describe('JSX', () => { + it('Should find JSX by tag name and prop', () => { + const fileContent = ` + + + + Dark Theme + + + + + + + ` + const query = ` + + + ` + const { matches, errors } = searchInStrings({ + mode: 'include', + files: [{ content: fileContent, path: '' }], + queryCodes: [query], + }) + + expect(errors).toHaveLength(0) + expect(matches.length).toBe(1) + }) + + it('Should find JSX by prop name', () => { + const fileContent = ` + ( +
+ +
+
+ ) + ` + + const query = `<$$ value={$$$} />` + const { matches, errors } = searchInStrings({ + mode: 'include', + files: [{ content: fileContent, path: '' }], + queryCodes: [query], + }) + expect(errors).toHaveLength(0) + expect(matches.length).toBe(2) + }) + + it('Should find JSX by text content', () => { + const fileContent = ` +
+ RTL + TTL +
+ ` + + const query = `RTL` + const { matches, errors } = searchInStrings({ + mode: 'include', + files: [{ content: fileContent, path: '' }], + queryCodes: [query], + }) + expect(errors).toHaveLength(0) + expect(matches.length).toBe(1) + }) + + it('Should find JSX by text content regardless formatting', () => { + const fileContent = ` +
+ Some + + Text + +
+ ` + + const query = ` Some Text ` + const { matches, errors } = searchInStrings({ + mode: 'include', + files: [{ content: fileContent, path: '' }], + queryCodes: [query], + }) + expect(errors).toHaveLength(0) + expect(matches.length).toBe(1) + }) + + it('Should find JSX by text content with wildcard case insensitive', () => { + const fileContent = ` +
+ RTL + RRL +
+ ` + + const query = `r$$L` + const { matches, errors } = searchInStrings({ + mode: 'include', + files: [{ content: fileContent, path: '' }], + queryCodes: [query], + caseInsensitive: true, + }) + expect(errors).toHaveLength(0) + expect(matches.length).toBe(2) + }) + + it('Should find JSX by text content case insensitive', () => { + const fileContent = ` +
+ RTL + RRL +
+ ` + const query = `rtl` + const { matches, errors } = searchInStrings({ + mode: 'include', + files: [{ content: fileContent, path: '' }], + caseInsensitive: true, + queryCodes: [query], + }) + expect(errors).toHaveLength(0) + expect(matches.length).toBe(1) + }) + + it('Should find exact multiline JSX', () => { + const fileContent = ` + const node =
+ + Outlined + + setIsOutlined((prevIsOutlined) => !prevIsOutlined) + } + /> + +
+ ` + const query = ` + + Outlined + + setIsOutlined((prevIsOutlined) => !prevIsOutlined) + } + /> + + ` + const { matches, errors } = searchInStrings({ + mode: 'exact', + files: [{ content: fileContent, path: '' }], + queryCodes: [query], + }) + + expect(errors).toHaveLength(0) + expect(matches.length).toBe(1) + }) + + it('Should find usages of component passed as a prop', () => { + const fileContent = ` +
+
( + + )} + /> + ( + + )} + /> +
+ ` + + const query1 = ` + <$$$ + $$={() => ( + + )} + /> + ` + + const query2 = ` + <$$$ + $$={IconButton} + /> + ` + const { matches, errors } = searchInStrings({ + mode: 'include', + files: [{ content: fileContent, path: '' }], + queryCodes: [query1, query2], + }) + + expect(errors).toHaveLength(0) + expect(matches.length).toBe(2) + }) + + it('Should find anonymous function passed as a prop', () => { + const fileContent = ` + _setDrawerItem(index)} + /> + ` + + const queries = [ + ` + <$$$ + $$={() => $$$} + /> + `, + ] + + const { matches, errors } = searchInStrings({ + mode: 'include', + files: [{ content: fileContent, path: '' }], + queryCodes: queries, + }) + + expect(errors).toHaveLength(0) + expect(matches.length).toBe(1) + }) + + it('Should find anonymous functions passed as event listener handler', () => { + const fileContent = ` + _setDrawerItem(index)} + /> + ` + const queries = [ + ` + <$$$ + on$$={() => $$$} + /> + `, + ] + + const { matches, errors } = searchInStrings({ + mode: 'include', + files: [{ content: fileContent, path: '' }], + queryCodes: queries, + }) + + expect(errors).toHaveLength(0) + expect(matches.length).toBe(1) + }) + + it('Should find Elements pretending to be a wrapper', () => { + const fileContent = ` +
+ + +
+
+
+ ` + + const queries = [ + ` + <$$Wrapper/> + `, + ` + <$$Wrapper> + + `, + ] + + const { matches, errors } = searchInStrings({ + mode: 'include', + files: [{ content: fileContent, path: '' }], + queryCodes: queries, + }) + + expect(errors).toHaveLength(0) + expect(matches.length).toBe(2) + }) + + it('Should find title prop values which are strings', () => { + const fileContent = ` +
+
+
+
+ ` + + const queries = [ + ` + <$$$ title="$$" /> + `, + ` + <$$$ title="$$"> + + `, + ` + <$$$ title={"$$"} /> + `, + ` + <$$$ title={"$$"}> + + `, + ] + + const { matches, errors } = searchInStrings({ + mode: 'include', + files: [{ content: fileContent, path: '' }], + queryCodes: queries, + }) + + expect(errors).toHaveLength(0) + expect(matches.length).toBe(2) + }) + + it('Should find jsx with title prop using query without title prop', () => { + const fileContent = ` +
+ asd +
+ ` + + const queries = [ + ` + <$$>asd + `, + ] + + const { matches, errors } = searchInStrings({ + mode: 'include', + files: [{ content: fileContent, path: '' }], + queryCodes: queries, + }) + + expect(errors).toHaveLength(0) + expect(matches.length).toBe(1) + }) +}) diff --git a/packages/core/__tests__/JavaScriptWithJSX/matchCodePatterns/other.test.ts b/packages/core/__tests__/JavaScriptWithJSX/matchCodePatterns/other.test.ts new file mode 100644 index 0000000..f19eaac --- /dev/null +++ b/packages/core/__tests__/JavaScriptWithJSX/matchCodePatterns/other.test.ts @@ -0,0 +1,84 @@ +import { searchInStrings } from '../../../src/searchInStrings' + +describe('', () => { + it('should match possible falsy event listeners', () => { + const fileContent = ` +
+
+
+
+
+ ` + + const queries = [ + ` + <$$ + $$={$$$ && $$$} + /> + `, + ` + <$$ + $$={$$$ && $$$} + > + + `, + ] + + const { matches, errors } = searchInStrings({ + mode: 'exact', + files: [{ content: fileContent, path: '' }], + queryCodes: queries, + }) + + expect(errors).toHaveLength(0) + expect(matches.length).toBe(2) + }) + + it('should find all JSX props which always creates new reference', () => { + const fileContent = ` +
+
{}} + /> +
+
+
+
+ ` + + const queries = [ + ` + <$$ + $$={()=>{}} + /> + `, + ` + <$$ + $$={[]} + /> + `, + ` + <$$ + $$={{}} + /> + `, + ] + + const { matches, errors } = searchInStrings({ + mode: 'include', + files: [{ content: fileContent, path: '' }], + queryCodes: queries, + }) + + expect(errors).toHaveLength(0) + expect(matches.length).toBe(3) + }) +}) diff --git a/packages/core/__tests__/JavaScriptWithJSX/other/astUtils.test.ts b/packages/core/__tests__/JavaScriptWithJSX/other/astUtils.test.ts new file mode 100644 index 0000000..76ecc39 --- /dev/null +++ b/packages/core/__tests__/JavaScriptWithJSX/other/astUtils.test.ts @@ -0,0 +1,150 @@ +import { compareCode } from '../../utils' +import { createWildcardUtils } from '../../../src/wildcardUtilsFactory' +import { PoorNodeType } from '../../../src/types' +import { sortByLeastIdentifierStrength } from '../../../src/astUtils' + +describe('AST utils', () => { + it('should compare code as equal', () => { + const code1 = ` + import * as React from 'react'; + import { Paragraph, Button, Portal, Dialog, Colors } from 'react-native-paper'; + + const DialogWithCustomColors = ({ + visible, + close, + }) => ( + + + Alert + + + This is a dialog with custom colors + + + + + + + + ); + + export default DialogWithCustomColors; + ` + + const code2 = ` + import * as React from 'react'; + import { + Paragraph, + Button, + Portal, + Dialog, + Colors + } from 'react-native-paper'; + + const DialogWithCustomColors = ({ + visible, + close, + }) => ( + + + Alert + + This is a dialog with custom colors + + + + + + + ); + + export default DialogWithCustomColors; + ` + expect(compareCode(code1, code2)).toBeTruthy() + }) + + it('should compare code as unequal', () => { + const code1 = ` + import * as React from 'react'; + import { Paragraph, Button, Portal, Dialog, Colors } from 'react-native-paper'; + + const DialogWithCustomColors = ({ + visible, + close, + }) => ( + + + Alert + + + This is a dialog with custom colors + + + + + + + + ); + + export default DialogWithCustomColors; + ` + + const code2 = + // no Colors import + + ` + import * as React from 'react'; + import { + Paragraph, + Button, + Portal, + Dialog + } from 'react-native-paper'; + + const DialogWithCustomColors = ({ + visible, + close, + }) => ( + + + Alert + + + This is a dialog with custom colors + + + + + + + + ); + + export default DialogWithCustomColors; + ` + expect(compareCode(code1, code2)).toBe(false) + }) +}) diff --git a/packages/core/__tests__/JavaScriptWithJSX/other/parseQuery.test.ts b/packages/core/__tests__/JavaScriptWithJSX/other/parseQuery.test.ts new file mode 100644 index 0000000..4b2fb94 --- /dev/null +++ b/packages/core/__tests__/JavaScriptWithJSX/other/parseQuery.test.ts @@ -0,0 +1,55 @@ +import { getUniqueTokens, extractQueryNode } from '../../../src/parseQuery' +import { getParserSettings } from '../../utils' + +describe('parse query', () => { + const parserSettings = getParserSettings() + + it('should get unique tokens', () => { + const queryCode = ` + require('some$$wildcard$$string') + const a = 'b' + const alpha = 'beta' + () + 123; + pre$$_ref1_post + $$_ref1_content$$_ref2 + asd$$_invalidRef_$$ + asd$$_invalidRef$$ + { + { + () => { + 0x0; + return 'test' + } + } + } + f('some$$_ref1_tttt$$_ref2') + ` + const { queryNode } = extractQueryNode( + parserSettings.parseCode(queryCode), + parserSettings, + ) + + const uniqueTokens = [...getUniqueTokens(queryNode, false, parserSettings)] + + expect(uniqueTokens).toMatchObject([ + 'require', + 'some', + 'wildcard', + 'string', + 'alpha', + 'beta', + 'SomeJSX', + '123', + 'pre', + 'post', + 'content', + 'asd', + '_invalidRef_', + '_invalidRef', + 'test', + // 'some', // only 'tttt' is new + 'tttt', + ]) + }) +}) diff --git a/packages/core/__tests__/Lua/matchCodePatterns/basic.test.ts b/packages/core/__tests__/Lua/matchCodePatterns/basic.test.ts new file mode 100644 index 0000000..962f442 --- /dev/null +++ b/packages/core/__tests__/Lua/matchCodePatterns/basic.test.ts @@ -0,0 +1,203 @@ +import { searchInStrings } from '../../../src/searchInStrings' +import { getParserSettings, compareCode } from '../../utils' + +describe('Basic queries', () => { + beforeAll(async () => { + await getParserSettings().init?.() + }) + + it('Should exact match identifier', async () => { + const fileContent = ` + print + ` + const queries = [fileContent] + + const { matches, errors } = searchInStrings({ + mode: 'exact', + caseInsensitive: true, + queryCodes: queries, + files: [ + { + path: 'mock', + content: fileContent, + }, + ], + }) + + expect(errors).toHaveLength(0) + expect(matches.length).toBe(1) + }) + + it('Should exact match function definition', () => { + const fileContent = ` + function fact (n) + if n == 0 then + return 1 + else + return n * fact(n-1) + end + end + ` + const queries = [fileContent] + + const { matches, errors } = searchInStrings({ + mode: 'exact', + caseInsensitive: true, + queryCodes: queries, + files: [ + { + path: 'mock', + content: fileContent, + }, + ], + }) + + expect(errors).toHaveLength(0) + expect(matches.length).toBe(1) + }) + + it('Should partial match function definition', () => { + const fileContent = ` + function fact (n) + if n == 0 then + return 1 + else + return n * fact(n-1) + end + end + ` + const queries = [ + ` + function fact (n) + if n == 0 then + return 1 + end + end + `, + ] + + const { matches, errors } = searchInStrings({ + mode: 'include', + caseInsensitive: true, + queryCodes: queries, + files: [ + { + path: 'mock', + content: fileContent, + }, + ], + }) + + expect(errors).toHaveLength(0) + expect(matches.length).toBe(1) + }) + + it('Should match multiline', () => { + const fileContent = ` + function setupWS(mode, transmit, setChannel) + local deviceType = mode == "TX" and "transmitter" or "receiver" + local url = "ws://" .. loraServerAddress .. "/" .. deviceType + local headers = {["Authorization"] = "Basic ZnJvZzpoYWt1bmFtYXRhdGE0MzM="} + local ws = wsModule(url, headers) + local transmitEvent = "SEND_" .. (mode == "TX" and "TRANSMITTER" or "RECEIVER") + local transmissionParamsEvent = "TRANSMISSION_PARAMS_" .. (mode == "TX" and "TRANSMITTER" or "RECEIVER") + + ws.on( + transmitEvent, + function(data) + transmit(data.loraPktData) + ws.send(transmitEvent .. "_ACK", true) + end + ) + + ws.on( + transmissionParamsEvent, + function(data) + data.spreadingFactor = "SF" .. data.spreadingFactor + local saveResult = config.saveConfig(data) + setChannel(config.computeFrequency(data.frequency), data.spreadingFactor, data.errorCorrection) + ws.send(transmissionParamsEvent .. "_ACK", saveResult) + end + ) + + return function(data) + local event = mode == "TX" and "TRANSMITTER_DATA" or "RECEIVER_DATA" + ws.send(event, data) + end + end + ` + + const queries = [ + ` + local transmitEvent = "SEND_" .. (mode == "TX" and "TRANSMITTER" or "RECEIVER") + local transmissionParamsEvent = "TRANSMISSION_PARAMS_" .. (mode == "TX" and "TRANSMITTER" or "RECEIVER") + + ws.on( + transmissionParamsEvent, + function(data) + data.spreadingFactor = "SF" .. data.spreadingFactor + local saveResult = config.saveConfig(data) + setChannel(config.computeFrequency(data.frequency), data.spreadingFactor, data.errorCorrection) + ws.send(transmissionParamsEvent .. "_ACK", saveResult) + end + ) + `, + ] + + const { matches, errors } = searchInStrings({ + mode: 'include', + caseInsensitive: true, + queryCodes: queries, + files: [ + { + path: 'mock', + content: fileContent, + }, + ], + }) + + expect(errors).toHaveLength(0) + expect(matches.length).toBe(1) + + const match = matches[0] + + expect(match.loc).toMatchObject({ + start: { line: 7, column: 6 }, + end: { line: 26, column: 7 }, + }) + }) + + it('Should match tuple', () => { + const fileContent = ` + local var = 1,"text" + ` + + const queries = [ + ` + 1,"text" + `, + ] + + const { matches, errors } = searchInStrings({ + mode: 'include', + caseInsensitive: true, + queryCodes: queries, + files: [ + { + path: 'mock', + content: fileContent, + }, + ], + }) + + expect(errors).toHaveLength(0) + expect(matches.length).toBe(1) + + const match = matches[0] + + expect(match.loc).toStrictEqual({ + start: { line: 2, column: 18, index: 19 }, + end: { line: 2, column: 26, index: 27 }, + }) + }) +}) diff --git a/packages/core/__tests__/Lua/matchCodePatterns/binaryExpression.test.ts b/packages/core/__tests__/Lua/matchCodePatterns/binaryExpression.test.ts new file mode 100644 index 0000000..b3a91f4 --- /dev/null +++ b/packages/core/__tests__/Lua/matchCodePatterns/binaryExpression.test.ts @@ -0,0 +1,37 @@ +import { searchInStrings } from '../../../src/searchInStrings' +import { getParserSettings, compareCode } from '../../utils' + +describe('Binary Expression', () => { + beforeAll(async () => { + await getParserSettings().init?.() + }) + + it('Should binary expression based on operator (which is not defined by tree-sitter)', async () => { + const fileContent = ` + local var = a ^ b + local var = a * b + local var = a and b + ` + const queries = [ + ` + local var = a*b + `, + ] + + const { matches, errors } = searchInStrings({ + mode: 'exact', + caseInsensitive: true, + queryCodes: queries, + files: [ + { + path: 'mock', + content: fileContent, + }, + ], + }) + + expect(errors).toHaveLength(0) + expect(matches.length).toBe(1) + expect(matches[0].code).toBe('local var = a * b') + }) +}) diff --git a/packages/core/__tests__/Lua/matchCodePatterns/functions.test.ts b/packages/core/__tests__/Lua/matchCodePatterns/functions.test.ts new file mode 100644 index 0000000..53ed420 --- /dev/null +++ b/packages/core/__tests__/Lua/matchCodePatterns/functions.test.ts @@ -0,0 +1,39 @@ +import { searchInStrings } from '../../../src/searchInStrings' +import { getParserSettings, compareCode } from '../../utils' + +describe('Functions', () => { + beforeAll(async () => { + await getParserSettings().init?.() + }) + + it('Should match anonymous function passed as parameter by query with function declaration without name', async () => { + const fileContent = ` + passCallback(function (a) + return a + end + ) + ` + const queries = [ + ` + function (a) + return a + end + `, + ] + + const { matches, errors } = searchInStrings({ + mode: 'exact', + caseInsensitive: true, + queryCodes: queries, + files: [ + { + path: 'mock', + content: fileContent, + }, + ], + }) + + expect(errors).toHaveLength(0) + expect(matches.length).toBe(1) + }) +}) diff --git a/packages/core/__tests__/Lua/matchCodePatterns/ifstatement.test.ts b/packages/core/__tests__/Lua/matchCodePatterns/ifstatement.test.ts new file mode 100644 index 0000000..13e72ef --- /dev/null +++ b/packages/core/__tests__/Lua/matchCodePatterns/ifstatement.test.ts @@ -0,0 +1,34 @@ +import { searchInStrings } from '../../../src/searchInStrings' +import { getParserSettings, compareCode } from '../../utils' + +describe('If statement', () => { + beforeAll(async () => { + await getParserSettings().init?.() + }) + + it('Should match any if statement ', async () => { + const fileContent = ` + if (a) then b() else c() end + ` + const queries = [ + ` + if () then else end + `, + ] + + const { matches, errors } = searchInStrings({ + mode: 'include', + caseInsensitive: true, + queryCodes: queries, + files: [ + { + path: 'mock', + content: fileContent, + }, + ], + }) + + expect(errors).toHaveLength(0) + expect(matches.length).toBe(1) + }) +}) diff --git a/packages/core/__tests__/Lua/matchCodePatterns/literals.test.ts b/packages/core/__tests__/Lua/matchCodePatterns/literals.test.ts new file mode 100644 index 0000000..8088de4 --- /dev/null +++ b/packages/core/__tests__/Lua/matchCodePatterns/literals.test.ts @@ -0,0 +1,149 @@ +import { searchInStrings } from '../../../src/searchInStrings' +import { getParserSettings } from '../../utils' + +describe('Literals', () => { + beforeAll(async () => { + await getParserSettings().init?.() + }) + + it('Should match string literal', () => { + const fileContent = ` + ws.on( + transmitEvent, + function(data) + transmit(data.loraPktData) + ws.send(transmitEvent .. "_ACK", true) + end + ) + ` + + const queries = [ + ` + '_ACK' + `, + ] + + const { matches, errors } = searchInStrings({ + mode: 'include', + caseInsensitive: true, + queryCodes: queries, + files: [ + { + path: 'mock', + content: fileContent, + }, + ], + }) + + expect(errors).toHaveLength(0) + expect(matches.length).toBe(1) + }) + + it('Should exact match template string literal', () => { + const fileContent = ` + f"project:{self.project_id}:rules" + ` + + const queries = [ + ` + f"project:{self.project_id}:rules" + `, + ] + + const { matches, errors } = searchInStrings({ + mode: 'include', + caseInsensitive: true, + queryCodes: queries, + files: [ + { + path: 'mock', + content: fileContent, + }, + ], + }) + + expect(errors).toHaveLength(0) + expect(matches.length).toBe(1) + }) + + it('Should match Integer literal', () => { + const fileContent = ` + val = 123 + ` + + const queries = [ + ` + 123 + `, + ] + + const { matches, errors } = searchInStrings({ + mode: 'include', + caseInsensitive: true, + queryCodes: queries, + files: [ + { + path: 'mock', + content: fileContent, + }, + ], + }) + + expect(errors).toHaveLength(0) + expect(matches.length).toBe(1) + }) + + it('Should match Float literal', () => { + const fileContent = ` + val = 123.123 + ` + + const queries = [ + ` + 123.123 + `, + ] + + const { matches, errors } = searchInStrings({ + mode: 'include', + caseInsensitive: true, + queryCodes: queries, + files: [ + { + path: 'mock', + content: fileContent, + }, + ], + }) + + expect(errors).toHaveLength(0) + expect(matches.length).toBe(1) + }) + + it('Should match Hex literal', () => { + const fileContent = ` + val = 0xFF + ` + + const queries = [ + ` + 0xFF + `, + ] + + const { matches, errors } = searchInStrings({ + mode: 'include', + caseInsensitive: true, + queryCodes: queries, + files: [ + { + path: 'mock', + content: fileContent, + }, + ], + }) + + expect(errors).toHaveLength(0) + expect(matches.length).toBe(1) + }) +}) diff --git a/packages/core/__tests__/Lua/matchCodePatterns/table.test.ts b/packages/core/__tests__/Lua/matchCodePatterns/table.test.ts new file mode 100644 index 0000000..9326f9b --- /dev/null +++ b/packages/core/__tests__/Lua/matchCodePatterns/table.test.ts @@ -0,0 +1,112 @@ +import { searchInStrings } from '../../../src/searchInStrings' +import { getParserSettings, compareCode } from '../../utils' + +describe('Tables', () => { + beforeAll(async () => { + await getParserSettings().init?.() + }) + + it('Should match table with keys exact', async () => { + const fileContent = ` + local header = { + operationType = sdLib.operationTypeNumbers.repeater_telemetry, + transactionNumber = "0" + } + ` + + const queries = [ + ` + { + operationType = sdLib.operationTypeNumbers.repeater_telemetry, + transactionNumber = "0" + } + `, + ] + + const { matches, errors } = searchInStrings({ + mode: 'exact', + caseInsensitive: true, + queryCodes: queries, + files: [ + { + path: 'mock', + content: fileContent, + }, + ], + }) + + expect(errors).toHaveLength(0) + expect(matches.length).toBe(1) + + expect(matches[0].loc).toMatchObject({ + start: { + line: 2, + column: 21, + }, + end: { line: 5, column: 7 }, + }) + }) + + it('Should match table with keys partial', async () => { + const fileContent = ` + local header = { + operationType = sdLib.operationTypeNumbers.repeater_telemetry, + transactionNumber = "0" + } + ` + + const queries = [ + ` + { + transactionNumber = "0" + } + `, + ] + + const { matches, errors } = searchInStrings({ + mode: 'include', + caseInsensitive: true, + queryCodes: queries, + files: [ + { + path: 'mock', + content: fileContent, + }, + ], + }) + + expect(errors).toHaveLength(0) + expect(matches.length).toBe(1) + }) + + it('Should match table with values exact', async () => { + const fileContent = ` + local header = { + "text", 123, {nested = 'table'} + } + ` + + const queries = [ + ` + { + "text", 123, {nested = "table"} + } + `, + ] + + const { matches, errors } = searchInStrings({ + mode: 'exact', + caseInsensitive: true, + queryCodes: queries, + files: [ + { + path: 'mock', + content: fileContent, + }, + ], + }) + + expect(errors).toHaveLength(0) + expect(matches.length).toBe(1) + }) +}) diff --git a/packages/core/__tests__/Lua/matchCodePatterns/wildcards.test.ts b/packages/core/__tests__/Lua/matchCodePatterns/wildcards.test.ts new file mode 100644 index 0000000..715b32d --- /dev/null +++ b/packages/core/__tests__/Lua/matchCodePatterns/wildcards.test.ts @@ -0,0 +1,154 @@ +import { searchInStrings } from '../../../src/searchInStrings' +import { getParserSettings } from '../../utils' + +describe('Wildcards', () => { + beforeAll(async () => { + await getParserSettings().init?.() + }) + + it('Should match string wildcard', () => { + const fileContent = ` + ws.on( + transmitEvent, + function(data) + transmit(data.loraPktData) + ws.send(transmitEvent .. "_ACK", true) + end + ) + ` + + const queries = [ + ` + "_$$K" + `, + ] + + const { matches, errors } = searchInStrings({ + mode: 'include', + caseInsensitive: true, + queryCodes: queries, + files: [ + { + path: 'mock', + content: fileContent, + }, + ], + }) + + expect(errors).toHaveLength(0) + expect(matches.length).toBe(1) + }) + + it('Should match node wildcard', () => { + const fileContent = ` + scopes = function(data) + data.spreadingFactor = "SF" .. data.spreadingFactor + local saveResult = config.saveConfig(data) + setChannel(config.computeFrequency(data.frequency), data.spreadingFactor, data.errorCorrection) + ws.send(transmissionParamsEvent .. "_ACK", saveResult) + end + ` + + const queries = [ + ` + scopes = $$$ + `, + ] + + const { matches, errors } = searchInStrings({ + mode: 'include', + caseInsensitive: true, + queryCodes: queries, + files: [ + { + path: 'mock', + content: fileContent, + }, + ], + }) + + expect(errors).toHaveLength(0) + expect(matches.length).toBe(1) + }) + + it('Should match identifier wildcard', () => { + const fileContent = ` + scopes = ws.send(transmissionParamsEvent .. "_ACK", saveResult) + ` + + const queries = [ + ` + scopes = ws.$$() + `, + ] + + const { matches, errors } = searchInStrings({ + mode: 'include', + caseInsensitive: true, + queryCodes: queries, + files: [ + { + path: 'mock', + content: fileContent, + }, + ], + }) + + expect(errors).toHaveLength(0) + expect(matches.length).toBe(1) + }) + + it('Should match integer wildcard', () => { + const fileContent = ` + val = 123 + ` + + const queries = [ + ` + 0x0 + `, + ] + + const { matches, errors } = searchInStrings({ + mode: 'include', + caseInsensitive: true, + queryCodes: queries, + files: [ + { + path: 'mock', + content: fileContent, + }, + ], + }) + + expect(errors).toHaveLength(0) + expect(matches.length).toBe(1) + }) + + it('Should match float wildcard', () => { + const fileContent = ` + val = 123.123 + ` + + const queries = [ + ` + 0x0 + `, + ] + + const { matches, errors } = searchInStrings({ + mode: 'include', + caseInsensitive: true, + queryCodes: queries, + files: [ + { + path: 'mock', + content: fileContent, + }, + ], + }) + + expect(errors).toHaveLength(0) + expect(matches.length).toBe(1) + }) +}) diff --git a/packages/core/__tests__/Lua/other/searchWithContext.test.ts b/packages/core/__tests__/Lua/other/searchWithContext.test.ts new file mode 100644 index 0000000..698cffd --- /dev/null +++ b/packages/core/__tests__/Lua/other/searchWithContext.test.ts @@ -0,0 +1,64 @@ +import { searchInStrings } from '../../../src/searchInStrings' +import { compareCode, getParserSettings } from '../../utils' + +describe('Search with context', () => { + beforeAll(async () => { + await getParserSettings().init?.() + }) + + it('Should partial match function definition using wildcard and alias', () => { + const fileContent = ` + function fact (n) + if n == 0 then + print() + return 1 + else + print() + return n * fact(n-1) + end + end + + ` + const queries = [ + ` + function fact (n) + if n == 0 then + $$_refPrint() + return 1 + else + $$_refPrint() + return n * fact(n-1) + end + end + `, + ] + + const { matches, errors } = searchInStrings({ + mode: 'include', + caseInsensitive: true, + queryCodes: queries, + files: [ + { + path: 'mock', + content: fileContent, + }, + ], + }) + + expect(errors).toHaveLength(0) + expect(matches.length).toBe(1) + + const match = matches[0] + + expect(match.aliases.identifierAliasesMap['refPrint'].aliasValue).toBe( + 'print', + ) + + expect(compareCode(fileContent, match.code)).toBe(true) + + expect(match.loc).toStrictEqual({ + start: { line: 2, column: 6, index: 7 }, + end: { line: 10, column: 9, index: 168 }, + }) + }) +}) diff --git a/packages/core/__tests__/Python/matchCodePatterns/basic.test.ts b/packages/core/__tests__/Python/matchCodePatterns/basic.test.ts new file mode 100644 index 0000000..2d8dce2 --- /dev/null +++ b/packages/core/__tests__/Python/matchCodePatterns/basic.test.ts @@ -0,0 +1,203 @@ +import { searchInStrings } from '../../../src/searchInStrings' +import { getParserSettings, compareCode } from '../../utils' + +describe('Basic queries', () => { + beforeAll(async () => { + await getParserSettings().init?.() + }) + + it('Should exact match identifier', async () => { + const fileContent = ` + print; + ` + const queries = [fileContent] + + const { matches, errors } = searchInStrings({ + mode: 'exact', + caseInsensitive: true, + queryCodes: queries, + files: [ + { + path: 'mock', + content: fileContent, + }, + ], + }) + + expect(errors).toHaveLength(0) + expect(matches.length).toBe(1) + }) + + it('Should exact match function definition', () => { + const fileContent = ` + def fib(n): + a, b = 0, 1 + while a < n: + print(a, end=' ') + a, b = b, a+b + print() + ` + const queries = [fileContent] + + const { matches, errors } = searchInStrings({ + mode: 'exact', + caseInsensitive: true, + queryCodes: queries, + files: [ + { + path: 'mock', + content: fileContent, + }, + ], + }) + + expect(errors).toHaveLength(0) + expect(matches.length).toBe(1) + }) + + it('Should partial match function definition', () => { + const fileContent = ` + def fib(n): + a, b = 0, 1 + while a < n: + print(a, end=' ') + a, b = b, a+b + print() + ` + const queries = [ + ` + def fib(n): + print() + `, + ] + + const { matches, errors } = searchInStrings({ + mode: 'include', + caseInsensitive: true, + queryCodes: queries, + files: [ + { + path: 'mock', + content: fileContent, + }, + ], + }) + + expect(errors).toHaveLength(0) + expect(matches.length).toBe(1) + }) + + it('Should match function with return type by query without return type in include mode', () => { + const fileContent = ` + def accepts() -> str: + a + ` + const queries = [ + ` + def accepts(): + a + `, + ] + + const { matches, errors } = searchInStrings({ + mode: 'include', + caseInsensitive: true, + queryCodes: queries, + files: [ + { + path: 'mock', + content: fileContent, + }, + ], + }) + + expect(errors).toHaveLength(0) + expect(matches.length).toBe(1) + + const match = matches[0] + + expect(match.loc).toStrictEqual({ + start: { line: 2, column: 6, index: 7 }, + end: { line: 3, column: 9, index: 38 }, + }) + }) + + it('Should match multiline', () => { + const fileContent = ` + def from_dsn(cls, dsn): + urlparts = urlparse(dsn) + + public_key = urlparts.username + project_id = urlparts.path.rsplit("/", 1)[-1] + + try: + return ProjectKey.objects.get(public_key=public_key, project=project_id) + except ValueError: + raise ProjectKey.DoesNotExist("ProjectKey matching query does not exist.") + ` + + const queries = [ + ` + public_key = urlparts.username + project_id = urlparts.path.rsplit("/", 1)[-1] + `, + ] + + const { matches, errors } = searchInStrings({ + mode: 'include', + caseInsensitive: true, + queryCodes: queries, + files: [ + { + path: 'mock', + content: fileContent, + }, + ], + }) + + expect(errors).toHaveLength(0) + expect(matches.length).toBe(1) + + const match = matches[0] + + expect(match.loc).toStrictEqual({ + start: { line: 5, column: 8, index: 73 }, + end: { line: 6, column: 53, index: 157 }, + }) + }) + + it('Should match function named params with different formatting', () => { + const fileContent = ` + ProjectKey.objects.get(public_key = public_key, project= project_id) + ` + + const queries = [ + ` + ProjectKey.objects.get(public_key=public_key, project = project_id) + + `, + ] + + const { matches, errors } = searchInStrings({ + mode: 'include', + caseInsensitive: true, + queryCodes: queries, + files: [ + { + path: 'mock', + content: fileContent, + }, + ], + }) + + expect(errors).toHaveLength(0) + expect(matches.length).toBe(1) + + const match = matches[0] + + expect(match.loc).toStrictEqual({ + start: { line: 2, column: 6, index: 7 }, + end: { line: 2, column: 76, index: 77 }, + }) + }) +}) diff --git a/packages/core/__tests__/Python/matchCodePatterns/literals.test.ts b/packages/core/__tests__/Python/matchCodePatterns/literals.test.ts new file mode 100644 index 0000000..bc59a4e --- /dev/null +++ b/packages/core/__tests__/Python/matchCodePatterns/literals.test.ts @@ -0,0 +1,174 @@ +import { searchInStrings } from '../../../src/searchInStrings' +import { getParserSettings } from '../../utils' + +describe('Literals', () => { + beforeAll(async () => { + await getParserSettings().init?.() + }) + + it('Should match string literal', () => { + const fileContent = ` + scopes = BitField( + flags=( + ("project:read", "project:read") + ) + ) + ` + + const queries = [ + ` + "project:read" + `, + ] + + const { matches, errors } = searchInStrings({ + mode: 'include', + caseInsensitive: true, + queryCodes: queries, + files: [ + { + path: 'mock', + content: fileContent, + }, + ], + }) + + expect(errors).toHaveLength(0) + expect(matches.length).toBe(2) + }) + + it('Should exact match template string literal', () => { + const fileContent = ` + f"project:{self.project_id}:rules" + ` + + const queries = [ + ` + f"project:{self.project_id}:rules" + `, + ] + + const { matches, errors } = searchInStrings({ + mode: 'include', + caseInsensitive: true, + queryCodes: queries, + files: [ + { + path: 'mock', + content: fileContent, + }, + ], + }) + + expect(errors).toHaveLength(0) + expect(matches.length).toBe(1) + }) + + it('Should partial match template string literal', () => { + const fileContent = ` + f"project:{self.project_id}:rules{value}" + ` + + const queries = [ + ` + f"project:{value}:rules" + `, + ] + + const { matches, errors } = searchInStrings({ + mode: 'include', + caseInsensitive: true, + queryCodes: queries, + files: [ + { + path: 'mock', + content: fileContent, + }, + ], + }) + + expect(errors).toHaveLength(0) + expect(matches.length).toBe(1) + }) + + it('Should match Integer literal', () => { + const fileContent = ` + val = 123 + ` + + const queries = [ + ` + 123 + `, + ] + + const { matches, errors } = searchInStrings({ + mode: 'include', + caseInsensitive: true, + queryCodes: queries, + files: [ + { + path: 'mock', + content: fileContent, + }, + ], + }) + + expect(errors).toHaveLength(0) + expect(matches.length).toBe(1) + }) + + it('Should match Float literal', () => { + const fileContent = ` + val = 123.123 + ` + + const queries = [ + ` + 123.123 + `, + ] + + const { matches, errors } = searchInStrings({ + mode: 'include', + caseInsensitive: true, + queryCodes: queries, + files: [ + { + path: 'mock', + content: fileContent, + }, + ], + }) + + expect(errors).toHaveLength(0) + expect(matches.length).toBe(1) + }) + + it('Should match Hex literal', () => { + const fileContent = ` + val = 0xFF + ` + + const queries = [ + ` + 0xFF + `, + ] + + const { matches, errors } = searchInStrings({ + mode: 'include', + caseInsensitive: true, + queryCodes: queries, + files: [ + { + path: 'mock', + content: fileContent, + }, + ], + }) + + expect(errors).toHaveLength(0) + expect(matches.length).toBe(1) + }) +}) diff --git a/packages/core/__tests__/Python/matchCodePatterns/wildcards.test.ts b/packages/core/__tests__/Python/matchCodePatterns/wildcards.test.ts new file mode 100644 index 0000000..8387101 --- /dev/null +++ b/packages/core/__tests__/Python/matchCodePatterns/wildcards.test.ts @@ -0,0 +1,155 @@ +import { searchInStrings } from '../../../src/searchInStrings' +import { getParserSettings } from '../../utils' + +describe('Wildcards', () => { + beforeAll(async () => { + await getParserSettings().init?.() + }) + + it('Should match string wildcard', () => { + const fileContent = ` + scopes = BitField( + flags=( + ("project:read", "project:read") + ) + ) + ` + + const queries = [ + ` + "pro$$t:read" + `, + ] + + const { matches, errors } = searchInStrings({ + mode: 'include', + caseInsensitive: true, + queryCodes: queries, + files: [ + { + path: 'mock', + content: fileContent, + }, + ], + }) + + expect(errors).toHaveLength(0) + expect(matches.length).toBe(2) + }) + + it('Should match node wildcard', () => { + const fileContent = ` + scopes = BitField( + flags=( + ("project:read", "project:read") + ) + ) + ` + + const queries = [ + ` + scopes = $$$ + `, + ] + + const { matches, errors } = searchInStrings({ + mode: 'include', + caseInsensitive: true, + queryCodes: queries, + files: [ + { + path: 'mock', + content: fileContent, + }, + ], + }) + + expect(errors).toHaveLength(0) + expect(matches.length).toBe(1) + }) + + it('Should match identifier wildcard', () => { + const fileContent = ` + scopes = BitField( + flags=( + ("project:read", "project:read") + ) + ) + ` + + const queries = [ + ` + scopes = $$() + `, + ] + + const { matches, errors } = searchInStrings({ + mode: 'include', + caseInsensitive: true, + queryCodes: queries, + files: [ + { + path: 'mock', + content: fileContent, + }, + ], + }) + + expect(errors).toHaveLength(0) + expect(matches.length).toBe(1) + }) + + it('Should match integer wildcard', () => { + const fileContent = ` + val = 123 + ` + + const queries = [ + ` + 0x0 + `, + ] + + const { matches, errors } = searchInStrings({ + mode: 'include', + caseInsensitive: true, + queryCodes: queries, + files: [ + { + path: 'mock', + content: fileContent, + }, + ], + }) + + expect(errors).toHaveLength(0) + expect(matches.length).toBe(1) + }) + + it('Should match float wildcard', () => { + const fileContent = ` + val = 123.123 + ` + + const queries = [ + ` + 0x0 + `, + ] + + const { matches, errors } = searchInStrings({ + mode: 'include', + caseInsensitive: true, + queryCodes: queries, + files: [ + { + path: 'mock', + content: fileContent, + }, + ], + }) + + expect(errors).toHaveLength(0) + expect(matches.length).toBe(1) + }) +}) diff --git a/packages/core/__tests__/Python/other/searchWithContext.test.ts b/packages/core/__tests__/Python/other/searchWithContext.test.ts new file mode 100644 index 0000000..75c9050 --- /dev/null +++ b/packages/core/__tests__/Python/other/searchWithContext.test.ts @@ -0,0 +1,55 @@ +import { searchInStrings } from '../../../src/searchInStrings' +import { compareCode, getParserSettings } from '../../utils' + +describe('Search with context', () => { + beforeAll(async () => { + await getParserSettings().init?.() + }) + + it('Should partial match function definition using wildcard and alias', () => { + const fileContent = ` + def fib(n): + a, b = 0, 1 + print() + while a < n: + print(a, end=' ') + a, b = b, a+b + print() + ` + const queries = [ + ` + def fib(n): + $$_refPrint() + $$_refPrint() + `, + ] + + const { matches, errors } = searchInStrings({ + mode: 'include', + caseInsensitive: true, + queryCodes: queries, + files: [ + { + path: 'mock', + content: fileContent, + }, + ], + }) + + expect(errors).toHaveLength(0) + expect(matches.length).toBe(1) + + const match = matches[0] + + expect(match.aliases.identifierAliasesMap['refPrint'].aliasValue).toBe( + 'print', + ) + + expect(compareCode(fileContent, match.code)).toBe(true) + + expect(match.loc).toStrictEqual({ + start: { line: 2, column: 6, index: 7 }, + end: { line: 8, column: 15, index: 147 }, + }) + }) +}) diff --git a/packages/core/__tests__/search/textSearch.test.ts b/packages/core/__tests__/TextSearch/textSearch.test.ts similarity index 95% rename from packages/core/__tests__/search/textSearch.test.ts rename to packages/core/__tests__/TextSearch/textSearch.test.ts index 5a994ac..1aef5db 100644 --- a/packages/core/__tests__/search/textSearch.test.ts +++ b/packages/core/__tests__/TextSearch/textSearch.test.ts @@ -1,15 +1,16 @@ -import { getFilesList } from '/getFilesList' +import { getFilesList } from '../../src/getFilesList' import path from 'path' -import { searchInFileSystem } from '/searchInFs' +import { searchInFileSystem } from '../../src/searchInFs' import dedent from 'dedent' -import searchInStrings from '/searchInStrings' +import searchInStrings from '../../src/searchInStrings' +import { fixturesPath, fixturesOtherPath } from '../utils' describe('Text search mode', () => { let filesList = [] as string[] beforeAll(async () => { filesList = await getFilesList({ - searchRoot: path.resolve(__dirname, '__fixtures__'), + searchRoot: fixturesPath, omitGitIgnore: true, }) }) @@ -273,9 +274,7 @@ describe('Text search mode', () => { `, ], - filePaths: [ - path.resolve(__dirname, '__fixturesOther__', 'textSearch.ts'), - ], + filePaths: [fixturesOtherPath + '/textSearch.ts'], mode: 'text', caseInsensitive: true, }) diff --git a/packages/core/__tests__/TypeScript/matchCodePatterns/assignmentPattern.test.ts b/packages/core/__tests__/TypeScript/matchCodePatterns/assignmentPattern.test.ts new file mode 100644 index 0000000..2a51bb1 --- /dev/null +++ b/packages/core/__tests__/TypeScript/matchCodePatterns/assignmentPattern.test.ts @@ -0,0 +1,65 @@ +import { searchInStrings } from '../../../src/searchInStrings' + +describe('AssignmentPattern improvements in include mode', () => { + it('should match assignment pattern in function arguments type annotation', () => { + const fileContent = ` + function some(param: [] | null = null) { + + } + ` + + const queries = [ + ` + function some(param: [] | null) { + + } + `, + ] + + const { matches, errors } = searchInStrings({ + mode: 'include', + caseInsensitive: true, + queryCodes: queries, + files: [ + { + path: 'mock', + content: fileContent, + }, + ], + }) + + expect(errors).toHaveLength(0) + expect(matches.length).toBe(1) + }) + + it('should not match assignment pattern in function arguments with wildcard but without types', () => { + const fileContent = ` + function some(param = null) { + + } + ` + + const queries = [ + ` + function some($$: [] | null = null) { + + } + `, + ] + + const { matches, errors } = searchInStrings({ + mode: 'include', + caseInsensitive: true, + queryCodes: queries, + files: [ + { + path: 'mock', + content: fileContent, + }, + ], + }) + + expect(errors).toHaveLength(0) + expect(matches.length).toBe(0) + }) +}) diff --git a/packages/core/__tests__/TypeScript/matchCodePatterns/functionsAndTypes.test.ts b/packages/core/__tests__/TypeScript/matchCodePatterns/functionsAndTypes.test.ts new file mode 100644 index 0000000..f2f6fb9 --- /dev/null +++ b/packages/core/__tests__/TypeScript/matchCodePatterns/functionsAndTypes.test.ts @@ -0,0 +1,242 @@ +import { searchInStrings } from '../../../src/searchInStrings' +import { compareCode } from '../../utils' + +describe('functions', () => { + const fileContent = ` + const onScroll = ({ + nativeEvent + }: NativeSyntheticEvent) => { + const currentScrollPosition = Math.floor(nativeEvent?.contentOffset?.y) ?? 0; + + if (!isIOS) { + return velocity.setValue(currentScrollPosition); + } + + setExtended(currentScrollPosition <= 0); + }; + ` + + it('should match exact function with body', () => { + const queries = [ + ` + const onScroll = ({ + nativeEvent + }: NativeSyntheticEvent) => { + const currentScrollPosition = Math.floor(nativeEvent?.contentOffset?.y) ?? 0; + + if (!isIOS) { + return velocity.setValue(currentScrollPosition); + } + + setExtended(currentScrollPosition <= 0); + }; + `, + ] + + const { matches, errors } = searchInStrings({ + mode: 'exact', + files: [{ content: fileContent, path: '' }], + queryCodes: queries, + }) + + expect(errors).toHaveLength(0) + expect(matches.length).toBe(1) + + expect(compareCode(matches[0].code, queries[0])).toBeTruthy() + }) + + it('should match function with body statements in order with exact statements', () => { + const queries = [ + ` + const onScroll = ({ + nativeEvent + }: NativeSyntheticEvent) => { + const currentScrollPosition = Math.floor(nativeEvent?.contentOffset?.y) ?? 0; + + + if (!isIOS) { + return velocity.setValue(currentScrollPosition); + } + + setExtended(currentScrollPosition <= 0); + + }; + `, + ] + + const { matches, errors } = searchInStrings({ + mode: 'include-with-order', + files: [{ content: fileContent, path: '' }], + queryCodes: queries, + }) + expect(errors).toHaveLength(0) + expect(matches.length).toBe(1) + }) + + it('should match function with body statements in order but without all statements', () => { + const queries = [ + ` + const onScroll = ({ + nativeEvent + }: NativeSyntheticEvent) => { + const currentScrollPosition = Math.floor(nativeEvent?.contentOffset?.y) ?? 0; + + + if (!isIOS) { + return velocity.setValue(currentScrollPosition); + } + + }; + `, + ] + + const { matches, errors } = searchInStrings({ + mode: 'include-with-order', + files: [{ content: fileContent, path: '' }], + queryCodes: queries, + }) + + expect(errors).toHaveLength(0) + expect(matches.length).toBe(1) + }) + + it('should not match function with body statements in different order', () => { + const queries = [ + ` + const onScroll = ({ + nativeEvent + }: NativeSyntheticEvent) => { + const currentScrollPosition = Math.floor(nativeEvent?.contentOffset?.y) ?? 0; + + setExtended(currentScrollPosition <= 0); + + if (!isIOS) { + return velocity.setValue(currentScrollPosition); + } + + }; + `, + ] + + const { matches, errors } = searchInStrings({ + mode: 'include-with-order', + files: [{ content: fileContent, path: '' }], + queryCodes: queries, + }) + + expect(errors).toHaveLength(0) + expect(matches.length).toBe(0) + }) + + it('should not match function with body statements in different order without all statements', () => { + const queries = [ + ` + const onScroll = ({ + nativeEvent + }: NativeSyntheticEvent) => { + + if (!isIOS) { + return velocity.setValue(currentScrollPosition); + } + + const currentScrollPosition = Math.floor(nativeEvent?.contentOffset?.y) ?? 0; + + }; + `, + ] + + const { matches, errors } = searchInStrings({ + mode: 'include-with-order', + files: [{ content: fileContent, path: '' }], + queryCodes: queries, + }) + + expect(errors).toHaveLength(0) + expect(matches.length).toBe(0) + }) + + it('should match function with body statements in different order', () => { + const queries = [ + ` + const onScroll = ({ + nativeEvent + }: NativeSyntheticEvent) => { + const currentScrollPosition = Math.floor(nativeEvent?.contentOffset?.y) ?? 0; + + setExtended(currentScrollPosition <= 0); + + if (!isIOS) { + return velocity.setValue(currentScrollPosition); + } + + }; + `, + ] + + const { matches, errors } = searchInStrings({ + mode: 'include', + files: [{ content: fileContent, path: '' }], + queryCodes: queries, + }) + + expect(errors).toHaveLength(0) + expect(matches.length).toBe(1) + }) + + it('should match function with body statements in different order without all statements', () => { + const queries = [ + ` + const onScroll = ({ + nativeEvent + }: NativeSyntheticEvent) => { + + if (!isIOS) { + return velocity.setValue(currentScrollPosition); + } + + const currentScrollPosition = Math.floor(nativeEvent?.contentOffset?.y) ?? 0; + + }; + `, + ] + + const { matches, errors } = searchInStrings({ + mode: 'include', + files: [{ content: fileContent, path: '' }], + queryCodes: queries, + }) + + expect(errors).toHaveLength(0) + expect(matches.length).toBe(1) + }) + + it('should match inline types in function params', () => { + const fileContent = ` + const onScroll = ({ + onClick + }: { onClick : () => string }) => { + const currentScrollPosition = Math.floor(nativeEvent?.contentOffset?.y) ?? 0; + + setExtended(currentScrollPosition <= 0); + }; + ` + const queries = [ + ` + const $$ = ({ + $$, + }: { + $$: () => $$$; + }) => {} + `, + ] + + const { matches, errors } = searchInStrings({ + mode: 'include', + files: [{ content: fileContent, path: '' }], + queryCodes: queries, + }) + + expect(errors).toHaveLength(0) + expect(matches.length).toBe(1) + }) +}) diff --git a/packages/core/__tests__/TypeScript/matchCodePatterns/other.test.ts b/packages/core/__tests__/TypeScript/matchCodePatterns/other.test.ts new file mode 100644 index 0000000..684c694 --- /dev/null +++ b/packages/core/__tests__/TypeScript/matchCodePatterns/other.test.ts @@ -0,0 +1,77 @@ +import { useTraverseApproachTestOnly } from '../../../src/testOnlyConfig' +import { searchInFileSystem } from '../../../src/searchInFs' + +import { searchInStrings } from '../../../src/searchInStrings' + +describe('other', () => { + it('should match cast to any', () => { + const fileContent = ` + const val = 5 as any + const val2 = (fn() as any) as MyType + ` + + const queries = [ + ` + ($$$ as any) + `, + ] + + const { matches, errors } = searchInStrings({ + mode: 'exact', + files: [{ content: fileContent, path: '' }], + + queryCodes: queries, + }) + + expect(errors).toHaveLength(0) + expect(matches.length).toBe(2) + }) + + it('should match anything', () => { + const fileContent = ` + const a:Object = { + val : 5 + } + ` + + const queries = [ + ` + $$$ + `, + ] + + const { matches, errors } = searchInStrings({ + mode: 'include', + files: [{ content: fileContent, path: '' }], + queryCodes: queries, + }) + + // In traversal we only project query to identifier nodes + const expectedMatchesCount = global.testSettings?.isTraversal ? 3 : 10 + + expect(errors).toHaveLength(0) + expect(matches.length).toBe(expectedMatchesCount) + }) + + it('should not include the same result twice', () => { + const fileContent = `type MyType = ScrollViewProps & BoxProps` + + const queries = [ + ` + type $$ = ScrollViewProps & $$$ + `, + ` + type $$ = $$$ & ScrollViewProps + `, + ] + + const { matches, errors } = searchInStrings({ + mode: 'include', + files: [{ content: fileContent, path: '' }], + queryCodes: queries, + }) + + expect(errors).toHaveLength(0) + expect(matches.length).toBe(1) + }) +}) diff --git a/packages/core/__tests__/search/types.test.ts b/packages/core/__tests__/TypeScript/matchCodePatterns/types.test.ts similarity index 91% rename from packages/core/__tests__/search/types.test.ts rename to packages/core/__tests__/TypeScript/matchCodePatterns/types.test.ts index e6b1bc5..6e68973 100644 --- a/packages/core/__tests__/search/types.test.ts +++ b/packages/core/__tests__/TypeScript/matchCodePatterns/types.test.ts @@ -1,14 +1,14 @@ -import { searchInFileSystem } from '/searchInFs' -import path from 'path' -import { getFilesList } from '/getFilesList' -import { searchInStrings } from '../../src/searchInStrings' +import { searchInFileSystem } from '../../../src/searchInFs' +import { fixturesPath } from '../../utils' +import { getFilesList } from '../../../src/getFilesList' +import { searchInStrings } from '../../../src/searchInStrings' describe('Types', () => { let filesList = [] as string[] beforeAll(async () => { filesList = await getFilesList({ - searchRoot: path.resolve(__dirname, '__fixtures__'), + searchRoot: fixturesPath, omitGitIgnore: true, }) }) @@ -61,7 +61,7 @@ describe('Types', () => { queryCodes: queries, }) - expect(errors.length).toBe(0) + expect(errors).toHaveLength(0) expect(matches.length).toBe(1) }) @@ -78,7 +78,7 @@ describe('Types', () => { queryCodes: queries, }) - expect(errors.length).toBe(0) + expect(errors).toHaveLength(0) expect(matches.length).toBe(1) }) @@ -95,7 +95,7 @@ describe('Types', () => { queryCodes: queries, }) - expect(errors.length).toBe(0) + expect(errors).toHaveLength(0) expect(matches.length).toBe(1) }) @@ -112,7 +112,7 @@ describe('Types', () => { queryCodes: queries, }) - expect(errors.length).toBe(0) + expect(errors).toHaveLength(0) expect(matches.length).toBe(3) }) @@ -131,7 +131,7 @@ describe('Types', () => { queryCodes: queries, }) - expect(errors.length).toBe(0) + expect(errors).toHaveLength(0) expect(matches.length).toBe(5) }) @@ -150,7 +150,7 @@ describe('Types', () => { queryCodes: queries, }) - expect(errors.length).toBe(0) + expect(errors).toHaveLength(0) expect(matches.length).toBe(2) }) @@ -169,7 +169,7 @@ describe('Types', () => { queryCodes: queries, }) - expect(errors.length).toBe(0) + expect(errors).toHaveLength(0) expect(matches.length).toBe(2) }) @@ -188,7 +188,7 @@ describe('Types', () => { queryCodes: queries, }) - expect(errors.length).toBe(0) + expect(errors).toHaveLength(0) expect(matches.length).toBe(2) }) @@ -207,7 +207,7 @@ describe('Types', () => { queryCodes: queries, }) - expect(errors.length).toBe(0) + expect(errors).toHaveLength(0) expect(matches.length).toBe(2) }) @@ -224,7 +224,7 @@ describe('Types', () => { queryCodes: queries, }) - expect(errors.length).toBe(0) + expect(errors).toHaveLength(0) expect(matches.length).toBe(1) }) @@ -241,7 +241,7 @@ describe('Types', () => { queryCodes: queries, }) - expect(errors.length).toBe(0) + expect(errors).toHaveLength(0) expect(matches.length).toBe(1) }) @@ -258,7 +258,7 @@ describe('Types', () => { queryCodes: queries, }) - expect(errors.length).toBe(0) + expect(errors).toHaveLength(0) expect(matches.length).toBe(1) }) @@ -275,7 +275,7 @@ describe('Types', () => { queryCodes: queries, }) - expect(errors.length).toBe(0) + expect(errors).toHaveLength(0) expect(matches.length).toBe(1) }) @@ -292,7 +292,7 @@ describe('Types', () => { queryCodes: queries, }) - expect(errors.length).toBe(0) + expect(errors).toHaveLength(0) expect(matches.length).toBe(1) }) @@ -309,7 +309,7 @@ describe('Types', () => { queryCodes: queries, }) - expect(errors.length).toBe(0) + expect(errors).toHaveLength(0) expect(matches.length).toBe(1) }) @@ -440,7 +440,7 @@ describe('Types', () => { queryCodes: queries, }) - expect(errors.length).toBe(0) + expect(errors).toHaveLength(0) expect(matches.length).toBe(2) }) @@ -458,7 +458,7 @@ describe('Types', () => { queryCodes: queries, }) - expect(errors.length).toBe(0) + expect(errors).toHaveLength(0) expect(matches.length).toBe(1) }) @@ -476,7 +476,7 @@ describe('Types', () => { queryCodes: queries, }) - expect(errors.length).toBe(0) + expect(errors).toHaveLength(0) expect(matches.length).toBe(1) }) @@ -495,7 +495,7 @@ describe('Types', () => { queryCodes: queries, }) - expect(errors.length).toBe(0) + expect(errors).toHaveLength(0) expect(matches.length).toBe(2) }) @@ -514,7 +514,7 @@ describe('Types', () => { queryCodes: queries, }) - expect(errors.length).toBe(0) + expect(errors).toHaveLength(0) expect(matches.length).toBe(1) }) @@ -533,7 +533,7 @@ describe('Types', () => { queryCodes: queries, }) - expect(errors.length).toBe(0) + expect(errors).toHaveLength(0) expect(matches.length).toBe(1) }) @@ -553,7 +553,7 @@ describe('Types', () => { queryCodes: queries, }) - expect(errors.length).toBe(0) + expect(errors).toHaveLength(0) expect(matches.length).toBe(1) }) @@ -573,7 +573,7 @@ describe('Types', () => { queryCodes: queries, }) - expect(errors.length).toBe(0) + expect(errors).toHaveLength(0) expect(matches.length).toBe(0) }) @@ -602,7 +602,7 @@ describe('Types', () => { ], }) - expect(errors.length).toBe(0) + expect(errors).toHaveLength(0) expect(matches.length).toBe(3) }) @@ -631,7 +631,7 @@ describe('Types', () => { ], }) - expect(errors.length).toBe(0) + expect(errors).toHaveLength(0) expect(matches.length).toBe(3) }) }) diff --git a/packages/core/__tests__/astUtils.test.ts b/packages/core/__tests__/TypeScript/other/astUtils.test.ts similarity index 74% rename from packages/core/__tests__/astUtils.test.ts rename to packages/core/__tests__/TypeScript/other/astUtils.test.ts index 991ac47..2ee0848 100644 --- a/packages/core/__tests__/astUtils.test.ts +++ b/packages/core/__tests__/TypeScript/other/astUtils.test.ts @@ -1,5 +1,4 @@ -import { compareCode } from './utils' -import { createWildcardUtils } from '/wildcardUtilsFactory' +import { compareCode } from '../../utils' describe('AST utils', () => { it('should compare code as equal', () => { @@ -38,12 +37,12 @@ describe('AST utils', () => { const code2 = ` import * as React from 'react'; - import { + import { Paragraph, - Button, - Portal, - Dialog, - Colors + Button, + Portal, + Dialog, + Colors } from 'react-native-paper'; const DialogWithCustomColors = ({ @@ -118,11 +117,11 @@ describe('AST utils', () => { ` import * as React from 'react'; - import { - Paragraph, - Button, - Portal, - Dialog + import { + Paragraph, + Button, + Portal, + Dialog } from 'react-native-paper'; const DialogWithCustomColors = ({ @@ -157,43 +156,4 @@ describe('AST utils', () => { ` expect(compareCode(code1, code2)).toBe(false) }) - - it('should remove identifier ref from wildcard', () => { - const identifierTypes: string[] = [] // not needed for this test - const numericWildcard = '0x0' - const wildcardChar = '$' - const wildcardUtils = createWildcardUtils( - identifierTypes, - numericWildcard, - wildcardChar, - ) - - expect(wildcardUtils.removeIdentifierRefFromWildcard('$$$_ref1')).toBe( - '$$$', - ) - - expect(wildcardUtils.removeIdentifierRefFromWildcard('$$_ref1')).toBe('$$') - - expect(wildcardUtils.removeIdentifierRefFromWildcard('$$something')).toBe( - '$$something', - ) - - expect(wildcardUtils.removeIdentifierRefFromWildcard('$$_something')).toBe( - '$$', - ) - - expect(wildcardUtils.removeIdentifierRefFromWildcard('asd$$_ref')).toBe( - 'asd$$_ref', - ) - - // should not remove if ref is another wildcard - expect(wildcardUtils.removeIdentifierRefFromWildcard('$$$_$$')).toBe( - '$$$_$$', - ) - - // should not remove if ref is in the middle of string - expect(wildcardUtils.removeIdentifierRefFromWildcard('$$$_notRef_$$')).toBe( - '$$$_notRef_$$', - ) - }) }) diff --git a/packages/core/__tests__/parseQuery.test.ts b/packages/core/__tests__/TypeScript/other/parseQuery.test.ts similarity index 65% rename from packages/core/__tests__/parseQuery.test.ts rename to packages/core/__tests__/TypeScript/other/parseQuery.test.ts index 43ae79b..71de0b2 100644 --- a/packages/core/__tests__/parseQuery.test.ts +++ b/packages/core/__tests__/TypeScript/other/parseQuery.test.ts @@ -1,5 +1,5 @@ -import { getUniqueTokens, extractQueryNode } from '../src/parseQuery' -import { getParserSettings } from './utils' +import { getUniqueTokens, extractQueryNode } from '../../../src/parseQuery' +import { getParserSettings } from '../../utils' describe('parse query', () => { const parserSettings = getParserSettings() @@ -11,6 +11,10 @@ describe('parse query', () => { const alpha = 'beta' type MyType = boolean 123; + pre$$_ref1_post + $$_ref1_content$$_ref2 + asd$$_invalidRef_$$ + asd$$_invalidRef$$ { { () => { @@ -19,6 +23,7 @@ describe('parse query', () => { } } } + f('some$$_ref1_tttt$$_ref2') ` const { queryNode } = extractQueryNode( parserSettings.parseCode(queryCode), @@ -36,7 +41,15 @@ describe('parse query', () => { 'beta', 'MyType', '123', + 'pre', + 'post', + 'content', + 'asd', + '_invalidRef_', + '_invalidRef', 'test', + // 'some', // only 'tttt' is new + 'tttt', ]) }) }) diff --git a/packages/core/__tests__/searchMultiThread.test.ts b/packages/core/__tests__/TypeScript/other/searchMultiThread.test.ts similarity index 88% rename from packages/core/__tests__/searchMultiThread.test.ts rename to packages/core/__tests__/TypeScript/other/searchMultiThread.test.ts index 57ae995..c6fbb9b 100644 --- a/packages/core/__tests__/searchMultiThread.test.ts +++ b/packages/core/__tests__/TypeScript/other/searchMultiThread.test.ts @@ -1,9 +1,9 @@ -import { searchMultiThread as searchMultiThread } from '/searchMultiThread' -import { searchInFileSystem } from '/searchInFs' +import { searchMultiThread as searchMultiThread } from '../../../src/searchMultiThread' +import { searchInFileSystem } from '../../../src/searchInFs' -import { compareCode } from './utils' +import { compareCode, fixturesPath } from '../../utils' import path from 'path' -import { getFilesList } from '/getFilesList' +import { getFilesList } from '../../../src/getFilesList' jest.mock('worker_threads', () => { const actual = jest.requireActual('worker_threads') @@ -22,7 +22,7 @@ jest.mock('worker_threads', () => { it('should search using multiple threads and give the same matches count as single thread search', async () => { const filesList = await getFilesList({ - searchRoot: path.resolve(__dirname, 'search', '__fixtures__'), + searchRoot: fixturesPath, omitGitIgnore: true, }) const query = ` @@ -80,7 +80,7 @@ it('should search using multiple threads and give the same matches count as sing it('Should report each match separately for structural search', async () => { const filesList = await getFilesList({ - searchRoot: path.resolve(__dirname, 'search', '__fixtures__'), + searchRoot: fixturesPath, omitGitIgnore: true, }) @@ -107,7 +107,7 @@ it('Should report each match separately for structural search', async () => { it('Should report each match separately for text search', async () => { const filesList = await getFilesList({ - searchRoot: path.resolve(__dirname, 'search', '__fixtures__'), + searchRoot: fixturesPath, omitGitIgnore: true, }) diff --git a/packages/core/__tests__/search/JSX.test.ts b/packages/core/__tests__/TypeScript/searchSmoke/JSX.test.ts similarity index 51% rename from packages/core/__tests__/search/JSX.test.ts rename to packages/core/__tests__/TypeScript/searchSmoke/JSX.test.ts index cd42bf5..ae0fb14 100644 --- a/packages/core/__tests__/search/JSX.test.ts +++ b/packages/core/__tests__/TypeScript/searchSmoke/JSX.test.ts @@ -1,51 +1,19 @@ -import { compareCode } from '../utils' -import { searchInFileSystem } from '/searchInFs' +import { compareCode } from '../../utils' +import { searchInFileSystem } from '../../../src/searchInFs' -import path from 'path' -import { searchInStrings } from '../../src/searchInStrings' -import { getFilesList } from '/getFilesList' +import { fixturesPath } from '../../utils' +import { getFilesList } from '../../../src/getFilesList' describe('JSX', () => { let filesList = [] as string[] beforeAll(async () => { filesList = await getFilesList({ - searchRoot: path.resolve(__dirname, '__fixtures__'), + searchRoot: fixturesPath, omitGitIgnore: true, }) }) - const testFileContent = ` - <> - - - - - - - - - - - ` - - const mockedFilesList = [ - { - path: 'mock', - content: testFileContent, - }, - ] - it('Should find all self-closing JSX', () => { const query = `<$$ />` const { matches, errors } = searchInFileSystem({ @@ -111,26 +79,10 @@ describe('JSX', () => { filePaths: filesList, queryCodes: [query], }) - expect(errors.length).toBe(0) + expect(errors).toHaveLength(0) expect(matches.length).toBe(1) }) - it('Should find JSX by text content regardless formatting', () => { - const query = ` - - ` - const { matches, errors } = searchInStrings({ - mode: 'include', - files: mockedFilesList, - queryCodes: [query], - }) - expect(errors.length).toBe(0) - expect(matches.length).toBe(2) - }) - it('Should find JSX by text content with wildcard case insensitive', () => { const query = `r$$L` const { matches, errors } = searchInFileSystem({ @@ -139,7 +91,7 @@ describe('JSX', () => { queryCodes: [query], caseInsensitive: true, }) - expect(errors.length).toBe(0) + expect(errors).toHaveLength(0) expect(matches.length).toBe(1) }) @@ -151,7 +103,7 @@ describe('JSX', () => { caseInsensitive: true, queryCodes: [query], }) - expect(errors.length).toBe(0) + expect(errors).toHaveLength(0) expect(matches.length).toBe(1) }) @@ -228,7 +180,7 @@ describe('JSX', () => { queryCodes: [query1, query2], }) - expect(errors.length).toBe(0) + expect(errors).toHaveLength(0) expect(matches.length).toBe(2) }) @@ -350,275 +302,4 @@ describe('JSX', () => { expect(errors).toHaveLength(0) expect(matches.length).toBe(78) }) - - it('Should ignore all empty JSXText in search', () => { - const queries = [ - ` - <$$> - $$ - ; - `, - ] - - const { matches, errors } = searchInStrings({ - mode: 'include', - files: mockedFilesList, - queryCodes: queries, - }) - - expect(errors.length).toBe(0) - expect(matches.length).toBe(3) - - expect( - compareCode( - matches[0].code, - ` - `, - ), - ).toBeTruthy() - }) - - it('Should match code with nested JSX when using wildcard on text content', () => { - const queries = [ - ` - ; - `, - ] - - const { matches, errors } = searchInStrings({ - mode: 'include', - caseInsensitive: true, - files: mockedFilesList, - queryCodes: queries, - }) - - expect(errors.length).toBe(0) - expect(matches.length).toBe(1) - - expect( - compareCode( - matches[0].code, - ` - `, - ), - ).toBeTruthy() - }) - - describe('Self and not self closing JSX tags in include mode', () => { - it('Self-closing JSX tag in query should match also not self-closing tags', () => { - const fileContent = ` - asd; - bbc; - - ; - - - ` - - const queries = [ - ` - - `, - ] - - const { matches, errors } = searchInStrings({ - mode: 'include', - caseInsensitive: true, - queryCodes: queries, - files: [ - { - path: 'mock', - content: fileContent, - }, - ], - }) - - expect(errors.length).toBe(0) - expect(matches.length).toBe(4) - }) - - it('Not self-closing JSX tag in query should match also self-closing tags', () => { - const fileContent = ` - asd; - bbc; - - ; - - - ` - - const queries = [ - ` - - `, - ] - - const { matches, errors } = searchInStrings({ - mode: 'include', - caseInsensitive: true, - queryCodes: queries, - files: [ - { - path: 'mock', - content: fileContent, - }, - ], - }) - - expect(errors.length).toBe(0) - expect(matches.length).toBe(4) - }) - - it('Not self-closing JSX tag with children in query should not match self-closing tags', () => { - const fileContent = ` - ; - - - ` - - const queries = [ - ` - asd - `, - ] - - const { matches, errors } = searchInStrings({ - mode: 'include', - caseInsensitive: true, - queryCodes: queries, - files: [ - { - path: 'mock', - content: fileContent, - }, - ], - }) - - expect(errors.length).toBe(0) - expect(matches.length).toBe(0) - }) - - it('Self-closing JSX tag with prop in query should match also not self-closing tag with prop', () => { - const fileContent = ` - asd; - bbc; - - ; - - - ` - - const queries = [ - ` - - `, - ] - - const { matches, errors } = searchInStrings({ - mode: 'include', - caseInsensitive: true, - queryCodes: queries, - files: [ - { - path: 'mock', - content: fileContent, - }, - ], - }) - - expect(errors.length).toBe(0) - expect(matches.length).toBe(2) - }) - }) - - describe('JSXIdentifiers like Identifiers', () => { - it('Should match JSXIdentifier when looking for Identifier', () => { - const fileContent = ` - asd; - ` - - const queries = [ - ` - Comp; - `, - ] - - const { matches, errors } = searchInStrings({ - mode: 'include', - caseInsensitive: true, - queryCodes: queries, - files: [ - { - path: 'mock', - content: fileContent, - }, - ], - }) - - expect(errors.length).toBe(0) - expect(matches.length).toBe(2) - }) - - it('Should match JSXIdentifier when looking for Identifier wildcard', () => { - const fileContent = ` - asd; - ` - - const queries = [ - ` - Co$$; - `, - ] - - const { matches, errors } = searchInStrings({ - mode: 'include', - caseInsensitive: true, - queryCodes: queries, - files: [ - { - path: 'mock', - content: fileContent, - }, - ], - }) - - expect(errors.length).toBe(0) - expect(matches.length).toBe(2) - }) - }) - - it('Should not match too much values using wildcards in JSXText', () => { - const fileContent = ` - Edit Client - Dweet - ` - - const queries = [ - ` - $$| Dweet |$$; - `, - ] - - const { matches, errors } = searchInStrings({ - mode: 'include', - caseInsensitive: true, - queryCodes: queries, - files: [ - { - path: 'mock', - content: fileContent, - }, - ], - }) - - expect(errors.length).toBe(0) - expect(matches.length).toBe(0) - }) }) diff --git a/packages/core/__tests__/TypeScript/searchSmoke/blocks.test.ts b/packages/core/__tests__/TypeScript/searchSmoke/blocks.test.ts new file mode 100644 index 0000000..f99c32c --- /dev/null +++ b/packages/core/__tests__/TypeScript/searchSmoke/blocks.test.ts @@ -0,0 +1,79 @@ +import { searchInFileSystem } from '../../../src/searchInFs' +import { compareCode } from '../../utils' + +import { fixturesPath } from '../../utils' +import { getFilesList } from '../../../src/getFilesList' + +describe('blocks', () => { + let filesList = [] as string[] + + beforeAll(async () => { + filesList = await getFilesList({ + searchRoot: fixturesPath, + omitGitIgnore: true, + }) + }) + + it('should match exact whole block', () => { + const queries = [ + ` + () => { + toggleRTL(); + I18nManager.forceRTL(!isRTL); + Updates.reloadAsync(); + } + `, + ] + + const { matches, errors } = searchInFileSystem({ + mode: 'exact', + filePaths: filesList, + queryCodes: queries, + }) + + expect(errors).toHaveLength(0) + expect(matches.length).toBe(1) + + expect(compareCode(matches[0].code, queries[0])).toBeTruthy() + }) + + it('should match block using query without all statements and different order', () => { + const queries = [ + ` + () => { + Updates.reloadAsync(); + toggleRTL(); + } + `, + ] + + const { matches, errors } = searchInFileSystem({ + mode: 'include', + filePaths: filesList, + queryCodes: queries, + }) + + expect(errors).toHaveLength(0) + expect(matches.length).toBe(1) + }) + + it('should match block using query without all statements, but with order', () => { + const queries = [ + ` + () => { + toggleRTL(); + Updates.reloadAsync(); + } + `, + ] + + const { matches, errors } = searchInFileSystem({ + mode: 'include-with-order', + filePaths: filesList, + queryCodes: queries, + }) + + expect(errors).toHaveLength(0) + expect(matches.length).toBe(1) + }) +}) diff --git a/packages/core/__tests__/search/codePatterns.test.ts b/packages/core/__tests__/TypeScript/searchSmoke/codePatterns.test.ts similarity index 89% rename from packages/core/__tests__/search/codePatterns.test.ts rename to packages/core/__tests__/TypeScript/searchSmoke/codePatterns.test.ts index e0d07fa..fef8953 100644 --- a/packages/core/__tests__/search/codePatterns.test.ts +++ b/packages/core/__tests__/TypeScript/searchSmoke/codePatterns.test.ts @@ -1,15 +1,15 @@ -import { searchInFileSystem } from '/searchInFs' -import { compareCode } from '../utils' +import { searchInFileSystem } from '../../../src/searchInFs' +import { compareCode } from '../../utils' -import path from 'path' -import { getFilesList } from '/getFilesList' +import { fixturesPath } from '../../utils' +import { getFilesList } from '../../../src/getFilesList' describe('code patterns', () => { let filesList = [] as string[] beforeAll(async () => { filesList = await getFilesList({ - searchRoot: path.resolve(__dirname, '__fixtures__'), + searchRoot: fixturesPath, omitGitIgnore: true, }) }) @@ -41,7 +41,7 @@ describe('code patterns', () => { }; ` - expect(errors.length).toBe(0) + expect(errors).toHaveLength(0) expect(matches.length).toBe(10) expect(compareCode(matches[0].code, firstResult)).toBeTruthy() }) @@ -67,7 +67,7 @@ describe('code patterns', () => { queryCodes: queries, }) - expect(errors.length).toBe(0) + expect(errors).toHaveLength(0) expect(matches.length).toBe(1) }) @@ -166,7 +166,7 @@ describe('code patterns', () => { filePaths: filesList, queryCodes: queries, }) - expect(errors.length).toBe(0) + expect(errors).toHaveLength(0) expect(matches.length).toBe(1) }) @@ -183,7 +183,7 @@ describe('code patterns', () => { queryCodes: queries, }) - expect(errors.length).toBe(0) + expect(errors).toHaveLength(0) expect(matches.length).toBe(2) }) @@ -197,7 +197,7 @@ describe('code patterns', () => { queryCodes: [query], }) - expect(errors.length).toBe(0) + expect(errors).toHaveLength(0) expect(matches.length).toBe(3) expect(matches[2].code).toBe("console.log('Pressed')") @@ -216,7 +216,7 @@ describe('code patterns', () => { queryCodes: queries, }) - expect(errors.length).toBe(0) + expect(errors).toHaveLength(0) expect(matches.length).toBe(6) }) @@ -233,7 +233,7 @@ describe('code patterns', () => { queryCodes: queries, }) - expect(errors.length).toBe(0) + expect(errors).toHaveLength(0) expect(matches.length).toBe(0) }) }) diff --git a/packages/core/__tests__/search/functions.test.ts b/packages/core/__tests__/TypeScript/searchSmoke/functions.test.ts similarity index 74% rename from packages/core/__tests__/search/functions.test.ts rename to packages/core/__tests__/TypeScript/searchSmoke/functions.test.ts index 6b1bb08..3b31977 100644 --- a/packages/core/__tests__/search/functions.test.ts +++ b/packages/core/__tests__/TypeScript/searchSmoke/functions.test.ts @@ -1,8 +1,8 @@ -import { searchInFileSystem } from '/searchInFs' -import { compareCode } from '../utils' -import { searchInStrings } from '/searchInStrings' -import path from 'path' -import { getFilesList } from '/getFilesList' +import { searchInFileSystem } from '../../../src/searchInFs' +import { compareCode, parserType } from '../../utils' +import { searchInStrings } from '../../../src/searchInStrings' +import { fixturesPath } from '../../utils' +import { getFilesList } from '../../../src/getFilesList' import fs from 'fs' describe('functions', () => { @@ -10,24 +10,11 @@ describe('functions', () => { beforeAll(async () => { filesList = await getFilesList({ - searchRoot: path.resolve(__dirname, '__fixtures__'), + searchRoot: fixturesPath, omitGitIgnore: true, }) }) - const testFileContent = ` - (a,b,c) => {}; - (a,d) => {}; - (a, { b}) => {}; - ` - - const mockedFilesList = [ - { - path: 'mock', - content: testFileContent, - }, - ] - it('should match inline types in function params', () => { const queries = [ ` @@ -52,7 +39,7 @@ describe('functions', () => { queryCodes: queries, }) - expect(errors.length).toBe(0) + expect(errors).toHaveLength(0) expect(matches.length).toBe(4) }) @@ -79,7 +66,7 @@ describe('functions', () => { queryCodes: queries, }) - expect(errors.length).toBe(0) + expect(errors).toHaveLength(0) expect(matches.length).toBe(1) expect(compareCode(matches[0].code, queries[0])).toBeTruthy() @@ -109,7 +96,7 @@ describe('functions', () => { filePaths: filesList, queryCodes: queries, }) - expect(errors.length).toBe(0) + expect(errors).toHaveLength(0) expect(matches.length).toBe(1) }) @@ -136,7 +123,7 @@ describe('functions', () => { queryCodes: queries, }) - expect(errors.length).toBe(0) + expect(errors).toHaveLength(0) expect(matches.length).toBe(1) }) @@ -164,7 +151,7 @@ describe('functions', () => { queryCodes: queries, }) - expect(errors.length).toBe(0) + expect(errors).toHaveLength(0) expect(matches.length).toBe(0) }) @@ -191,7 +178,7 @@ describe('functions', () => { queryCodes: queries, }) - expect(errors.length).toBe(0) + expect(errors).toHaveLength(0) expect(matches.length).toBe(0) }) @@ -219,7 +206,7 @@ describe('functions', () => { queryCodes: queries, }) - expect(errors.length).toBe(0) + expect(errors).toHaveLength(0) expect(matches.length).toBe(1) }) @@ -246,58 +233,7 @@ describe('functions', () => { queryCodes: queries, }) - expect(errors.length).toBe(0) - expect(matches.length).toBe(1) - }) - - it('should match function with 2 arguments', () => { - const queries = [ - ` - ($$_ref1, $$_ref2) => {} - `, - ] - - const { matches, errors } = searchInStrings({ - mode: 'include', - files: mockedFilesList, - queryCodes: queries, - }) - - expect(errors.length).toBe(0) - expect(matches.length).toBe(2) - }) - - it('should match function with 2 arguments using double wildcard', () => { - const queries = [ - ` - ($$_ref1, $$$_ref2) => {} - `, - ] - - const { matches, errors } = searchInStrings({ - mode: 'include', - files: mockedFilesList, - queryCodes: queries, - }) - - expect(errors.length).toBe(0) - expect(matches.length).toBe(3) - }) - - it('should match function with 3 arguments', () => { - const queries = [ - ` - ($$_ref1, $$_ref2, $$_ref3) => {} - `, - ] - - const { matches, errors } = searchInStrings({ - mode: 'include', - files: mockedFilesList, - queryCodes: queries, - }) - - expect(errors.length).toBe(0) + expect(errors).toHaveLength(0) expect(matches.length).toBe(1) }) }) diff --git a/packages/core/__tests__/search/import.test.ts b/packages/core/__tests__/TypeScript/searchSmoke/import.test.ts similarity index 88% rename from packages/core/__tests__/search/import.test.ts rename to packages/core/__tests__/TypeScript/searchSmoke/import.test.ts index 8db65aa..b4b689f 100644 --- a/packages/core/__tests__/search/import.test.ts +++ b/packages/core/__tests__/TypeScript/searchSmoke/import.test.ts @@ -1,14 +1,14 @@ -import { searchInFileSystem } from '/searchInFs' +import { searchInFileSystem } from '../../../src/searchInFs' -import path from 'path' -import { getFilesList } from '/getFilesList' +import { fixturesPath } from '../../utils' +import { getFilesList } from '../../../src/getFilesList' -describe('JSX', () => { +describe('Import', () => { let filesList = [] as string[] beforeAll(async () => { filesList = await getFilesList({ - searchRoot: path.resolve(__dirname, '__fixtures__'), + searchRoot: fixturesPath, omitGitIgnore: true, }) }) @@ -26,7 +26,7 @@ describe('JSX', () => { queryCodes: [query], }) - expect(errors.length).toBe(0) + expect(errors).toHaveLength(0) expect(matches.length).toBe(1) }) @@ -43,7 +43,7 @@ describe('JSX', () => { queryCodes: [query], }) - expect(errors.length).toBe(0) + expect(errors).toHaveLength(0) expect(matches.length).toBe(0) }) @@ -100,7 +100,7 @@ describe('JSX', () => { queryCodes: [query], }) - expect(errors.length).toBe(0) + expect(errors).toHaveLength(0) expect(matches.length).toBe(2) }) @@ -114,7 +114,7 @@ describe('JSX', () => { queryCodes: [query], }) - expect(errors.length).toBe(0) + expect(errors).toHaveLength(0) expect(matches.length).toBe(2) }) @@ -129,7 +129,7 @@ describe('JSX', () => { queryCodes: [query], }) - expect(errors.length).toBe(0) + expect(errors).toHaveLength(0) expect(matches.length).toBe(6) }) }) diff --git a/packages/core/__tests__/TypeScript/searchSmoke/multiStatement.test.ts b/packages/core/__tests__/TypeScript/searchSmoke/multiStatement.test.ts new file mode 100644 index 0000000..64d1a92 --- /dev/null +++ b/packages/core/__tests__/TypeScript/searchSmoke/multiStatement.test.ts @@ -0,0 +1,152 @@ +import { searchInFileSystem } from '../../../src/searchInFs' +import { compareCode, fixturesPath } from '../../utils' + +import { getFilesList } from '../../../src/getFilesList' + +describe('multi statements', () => { + let filesList = [] as string[] + + beforeAll(async () => { + filesList = await getFilesList({ + searchRoot: fixturesPath, + omitGitIgnore: true, + }) + }) + + it('should match three expressions without block wrapper in function body', () => { + const queries = [ + ` + toggleRTL(); + I18nManager.forceRTL(!isRTL); + Updates.reloadAsync(); + `, + ] + + const { matches, errors } = searchInFileSystem({ + mode: 'include', + filePaths: filesList, + queryCodes: queries, + }) + + expect(errors).toHaveLength(0) + expect(matches.length).toBe(1) + }) + + it('should match three expressions without block wrapper in function body with exact mode', () => { + const queries = [ + ` + toggleRTL(); + I18nManager.forceRTL(!isRTL); + Updates.reloadAsync(); + `, + ] + + const { matches, errors } = searchInFileSystem({ + mode: 'exact', + filePaths: filesList, + queryCodes: queries, + }) + + expect(errors).toHaveLength(0) + expect(matches.length).toBe(1) + }) + + it('should match three expressions without block wrapper in function body with different order', () => { + const queries = [ + ` + Updates.reloadAsync(); + I18nManager.forceRTL(!isRTL); + toggleRTL(); + `, + ] + + const { matches, errors } = searchInFileSystem({ + mode: 'include', + filePaths: filesList, + queryCodes: queries, + }) + + expect(errors).toHaveLength(0) + expect(matches.length).toBe(1) + }) + + it('should match three expressions with block wrapper in function body', () => { + const queries = [ + ` + { + toggleRTL(); + I18nManager.forceRTL(!isRTL); + Updates.reloadAsync(); + } + `, + ] + + const { matches, errors } = searchInFileSystem({ + mode: 'include', + filePaths: filesList, + queryCodes: queries, + }) + + expect(errors).toHaveLength(0) + expect(matches.length).toBe(1) + }) + + it('should match two expressions in program body and include middle line in result', () => { + const queries = [ + ` + import { createStackNavigator } from '@react-navigation/stack'; + const Stack = createStackNavigator(); + `, + ] + + const { matches, errors } = searchInFileSystem({ + mode: 'include', + filePaths: filesList, + queryCodes: queries, + }) + + expect(errors).toHaveLength(0) + expect(matches.length).toBe(1) + + expect(matches[0].code.split('\n').filter(Boolean)).toHaveLength(3) + }) + + it('should match three expressions in program body and only include them in result (without whole block)', () => { + const queries = [ + ` + import { createStackNavigator } from '@react-navigation/stack' + import ExampleList, { examples } from './ExampleList' + const Stack = createStackNavigator() + `, + ] + + const { matches, errors } = searchInFileSystem({ + mode: 'include', + filePaths: filesList, + queryCodes: queries, + }) + + expect(errors).toHaveLength(0) + expect(matches.length).toBe(1) + + expect(compareCode(matches[0].code, queries[0])).toBeTruthy() + }) + + it('should not match two expressions in program body if order does not match', () => { + const queries = [ + ` + const Stack = createStackNavigator(); + import { createStackNavigator } from '@react-navigation/stack'; + `, + ] + + const { matches, errors } = searchInFileSystem({ + mode: 'include-with-order', + filePaths: filesList, + queryCodes: queries, + }) + + expect(errors).toHaveLength(0) + expect(matches.length).toBe(0) + }) +}) diff --git a/packages/core/__tests__/TypeScript/searchSmoke/objects.test.ts b/packages/core/__tests__/TypeScript/searchSmoke/objects.test.ts new file mode 100644 index 0000000..8030844 --- /dev/null +++ b/packages/core/__tests__/TypeScript/searchSmoke/objects.test.ts @@ -0,0 +1,33 @@ +import { getFilesList } from '../../../src/getFilesList' +import { searchInFileSystem } from '../../../src/searchInFs' +import { fixturesPath } from '../../utils' + +describe('Objects', () => { + it('should match possibly repeated object properties', async () => { + const filesList = await getFilesList({ + searchRoot: fixturesPath, + omitGitIgnore: true, + }) + + const queries = [ + ` + StyleSheet.create({ + $$: { + flex: 1, + alignItems: 'center', + justifyContent: 'center', + }, + }); + `, + ] + + const { matches, errors } = searchInFileSystem({ + mode: 'include', + filePaths: filesList, + queryCodes: queries, + }) + + expect(errors).toHaveLength(0) + expect(matches.length).toBe(2) + }) +}) diff --git a/packages/core/__tests__/TypeScript/searchSmoke/other.test.ts b/packages/core/__tests__/TypeScript/searchSmoke/other.test.ts new file mode 100644 index 0000000..a899511 --- /dev/null +++ b/packages/core/__tests__/TypeScript/searchSmoke/other.test.ts @@ -0,0 +1,95 @@ +import { fixturesPath } from '../../utils' +import { useTraverseApproachTestOnly } from '../../../src/testOnlyConfig' +import { getFilesList } from '../../../src/getFilesList' +import { searchInFileSystem } from '../../../src/searchInFs' + +describe('Other', () => { + let filesList = [] as string[] + + beforeAll(async () => { + filesList = await getFilesList({ + searchRoot: fixturesPath, + omitGitIgnore: true, + }) + }) + + it('should not include the same result twice', () => { + const queries = [ + ` + type $$ = ScrollViewProps & $$$ + `, + ` + type $$ = $$$ & ScrollViewProps + `, + ] + + const { matches, errors } = searchInFileSystem({ + mode: 'include', + filePaths: filesList, + queryCodes: queries, + }) + + expect(errors).toHaveLength(0) + expect(matches.length).toBe(1) + }) + + it('should not include the same result twice 2', () => { + const queries = [ + ` + <$$$ + $$={() => {}} + /> + `, + ` + <$$$ + $$={() => $$$} + /> + `, + ` + <$$$ + $$={() => {}} + > + + `, + ` + <$$$ + $$={() => $$$} + > + + `, + ] + + const { matches, errors } = searchInFileSystem({ + mode: 'include', + filePaths: filesList, + queryCodes: queries, + }) + + expect(errors).toHaveLength(0) + expect(matches.length).toBe(190) + }) + + it('should match anything', () => { + const queries = [ + ` + $$$ + `, + ] + + const { matches, errors } = searchInFileSystem({ + mode: 'include', + filePaths: filesList, + queryCodes: queries, + }) + + const countForTraversalMode = 6381 // will just match identifier-like nodes + const countForNonTraversalMode = 17149 // will match any node + + const expectedCount = useTraverseApproachTestOnly + ? countForTraversalMode + : countForNonTraversalMode + + expect(errors).toHaveLength(0) + expect(matches.length).toBe(expectedCount) + }) +}) diff --git a/packages/core/__tests__/search/__fixturesOther__/textSearch.ts b/packages/core/__tests__/__fixturesOther__/textSearch.ts similarity index 100% rename from packages/core/__tests__/search/__fixturesOther__/textSearch.ts rename to packages/core/__tests__/__fixturesOther__/textSearch.ts diff --git a/packages/core/__tests__/getFilesList.test.ts b/packages/core/__tests__/common/getFilesList.test.ts similarity index 91% rename from packages/core/__tests__/getFilesList.test.ts rename to packages/core/__tests__/common/getFilesList.test.ts index 74452fc..8945b62 100644 --- a/packages/core/__tests__/getFilesList.test.ts +++ b/packages/core/__tests__/common/getFilesList.test.ts @@ -1,5 +1,9 @@ import mockFs from 'mock-fs' -import { getFilesList, getFsRoot, filterIncludeExclude } from '/getFilesList' +import { + getFilesList, + getFsRoot, + filterIncludeExclude, +} from '../../src/getFilesList' import dedent from 'dedent' afterEach(() => { @@ -269,6 +273,41 @@ it('should return files for project root with omitting gitignore', async () => { ) }) +it('should use .gitignore only within the current repository', async () => { + mockFs({ + [root]: { + '.git': {}, + project: { + 'fileA.ts': 'content', + '.gitignore': 'packageA/*', + packageA: { + '.git': {}, + '.env': 'content', + '.gitignore': ` + .env + `, + src: { + 'fileE.json': '', + }, + }, + }, + }, + }) + + const filesList = removeCwd( + await getFilesList({ + searchRoot: toPlatformSpecificPath(`${root}/project/packageA`), + }), + ) + mockFs.restore() + + expect(filesList.sort()).toMatchObject( + [`${root}/project/packageA/src/fileE.json`] + .sort() + .map(toPlatformSpecificPath), + ) +}) + // We will add option to search in ignored files, so that exception is not needed it.skip('should return files for project root ignored by parent gitignore, but ignore the nested directories', async () => { mockFs({ @@ -317,6 +356,7 @@ it.skip('should return files for project root ignored by parent gitignore, but i it('should ignore files from parent .gitignore', async () => { mockFs({ [root]: { + '.git': {}, project: { 'fileA.ts': 'content', 'fileB.js': 'content', diff --git a/packages/core/__tests__/common/matchContext.test.ts b/packages/core/__tests__/common/matchContext.test.ts new file mode 100644 index 0000000..d099d82 --- /dev/null +++ b/packages/core/__tests__/common/matchContext.test.ts @@ -0,0 +1,50 @@ +import { createMatchContext } from '../../src/matchContext' + +describe('Match context data structure', () => { + it('should prove creating new match context form other outputs independent data structure', () => { + const first = createMatchContext() + + first.addIdentifierAlias({ + wildcard: '$$', + alias: 'ref', + aliasValue: 'identifier', + }) + + const second = createMatchContext(first.getAllAliases()) + + second.addIdentifierAlias({ + wildcard: '$$', + alias: 'ref2', + aliasValue: 'identifier2', + }) + + expect(first.getIdentifierAlias('ref')?.aliasValue).toBe('identifier') + expect(first.getIdentifierAlias('ref2')).toBe(null) + + expect(second.getIdentifierAlias('ref')?.aliasValue).toBe('identifier') + expect(second.getIdentifierAlias('ref2')?.aliasValue).toBe('identifier2') + }) + + it('should merge one context into another', () => { + const first = createMatchContext() + + first.addIdentifierAlias({ + wildcard: '$$', + alias: 'ref', + aliasValue: 'identifier', + }) + + const second = createMatchContext() + + second.addIdentifierAlias({ + wildcard: '$$', + alias: 'ref2', + aliasValue: 'identifier2', + }) + + first.merge(second.getAllAliases()) + + expect(first.getIdentifierAlias('ref')?.aliasValue).toBe('identifier') + expect(first.getIdentifierAlias('ref2')?.aliasValue).toBe('identifier2') + }) +}) diff --git a/packages/core/__tests__/utils.test.ts b/packages/core/__tests__/common/utils.test.ts similarity index 94% rename from packages/core/__tests__/utils.test.ts rename to packages/core/__tests__/common/utils.test.ts index 87a2858..68a3ce8 100644 --- a/packages/core/__tests__/utils.test.ts +++ b/packages/core/__tests__/common/utils.test.ts @@ -1,8 +1,12 @@ -import { getExtendedCodeFrame, regExpTest } from '/utils' +import { getExtendedCodeFrame, regExpTest } from '../../src/utils' import dedent from 'dedent' -import { createWildcardUtils } from '/wildcardUtilsFactory' +import { createWildcardUtils } from '../../src/wildcardUtilsFactory' +import { PoorNodeType } from '../../src/types' describe('Utils', () => { + const getIdentifierNodeName = (node: PoorNodeType) => node.name as string + const getNodeType = (node: PoorNodeType) => node.type as string + const identifierTypes: string[] = [] // not needed for this test const numericWildcard = '0x0' const wildcardChar = '$' @@ -10,6 +14,8 @@ describe('Utils', () => { identifierTypes, numericWildcard, wildcardChar, + getIdentifierNodeName, + getNodeType, ) describe('should transform strings to regexes', () => { diff --git a/packages/core/__tests__/search/other.test.ts b/packages/core/__tests__/search/other.test.ts deleted file mode 100644 index 94df540..0000000 --- a/packages/core/__tests__/search/other.test.ts +++ /dev/null @@ -1,176 +0,0 @@ -import path from 'path' -import { useTraverseApproachTestOnly } from '../../src/config' -import { searchInStrings } from '../../src/searchInStrings' -import { getFilesList } from '/getFilesList' -import { searchInFileSystem } from '/searchInFs' - -describe('Other', () => { - let filesList = [] as string[] - - beforeAll(async () => { - filesList = await getFilesList({ - searchRoot: path.resolve(__dirname, '__fixtures__'), - omitGitIgnore: true, - }) - }) - - it('should not include the same result twice', () => { - const queries = [ - ` - type $$ = ScrollViewProps & $$$ - `, - ` - type $$ = $$$ & ScrollViewProps - `, - ] - - const { matches, errors } = searchInFileSystem({ - mode: 'include', - filePaths: filesList, - queryCodes: queries, - }) - - expect(errors.length).toBe(0) - expect(matches.length).toBe(1) - }) - - it('should not include the same result twice 2', () => { - const queries = [ - ` - <$$$ - $$={() => {}} - /> - `, - ` - <$$$ - $$={() => $$$} - /> - `, - ` - <$$$ - $$={() => {}} - > - - `, - ` - <$$$ - $$={() => $$$} - > - - `, - ] - - const { matches, errors } = searchInFileSystem({ - mode: 'include', - filePaths: filesList, - queryCodes: queries, - }) - - expect(errors).toHaveLength(0) - expect(matches.length).toBe(190) - }) - - it('should match anything', () => { - const queries = [ - ` - $$$ - `, - ] - - const { matches, errors } = searchInFileSystem({ - mode: 'include', - filePaths: filesList, - queryCodes: queries, - }) - - const countForTraversalMode = 6381 // will just match identifier-like nodes - const countForNonTraversalMode = 17149 // will match any node - - const expectedCount = useTraverseApproachTestOnly - ? countForTraversalMode - : countForNonTraversalMode - - expect(errors.length).toBe(0) - expect(matches.length).toBe(expectedCount) - }) - - it('Should properly match identifiers with multiple wildcard sections', () => { - const fileContent = ` - varOne; - var_two; - ` - - const queries = [`$$_$$`] - - const { matches, errors } = searchInStrings({ - mode: 'exact', - caseInsensitive: true, - queryCodes: queries, - files: [ - { - path: 'mock', - content: fileContent, - }, - ], - }) - - expect(errors.length).toBe(0) - expect(matches.length).toBe(1) - }) - - it('Should match try-catch block with statement and re-throw of error', () => { - const fileContent = ` - try { - await bla.bla.bla - } catch (error) { - logger.error(error); - throw error; - } - ` - - const queries = [`try {} catch($$) { Logger.error($$); throw $$ }`] - - const { matches, errors } = searchInStrings({ - mode: 'include', - caseInsensitive: true, - queryCodes: queries, - files: [ - { - path: 'mock', - content: fileContent, - }, - ], - }) - - expect(errors.length).toBe(0) - expect(matches.length).toBe(1) - }) - - it('Should match class with static method', () => { - const fileContent = ` - class Test { - method() { return 0 } - static getInstance(){ - return new Test() - } - } - ` - - const queries = [`class $$ {static getInstance(){}}`] - - const { matches, errors } = searchInStrings({ - mode: 'include', - caseInsensitive: true, - queryCodes: queries, - files: [ - { - path: 'mock', - content: fileContent, - }, - ], - }) - - expect(errors.length).toBe(0) - expect(matches.length).toBe(1) - }) -}) diff --git a/packages/core/__tests__/utils.ts b/packages/core/__tests__/utils.ts index e34e0d1..352b274 100644 --- a/packages/core/__tests__/utils.ts +++ b/packages/core/__tests__/utils.ts @@ -1,8 +1,13 @@ -import { compareCode as compareCodeBase } from '/astUtils' -import { ParserType } from '/types' +import { + compareCode as compareCodeBase, + compareAst as compareAstBase, +} from '../src/astUtils' +import { PoorNodeType } from '../src/types' import { parserSettingsMap } from '../src/parserSettings/index' +import path from 'path' +import { testParserTypeOverride } from '../src/testOnlyConfig' -const parserType = process.env.TEST_PARSER_TYPE as ParserType +export const parserType = testParserTypeOverride export const getParserSettings = () => { if (parserType === undefined) { @@ -19,3 +24,31 @@ export const compareCode = (codeA: string, codeB: string) => { return compareCodeBase(codeA, codeB, parserSettings) } + +export const compareAst = (astA: PoorNodeType, astB: PoorNodeType) => { + const parserSettings = getParserSettings() + + return compareAstBase(astA, astB, parserSettings) +} + +export const compareAstToCode = (ast: PoorNodeType, code: string) => { + const parserSettings = getParserSettings() + + const astFromCode = parserSettings.unwrapExpressionStatement( + parserSettings.getProgramBodyFromRootNode( + parserSettings.parseCode(code), + )[0], + ) + + return compareAstBase(ast, astFromCode, parserSettings) +} + +export const fixturesPath = path.resolve( + process.cwd(), + '__tests__/__fixtures__', +) + +export const fixturesOtherPath = path.resolve( + process.cwd(), + '__tests__/__fixturesOther__', +) diff --git a/packages/core/declarations.d.ts b/packages/core/declarations.d.ts new file mode 100644 index 0000000..2df47c9 --- /dev/null +++ b/packages/core/declarations.d.ts @@ -0,0 +1,3 @@ +declare module '@babel/eslint-parser' +declare module 'espree' +declare module '@angular-eslint/template-parser' diff --git a/packages/core/jest.config.js b/packages/core/jest.config.js index 58ef380..a6c114c 100644 --- a/packages/core/jest.config.js +++ b/packages/core/jest.config.js @@ -1,25 +1,187 @@ -const { pathsToModuleNameMapper } = require('ts-jest') -const fs = require('fs') +const sharedConfig = { + preset: 'ts-jest', + testEnvironment: 'node', + setupFiles: ['/jest/shared.setup.ts'], +} + +const javaScriptWithJSXParserTestFiles = [ + '/__tests__/JavaScript/**/*.test.ts', + '/__tests__/JavaScriptWithJSX/**/*.test.ts', +] -const tsConfig = JSON.parse( - fs - .readFileSync(__dirname + '/tsconfig.json') - .toString() - .replace(/^(\s)*\/\//gm, '') - .replace(/\/\*.+?\*\//gm, ''), -) +const typeScriptParserTestFiles = [ + '/__tests__/JavaScript/**/*.test.ts', + '/__tests__/JavaScriptWithJSX/**/*.test.ts', + '/__tests__/TypeScript/**/*.test.ts', +] + +const htmlParserTestFiles = ['/__tests__/HTML/**/*.test.ts'] +const cssParserTestFiles = ['/__tests__/CSS/**/*.test.ts'] +const pythonParserTestFiles = ['/__tests__/Python/**/*.test.ts'] +const luaParserTestFiles = ['/__tests__/Lua/**/*.test.ts'] +const csharpParserTestFiles = ['/__tests__/CSharp/**/*.test.ts'] module.exports = { - preset: 'ts-jest', - testEnvironment: 'node', - moduleNameMapper: pathsToModuleNameMapper(tsConfig.compilerOptions.paths, { - prefix: '', - }), testPathIgnorePatterns: [ '__fixtures__', '__fixturesOther__', 'ts-dist', 'utils.ts', ], - setupFiles: ['./jest.setup.js'], + projects: [ + { + displayName: { name: 'common', color: 'white' }, + ...sharedConfig, + testMatch: ['/__tests__/common/**/*.test.ts'], + }, + { + displayName: { name: 'text-search', color: 'cyan' }, + ...sharedConfig, + testMatch: ['/__tests__/TextSearch/**/*.test.ts'], + }, + { + ...sharedConfig, + displayName: { name: 'typescript-eslint-parser', color: 'magenta' }, + testMatch: typeScriptParserTestFiles, + setupFiles: [ + '/jest/shared.setup.ts', + '/jest/typescript-eslint-parser.setup.ts', + ], + }, + { + ...sharedConfig, + displayName: { + name: 'typescript-eslint-parser:traversal', + color: 'magenta', + }, + testMatch: typeScriptParserTestFiles, + setupFiles: [ + '/jest/shared.setup.ts', + '/jest/typescript-eslint-parser:traversal.setup.ts', + ], + }, + { + ...sharedConfig, + displayName: { name: 'babel', color: 'yellow' }, + testMatch: typeScriptParserTestFiles, + setupFiles: [ + '/jest/shared.setup.ts', + '/jest/babel.setup.ts', + ], + }, + { + ...sharedConfig, + displayName: { + name: 'babel:traversal', + color: 'yellow', + }, + testMatch: typeScriptParserTestFiles, + setupFiles: [ + '/jest/shared.setup.ts', + '/jest/babel:traversal.setup.ts', + ], + }, + { + ...sharedConfig, + displayName: { name: 'babel-eslint-parser', color: 'yellow' }, + testMatch: javaScriptWithJSXParserTestFiles, + setupFiles: [ + '/jest/shared.setup.ts', + '/jest/babel-eslint-parser.setup.ts', + ], + }, + { + ...sharedConfig, + displayName: { + name: 'babel-eslint-parser:traversal', + color: 'yellow', + }, + testMatch: javaScriptWithJSXParserTestFiles, + setupFiles: [ + '/jest/shared.setup.ts', + '/jest/babel-eslint-parser:traversal.setup.ts', + ], + }, + { + ...sharedConfig, + displayName: { name: 'esprima', color: 'gray' }, + testMatch: javaScriptWithJSXParserTestFiles, + setupFiles: [ + '/jest/shared.setup.ts', + '/jest/esprima.setup.ts', + ], + }, + { + ...sharedConfig, + displayName: { name: 'esprima:traversal', color: 'gray' }, + testMatch: javaScriptWithJSXParserTestFiles, + setupFiles: [ + '/jest/shared.setup.ts', + '/jest/esprima:traversal.setup.ts', + ], + }, + { + ...sharedConfig, + displayName: { name: 'espree', color: 'blue' }, + testMatch: javaScriptWithJSXParserTestFiles, + setupFiles: [ + '/jest/shared.setup.ts', + '/jest/espree.setup.ts', + ], + }, + { + ...sharedConfig, + displayName: { name: 'espree:traversal', color: 'blue' }, + testMatch: javaScriptWithJSXParserTestFiles, + setupFiles: [ + '/jest/shared.setup.ts', + '/jest/espree:traversal.setup.ts', + ], + }, + { + ...sharedConfig, + displayName: { name: 'angular-eslint-template-parser', color: 'orange' }, + testMatch: htmlParserTestFiles, + setupFiles: [ + '/jest/shared.setup.ts', + '/jest/angular-eslint-template-parser.setup.ts', + ], + }, + { + ...sharedConfig, + displayName: { name: 'css-tree', color: 'blue' }, + testMatch: cssParserTestFiles, + setupFiles: [ + '/jest/shared.setup.ts', + '/jest/css-tree.setup.ts', + ], + }, + { + ...sharedConfig, + displayName: { name: 'python', color: 'blue' }, + testMatch: pythonParserTestFiles, + setupFiles: [ + '/jest/shared.setup.ts', + '/jest/python.setup.ts', + ], + }, + { + ...sharedConfig, + displayName: { name: 'lua', color: 'gray' }, + testMatch: luaParserTestFiles, + setupFiles: [ + '/jest/shared.setup.ts', + '/jest/lua.setup.ts', + ], + }, + { + ...sharedConfig, + displayName: { name: 'csharp', color: 'magenta' }, + testMatch: csharpParserTestFiles, + setupFiles: [ + '/jest/shared.setup.ts', + '/jest/csharp.setup.ts', + ], + }, + ], } diff --git a/packages/core/jest.setup.js b/packages/core/jest.setup.js deleted file mode 100644 index b1e2bd6..0000000 --- a/packages/core/jest.setup.js +++ /dev/null @@ -1 +0,0 @@ -process.env.NODE_ENV = 'test' diff --git a/packages/core/jest/angular-eslint-template-parser.setup.ts b/packages/core/jest/angular-eslint-template-parser.setup.ts new file mode 100644 index 0000000..518c002 --- /dev/null +++ b/packages/core/jest/angular-eslint-template-parser.setup.ts @@ -0,0 +1,4 @@ +global.testSettings = { + parserType: 'angular-eslint-template-parser', + isTraversal: false, +} diff --git a/packages/core/jest/babel-eslint-parser.setup.ts b/packages/core/jest/babel-eslint-parser.setup.ts new file mode 100644 index 0000000..42e8fa9 --- /dev/null +++ b/packages/core/jest/babel-eslint-parser.setup.ts @@ -0,0 +1,4 @@ +global.testSettings = { + parserType: 'babel-eslint-parser', + isTraversal: false, +} diff --git a/packages/core/jest/babel-eslint-parser:traversal.setup.ts b/packages/core/jest/babel-eslint-parser:traversal.setup.ts new file mode 100644 index 0000000..2ada1d1 --- /dev/null +++ b/packages/core/jest/babel-eslint-parser:traversal.setup.ts @@ -0,0 +1,4 @@ +global.testSettings = { + parserType: 'babel-eslint-parser', + isTraversal: true, +} diff --git a/packages/core/jest/babel.setup.ts b/packages/core/jest/babel.setup.ts new file mode 100644 index 0000000..5f7cc46 --- /dev/null +++ b/packages/core/jest/babel.setup.ts @@ -0,0 +1,4 @@ +global.testSettings = { + parserType: 'babel', + isTraversal: false, +} diff --git a/packages/core/jest/babel:traversal.setup.ts b/packages/core/jest/babel:traversal.setup.ts new file mode 100644 index 0000000..d7cbbef --- /dev/null +++ b/packages/core/jest/babel:traversal.setup.ts @@ -0,0 +1,4 @@ +global.testSettings = { + parserType: 'babel', + isTraversal: true, +} diff --git a/packages/core/jest/csharp.setup.ts b/packages/core/jest/csharp.setup.ts new file mode 100644 index 0000000..2151adc --- /dev/null +++ b/packages/core/jest/csharp.setup.ts @@ -0,0 +1,4 @@ +global.testSettings = { + parserType: 'csharp', + isTraversal: false, +} diff --git a/packages/core/jest/css-tree.setup.ts b/packages/core/jest/css-tree.setup.ts new file mode 100644 index 0000000..014a917 --- /dev/null +++ b/packages/core/jest/css-tree.setup.ts @@ -0,0 +1,4 @@ +global.testSettings = { + parserType: 'css-tree', + isTraversal: false, +} diff --git a/packages/core/jest/espree.setup.ts b/packages/core/jest/espree.setup.ts new file mode 100644 index 0000000..a773abc --- /dev/null +++ b/packages/core/jest/espree.setup.ts @@ -0,0 +1,4 @@ +global.testSettings = { + parserType: 'espree', + isTraversal: false, +} diff --git a/packages/core/jest/espree:traversal.setup.ts b/packages/core/jest/espree:traversal.setup.ts new file mode 100644 index 0000000..02d3041 --- /dev/null +++ b/packages/core/jest/espree:traversal.setup.ts @@ -0,0 +1,4 @@ +global.testSettings = { + parserType: 'espree', + isTraversal: true, +} diff --git a/packages/core/jest/esprima.setup.ts b/packages/core/jest/esprima.setup.ts new file mode 100644 index 0000000..3d0f844 --- /dev/null +++ b/packages/core/jest/esprima.setup.ts @@ -0,0 +1,4 @@ +global.testSettings = { + parserType: 'esprima', + isTraversal: false, +} diff --git a/packages/core/jest/esprima:traversal.setup.ts b/packages/core/jest/esprima:traversal.setup.ts new file mode 100644 index 0000000..87df7d7 --- /dev/null +++ b/packages/core/jest/esprima:traversal.setup.ts @@ -0,0 +1,4 @@ +global.testSettings = { + parserType: 'esprima', + isTraversal: true, +} diff --git a/packages/core/jest/lua.setup.ts b/packages/core/jest/lua.setup.ts new file mode 100644 index 0000000..70af4be --- /dev/null +++ b/packages/core/jest/lua.setup.ts @@ -0,0 +1,4 @@ +global.testSettings = { + parserType: 'lua', + isTraversal: false, +} diff --git a/packages/core/jest/python.setup.ts b/packages/core/jest/python.setup.ts new file mode 100644 index 0000000..3605358 --- /dev/null +++ b/packages/core/jest/python.setup.ts @@ -0,0 +1,4 @@ +global.testSettings = { + parserType: 'python', + isTraversal: false, +} diff --git a/packages/core/jest/shared.setup.ts b/packages/core/jest/shared.setup.ts new file mode 100644 index 0000000..cae8393 --- /dev/null +++ b/packages/core/jest/shared.setup.ts @@ -0,0 +1,12 @@ +process.env.NODE_ENV = 'test' + +global.console = { + ...console, + warn: (...inputs) => { + if (typeof inputs[0] === 'string' && inputs[0].includes('Browserslist')) { + return + } else { + return console.log('console.warn', ...inputs) + } + }, +} diff --git a/packages/core/jest/typescript-eslint-parser.setup.ts b/packages/core/jest/typescript-eslint-parser.setup.ts new file mode 100644 index 0000000..7ca8b6c --- /dev/null +++ b/packages/core/jest/typescript-eslint-parser.setup.ts @@ -0,0 +1,4 @@ +global.testSettings = { + parserType: 'typescript-eslint-parser', + isTraversal: false, +} diff --git a/packages/core/jest/typescript-eslint-parser:traversal.setup.ts b/packages/core/jest/typescript-eslint-parser:traversal.setup.ts new file mode 100644 index 0000000..62bb4ff --- /dev/null +++ b/packages/core/jest/typescript-eslint-parser:traversal.setup.ts @@ -0,0 +1,4 @@ +global.testSettings = { + parserType: 'typescript-eslint-parser', + isTraversal: true, +} diff --git a/packages/core/package.json b/packages/core/package.json index fc6b395..ce75449 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,7 +1,7 @@ { "name": "@codeque/core", - "version": "0.4.0", - "description": "Supercharged multiline code search and replace tool", + "version": "0.6.1", + "description": "Multiline code search for every language. Structural code search for JavaScript, TypeScript, HTML and CSS", "author": "Jakub Mazurek (@jayu) ", "license": "Sustainable Use License", "engines": { @@ -26,10 +26,13 @@ "url": "https://github.com/codeque-co/codeque" }, "devDependencies": { + "@angular-eslint/template-parser": "^15.2.1", + "@babel/eslint-parser": "7.28.5", + "@babel/generator": "7.28.5", "@types/dedent": "^0.7.0", "@types/esprima": "^4.0.3", "@types/glob": "^7.2.0", - "@types/jest": "^27.0.3", + "@types/jest": "^29.4.0", "@types/mock-fs": "^4.13.1", "@types/node-fetch": "^3.0.3", "@types/object.omit": "^3.0.0", @@ -39,43 +42,58 @@ "babel-jest": "^27.4.4", "babel-plugin-root-import": "^6.6.0", "concurrently": "^7.2.1", + "css-tree": "^2.3.1", "deep-object-diff": "^1.1.7", + "espree": "^9.5.2", "esprima": "^4.0.1", "jest": "^27.4.4", "mock-fs": "^5.1.2", "node-fetch-commonjs": "^3.1.1", "release-it": "^15.0.0", "ts-jest": "^27.1.1", - "unzipper": "^0.10.11" + "unzipper": "^0.10.11", + "typescript": "5.9.3" }, "dependencies": { - "@babel/parser": "7.19.4", - "@babel/plugin-syntax-typescript": "7.18.6", - "@babel/types": "7.19.4", + "@babel/parser": "7.28.5", + "@babel/plugin-syntax-typescript": "7.27.1", + "@types/css-tree": "^2.3.1", "dedent": "^0.7.0", "dpdm": "^3.12.0", "glob-escape": "^0.0.2", "ignore": "^5.1.9", - "minimatch": "^5.1.0" + "minimatch": "^5.1.0", + "web-tree-sitter": "^0.20.8" }, "peerDependencies": { "@typescript-eslint/parser": "^5.50.0", "esprima": "^4.0.1" }, "scripts": { - "build": "rm -rf dist && concurrently \"yarn build:declarations\" \"BABEL_ENV=production yarn build:code\" ", + "build": "rm -rf dist && concurrently \"yarn build:declarations\" \"BABEL_ENV=production yarn build:code\"", "build:performance": "rm -rf dist && concurrently \"yarn build:declarations\" \"BABEL_ENV=production_performance yarn build:code\" ", "build:test": "rm -rf dist && concurrently \"yarn build:declarations\" \"BABEL_ENV=test yarn build:code\" ", "build:watch": "rm -rf dist && concurrently \"yarn build:declarations --watch\" \"yarn build:code --watch\" ", "build:declarations": "tsc --project tsconfig.build.json", "build:code": "babel ./src --extensions \".ts,.tsx\" -d dist", "typecheck": "tsc --project tsconfig.json", - "test": "yarn build:test && yarn test:babel && yarn test:typescript-eslint && yarn test:babel:traversal && yarn test:typescript-eslint:traversal", - "test:babel": "TEST_PARSER_TYPE=babel jest", - "test:babel:traversal": "TEST_PARSER_TYPE=babel TEST_TRAVERSAL=true jest", - "test:typescript-eslint": "TEST_PARSER_TYPE=typescript-eslint jest", - "test:typescript-eslint:traversal": "TEST_PARSER_TYPE=typescript-eslint TEST_TRAVERSAL=true jest", - "test:esprima": "TEST_PARSER_TYPE=esprima jest", + "test": "yarn build:test && NODE_OPTIONS=--max-old-space-size=4000 yarn jest --maxWorkers=25%", + "test:babel": "jest --selectProjects=babel", + "test:common": "jest --selectProjects=common", + "test:babel:traversal": "jest --selectProjects=babel:traversal", + "test:babel-eslint-parser": "jest --selectProjects=babel-eslint-parser", + "test:babel-eslint-parser:traversal": "jest --selectProjects=babel-eslint-parser:traversal", + "test:typescript-eslint-parser": "jest --selectProjects=typescript-eslint-parser", + "test:typescript-eslint-parser:traversal": "jest --selectProjects=typescript-eslint-parser:traversal", + "test:esprima": "jest --selectProjects=esprima", + "test:esprima:traversal": "jest --selectProjects=esprima:traversal", + "test:espree": "jest --selectProjects=espree", + "test:espree:traversal": "jest --selectProjects=espree:traversal", + "test:angular-eslint-template-parser": "jest --selectProjects=angular-eslint-template-parser", + "test:python": "jest --selectProjects=python", + "test:lua": "jest --selectProjects=lua", + "test:csharp": "jest --selectProjects=csharp", + "test:css-tree": "jest --selectProjects=css-tree", "test:circular": "dpdm --exit-code circular:1 --tree=false --warning=false './src/**'", "test:setup": "node ./tools/getFixtures.js", "lint": "eslint --ext .js,.ts src", @@ -112,4 +130,4 @@ "regexp search", "json search" ] -} \ No newline at end of file +} diff --git a/packages/core/src/astUtils.ts b/packages/core/src/astUtils.ts index becb3db..4af9f88 100644 --- a/packages/core/src/astUtils.ts +++ b/packages/core/src/astUtils.ts @@ -1,4 +1,5 @@ -import { PoorNodeType, ParserSettings, WildcardUtils, Match } from './types' +import { MatchContextAliases } from './matchContext' +import { Match, ParserSettings, PoorNodeType, WildcardUtils } from './types' import { isNullOrUndef } from './utils' export const isNodeArray = ( @@ -31,19 +32,22 @@ const isNodeKey = ( node: PoorNodeType, key: string, keysToCheck: ParserSettings['astPropsToSkip'], + getNodeType: ParserSettings['getNodeType'], ) => + key.startsWith('__') || keysToCheck.some((keyToCheck) => typeof keyToCheck === 'string' ? key === keyToCheck - : node.type === keyToCheck.type && keyToCheck.key === key, + : getNodeType(node) === keyToCheck.type && keyToCheck.key === key, ) export const getKeysToCompare = ( node: PoorNodeType, astPropsToSkip: ParserSettings['astPropsToSkip'], + getNodeType: ParserSettings['getNodeType'], ) => { return Object.keys(node).filter( - (key) => !isNodeKey(node, key, astPropsToSkip), + (key) => !isNodeKey(node, key, astPropsToSkip, getNodeType), ) } @@ -52,12 +56,12 @@ export const getSetsOfKeysToCompare = ( queryNode: PoorNodeType, isExact: boolean, astPropsToSkip: ParserSettings['astPropsToSkip'], - isNodeFieldOptional: ParserSettings['isNodeFieldOptional'], + getNodeType: ParserSettings['getNodeType'], ) => { - const allFileKeys = getKeysToCompare(fileNode, astPropsToSkip) - const allQueryKeys = getKeysToCompare(queryNode, astPropsToSkip) + const allFileKeys = getKeysToCompare(fileNode, astPropsToSkip, getNodeType) + const allQueryKeys = getKeysToCompare(queryNode, astPropsToSkip, getNodeType) - if (isExact || fileNode.type !== queryNode.type) { + if (isExact || getNodeType(fileNode) !== getNodeType(queryNode)) { return [allFileKeys, allQueryKeys, allFileKeys, allQueryKeys] } @@ -71,8 +75,7 @@ export const getSetsOfKeysToCompare = ( const fileKeysToRemove = allFileKeys.filter( (fileKey) => - (!allQueryKeys.includes(fileKey) || isNullOrUndef(queryNode[fileKey])) && - isNodeFieldOptional(fileNode.type as string, fileKey), + !allQueryKeys.includes(fileKey) || isNullOrUndef(queryNode[fileKey]), ) const includeFileKeys = allFileKeys.filter( @@ -92,11 +95,12 @@ export const getSetsOfKeysToCompare = ( const removeKeysFromNode = ( obj: PoorNodeType, keys: ParserSettings['astPropsToSkip'], + getNodeType: ParserSettings['getNodeType'], ) => { const newObj = {} as PoorNodeType Object.entries(obj).forEach(([key, val]) => { - if (!isNodeKey(obj, key, keys)) { + if (!isNodeKey(obj, key, keys, getNodeType)) { newObj[key] = val } }) @@ -110,12 +114,15 @@ export const cleanupAst = ( isNode: ParserSettings['isNode'] shouldCompareNode: ParserSettings['shouldCompareNode'] astPropsToSkip: ParserSettings['astPropsToSkip'] - sanitizeNode: ParserSettings['sanitizeNode'] + getSanitizedNodeValue: ParserSettings['getSanitizedNodeValue'] + getNodeType: ParserSettings['getNodeType'] }, ) => { - parserSettings.sanitizeNode(ast) - - const cleanedAst = removeKeysFromNode(ast, parserSettings.astPropsToSkip) + const cleanedAst = removeKeysFromNode( + ast, + parserSettings.astPropsToSkip, + parserSettings.getNodeType, + ) Object.keys(cleanedAst).forEach((key) => { if (parserSettings.isNode(cleanedAst[key] as PoorNodeType)) { @@ -123,40 +130,78 @@ export const cleanupAst = ( cleanedAst[key] as PoorNodeType, parserSettings, ) - } - - if (isNodeArray(cleanedAst[key] as PoorNodeType[], parserSettings.isNode)) { + } else if ( + isNodeArray(cleanedAst[key] as PoorNodeType[], parserSettings.isNode) + ) { cleanedAst[key] = (cleanedAst[key] as PoorNodeType[]) .filter(parserSettings.shouldCompareNode) .map((subAst) => cleanupAst(subAst, parserSettings)) + } else { + cleanedAst[key] = parserSettings.getSanitizedNodeValue( + parserSettings.getNodeType(cleanedAst), + key, + cleanedAst[key], + ) } }) return cleanedAst } +type CompareCodeParserSettingsSubset = { + isNode: ParserSettings['isNode'] + shouldCompareNode: ParserSettings['shouldCompareNode'] + astPropsToSkip: ParserSettings['astPropsToSkip'] + parseCode: ParserSettings['parseCode'] + getSanitizedNodeValue: ParserSettings['getSanitizedNodeValue'] + getProgramNodeFromRootNode: ParserSettings['getProgramNodeFromRootNode'] + getNodeType: ParserSettings['getNodeType'] +} + export const compareCode = ( codeA: string, codeB: string, - parserSettings: { - isNode: ParserSettings['isNode'] - shouldCompareNode: ParserSettings['shouldCompareNode'] - astPropsToSkip: ParserSettings['astPropsToSkip'] - parseCode: ParserSettings['parseCode'] - sanitizeNode: ParserSettings['sanitizeNode'] - getProgramNodeFromRootNode: ParserSettings['getProgramNodeFromRootNode'] - }, + parserSettingsSubset: CompareCodeParserSettingsSubset, ) => { - const astA = parserSettings.getProgramNodeFromRootNode( - parserSettings.parseCode(codeA), + const astA = parserSettingsSubset.getProgramNodeFromRootNode( + parserSettingsSubset.parseCode(codeA), ) - const astB = parserSettings.getProgramNodeFromRootNode( - parserSettings.parseCode(codeB), + const astB = parserSettingsSubset.getProgramNodeFromRootNode( + parserSettingsSubset.parseCode(codeB), ) - const cleanedA = cleanupAst(astA, parserSettings) - const cleanedB = cleanupAst(astB, parserSettings) + return compareAst(astA, astB, parserSettingsSubset) +} + +const cloneAst = (ast: PoorNodeType) => { + const uniqueObjectLikeValuesCache: unknown[] = [] + + /** + * Skip circular references (like '__parent') + */ + const stringified = JSON.stringify(ast, (_: string, value: unknown) => { + if (typeof value === 'object' && value !== null) { + // Duplicate reference found, discard key + if (uniqueObjectLikeValuesCache.includes(value)) return + + // Store value in our collection + uniqueObjectLikeValuesCache.push(value) + } + + return value + }) + + return JSON.parse(stringified) +} + +export const compareAst = ( + astA: PoorNodeType, + astB: PoorNodeType, + parserSettingsSubset: CompareCodeParserSettingsSubset, +) => { + const cleanedA = cleanupAst(cloneAst(astA), parserSettingsSubset) + const cleanedB = cleanupAst(cloneAst(astB), parserSettingsSubset) return JSON.stringify(cleanedA) === JSON.stringify(cleanedB) } @@ -165,16 +210,25 @@ export const sortByLeastIdentifierStrength = ( nodeA: PoorNodeType, nodeB: PoorNodeType, wildcardUtils: WildcardUtils, + getIdentifierNodeName: (node: PoorNodeType) => string, ) => { - const aWildcard = wildcardUtils.getWildcardFromNode(nodeA) - const bWildcard = wildcardUtils.getWildcardFromNode(nodeB) + const aWildcards = wildcardUtils.getIdentifierWildcardsFromNode(nodeA) + const bWildcards = wildcardUtils.getIdentifierWildcardsFromNode(nodeB) - const aIsIdentifierWithWildcard = aWildcard !== null - const bIsIdentifierWithWildcard = bWildcard !== null + const aIsIdentifierWithWildcards = aWildcards.length > 0 + const bIsIdentifierWithWildcards = bWildcards.length > 0 - if (aIsIdentifierWithWildcard && bIsIdentifierWithWildcard) { - const idA = aWildcard.wildcardWithoutRef - const idB = bWildcard.wildcardWithoutRef + if (aIsIdentifierWithWildcards && bIsIdentifierWithWildcards) { + const idA = wildcardUtils.removeWildcardAliasesFromIdentifierName( + getIdentifierNodeName(nodeA), + ) + const idB = wildcardUtils.removeWildcardAliasesFromIdentifierName( + getIdentifierNodeName(nodeB), + ) + + if (idA === idB) { + return 0 + } if (idA === wildcardUtils.nodesTreeWildcard) { return 1 @@ -196,11 +250,11 @@ export const sortByLeastIdentifierStrength = ( return bNonWildcardCharsLen - aNonWildcardCharsLen } - if (aIsIdentifierWithWildcard) { + if (aIsIdentifierWithWildcards) { return 1 } - if (bIsIdentifierWithWildcard) { + if (bIsIdentifierWithWildcards) { return -1 } @@ -210,10 +264,12 @@ export const sortByLeastIdentifierStrength = ( export const getMatchFromNode = ( node: PoorNodeType, parserSettings: Pick, + aliases: MatchContextAliases, ) => ({ ...parserSettings.getNodePosition(node), node, + aliases, } as Match) export const getVisitorKeysForQueryNodeType = ( diff --git a/packages/core/src/config.ts b/packages/core/src/config.ts deleted file mode 100644 index 150e61b..0000000 --- a/packages/core/src/config.ts +++ /dev/null @@ -1,2 +0,0 @@ -export const useTraverseApproachTestOnly = - process.env['TEST_TRAVERSAL'] === 'true' diff --git a/packages/core/src/getFilesList.ts b/packages/core/src/getFilesList.ts index 4def437..f64b20a 100644 --- a/packages/core/src/getFilesList.ts +++ b/packages/core/src/getFilesList.ts @@ -2,7 +2,7 @@ import path from 'path' import { promises as fs } from 'fs' import ignore from 'ignore' -import { asyncFilter, measureStart } from './utils' +import { measureStart } from './utils' import minimatch from 'minimatch' import { parseDependencyTree } from 'dpdm/lib/index.js' import { spawnSync } from 'child_process' @@ -12,6 +12,11 @@ import escapeGlob from 'glob-escape' import { HardStopFlag } from './types' export const typeScriptFamilyExtensionTester = /\.(js|jsx|ts|tsx|json|mjs|cjs)$/ +export const htmlFamilyExtensionTester = /\.(html|htm)$/ +export const cssExtensionTester = /\.(css)$/ +export const pythonExtensionTester = /\.(py)$/ +export const luaExtensionTester = /\.(lua)$/ +export const csharpExtensionTester = /\.(cs)$/ /** * @deprecated use `typeScriptFamilyExtensionTester` instead @@ -161,7 +166,7 @@ export const filterExtensions = ( return filesList.filter((filePath) => extensionTester.test(filePath)) } -type GetFilesListArgs = { +export type GetFilesListArgs = { searchRoot: string entryPoint?: string byGitChanges?: boolean @@ -174,6 +179,32 @@ type GetFilesListArgs = { extensionTester?: RegExp } +const findRepoRoot = async ({ + searchRoot, + fsRoot, +}: { + searchRoot: string + fsRoot: string +}) => { + let currentDir = path.resolve(searchRoot) + while (currentDir !== fsRoot) { + try { + const gitDir = path.join(currentDir, '.git') + const stat = await fs.lstat(gitDir) + + if (stat.isDirectory()) { + return currentDir + } + } catch (_e) { + // .git not found, continue + } + + currentDir = path.dirname(currentDir) + } + + return searchRoot +} + export const getFilesList = async ({ searchRoot: _searchRoot, entryPoint = undefined, @@ -199,25 +230,21 @@ export const getFilesList = async ({ } else { const InitialIgnore = ignoreNodeModules ? ['node_modules'] : [] - // Get parent to root gitignore if (!omitGitIgnore) { - const searchRootSegments = searchRoot - .replace(fsRoot, '') - .split(pathSeparatorChar) + const repoRoot = await findRepoRoot({ searchRoot, fsRoot }) + const parentPaths = [] + let currentPath = searchRoot - const pathSegmentsToSystemRoot = [] - - for (let i = 0; i < searchRootSegments.length; i++) { - let currentPath = searchRootSegments.slice(0, i).join(pathSeparatorChar) - - currentPath = fsRoot + currentPath - - pathSegmentsToSystemRoot.push(currentPath) + while (currentPath !== repoRoot) { + parentPaths.push(currentPath) + currentPath = path.dirname(currentPath) } + parentPaths.push(repoRoot) + const parentDirsIgnore = ( await Promise.all( - pathSegmentsToSystemRoot.map((parentPath) => + parentPaths.map((parentPath) => getGitIgnoreContentForDirectory(parentPath), ), ) @@ -258,15 +285,38 @@ export const getFilesList = async ({ // add initial '/' or 'C:\' back .map((p) => `${fsRoot}${p}`) - let directories = await asyncFilter( - filteredAbsolutePaths, - async (pathName) => { - const stat = await fs.lstat(pathName) - - return stat.isDirectory() - }, + const absolutePathsWithStats = await Promise.all( + filteredAbsolutePaths.map(async (absolutePath) => { + try { + let resolvedPath = absolutePath + let stat = await fs.lstat(absolutePath) + + if (stat.isSymbolicLink()) { + resolvedPath = await fs.realpath(absolutePath) + stat = await fs.stat(resolvedPath) + } + + return { + absolutePath, + size: stat.size, + isFile: stat.isFile(), + isDirectory: stat.isDirectory(), + } + } catch (e) { + return { + absolutePath, + size: -1, + isFile: false, + isDirectory: false, + } + } + }), ) + let directories = absolutePathsWithStats + .filter(({ isDirectory }) => isDirectory) + .map(({ absolutePath }) => absolutePath) + directories = directories.filter((pathName) => { const isOnBlackList = directoriesBlackList.some((directoryName) => pathName.endsWith(directoryName), @@ -275,16 +325,12 @@ export const getFilesList = async ({ return !isOnBlackList }) - const files = await asyncFilter( - filteredAbsolutePaths, - async (pathName) => { - const stat = await fs.lstat(pathName) - - return ( - stat.isFile() && (stat.size < bigFileSizeInBytes || searchBigFiles) - ) - }, - ) + const files = absolutePathsWithStats + .filter( + ({ isFile, size }) => + (isFile && size < bigFileSizeInBytes) || searchBigFiles, + ) + .map(({ absolutePath }) => absolutePath) const directoriesScanResult = ( await Promise.all(directories.map((dir) => scan(dir, localIgnore))) diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index f0fa5fd..8610240 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -1,7 +1,6 @@ export { default as searchInStrings } from './searchInStrings' export { searchMultiThread } from './searchMultiThread' export { searchInFileSystem } from './searchInFs' - export { getMode, groupMatchesByFile } from './utils' export { parseQueries } from './parseQuery' export { @@ -9,6 +8,11 @@ export { filterIncludeExclude, extensionTester, typeScriptFamilyExtensionTester, + htmlFamilyExtensionTester, + cssExtensionTester, + pythonExtensionTester, + luaExtensionTester, + csharpExtensionTester, pathToPosix, filterExtensions, } from './getFilesList' @@ -22,6 +26,7 @@ import { getMatchFromNode, getVisitorKeysForQueryNodeType } from './astUtils' import { validateMatch } from './searchStages/validateMatch' import { getLocationOfMultilineMatch } from './searchStages/getLocationOfMultilineMatch' import { traverseAndMatch } from './searchStages/traverseAndMatch' +import { createMatchContext } from './matchContext' export const __internal = { parserSettingsMap, @@ -31,4 +36,5 @@ export const __internal = { getVisitorKeysForQueryNodeType, getLocationOfMultilineMatch, traverseAndMatch, + createMatchContext, } diff --git a/packages/core/src/index.web.ts b/packages/core/src/index.web.ts index 2a5598e..6462f43 100644 --- a/packages/core/src/index.web.ts +++ b/packages/core/src/index.web.ts @@ -1,2 +1,7 @@ export * from './types' export { default as searchInStrings } from './searchInStrings' +import { parserSettingsMap } from './parserSettings/index' + +export const __internal = { + parserSettingsMap, +} diff --git a/packages/core/src/matchContext.ts b/packages/core/src/matchContext.ts new file mode 100644 index 0000000..9ec7965 --- /dev/null +++ b/packages/core/src/matchContext.ts @@ -0,0 +1,122 @@ +import { Mode, ParserSettings, PoorNodeType } from './types' + +export type IdentifierWildcardAlias = { + alias: string + aliasValue: string + wildcard: string +} + +export type StringWildcardAlias = { + alias: string + aliasValue: string + wildcard: string +} + +export type NodesTreeWildcardAlias = { + alias: string + aliasNode: PoorNodeType + aliasValue: string + wildcard: string +} + +type IdentifierWildcardAliasesMap = Record< + IdentifierWildcardAlias['alias'], + IdentifierWildcardAlias +> + +type StringWildcardAliasesMap = Record< + StringWildcardAlias['alias'], + StringWildcardAlias +> + +type NodesTreeWildcardAliasesMap = Record< + NodesTreeWildcardAlias['alias'], + NodesTreeWildcardAlias +> + +export type MatchContextAliases = { + identifierAliasesMap: IdentifierWildcardAliasesMap + stringAliasesMap: StringWildcardAliasesMap + nodesTreeAliasesMap: NodesTreeWildcardAliasesMap +} + +export type MatchContext = { + addIdentifierAlias: (ref: IdentifierWildcardAlias) => void + getIdentifierAlias: (alias: string) => IdentifierWildcardAlias | null + addStringAlias: (ref: StringWildcardAlias) => void + getStringAlias: (alias: string) => StringWildcardAlias | null + addNodesTreeAlias: (ref: NodesTreeWildcardAlias) => void + getNodesTreeAlias: (alias: string) => NodesTreeWildcardAlias | null + getAllAliases: () => MatchContextAliases + merge: (contextMatchAliases: MatchContextAliases) => void +} + +export const createMatchContext = ( + initialContext?: MatchContextAliases, +): MatchContext => { + let identifierAliasesMap: IdentifierWildcardAliasesMap = initialContext + ? { ...(initialContext.identifierAliasesMap ?? {}) } + : {} + const addIdentifierAlias = (wildcardAlias: IdentifierWildcardAlias) => { + identifierAliasesMap[wildcardAlias.alias] = wildcardAlias + } + + const getIdentifierAlias = (alias: string) => { + return identifierAliasesMap[alias] ?? null + } + + let stringAliasesMap: StringWildcardAliasesMap = initialContext + ? { ...(initialContext.stringAliasesMap ?? {}) } + : {} + const addStringAlias = (wildcardAlias: StringWildcardAlias) => { + stringAliasesMap[wildcardAlias.alias] = wildcardAlias + } + + const getStringAlias = (alias: string) => { + return stringAliasesMap[alias] ?? null + } + + let nodesTreeAliasesMap: NodesTreeWildcardAliasesMap = initialContext + ? { ...(initialContext.nodesTreeAliasesMap ?? {}) } + : {} + + const addNodesTreeAlias = (wildcardAlias: NodesTreeWildcardAlias) => { + nodesTreeAliasesMap[wildcardAlias.alias] = wildcardAlias + } + + const getNodesTreeAlias = (alias: string) => { + return nodesTreeAliasesMap[alias] ?? null + } + + const merge = (aliasesToMerge: MatchContextAliases) => { + identifierAliasesMap = { + ...identifierAliasesMap, + ...aliasesToMerge.identifierAliasesMap, + } + + stringAliasesMap = { + ...stringAliasesMap, + ...aliasesToMerge.stringAliasesMap, + } + + nodesTreeAliasesMap = { + ...nodesTreeAliasesMap, + ...aliasesToMerge.nodesTreeAliasesMap, + } + } + + return { + addIdentifierAlias, + getIdentifierAlias, + addStringAlias, + getStringAlias, + addNodesTreeAlias, + getNodesTreeAlias, + getAllAliases: () => ({ + identifierAliasesMap, + stringAliasesMap, + nodesTreeAliasesMap, + }), + merge, + } +} diff --git a/packages/core/src/parseQuery.ts b/packages/core/src/parseQuery.ts index 3247712..77111ad 100644 --- a/packages/core/src/parseQuery.ts +++ b/packages/core/src/parseQuery.ts @@ -6,41 +6,47 @@ import { PoorNodeType, Position, } from './types' -import { measureStart, SPACE_CHAR, normalizeText } from './utils' +import { measureStart, decomposeString } from './utils' import { isNodeArray, getKeysToCompare } from './astUtils' const MIN_TOKEN_LEN = 2 -export const decomposeString = (str: string, anyStringWildcardRegExp: RegExp) => - str - .split(anyStringWildcardRegExp) - .map((part) => normalizeText(part).split(SPACE_CHAR)) - .flat(1) - -export const getUniqueTokens = ( - queryNode: PoorNodeType, - caseInsensitive: boolean, - parserSettings: ParserSettings, - tokens: Set = new Set(), -) => { - const { numericLiteralUtils, stringLikeLiteralUtils } = parserSettings +const defaultGetUniqueTokensFromStringOrIdentifierNode = ({ + queryNode, + caseInsensitive, + parserSettings, +}: { + queryNode: PoorNodeType + caseInsensitive: boolean + parserSettings: Pick< + ParserSettings, + | 'isIdentifierNode' + | 'stringLikeLiteralUtils' + | 'getIdentifierNodeName' + | 'wildcardUtils' + > +}) => { + const { stringLikeLiteralUtils, getIdentifierNodeName } = parserSettings const { anyStringWildcardRegExp } = parserSettings.wildcardUtils + const tokens: string[] = [] if (parserSettings.isIdentifierNode(queryNode)) { const trimmedWildcards = parserSettings.wildcardUtils - .removeIdentifierRefFromWildcard(queryNode.name as string) + .removeWildcardAliasesFromIdentifierName(getIdentifierNodeName(queryNode)) .split(parserSettings.wildcardUtils.identifierWildcard) trimmedWildcards.forEach((part) => { if (part.length >= MIN_TOKEN_LEN) { - tokens.add(caseInsensitive ? part.toLocaleLowerCase() : part) + tokens.push(caseInsensitive ? part.toLocaleLowerCase() : part) } }) } if (stringLikeLiteralUtils.isStringLikeLiteralNode(queryNode)) { const stringContent = - stringLikeLiteralUtils.getStringLikeLiteralValue(queryNode) + parserSettings.wildcardUtils.removeWildcardAliasesFromStringLiteral( + stringLikeLiteralUtils.getStringLikeLiteralValue(queryNode), + ) const trimmedWildcards = decomposeString( stringContent, @@ -49,22 +55,47 @@ export const getUniqueTokens = ( trimmedWildcards.forEach((part) => { if (part.length >= MIN_TOKEN_LEN) { - tokens.add(caseInsensitive ? part.toLocaleLowerCase() : part) + tokens.push(caseInsensitive ? part.toLocaleLowerCase() : part) } }) } + return tokens +} + +export const getUniqueTokens = ( + queryNode: PoorNodeType, + caseInsensitive: boolean, + parserSettings: ParserSettings, + tokens: Set = new Set(), +) => { + const { numericLiteralUtils, getUniqueTokensFromStringOrIdentifierNode } = + parserSettings + + const getUniqueTokensFn = + getUniqueTokensFromStringOrIdentifierNode ?? + defaultGetUniqueTokensFromStringOrIdentifierNode + + const tokensFromStringsOrIdNode = getUniqueTokensFn({ + queryNode, + caseInsensitive, + parserSettings, + }) + + tokensFromStringsOrIdNode.forEach(tokens.add, tokens) + if (numericLiteralUtils.isNumericLiteralNode(queryNode)) { const raw = numericLiteralUtils.getNumericLiteralValue(queryNode) if (raw !== parserSettings.wildcardUtils.numericWildcard) { - tokens.add(raw) + tokens.add(caseInsensitive ? raw.toLocaleLowerCase() : raw) } } const nodeKeys = getKeysToCompare( queryNode, parserSettings.astPropsToSkip, + parserSettings.getNodeType, ).filter( (key) => parserSettings.isNode(queryNode[key] as PoorNodeType) || @@ -94,10 +125,14 @@ export const getUniqueTokens = ( } export const extractQueryNode = ( - fileNode: PoorNodeType, + topLevelQueryNode: PoorNodeType, parserSettings: ParserSettings, ) => { - const queryBody = parserSettings.getProgramBodyFromRootNode(fileNode) + const queryBody = parserSettings.getProgramBodyFromRootNode(topLevelQueryNode) + + if (queryBody.length === 0) { + throw new Error('Query is empty or code was not parsed correctly') + } if (queryBody.length === 1) { return { @@ -106,7 +141,7 @@ export const extractQueryNode = ( } } - const position = parserSettings.getNodePosition(fileNode) + const position = parserSettings.getNodePosition(topLevelQueryNode) return { queryNode: parserSettings.createBlockStatementNode(queryBody, position), @@ -203,8 +238,13 @@ export const parseQueries = ( } } + const preprocessedQueryCode = + parserSettings.preprocessQueryCode?.(queryText) ?? queryText + try { - const parsedAsIs = parserSettings.parseCode(queryText) + const parsedAsIs = parserSettings.parseCode( + preprocessedQueryCode.trim(), + ) const { queryNode, isMultistatement } = extractQueryNode( parsedAsIs, @@ -232,9 +272,10 @@ export const parseQueries = ( } } + // TODO move to parse code, specific to JS try { const parsedAsExp = parserSettings.parseCode( - `(${queryText})`, + `(${preprocessedQueryCode})`, ) as unknown as PoorNodeType const { queryNode } = extractQueryNode(parsedAsExp, parserSettings) @@ -253,7 +294,9 @@ export const parseQueries = ( } }) .map(({ error, queryNode, isMultistatement }) => ({ - queryNode, + queryNode: + (queryNode && parserSettings.postprocessQueryNode?.(queryNode)) ?? + queryNode, error: !queryNode ? { text: 'Empty query!' } : error, isMultistatement, })) diff --git a/packages/core/src/parserSettings/_common/JSFamilyCommon.ts b/packages/core/src/parserSettings/_common/JSFamilyCommon.ts index 5a7bedd..261fa2d 100644 --- a/packages/core/src/parserSettings/_common/JSFamilyCommon.ts +++ b/packages/core/src/parserSettings/_common/JSFamilyCommon.ts @@ -1,3 +1,4 @@ +import type { ParserOptions, ParserPlugin } from '@babel/parser' export const numericWildcard = '0x0' export const wildcardChar = '$' @@ -10,3 +11,29 @@ export const supportedExtensions = [ 'mjs', 'json', ] + +export const babelPluginsWithoutJSX = [ + 'typescript', + 'decorators-legacy', + 'importAssertions', + 'doExpressions', +] as ParserPlugin[] + +export const babelPluginsWithJSX = [ + ...babelPluginsWithoutJSX, + 'jsx', +] as ParserPlugin[] + +export const babelParseOptionsWithJSX = { + sourceType: 'module', + plugins: babelPluginsWithJSX, + allowReturnOutsideFunction: true, + allowImportExportEverywhere: true, +} as ParserOptions + +export const babelParseOptionsWithoutJSX = { + sourceType: 'module', + plugins: babelPluginsWithoutJSX, + allowReturnOutsideFunction: true, + allowImportExportEverywhere: true, +} as ParserOptions diff --git a/packages/core/src/parserSettings/angularEslintTemplateParser/beforeWildcardsComparators.ts b/packages/core/src/parserSettings/angularEslintTemplateParser/beforeWildcardsComparators.ts new file mode 100644 index 0000000..d436f2d --- /dev/null +++ b/packages/core/src/parserSettings/angularEslintTemplateParser/beforeWildcardsComparators.ts @@ -0,0 +1,24 @@ +import { NodesComparator } from '../../types' +import { createMatchWildcardsInPropValueNodesComparator } from '../nodeComparatorFactories/Other/matchWildcardsInPropValueNodes' +import { matchMultilineQueriesNodesComparator } from './matchMultilineQueriesNodesComparator' + +const matchWildcardsInTextAttributeNodesComparator = + createMatchWildcardsInPropValueNodesComparator({ + nodeType: 'TextAttribute', + keysToTraverse: [], + // Order of keys definition does matter for matchContext! In case someone would use the same alias in eg. prop and value + keysWithWildcards: ['name', 'value'], + }) + +const matchWildcardsInElement$1NodesComparator = + createMatchWildcardsInPropValueNodesComparator({ + nodeType: 'Element$1', + keysToTraverse: ['attributes', 'children'], + keysWithWildcards: ['name'], + }) + +export const beforeWildcardsComparators: NodesComparator[] = [ + matchWildcardsInTextAttributeNodesComparator, + matchWildcardsInElement$1NodesComparator, + matchMultilineQueriesNodesComparator, +] diff --git a/packages/core/src/parserSettings/angularEslintTemplateParser/common.ts b/packages/core/src/parserSettings/angularEslintTemplateParser/common.ts new file mode 100644 index 0000000..eec4701 --- /dev/null +++ b/packages/core/src/parserSettings/angularEslintTemplateParser/common.ts @@ -0,0 +1,26 @@ +import { createWildcardUtils } from '../../wildcardUtilsFactory' +import { PoorNodeType } from '../../types' +import { getNodeType } from '../espreeParser/common' + +/* + * We don't have purely identifier nodes in this parser + * We compare everything using string wildcards + */ + +export const identifierNodeTypes: string[] = [] + +const wildcardChar = '$' +const numericWildcard = '0x0' + +export const getIdentifierNodeName = (node: PoorNodeType) => node.name as string +export const setIdentifierNodeName = (node: PoorNodeType, name: string) => { + node.name = name +} + +export const wildcardUtils = createWildcardUtils( + identifierNodeTypes, + numericWildcard, + wildcardChar, + getIdentifierNodeName, + getNodeType, +) diff --git a/packages/core/src/parserSettings/angularEslintTemplateParser/matchMultilineQueriesNodesComparator.ts b/packages/core/src/parserSettings/angularEslintTemplateParser/matchMultilineQueriesNodesComparator.ts new file mode 100644 index 0000000..7e349aa --- /dev/null +++ b/packages/core/src/parserSettings/angularEslintTemplateParser/matchMultilineQueriesNodesComparator.ts @@ -0,0 +1,19 @@ +import { NodesComparator } from '../../types' + +/* + * Support performing multiline html queries by changing query Program node into ElementNode + */ +export const matchMultilineQueriesNodesComparator: NodesComparator = ( + { queryNode, fileNode }, + _, + { fileKeysToTraverseForOtherMatches }, +) => { + if (queryNode?.type === 'Program' && fileNode?.type === 'Element$1') { + return { + levelMatch: true, // we are not interested in other fields than children of both + queryKeysToTraverseForValidatingMatch: ['templateNodes'], + fileKeysToTraverseForValidatingMatch: ['children'], + fileKeysToTraverseForOtherMatches, + } + } +} diff --git a/packages/core/src/parserSettings/angularEslintTemplateParser/settings.ts b/packages/core/src/parserSettings/angularEslintTemplateParser/settings.ts new file mode 100644 index 0000000..539696d --- /dev/null +++ b/packages/core/src/parserSettings/angularEslintTemplateParser/settings.ts @@ -0,0 +1,273 @@ +import { parseForESLint } from '@angular-eslint/template-parser' +import { + Location, + MatchPosition, + NumericLiteralUtils, + ParserSettings, + PoorNodeType, + ProgramNodeAndBlockNodeUtils, + StringLikeLiteralUtils, + NodesComparatorParameters, + GetUniqueTokensFromStringOrIdentifierNode, +} from '../../types' +import { + decomposeString, + normalizeText, + runNodesComparators, +} from '../../utils' +import { + getIdentifierNodeName, + identifierNodeTypes, + setIdentifierNodeName, + wildcardUtils, +} from './common' +import { traverseAst } from '../../searchStages/traverseAndMatch' +import { beforeWildcardsComparators } from './beforeWildcardsComparators' + +const supportedExtensions = ['html', 'htm'] + +const getProgramNodeFromRootNode = (rootNode: PoorNodeType) => rootNode // root node is program node + +const getProgramBodyFromRootNode = (fileNode: PoorNodeType) => { + return fileNode.templateNodes as PoorNodeType[] +} + +const unwrapExpressionStatement = (node: PoorNodeType) => { + return node as PoorNodeType +} + +const createBlockStatementNode = ( + templateNodes: PoorNodeType[], + position: MatchPosition, +) => ({ + type: 'Program', + templateNodes, + ...position, +}) + +const isNode = (maybeNode: PoorNodeType) => { + return typeof maybeNode?.type === 'string' +} + +const astPropsToSkip = [ + 'range', + 'sourceSpan', + 'startSourceSpan', + 'endSourceSpan', + 'valueSpan', + 'keySpan', + 'loc', + 'start', + 'end', + 'extra', + 'trailingComments', + 'leadingComments', + 'innerComments', + 'comments', + 'tail', // Support for partial matching of template literals +] + +const parseCode = (code: string, filePath = '') => { + return parseForESLint(code, { filePath, range: true, loc: true }) + .ast as PoorNodeType +} + +type NodeValueSanitizers = Record any>> + +const nodeValuesSanitizers: NodeValueSanitizers = { + ['Text$3']: { + value: normalizeText, + }, +} + +const getSanitizedNodeValue = ( + nodeType: string, + valueKey: string, + value: unknown, +) => { + const valueSanitizer = nodeValuesSanitizers?.[nodeType]?.[valueKey] + + if (valueSanitizer) { + return valueSanitizer(value) + } + + return value +} + +const shouldCompareNode = (node: PoorNodeType) => { + if (node.type === 'Text$3') { + const value: string = getSanitizedNodeValue('Text$3', 'value', node.value) + + const shouldCompare = value.length > 0 + + return shouldCompare + } + + return true +} + +const getNodeType = (node: PoorNodeType) => node.type as string + +const isIdentifierNode = (node: PoorNodeType) => + identifierNodeTypes.includes(getNodeType(node)) + +const stringLikeLiteralUtils: StringLikeLiteralUtils = { + // Text$3 is only pure string node + isStringLikeLiteralNode: (node: PoorNodeType) => node.type === 'Text$3', + getStringLikeLiteralValue: (node: PoorNodeType) => { + return node?.value as string + }, +} + +const numericLiteralUtils: NumericLiteralUtils = { + isNumericLiteralNode: (node: PoorNodeType) => node.type === 'NumericLiteral', + getNumericLiteralValue: (node: PoorNodeType) => + (node.extra as any).raw as string, +} + +const programNodeAndBlockNodeUtils: ProgramNodeAndBlockNodeUtils = { + isProgramNode: (node: PoorNodeType) => node.type === 'Program', + isBlockNode: (node: PoorNodeType) => node.type === 'Program', + programNodeBodyKey: 'templateNodes', + blockNodeBodyKey: 'templateNodes', +} + +const getNodePosition: ParserSettings['getNodePosition'] = ( + node: PoorNodeType, +) => ({ + start: ((node?.sourceSpan as any)?.start?.offset as number) ?? 0, + end: ((node?.sourceSpan as any)?.end?.offset as number) ?? 0, + loc: node.loc as unknown as Location, +}) + +const getParseErrorLocation = (e: any) => ({ + line: e.loc?.line ?? 0, + column: e.loc?.column ?? 0, +}) + +const alternativeNodeTypes = { + Identifier: identifierNodeTypes, +} + +/** + * To support wildcards in html we have to + * - encode wildcard, do it in query text before parsing $$ => a_a_$$ + * - decode wildcard, traverse parsed query and: a_a_$$ => $$ + * `$$` is invalid tag name start in all html parsers + */ +const encodedWildcardSequence = 'a_$$_x' + +const preprocessQueryCode = (code: string) => { + const queryCode = code.replace(/(\$\$)/g, () => encodedWildcardSequence) + + return queryCode +} + +const replaceEncodedWildcards = (value: string) => + value.replace(/a_\$\$_x/g, () => '$$') + +const postprocessQueryNode = (queryNode: PoorNodeType) => { + traverseAst(queryNode, isNode, getNodeType, { + Element$1: (node) => { + const nodeName = node.name as string + + if (nodeName.includes(encodedWildcardSequence)) { + node.name = replaceEncodedWildcards(nodeName) + } + }, + TextAttribute: (node) => { + const nodeValue = node.value as string + + if (nodeValue.includes(encodedWildcardSequence)) { + node.value = replaceEncodedWildcards(nodeValue) + } + }, + Text$3: (node) => { + const nodeValue = node.value as string + + if (nodeValue.includes(encodedWildcardSequence)) { + node.value = replaceEncodedWildcards(nodeValue) + } + }, + }) + + return queryNode +} + +const compareNodesBeforeWildcardsComparison = ( + ...nodeComparatorParams: NodesComparatorParameters +) => { + return runNodesComparators(beforeWildcardsComparators, nodeComparatorParams) +} + +const getUniqueTokensFromStringOrIdentifierNode: GetUniqueTokensFromStringOrIdentifierNode = + ({ queryNode, caseInsensitive, parserSettings }) => { + const MIN_TOKEN_LEN = 2 + + const { anyStringWildcardRegExp } = parserSettings.wildcardUtils + const tokens: string[] = [] + + const valuesToProcess: string[] = [] + + if (queryNode.type === 'TextAttribute') { + valuesToProcess.push(queryNode.name as string) + valuesToProcess.push(queryNode.value as string) + } + + if (queryNode.type === 'Element$1') { + valuesToProcess.push(queryNode.name as string) + } + + if (queryNode.type === 'Text$3') { + valuesToProcess.push(queryNode.value as string) + } + + valuesToProcess + .map((val) => + parserSettings.wildcardUtils.removeWildcardAliasesFromStringLiteral( + val, + ), + ) + .map((val) => decomposeString(val, anyStringWildcardRegExp)) + .flat(1) + .forEach((part) => { + if (part.length >= MIN_TOKEN_LEN) { + tokens.push(caseInsensitive ? part.toLocaleLowerCase() : part) + } + }) + + return tokens + } + +export const angularEslintTemplateParser: ParserSettings = { + supportedExtensions, + parseCode, + isNode, + isIdentifierNode, + astPropsToSkip, + getProgramBodyFromRootNode, + getProgramNodeFromRootNode, + getIdentifierNodeName, + getNodeType, + unwrapExpressionStatement, + createBlockStatementNode, + getSanitizedNodeValue, + identifierNodeTypes, + setIdentifierNodeName, + shouldCompareNode, + wildcardUtils, + compareNodesBeforeWildcardsComparison, + compareNodesAfterWildcardsComparison: () => undefined, + identifierTypeAnnotationFieldName: 'typeAnnotation', + stringLikeLiteralUtils, + numericLiteralUtils, + programNodeAndBlockNodeUtils, + getNodePosition, + getParseErrorLocation, + alternativeNodeTypes, + postprocessQueryNode, + preprocessQueryCode, + getUniqueTokensFromStringOrIdentifierNode, +} + +export default angularEslintTemplateParser diff --git a/packages/core/src/parserSettings/babelEslintParser/afterWildcardsComparators.ts b/packages/core/src/parserSettings/babelEslintParser/afterWildcardsComparators.ts new file mode 100644 index 0000000..57ef1a0 --- /dev/null +++ b/packages/core/src/parserSettings/babelEslintParser/afterWildcardsComparators.ts @@ -0,0 +1,23 @@ +import { NodesComparator } from '../../types' +import { wildcardForAnyImport } from './nodeComparators/wildcardForAnyImport' +import { matchJsxElementRegardlessClosingTagNodesComparator } from './nodeComparators/matchJsxElementRegardlessClosingTag' +import { matchJsxIdentifierUsingIdentifierNodesComparator } from './nodeComparators/matchJsxIdentifierUsingIdentifier' +import { partialMatchTemplateLiteralNodesComparator } from './nodeComparators/partialMatchTemplateLiteral' +import { matchOptionalFlagInMemberExpressionNodesComparator } from './nodeComparators/matchOptionalFlagInMemberExpression' +import { matchDestructPropBeforeRenameNodesComparator } from './nodeComparators/matchDestructPropBeforeRename' +import { wildcardForAnyTypeAnnotation } from './nodeComparators/wildcardForAnyTypeAnnotation' +import { wildcardForAnyTypeParameter } from './nodeComparators/wildcardForAnyTypeParameter' +import { matchObjectPropertiesOfDifferentTypesNodesComparator } from './nodeComparators/matchObjectPropertiesOfDifferentTypes' + +// Better keep this order +export const afterWildcardsComparators: NodesComparator[] = [ + wildcardForAnyImport, + wildcardForAnyTypeAnnotation, + wildcardForAnyTypeParameter, + matchDestructPropBeforeRenameNodesComparator, + matchObjectPropertiesOfDifferentTypesNodesComparator, + matchJsxElementRegardlessClosingTagNodesComparator, + matchJsxIdentifierUsingIdentifierNodesComparator, + partialMatchTemplateLiteralNodesComparator, + matchOptionalFlagInMemberExpressionNodesComparator, +] diff --git a/packages/core/src/parserSettings/babelEslintParser/beforeWildcardsComparators.ts b/packages/core/src/parserSettings/babelEslintParser/beforeWildcardsComparators.ts new file mode 100644 index 0000000..7c6c68d --- /dev/null +++ b/packages/core/src/parserSettings/babelEslintParser/beforeWildcardsComparators.ts @@ -0,0 +1,8 @@ +import { NodesComparator } from '../../types' +import { wildcardForTypeKeywordsNodesComparator } from './nodeComparators/wildcardForTypeKeywords' +import { wildcardForAssignmentPatternOrDefaultParamValuesNodesComparator } from './nodeComparators/wildcardForAssignmentPatternOrDefaultParamValues' + +export const beforeWildcardsComparators: NodesComparator[] = [ + wildcardForTypeKeywordsNodesComparator, + wildcardForAssignmentPatternOrDefaultParamValuesNodesComparator, +] diff --git a/packages/core/src/parserSettings/babelEslintParser/common.ts b/packages/core/src/parserSettings/babelEslintParser/common.ts new file mode 100644 index 0000000..72331f3 --- /dev/null +++ b/packages/core/src/parserSettings/babelEslintParser/common.ts @@ -0,0 +1,24 @@ +import { numericWildcard, wildcardChar } from '../_common/JSFamilyCommon' +import { createWildcardUtils } from '../../wildcardUtilsFactory' +import { PoorNodeType } from '../../types' + +export const identifierNodeTypes = [ + 'Identifier', + 'JSXIdentifier', + 'TSTypeParameter', +] + +export const getIdentifierNodeName = (node: PoorNodeType) => node.name as string +export const setIdentifierNodeName = (node: PoorNodeType, name: string) => { + node.name = name +} + +export const getNodeType = (node: PoorNodeType) => node.type as string + +export const wildcardUtils = createWildcardUtils( + identifierNodeTypes, + numericWildcard, + wildcardChar, + getIdentifierNodeName, + getNodeType, +) diff --git a/packages/core/src/parserSettings/babelEslintParser/nodeComparators/matchDestructPropBeforeRename.ts b/packages/core/src/parserSettings/babelEslintParser/nodeComparators/matchDestructPropBeforeRename.ts new file mode 100644 index 0000000..1d522cf --- /dev/null +++ b/packages/core/src/parserSettings/babelEslintParser/nodeComparators/matchDestructPropBeforeRename.ts @@ -0,0 +1,6 @@ +import { createMatchDestructPropBeforeRenameNodesComparator } from '../../nodeComparatorFactories/JavaScriptSpecific/matchDestructPropBeforeRename' + +export const matchDestructPropBeforeRenameNodesComparator = + createMatchDestructPropBeforeRenameNodesComparator({ + objectPropertyNodeName: 'Property', + }) diff --git a/packages/core/src/parserSettings/babelEslintParser/nodeComparators/matchJsxElementRegardlessClosingTag.ts b/packages/core/src/parserSettings/babelEslintParser/nodeComparators/matchJsxElementRegardlessClosingTag.ts new file mode 100644 index 0000000..a60143b --- /dev/null +++ b/packages/core/src/parserSettings/babelEslintParser/nodeComparators/matchJsxElementRegardlessClosingTag.ts @@ -0,0 +1,4 @@ +import { createMatchJsxElementRegardlessClosingTagNodesComparator } from '../../nodeComparatorFactories/JsxSpecific/matchJsxElementRegardlessClosingTag' + +export const matchJsxElementRegardlessClosingTagNodesComparator = + createMatchJsxElementRegardlessClosingTagNodesComparator() diff --git a/packages/core/src/parserSettings/babelEslintParser/nodeComparators/matchJsxIdentifierUsingIdentifier.ts b/packages/core/src/parserSettings/babelEslintParser/nodeComparators/matchJsxIdentifierUsingIdentifier.ts new file mode 100644 index 0000000..7a21bf7 --- /dev/null +++ b/packages/core/src/parserSettings/babelEslintParser/nodeComparators/matchJsxIdentifierUsingIdentifier.ts @@ -0,0 +1,4 @@ +import { createMatchJsxIdentifierUsingIdentifierNodesComparator } from '../../nodeComparatorFactories/JsxSpecific/matchJsxIdentifierUsingIdentifier' + +export const matchJsxIdentifierUsingIdentifierNodesComparator = + createMatchJsxIdentifierUsingIdentifierNodesComparator() diff --git a/packages/core/src/parserSettings/babelEslintParser/nodeComparators/matchObjectPropertiesOfDifferentTypes.ts b/packages/core/src/parserSettings/babelEslintParser/nodeComparators/matchObjectPropertiesOfDifferentTypes.ts new file mode 100644 index 0000000..99bdf9b --- /dev/null +++ b/packages/core/src/parserSettings/babelEslintParser/nodeComparators/matchObjectPropertiesOfDifferentTypes.ts @@ -0,0 +1,6 @@ +import { createMatchObjectPropertiesOfDifferentTypesNodesComparator } from '../../nodeComparatorFactories/JavaScriptSpecific/matchObjectPropertiesOfDifferentTypes' + +export const matchObjectPropertiesOfDifferentTypesNodesComparator = + createMatchObjectPropertiesOfDifferentTypesNodesComparator({ + objectPropertyNodeName: 'Property', + }) diff --git a/packages/core/src/parserSettings/babelEslintParser/nodeComparators/matchOptionalFlagInMemberExpression.ts b/packages/core/src/parserSettings/babelEslintParser/nodeComparators/matchOptionalFlagInMemberExpression.ts new file mode 100644 index 0000000..2287657 --- /dev/null +++ b/packages/core/src/parserSettings/babelEslintParser/nodeComparators/matchOptionalFlagInMemberExpression.ts @@ -0,0 +1,4 @@ +import { createMatchOptionalFlagInMemberExpressionNodesComparator } from '../../nodeComparatorFactories/JavaScriptSpecific/matchOptionalFlagInMemberExpression' + +export const matchOptionalFlagInMemberExpressionNodesComparator = + createMatchOptionalFlagInMemberExpressionNodesComparator() diff --git a/packages/core/src/parserSettings/babelEslintParser/nodeComparators/partialMatchTemplateLiteral.ts b/packages/core/src/parserSettings/babelEslintParser/nodeComparators/partialMatchTemplateLiteral.ts new file mode 100644 index 0000000..3579c69 --- /dev/null +++ b/packages/core/src/parserSettings/babelEslintParser/nodeComparators/partialMatchTemplateLiteral.ts @@ -0,0 +1,4 @@ +import { createPartialMatchTemplateLiteralNodesComparator } from '../../nodeComparatorFactories/JavaScriptSpecific/partialMatchTemplateLiteral' + +export const partialMatchTemplateLiteralNodesComparator = + createPartialMatchTemplateLiteralNodesComparator() diff --git a/packages/core/src/parserSettings/babelEslintParser/nodeComparators/wildcardForAnyImport.ts b/packages/core/src/parserSettings/babelEslintParser/nodeComparators/wildcardForAnyImport.ts new file mode 100644 index 0000000..103a66d --- /dev/null +++ b/packages/core/src/parserSettings/babelEslintParser/nodeComparators/wildcardForAnyImport.ts @@ -0,0 +1,6 @@ +import { createWildcardForAnyImportNodesComparator } from '../../nodeComparatorFactories/JavaScriptSpecific/wildcardForAnyImport' +import { wildcardUtils } from '../common' + +export const wildcardForAnyImport = createWildcardForAnyImportNodesComparator({ + wildcardUtils, +}) diff --git a/packages/core/src/parserSettings/babelEslintParser/nodeComparators/wildcardForAnyTypeAnnotation.ts b/packages/core/src/parserSettings/babelEslintParser/nodeComparators/wildcardForAnyTypeAnnotation.ts new file mode 100644 index 0000000..832977b --- /dev/null +++ b/packages/core/src/parserSettings/babelEslintParser/nodeComparators/wildcardForAnyTypeAnnotation.ts @@ -0,0 +1,7 @@ +import { createWildcardForAnyTypeAnnotationNodesComparator } from '../../nodeComparatorFactories/TypeScriptSpecific/wildcardForAnyTypeAnnotation' +import { wildcardUtils } from '../common' + +export const wildcardForAnyTypeAnnotation = + createWildcardForAnyTypeAnnotationNodesComparator({ + wildcardUtils, + }) diff --git a/packages/core/src/parserSettings/babelEslintParser/nodeComparators/wildcardForAnyTypeParameter.ts b/packages/core/src/parserSettings/babelEslintParser/nodeComparators/wildcardForAnyTypeParameter.ts new file mode 100644 index 0000000..1ed2a24 --- /dev/null +++ b/packages/core/src/parserSettings/babelEslintParser/nodeComparators/wildcardForAnyTypeParameter.ts @@ -0,0 +1,25 @@ +import { PoorNodeType, NodesComparator } from '../../../types' +import { wildcardUtils } from '../common' +export const wildcardForAnyTypeParameter: NodesComparator = ( + { queryNode }, + _, + { fileKeysToTraverseForOtherMatches, measureCompare }, +) => { + if (queryNode) { + if ( + (queryNode.type as string) === 'TSTypeParameter' && + wildcardUtils.removeWildcardAliasesFromIdentifierName( + (queryNode.name as PoorNodeType).name as string, + ) === wildcardUtils.nodesTreeWildcard + ) { + measureCompare() + + return { + levelMatch: true, + queryKeysToTraverseForValidatingMatch: [], + fileKeysToTraverseForValidatingMatch: [], + fileKeysToTraverseForOtherMatches, + } + } + } +} diff --git a/packages/core/src/parserSettings/babelEslintParser/nodeComparators/wildcardForAssignmentPatternOrDefaultParamValues.ts b/packages/core/src/parserSettings/babelEslintParser/nodeComparators/wildcardForAssignmentPatternOrDefaultParamValues.ts new file mode 100644 index 0000000..6f1c78c --- /dev/null +++ b/packages/core/src/parserSettings/babelEslintParser/nodeComparators/wildcardForAssignmentPatternOrDefaultParamValues.ts @@ -0,0 +1,4 @@ +import { createWildcardForAssignmentPatternOrDefaultParamValuesNodesComparator } from '../../nodeComparatorFactories/JavaScriptSpecific/wildcardForAssignmentPatternOrDefaultParamValues' + +export const wildcardForAssignmentPatternOrDefaultParamValuesNodesComparator = + createWildcardForAssignmentPatternOrDefaultParamValuesNodesComparator() diff --git a/packages/core/src/parserSettings/babelEslintParser/nodeComparators/wildcardForTypeKeywords.ts b/packages/core/src/parserSettings/babelEslintParser/nodeComparators/wildcardForTypeKeywords.ts new file mode 100644 index 0000000..3b1c143 --- /dev/null +++ b/packages/core/src/parserSettings/babelEslintParser/nodeComparators/wildcardForTypeKeywords.ts @@ -0,0 +1,5 @@ +import { createWildcardForTypeKeywordNodesComparator } from '../../nodeComparatorFactories/TypeScriptSpecific/wildcardForTypeKeywords' +import { wildcardUtils } from '../common' + +export const wildcardForTypeKeywordsNodesComparator = + createWildcardForTypeKeywordNodesComparator({ wildcardUtils }) diff --git a/packages/core/src/parserSettings/babelEslintParser/settings.ts b/packages/core/src/parserSettings/babelEslintParser/settings.ts new file mode 100644 index 0000000..b779aff --- /dev/null +++ b/packages/core/src/parserSettings/babelEslintParser/settings.ts @@ -0,0 +1,268 @@ +import { + ParserSettings, + PoorNodeType, + Location, + NodesComparatorParameters, + NumericLiteralUtils, + ProgramNodeAndBlockNodeUtils, + StringLikeLiteralUtils, + MatchPosition, +} from '../../types' +import { parse } from '@babel/eslint-parser' + +import { supportedExtensions } from '../_common/JSFamilyCommon' + +import { + getNodeType, + identifierNodeTypes, + wildcardUtils, + setIdentifierNodeName, + getIdentifierNodeName, +} from './common' + +import { normalizeText, runNodesComparators } from '../../utils' +import { afterWildcardsComparators } from './afterWildcardsComparators' +import { beforeWildcardsComparators } from './beforeWildcardsComparators' + +const parseCode = (code: string, filePath = '') => { + const maybeWrappedJSON = /\.json$/.test(filePath) ? `(${code})` : code + try { + return parse(maybeWrappedJSON, { + filePath, + sourceType: 'module', + requireConfigFile: false, + allowImportExportEverywhere: false, + babelOptions: { + babelrc: false, + /** + * ❗ Note that enabling config file makes babel look for package.json, which will crash in browser runtime + * At some point we might need to enable resolving plugins from users config files, it might be blocking some syntaxes. + * But since @babel/eslint-parser is bit retarded it terms of configuration, it might be an edge case which we will never run into. + */ + configFile: false, + parserOpts: { + plugins: ['jsx'], + }, + }, + }) as unknown as PoorNodeType + } catch (e) { + return parse(maybeWrappedJSON, { + filePath, + sourceType: 'module', + requireConfigFile: false, + allowImportExportEverywhere: false, + babelOptions: { + babelrc: false, + configFile: false, + parserOpts: { + plugins: [], + }, + }, + }) as unknown as PoorNodeType + } +} + +/** + * We mimic @babel/eslint-parser, but we don't use it directly, as it has not documented API + * We need this only for purpose of eslint plugin and to make sure that output AST is searchable + */ +const getProgramNodeFromRootNode = (fileOrProgramNode: PoorNodeType) => + 'program' in fileOrProgramNode + ? (fileOrProgramNode.program as PoorNodeType) + : fileOrProgramNode + +const getProgramBodyFromRootNode = (fileOrProgramNode: PoorNodeType) => { + return getProgramNodeFromRootNode(fileOrProgramNode).body as PoorNodeType[] +} + +const isIdentifierNode = (node: PoorNodeType) => + identifierNodeTypes.includes(getNodeType(node)) + +const alternativeNodeTypes = { + Identifier: identifierNodeTypes, + ChainExpression: ['MemberExpression'], + MemberExpression: ['ChainExpression'], + BlockStatement: ['Program'], +} + +const unwrapExpressionStatement = (node: PoorNodeType) => { + if (typeof node !== 'object') { + return node + } + + if (node.type === 'ExpressionStatement') { + return node.expression as PoorNodeType + } + + return node as PoorNodeType +} + +const createBlockStatementNode = ( + body: PoorNodeType[], + position: MatchPosition, +) => + ({ + type: 'BlockStatement', + body, + loc: position.loc, + range: [position.start, position.end], + } as unknown as PoorNodeType) + +const isNode = (maybeNode: PoorNodeType) => { + return typeof maybeNode?.type === 'string' +} + +const astPropsToSkip = [ + 'loc', + 'range', + 'raw', + 'trailingComments', + 'leadingComments', + 'comments', + 'tail', // Support for partial matching of template literals + 'parent', // in eslint there is parent prop in node + { type: 'ArrowFunctionExpression', key: 'expression' }, // flag on ArrowFunctionExpression + 'tokens', + 'start', + 'end', + 'extra', +] + +const sanitizeTemplateElementValue = ({ + raw, + cooked, +}: { + raw: string + cooked: string +}) => { + return { + raw: normalizeText(raw), + cooked: normalizeText(cooked), + } +} + +type NodeValueSanitizers = Record any>> + +const nodeValuesSanitizers: NodeValueSanitizers = { + ['JSXText']: { + value: normalizeText, + raw: normalizeText, + }, + ['TemplateElement']: { + value: sanitizeTemplateElementValue, + }, +} + +const getSanitizedNodeValue = ( + nodeType: string, + valueKey: string, + value: unknown, +) => { + const valueSanitizer = nodeValuesSanitizers?.[nodeType]?.[valueKey] + + if (valueSanitizer) { + return valueSanitizer(value) + } + + return value +} + +const shouldCompareNode = (node: PoorNodeType) => { + if (node.type === 'JSXText') { + const value: string = getSanitizedNodeValue('JSXText', 'value', node.value) + + return value.length > 0 + } + + return true +} + +const compareNodesBeforeWildcardsComparison = ( + ...nodeComparatorParams: NodesComparatorParameters +) => { + return runNodesComparators(beforeWildcardsComparators, nodeComparatorParams) +} + +const compareNodesAfterWildcardsComparison = ( + ...nodeComparatorParams: NodesComparatorParameters +) => { + return runNodesComparators(afterWildcardsComparators, nodeComparatorParams) +} + +const isFirstCharStringStart = (str: string) => + str.charAt(0) === `'` || str.charAt(0) === `"` + +const stringLikeLiteralUtils: StringLikeLiteralUtils = { + isStringLikeLiteralNode: (node: PoorNodeType) => + (node.type === 'Literal' && isFirstCharStringStart(node.raw as string)) || + node.type === 'TemplateElement' || + node.type === 'JSXText', + getStringLikeLiteralValue: (node: PoorNodeType) => { + if (node.type === 'TemplateElement') { + const { raw } = sanitizeTemplateElementValue( + node.value as { raw: string; cooked: string }, + ) + + return raw + } + + // (node.type === 'Literal' || node.type === 'JSXText' + return normalizeText(node.value as string) + }, +} + +const numericLiteralUtils: NumericLiteralUtils = { + isNumericLiteralNode: (node: PoorNodeType) => + node.type === 'Literal' && !isFirstCharStringStart(node.raw as string), + getNumericLiteralValue: (node: PoorNodeType) => node.raw as string, +} + +const programNodeAndBlockNodeUtils: ProgramNodeAndBlockNodeUtils = { + isProgramNode: (node: PoorNodeType) => node.type === 'Program', + isBlockNode: (node: PoorNodeType) => node.type === 'BlockStatement', + programNodeBodyKey: 'body', + blockNodeBodyKey: 'body', +} + +const getNodePosition: ParserSettings['getNodePosition'] = ( + node: PoorNodeType, +) => ({ + start: (node.range as any)[0] as number, + end: (node.range as any)[1] as number, + loc: node.loc as unknown as Location, +}) + +const getParseErrorLocation = (e: any) => ({ + line: e.lineNumber ?? 0, + column: e.column ?? 0, +}) + +export const babelEslintParserSettings: ParserSettings = { + parseCode, + getProgramNodeFromRootNode, + getProgramBodyFromRootNode, + isIdentifierNode, + getNodeType, + wildcardUtils, + setIdentifierNodeName, + alternativeNodeTypes, + astPropsToSkip, + supportedExtensions, + isNode, + identifierNodeTypes, + getIdentifierNodeName, + unwrapExpressionStatement, + createBlockStatementNode, + getSanitizedNodeValue, + shouldCompareNode, + compareNodesBeforeWildcardsComparison, + compareNodesAfterWildcardsComparison, + identifierTypeAnnotationFieldName: 'typeAnnotation', + stringLikeLiteralUtils, + numericLiteralUtils, + programNodeAndBlockNodeUtils, + getNodePosition, + getParseErrorLocation, +} + +export default babelEslintParserSettings diff --git a/packages/core/src/parserSettings/babelParser/common.ts b/packages/core/src/parserSettings/babelParser/common.ts index b2a7418..72331f3 100644 --- a/packages/core/src/parserSettings/babelParser/common.ts +++ b/packages/core/src/parserSettings/babelParser/common.ts @@ -1,5 +1,6 @@ import { numericWildcard, wildcardChar } from '../_common/JSFamilyCommon' import { createWildcardUtils } from '../../wildcardUtilsFactory' +import { PoorNodeType } from '../../types' export const identifierNodeTypes = [ 'Identifier', @@ -7,8 +8,17 @@ export const identifierNodeTypes = [ 'TSTypeParameter', ] +export const getIdentifierNodeName = (node: PoorNodeType) => node.name as string +export const setIdentifierNodeName = (node: PoorNodeType, name: string) => { + node.name = name +} + +export const getNodeType = (node: PoorNodeType) => node.type as string + export const wildcardUtils = createWildcardUtils( identifierNodeTypes, numericWildcard, wildcardChar, + getIdentifierNodeName, + getNodeType, ) diff --git a/packages/core/src/parserSettings/babelParser/settings.ts b/packages/core/src/parserSettings/babelParser/settings.ts index 332ec09..b78b157 100644 --- a/packages/core/src/parserSettings/babelParser/settings.ts +++ b/packages/core/src/parserSettings/babelParser/settings.ts @@ -1,5 +1,4 @@ import { parse, ParserOptions, ParserPlugin } from '@babel/parser' -import { NODE_FIELDS } from '@babel/types' import { ParserSettings, PoorNodeType, @@ -8,14 +7,24 @@ import { NumericLiteralUtils, ProgramNodeAndBlockNodeUtils, Location, - Match, + MatchPosition, } from '../../types' + import { normalizeText, runNodesComparators } from '../../utils' import { beforeWildcardsComparators } from './beforeWildcardsComparators' import { afterWildcardsComparators } from './afterWildcardsComparators' -import { supportedExtensions } from '../_common/JSFamilyCommon' -import {} from '../../wildcardUtilsFactory' -import { identifierNodeTypes, wildcardUtils } from './common' +import { + supportedExtensions, + babelParseOptionsWithJSX, + babelParseOptionsWithoutJSX, +} from '../_common/JSFamilyCommon' +import { + getIdentifierNodeName, + getNodeType, + identifierNodeTypes, + wildcardUtils, + setIdentifierNodeName, +} from './common' const getProgramNodeFromRootNode = (fileNode: PoorNodeType) => fileNode.program as PoorNodeType @@ -38,7 +47,7 @@ const unwrapExpressionStatement = (node: PoorNodeType) => { const createBlockStatementNode = ( body: PoorNodeType[], - position: Omit, + position: MatchPosition, ) => ({ type: 'BlockStatement', body, @@ -50,14 +59,6 @@ const isNode = (maybeNode: PoorNodeType) => { return typeof maybeNode?.type === 'string' } -const isNodeFieldOptional = (nodeType: string, nodeFieldKey: string) => { - return Boolean( - (NODE_FIELDS[nodeType] as { [key: string]: { optional: boolean } })[ - nodeFieldKey - ]?.optional ?? true, - ) -} - const astPropsToSkip = [ 'loc', 'start', @@ -71,70 +72,77 @@ const astPropsToSkip = [ ] const parseCode = (code: string, filePath = '') => { - const pluginsWithoutJSX = [ - 'typescript', - 'decorators-legacy', - 'importAssertions', - 'doExpressions', - ] as ParserPlugin[] - const pluginsWithJSX = [...pluginsWithoutJSX, 'jsx'] as ParserPlugin[] - - const parseOptionsWithJSX = { - sourceType: 'module', - plugins: pluginsWithJSX, - allowReturnOutsideFunction: true, - } as ParserOptions - - const parseOptionsWithoutJSX = { - sourceType: 'module', - plugins: pluginsWithoutJSX, - allowReturnOutsideFunction: true, - } as ParserOptions - const maybeWrappedJSON = /\.json$/.test(filePath) ? `(${code})` : code - try { return parse( maybeWrappedJSON, - parseOptionsWithJSX, + babelParseOptionsWithJSX, ) as unknown as PoorNodeType } catch (e) { return parse( maybeWrappedJSON, - parseOptionsWithoutJSX, + babelParseOptionsWithoutJSX, ) as unknown as PoorNodeType } } -const sanitizeJSXText = (node: PoorNodeType) => { - //@ts-ignore - node.value = normalizeText(node.value) - //@ts-ignore - node.extra.raw = normalizeText(node.extra.raw) - //@ts-ignore - node.extra.rawValue = normalizeText(node.extra.rawValue) +const sanitizeJSXTextExtraValue = ({ + raw, + rawValue, +}: { + raw: string + rawValue: string +}) => { + return { + raw: normalizeText(raw), + rawValue: normalizeText(rawValue), + } } -const sanitizeTemplateElement = (node: PoorNodeType) => { - //@ts-ignore - node.value.raw = normalizeText(node.value.raw) - //@ts-ignore - node.value.cooked = normalizeText(node.value.cooked) +const sanitizeTemplateElementValue = ({ + raw, + cooked, +}: { + raw: string + cooked: string +}) => { + return { + raw: normalizeText(raw), + cooked: normalizeText(cooked), + } } -const sanitizeNode = (node: PoorNodeType) => { - if (node?.type === 'TemplateElement') { - sanitizeTemplateElement(node) - } else if (node?.type === 'JSXText') { - sanitizeJSXText(node) +type NodeValueSanitizers = Record any>> + +const nodeValuesSanitizers: NodeValueSanitizers = { + ['JSXText']: { + value: normalizeText, + extra: sanitizeJSXTextExtraValue, + }, + ['TemplateElement']: { + value: sanitizeTemplateElementValue, + }, +} + +const getSanitizedNodeValue = ( + nodeType: string, + valueKey: string, + value: unknown, +) => { + const valueSanitizer = nodeValuesSanitizers?.[nodeType]?.[valueKey] + + if (valueSanitizer) { + return valueSanitizer(value) } + + return value } const shouldCompareNode = (node: PoorNodeType) => { if (node.type === 'JSXText') { - sanitizeJSXText(node) + const value: string = getSanitizedNodeValue('JSXText', 'value', node.value) - return (node.value as string).length > 0 + return value.length > 0 } return true @@ -152,9 +160,6 @@ const compareNodesAfterWildcardsComparison = ( return runNodesComparators(afterWildcardsComparators, nodeComparatorParams) } -const getIdentifierNodeName = (node: PoorNodeType) => node.name as string -const getNodeType = (node: PoorNodeType) => node.type as string - const isIdentifierNode = (node: PoorNodeType) => identifierNodeTypes.includes(getNodeType(node)) @@ -164,7 +169,16 @@ const stringLikeLiteralUtils: StringLikeLiteralUtils = { node.type === 'TemplateElement' || node.type === 'JSXText', getStringLikeLiteralValue: (node: PoorNodeType) => { - return ((node.value as any)?.raw as string) ?? (node?.value as string) + if (node.type === 'TemplateElement') { + const { raw } = sanitizeTemplateElementValue( + node.value as { raw: string; cooked: string }, + ) + + return raw + } + + // (node.type === 'StringLiteral' || node.type === 'JSXText' + return normalizeText(node.value as string) }, } @@ -183,7 +197,7 @@ const programNodeAndBlockNodeUtils: ProgramNodeAndBlockNodeUtils = { const getNodePosition: ParserSettings['getNodePosition'] = ( node: PoorNodeType, -) => ({ +): MatchPosition => ({ start: node.start as number, end: node.end as number, loc: node.loc as unknown as Location, @@ -206,15 +220,16 @@ export const babelParserSettings: ParserSettings = { parseCode, isNode, isIdentifierNode, + identifierNodeTypes, astPropsToSkip, - isNodeFieldOptional, getProgramBodyFromRootNode, getProgramNodeFromRootNode, getIdentifierNodeName, + setIdentifierNodeName, getNodeType, unwrapExpressionStatement, createBlockStatementNode, - sanitizeNode, + getSanitizedNodeValue, shouldCompareNode, wildcardUtils, compareNodesBeforeWildcardsComparison, diff --git a/packages/core/src/parserSettings/csharp/common.ts b/packages/core/src/parserSettings/csharp/common.ts new file mode 100644 index 0000000..c045f63 --- /dev/null +++ b/packages/core/src/parserSettings/csharp/common.ts @@ -0,0 +1,24 @@ +import { createWildcardUtils } from '../../wildcardUtilsFactory' +import { PoorNodeType } from '../../types' + +export const identifierNodeTypes: string[] = ['identifier'] + +const wildcardChar = '$' +const numericWildcard = '0x0' + +export const getIdentifierNodeName = (node: PoorNodeType) => + node.rawValue as string + +export const setIdentifierNodeName = (node: PoorNodeType, name: string) => { + node.rawValue = name +} + +export const getNodeType = (node: PoorNodeType) => node.nodeType as string + +export const wildcardUtils = createWildcardUtils( + identifierNodeTypes, + numericWildcard, + wildcardChar, + getIdentifierNodeName, + getNodeType, +) diff --git a/packages/core/src/parserSettings/csharp/parseCode.ts b/packages/core/src/parserSettings/csharp/parseCode.ts new file mode 100644 index 0000000..e9688cf --- /dev/null +++ b/packages/core/src/parserSettings/csharp/parseCode.ts @@ -0,0 +1,17 @@ +import { treeSitterParserModuleFactory } from '../../treeSitterUtils' + +const defineRawValueForNodeTypes = [ + 'identifier', + 'integer_literal', + 'string_literal_content', + 'real_literal', +] + +export const parserModule = treeSitterParserModuleFactory({ + treeSitterParserName: 'tree-sitter-c-sharp', + defineRawValueForNodeTypes, +}) + +export function parseCode(code: string) { + return parserModule.parse(code) +} diff --git a/packages/core/src/parserSettings/csharp/settings.ts b/packages/core/src/parserSettings/csharp/settings.ts new file mode 100644 index 0000000..5134784 --- /dev/null +++ b/packages/core/src/parserSettings/csharp/settings.ts @@ -0,0 +1,199 @@ +import { traverseAst } from '../../searchStages/traverseAndMatch' +import { + Location, + MatchPosition, + NumericLiteralUtils, + ParserSettings, + PoorNodeType, + ProgramNodeAndBlockNodeUtils, + StringLikeLiteralUtils, +} from '../../types' + +import { + getIdentifierNodeName, + getNodeType, + identifierNodeTypes, + setIdentifierNodeName, + wildcardUtils, +} from './common' +import { parseCode, parserModule } from './parseCode' + +const supportedExtensions = ['cs'] + +const getProgramNodeFromRootNode = (rootNode: PoorNodeType) => rootNode // root node is program node + +const getProgramBodyFromRootNode = (fileNode: PoorNodeType) => { + return fileNode.children as PoorNodeType[] +} + +const unwrapExpressionStatement = (node: PoorNodeType) => { + if (node.nodeType === 'global_statement' && node.children) { + const exprChild = (node.children as PoorNodeType[])[0] as PoorNodeType + + if (exprChild.nodeType === 'expression_statement' && exprChild.children) { + const parChild = (exprChild.children as PoorNodeType[])[0] as PoorNodeType + + if ( + parChild.nodeType === 'parenthesized_expression' && + parChild.children + ) { + return (parChild.children as PoorNodeType[])[0] as PoorNodeType + } + } + } + + return node +} + +const createBlockStatementNode = ( + children: PoorNodeType[], + position: MatchPosition, +) => ({ + nodeType: 'block', + children, + ...position, +}) + +const isNode = (maybeNode: PoorNodeType) => { + return typeof maybeNode?.nodeType === 'string' +} + +/* start and end is added by CQ in multiline queries */ +const astPropsToSkip = ['loc', 'start', 'end'] + +type NodeValueSanitizers = Record any>> + +const nodeValuesSanitizers: NodeValueSanitizers = {} + +const getSanitizedNodeValue = ( + nodeType: string, + valueKey: string, + value: unknown, +) => { + const valueSanitizer = nodeValuesSanitizers?.[nodeType]?.[valueKey] + + if (valueSanitizer) { + return valueSanitizer(value) + } + + return value +} + +const shouldCompareNode = (node: PoorNodeType) => { + return true +} + +const isIdentifierNode = (node: PoorNodeType) => + identifierNodeTypes.includes(getNodeType(node)) + +const stringLikeLiteralUtils: StringLikeLiteralUtils = { + isStringLikeLiteralNode: (node: PoorNodeType) => + node.nodeType === 'string_literal_content', + getStringLikeLiteralValue: (node: PoorNodeType) => { + return node?.rawValue as string + }, +} + +const numericLiteralUtils: NumericLiteralUtils = { + isNumericLiteralNode: (node: PoorNodeType) => + node.nodeType === 'integer_literal' || node.nodeType === 'real_literal', + getNumericLiteralValue: (node: PoorNodeType) => node?.rawValue as string, +} + +const programNodeAndBlockNodeUtils: ProgramNodeAndBlockNodeUtils = { + isProgramNode: (node: PoorNodeType) => node.nodeType === 'compilation_unit', + isBlockNode: (node: PoorNodeType) => node.nodeType === 'block', + programNodeBodyKey: 'children', + blockNodeBodyKey: 'children', +} + +const getNodePosition: ParserSettings['getNodePosition'] = ( + node: PoorNodeType, +) => ({ + start: ((node?.loc as any)?.start?.index as number) ?? 0, + end: ((node?.loc as any)?.end?.index as number) ?? 0, + loc: node.loc as unknown as Location, +}) + +const getParseErrorLocation = (e: any) => ({ + line: e.loc?.start?.line ?? 0, + column: e.loc?.start?.column ?? 0, +}) + +const alternativeNodeTypes = { + identifier: identifierNodeTypes, +} + +const encodedIdentifierWildcardSequence = 'a_x_2_x_a' +const encodedNodeWildcardSequence = 'a_x_3_x_a' + +const preprocessQueryCode = (code: string) => { + const queryCode = code + .replace(/(\$\$\$)/g, () => encodedNodeWildcardSequence) + .replace(/(\$\$)/g, () => encodedIdentifierWildcardSequence) + + return queryCode +} + +const replaceEncodedWildcards = (value: string) => + value.replace(/a_x_3_x_a/g, () => '$$$').replace(/a_x_2_x_a/g, () => '$$') + +const postprocessQueryNode = (queryNode: PoorNodeType) => { + traverseAst(queryNode, isNode, getNodeType, { + identifier: (node) => { + const nodeName = node.rawValue as string + + if ( + nodeName.includes(encodedNodeWildcardSequence) || + nodeName.includes(encodedIdentifierWildcardSequence) + ) { + node.rawValue = replaceEncodedWildcards(nodeName) + } + }, + string_literal_content: (node) => { + const nodeName = node.rawValue as string + + if ( + nodeName.includes(encodedNodeWildcardSequence) || + nodeName.includes(encodedIdentifierWildcardSequence) + ) { + node.rawValue = replaceEncodedWildcards(nodeName) + } + }, + }) + + return queryNode +} + +export const csharpParser: ParserSettings = { + supportedExtensions, + parseCode, + isNode, + isIdentifierNode, + astPropsToSkip, + getProgramBodyFromRootNode, + getProgramNodeFromRootNode, + getIdentifierNodeName, + getNodeType, + unwrapExpressionStatement, + createBlockStatementNode, + getSanitizedNodeValue, + identifierNodeTypes, + setIdentifierNodeName, + shouldCompareNode, + wildcardUtils, + compareNodesBeforeWildcardsComparison: () => undefined, + compareNodesAfterWildcardsComparison: () => undefined, + identifierTypeAnnotationFieldName: '', + stringLikeLiteralUtils, + numericLiteralUtils, + programNodeAndBlockNodeUtils, + getNodePosition, + getParseErrorLocation, + alternativeNodeTypes, + preprocessQueryCode, + postprocessQueryNode, + init: parserModule.init, +} + +export default csharpParser diff --git a/packages/core/src/parserSettings/cssTree/afterWildcardsComparators.ts b/packages/core/src/parserSettings/cssTree/afterWildcardsComparators.ts new file mode 100644 index 0000000..20c7a78 --- /dev/null +++ b/packages/core/src/parserSettings/cssTree/afterWildcardsComparators.ts @@ -0,0 +1,6 @@ +import { NodesComparator } from '../../types' +import { matchRuleWithoutSelector } from './matchRuleWithoutSelector' + +export const afterWildcardsComparators: NodesComparator[] = [ + matchRuleWithoutSelector, +] diff --git a/packages/core/src/parserSettings/cssTree/beforeWildcardsComparators.ts b/packages/core/src/parserSettings/cssTree/beforeWildcardsComparators.ts new file mode 100644 index 0000000..73a81ee --- /dev/null +++ b/packages/core/src/parserSettings/cssTree/beforeWildcardsComparators.ts @@ -0,0 +1,35 @@ +import { NodesComparator } from '../../types' +import { createMatchWildcardsInPropValueNodesComparator } from '../nodeComparatorFactories/Other/matchWildcardsInPropValueNodes' +import { matchWildcardInDeclarationProperty } from './matchWildcardInDeclarationProperty' +import { matchWildcardsInDimension } from './matchWildcardsInDimension' +import { matchHashWithWildcard } from './matchHashWithWildcard' + +const nodeTypesWithNameAndChildren = ['Function'] + +const matchWildcardsInNodeTypesWithNameAndChildrenNodesComparator = + nodeTypesWithNameAndChildren.map((nodeType) => + createMatchWildcardsInPropValueNodesComparator({ + nodeType, + keysToTraverse: ['children'], + keysWithWildcards: ['name'], + }), + ) + +const nodeTypesWithNameAndValue = ['MediaFeature'] + +const matchWildcardsInNodeTypesWithNameAndValueNodesComparator = + nodeTypesWithNameAndValue.map((nodeType) => + createMatchWildcardsInPropValueNodesComparator({ + nodeType, + keysToTraverse: ['value'], + keysWithWildcards: ['name'], + }), + ) + +export const beforeWildcardsComparators: NodesComparator[] = [ + ...matchWildcardsInNodeTypesWithNameAndChildrenNodesComparator, + ...matchWildcardsInNodeTypesWithNameAndValueNodesComparator, + matchWildcardInDeclarationProperty, + matchWildcardsInDimension, + matchHashWithWildcard, +] diff --git a/packages/core/src/parserSettings/cssTree/common.ts b/packages/core/src/parserSettings/cssTree/common.ts new file mode 100644 index 0000000..b420010 --- /dev/null +++ b/packages/core/src/parserSettings/cssTree/common.ts @@ -0,0 +1,26 @@ +import { createWildcardUtils } from '../../wildcardUtilsFactory' +import { PoorNodeType } from '../../types' +import { getNodeType } from '../espreeParser/common' + +/* + * We don't have purely identifier nodes in this parser + * We compare everything using string wildcards + */ + +export const identifierNodeTypes: string[] = ['Identifier'] + +const wildcardChar = '$' +const numericWildcard = '0x0' + +export const getIdentifierNodeName = (node: PoorNodeType) => node.name as string +export const setIdentifierNodeName = (node: PoorNodeType, name: string) => { + node.name = name +} + +export const wildcardUtils = createWildcardUtils( + identifierNodeTypes, + numericWildcard, + wildcardChar, + getIdentifierNodeName, + getNodeType, +) diff --git a/packages/core/src/parserSettings/cssTree/matchHashWithWildcard.ts b/packages/core/src/parserSettings/cssTree/matchHashWithWildcard.ts new file mode 100644 index 0000000..0a336ff --- /dev/null +++ b/packages/core/src/parserSettings/cssTree/matchHashWithWildcard.ts @@ -0,0 +1,51 @@ +import { NodesComparator, PoorNodeType } from '../../types' +import { matchStringOrIdentifierAliases } from '../../searchStages/matchStringOrIdentifierAliases' + +/* + * Adds support for matching color Hash with wildcard + * Q: {color: $$ } C: {color: #000 } + */ +export const matchHashWithWildcard: NodesComparator = ( + { queryNode, fileNode, searchSettings, matchContext }, + _, + { fileKeysToTraverseForOtherMatches, log }, +) => { + if (queryNode?.type === 'Identifier' && fileNode?.type === 'Hash') { + log( + 'Compare Identifier with Hash node', + queryNode.name as string, + fileNode.value as string, + ) + + const { wildcardUtils } = searchSettings.parserSettings + const { caseInsensitive } = searchSettings + + const queryNodeStringContent = queryNode.name as string + + const fileNodeStringContent = fileNode.value as string + + const wildcardsMeta = wildcardUtils.getStringWildcardsFromString( + queryNodeStringContent, + ) + + if (wildcardsMeta.length > 0) { + const levelMatch = matchStringOrIdentifierAliases({ + queryValue: queryNodeStringContent, + fileValue: fileNodeStringContent, + wildcardsMeta, + matchContext, + wildcardUtils, + caseInsensitive, + }) + + if (levelMatch) { + return { + levelMatch, + queryKeysToTraverseForValidatingMatch: [], + fileKeysToTraverseForValidatingMatch: [], + fileKeysToTraverseForOtherMatches, + } + } + } + } +} diff --git a/packages/core/src/parserSettings/cssTree/matchRuleWithoutSelector.ts b/packages/core/src/parserSettings/cssTree/matchRuleWithoutSelector.ts new file mode 100644 index 0000000..b6e2575 --- /dev/null +++ b/packages/core/src/parserSettings/cssTree/matchRuleWithoutSelector.ts @@ -0,0 +1,25 @@ +import { NodesComparator, PoorNodeType } from '../../types' + +/* + * Skip comparing `prelude` for rules where prelude is empty 'Raw' + * Q: {background-color: red} C: p {background-color: red} + */ +export const matchRuleWithoutSelector: NodesComparator = ( + { queryNode, fileNode }, + _, + { fileKeysToTraverseForOtherMatches }, +) => { + if ( + queryNode?.type === 'Rule' && + fileNode?.type === 'Rule' && + (queryNode?.prelude as PoorNodeType)?.type === 'Raw' && + (queryNode?.prelude as PoorNodeType)?.value === '' + ) { + return { + levelMatch: true, + queryKeysToTraverseForValidatingMatch: ['block'], + fileKeysToTraverseForValidatingMatch: ['block'], + fileKeysToTraverseForOtherMatches, + } + } +} diff --git a/packages/core/src/parserSettings/cssTree/matchWildcardInDeclarationProperty.ts b/packages/core/src/parserSettings/cssTree/matchWildcardInDeclarationProperty.ts new file mode 100644 index 0000000..1484ff9 --- /dev/null +++ b/packages/core/src/parserSettings/cssTree/matchWildcardInDeclarationProperty.ts @@ -0,0 +1,60 @@ +import { NodesComparator, PoorNodeType } from '../../types' +import { matchStringOrIdentifierAliases } from '../../searchStages/matchStringOrIdentifierAliases' + +/* + * Adds support for matching wildcard in "Declaration" property key + * Q: {background-$$: red} C: p {background-color: red} + */ +export const matchWildcardInDeclarationProperty: NodesComparator = ( + { queryNode, fileNode, searchSettings, matchContext }, + _, + { fileKeysToTraverseForOtherMatches }, +) => { + if (queryNode?.type === 'Declaration' && fileNode?.type === 'Declaration') { + const { wildcardUtils } = searchSettings.parserSettings + const { caseInsensitive, mode } = searchSettings + const isExact = mode === 'exact' + + // Important modifier is optional for query in include mode + let levelMatch = isExact + ? queryNode.important === fileNode.important + : queryNode.important + ? Boolean(fileNode.important) + : true + + const queryNodeStringContent = queryNode.property as string + + const fileNodeStringContent = fileNode.property as string + + const wildcardsMeta = wildcardUtils.getStringWildcardsFromString( + queryNodeStringContent, + ) + + if (wildcardsMeta.length > 0) { + levelMatch = + levelMatch && + matchStringOrIdentifierAliases({ + queryValue: queryNodeStringContent, + fileValue: fileNodeStringContent, + wildcardsMeta, + matchContext, + wildcardUtils, + caseInsensitive, + }) + } else { + /** + * If there are no wildcards in given prop, compare prop values directly + */ + levelMatch = + levelMatch && queryNodeStringContent === fileNodeStringContent + } + + // We always want to return here, otherwise generic string wildcard matching would take over and match incorrectly + return { + levelMatch, + queryKeysToTraverseForValidatingMatch: ['value'], + fileKeysToTraverseForValidatingMatch: ['value'], + fileKeysToTraverseForOtherMatches, + } + } +} diff --git a/packages/core/src/parserSettings/cssTree/matchWildcardsInDimension.ts b/packages/core/src/parserSettings/cssTree/matchWildcardsInDimension.ts new file mode 100644 index 0000000..37f4852 --- /dev/null +++ b/packages/core/src/parserSettings/cssTree/matchWildcardsInDimension.ts @@ -0,0 +1,71 @@ +import { NodesComparator, PoorNodeType } from '../../types' +import { matchStringOrIdentifierAliases } from '../../searchStages/matchStringOrIdentifierAliases' + +/* + * Adds support for matching wildcard in "Dimension" unit and value + * Q: {width: 0x0px} C: {width: 5px} + * Q: {width: 5$$} C: {width: 5px} + * Q: {width: 0x0$$} C: {width: 5px} + */ +export const matchWildcardsInDimension: NodesComparator = ( + { queryNode, fileNode, searchSettings, matchContext }, + _, + { fileKeysToTraverseForOtherMatches, log }, +) => { + if (queryNode?.type === 'Dimension' && fileNode?.type === 'Dimension') { + log( + 'Compare dimension nodes', + queryNode.value as string, + queryNode.unit as string, + fileNode.value as string, + fileNode.unit as string, + ) + + const { wildcardUtils } = searchSettings.parserSettings + const { caseInsensitive } = searchSettings + + let levelMatch = true + + const queryNodeStringContent = queryNode.unit as string + + const fileNodeStringContent = fileNode.unit as string + + const wildcardsMeta = wildcardUtils.getStringWildcardsFromString( + queryNodeStringContent, + ) + + if (wildcardsMeta.length > 0) { + levelMatch = + levelMatch && + matchStringOrIdentifierAliases({ + queryValue: queryNodeStringContent, + fileValue: fileNodeStringContent, + wildcardsMeta, + matchContext, + wildcardUtils, + caseInsensitive, + }) + } else { + /** + * If there are no wildcards in given prop, compare prop values directly + */ + levelMatch = + levelMatch && queryNodeStringContent === fileNodeStringContent + } + + /** + * Compare values only if there is no numeric wildcard in query + */ + if (queryNode.value !== wildcardUtils.numericWildcard) { + levelMatch = levelMatch && queryNode.value === fileNode.value + } + + // We always want to return here, otherwise generic string wildcard matching would take over and match incorrectly + return { + levelMatch, + queryKeysToTraverseForValidatingMatch: [], + fileKeysToTraverseForValidatingMatch: [], + fileKeysToTraverseForOtherMatches, + } + } +} diff --git a/packages/core/src/parserSettings/cssTree/settings.ts b/packages/core/src/parserSettings/cssTree/settings.ts new file mode 100644 index 0000000..1cffbca --- /dev/null +++ b/packages/core/src/parserSettings/cssTree/settings.ts @@ -0,0 +1,383 @@ +import { parse, ParseOptions, toPlainObject } from 'css-tree' +import { + Location, + MatchPosition, + NumericLiteralUtils, + ParserSettings, + PoorNodeType, + ProgramNodeAndBlockNodeUtils, + StringLikeLiteralUtils, + NodesComparatorParameters, + GetUniqueTokensFromStringOrIdentifierNode, +} from '../../types' +import { + decomposeString, + normalizeText, + runNodesComparators, +} from '../../utils' +import { + getIdentifierNodeName, + identifierNodeTypes, + setIdentifierNodeName, + wildcardUtils, +} from './common' +import { traverseAst } from '../../searchStages/traverseAndMatch' +import { beforeWildcardsComparators } from './beforeWildcardsComparators' +import { afterWildcardsComparators } from './afterWildcardsComparators' + +const supportedExtensions = ['css'] + +const getProgramNodeFromRootNode = (rootNode: PoorNodeType) => rootNode // root node is program node + +const getProgramBodyFromRootNode = (fileNode: PoorNodeType) => { + return fileNode.children as PoorNodeType[] +} + +const unwrapExpressionStatement = (node: PoorNodeType) => { + return node as PoorNodeType +} + +const createBlockStatementNode = ( + children: PoorNodeType[], + position: MatchPosition, +) => ({ + type: 'Block', + children, + ...position, +}) + +const isNode = (maybeNode: PoorNodeType) => { + return typeof maybeNode?.type === 'string' +} + +const astPropsToSkip = ['loc'] + +const parseCode = (code: string) => { + const sharedOptions: ParseOptions = { + parseAtrulePrelude: true, + parseRulePrelude: true, + parseValue: true, + positions: true, + } + + if (code.includes('{')) { + return toPlainObject( + parse(code, { ...sharedOptions, context: 'stylesheet' }), + ) as unknown as PoorNodeType + } else { + return toPlainObject( + parse(code, { ...sharedOptions, context: 'declarationList' }), + ) as unknown as PoorNodeType + } +} + +type NodeValueSanitizers = Record any>> + +const nodeValuesSanitizers: NodeValueSanitizers = { + ['Raw']: { + value: normalizeText, + }, +} + +const getSanitizedNodeValue = ( + nodeType: string, + valueKey: string, + value: unknown, +) => { + const valueSanitizer = nodeValuesSanitizers?.[nodeType]?.[valueKey] + + if (valueSanitizer) { + return valueSanitizer(value) + } + + return value +} + +const shouldCompareNode = (node: PoorNodeType) => { + if (node.type === 'WhiteSpace') { + return false + } + + if (node.type === 'Raw') { + const value: string = getSanitizedNodeValue('Raw', 'value', node.value) + + const shouldCompare = value.length > 0 + + return shouldCompare + } + + return true +} + +const getNodeType = (node: PoorNodeType) => node.type as string + +const isIdentifierNode = (node: PoorNodeType) => + identifierNodeTypes.includes(getNodeType(node)) + +const stringLikeNodeTypes = [ + 'TypeSelector', + 'Raw', + 'ClassSelector', + 'Identifier', + 'IdSelector', + 'Url', +] + +const stringLikeLiteralUtils: StringLikeLiteralUtils = { + // Raw is only pure string node + isStringLikeLiteralNode: (node: PoorNodeType) => + stringLikeNodeTypes.includes(node.type as string), + getStringLikeLiteralValue: (node: PoorNodeType) => { + return (node?.value as string) || (node?.name as string) + }, +} + +const pureNumericNodes = ['Percentage', 'Number', 'Hash'] + +const numericLiteralUtils: NumericLiteralUtils = { + isNumericLiteralNode: (node: PoorNodeType) => + pureNumericNodes.includes(node.type as string), + getNumericLiteralValue: (node: PoorNodeType) => node.value as string, +} + +const programNodeAndBlockNodeUtils: ProgramNodeAndBlockNodeUtils = { + isProgramNode: (node: PoorNodeType) => node.type === 'StyleSheet', + isBlockNode: (node: PoorNodeType) => node.type === 'Block', + programNodeBodyKey: 'children', + blockNodeBodyKey: 'children', +} + +const getNodePosition: ParserSettings['getNodePosition'] = ( + node: PoorNodeType, +) => { + const location = node.loc as unknown as Location + + return { + start: ((location as any)?.start?.offset as number) ?? 0, + end: ((location as any)?.end?.offset as number) ?? 0, + loc: { + start: { + line: location.start.line, + column: location.start.column - 1, // We need 0-based, parser return 1-based + }, + end: { + line: location.end.line, + column: location.end.column - 1, // We need 0-based, parser return 1-based + }, + }, + } +} + +const getParseErrorLocation = (e: any) => ({ + line: e.loc?.line ?? 0, + column: e.loc?.column ?? 0, +}) + +const alternativeNodeTypes = { + Identifier: identifierNodeTypes, +} + +/** + * To support wildcards in caa we have to + * - encode wildcard, do it in query text before parsing $$ => a_a_x + * - decode wildcard, traverse parsed query and: a_a_x => $$ + * - Same for numeric wildcard 0x0 -> 00000000 // 0{8} + * `$$` is invalid tag name start in all html parsers + */ +const encodedStringWildcardSequence = 'a_a_a' +const encodedNodesTreeWildcardSequence = 'z_z_z' + +const encodedNumericWildcardSequence = '00000000' + +const preprocessQueryCode = (code: string) => { + const queryCode = code + .replace(/(\$\$\$)/g, () => encodedNodesTreeWildcardSequence) + .replace(/(\$\$)/g, () => encodedStringWildcardSequence) + .replace(/0x0/g, encodedNumericWildcardSequence) + + return queryCode +} + +const replaceEncodedWildcards = (value: string) => + value + .replace(/a_a_a/g, () => '$$') + .replace(/z_z_z/g, () => '$$$') + .replace(/0{8}/g, '0x0') + +const stringNodeTypes = { + withName: [ + 'Identifier', + 'IdSelector', + 'MediaFeature', + 'ClassSelector', + 'PseudoClassSelector', + 'PseudoElementSelector', + 'TypeSelector', + 'Function', + 'Combinator', + ], + withValue: ['String', 'Url'], + withProperty: ['Declaration'], +} + +const postprocessQueryNodeWithName = (node: PoorNodeType) => { + const name = node.name as string + + if ( + name.includes(encodedStringWildcardSequence) || + name.includes(encodedNodesTreeWildcardSequence) + ) { + node.name = replaceEncodedWildcards(name) + } +} + +const postprocessQueryNodeWithValue = (node: PoorNodeType) => { + const value = node.value as string + + if ( + value.includes(encodedStringWildcardSequence) || + value.includes(encodedNodesTreeWildcardSequence) || + value.includes(encodedNumericWildcardSequence) + ) { + node.value = replaceEncodedWildcards(value) + } +} + +const postprocessQueryNodeWithProperty = (node: PoorNodeType) => { + const property = node.property as string + + if ( + property.includes(encodedStringWildcardSequence) || + property.includes(encodedNodesTreeWildcardSequence) + ) { + node.property = replaceEncodedWildcards(property) + } +} + +const createVisitorsForNodeTypes = ( + types: string[], + visitorFn: (node: PoorNodeType) => void, +) => + types.reduce( + (visitorsMap, nodeType) => ({ + ...visitorsMap, + [nodeType]: visitorFn, + }), + {}, + ) + +const postprocessVisitors = { + ...createVisitorsForNodeTypes( + stringNodeTypes.withName, + postprocessQueryNodeWithName, + ), + ...createVisitorsForNodeTypes( + stringNodeTypes.withProperty, + postprocessQueryNodeWithProperty, + ), + ...createVisitorsForNodeTypes( + [...stringNodeTypes.withValue, ...pureNumericNodes], + postprocessQueryNodeWithValue, + ), + Dimension: (node: PoorNodeType) => { + const unit = node.unit as string + const value = node.value as string + + if (unit.includes(encodedStringWildcardSequence)) { + node.unit = replaceEncodedWildcards(unit) + } + + if (value === encodedNumericWildcardSequence) { + node.value = replaceEncodedWildcards(value) + } + }, +} + +const postprocessQueryNode = (queryNode: PoorNodeType) => { + traverseAst(queryNode, isNode, getNodeType, postprocessVisitors) + + return queryNode +} + +const compareNodesBeforeWildcardsComparison = ( + ...nodeComparatorParams: NodesComparatorParameters +) => { + return runNodesComparators(beforeWildcardsComparators, nodeComparatorParams) +} + +const compareNodesAfterWildcardsComparison = ( + ...nodeComparatorParams: NodesComparatorParameters +) => { + return runNodesComparators(afterWildcardsComparators, nodeComparatorParams) +} + +const getUniqueTokensFromStringOrIdentifierNode: GetUniqueTokensFromStringOrIdentifierNode = + ({ queryNode, caseInsensitive, parserSettings }) => { + const MIN_TOKEN_LEN = 2 + + const { anyStringWildcardRegExp } = parserSettings.wildcardUtils + const tokens: string[] = [] + + const valuesToProcess: string[] = [] + + if (stringNodeTypes.withName.includes(queryNode.type as string)) { + valuesToProcess.push(queryNode.name as string) + } + + if (stringNodeTypes.withProperty.includes(queryNode.type as string)) { + valuesToProcess.push(queryNode.property as string) + } + + if (stringNodeTypes.withValue.includes(queryNode.type as string)) { + valuesToProcess.push(queryNode.value as string) + } + + valuesToProcess + .map((val) => + parserSettings.wildcardUtils.removeWildcardAliasesFromStringLiteral( + val, + ), + ) + .map((val) => decomposeString(val, anyStringWildcardRegExp)) + .flat(1) + .forEach((part) => { + if (part.length >= MIN_TOKEN_LEN) { + tokens.push(caseInsensitive ? part.toLocaleLowerCase() : part) + } + }) + + return tokens + } + +export const cssTree: ParserSettings = { + supportedExtensions, + parseCode, + isNode, + isIdentifierNode, + astPropsToSkip, + getProgramBodyFromRootNode, + getProgramNodeFromRootNode, + getIdentifierNodeName, + getNodeType, + unwrapExpressionStatement, + createBlockStatementNode, + getSanitizedNodeValue, + identifierNodeTypes, + setIdentifierNodeName, + shouldCompareNode, + wildcardUtils, + compareNodesBeforeWildcardsComparison, + compareNodesAfterWildcardsComparison, + identifierTypeAnnotationFieldName: 'typeAnnotation', + stringLikeLiteralUtils, + numericLiteralUtils, + programNodeAndBlockNodeUtils, + getNodePosition, + getParseErrorLocation, + alternativeNodeTypes, + postprocessQueryNode, + preprocessQueryCode, + getUniqueTokensFromStringOrIdentifierNode, +} + +export default cssTree diff --git a/packages/core/src/parserSettings/espreeParser/afterWildcardsComparators.ts b/packages/core/src/parserSettings/espreeParser/afterWildcardsComparators.ts new file mode 100644 index 0000000..cdee906 --- /dev/null +++ b/packages/core/src/parserSettings/espreeParser/afterWildcardsComparators.ts @@ -0,0 +1,19 @@ +import { NodesComparator } from '../../types' +import { wildcardForAnyImport } from './nodeComparators/wildcardForAnyImport' +import { matchJsxElementRegardlessClosingTagNodesComparator } from './nodeComparators/matchJsxElementRegardlessClosingTag' +import { matchJsxIdentifierUsingIdentifierNodesComparator } from './nodeComparators/matchJsxIdentifierUsingIdentifier' +import { partialMatchTemplateLiteralNodesComparator } from './nodeComparators/partialMatchTemplateLiteral' +import { matchOptionalFlagInMemberExpressionNodesComparator } from './nodeComparators/matchOptionalFlagInMemberExpression' +import { matchDestructPropBeforeRenameNodesComparator } from './nodeComparators/matchDestructPropBeforeRename' +import { matchObjectPropertiesOfDifferentTypesNodesComparator } from './nodeComparators/matchObjectPropertiesOfDifferentTypes' + +// Better keep this order +export const afterWildcardsComparators: NodesComparator[] = [ + wildcardForAnyImport, + matchDestructPropBeforeRenameNodesComparator, + matchObjectPropertiesOfDifferentTypesNodesComparator, + matchJsxElementRegardlessClosingTagNodesComparator, + matchJsxIdentifierUsingIdentifierNodesComparator, + partialMatchTemplateLiteralNodesComparator, + matchOptionalFlagInMemberExpressionNodesComparator, +] diff --git a/packages/core/src/parserSettings/espreeParser/beforeWildcardsComparators.ts b/packages/core/src/parserSettings/espreeParser/beforeWildcardsComparators.ts new file mode 100644 index 0000000..6203233 --- /dev/null +++ b/packages/core/src/parserSettings/espreeParser/beforeWildcardsComparators.ts @@ -0,0 +1,6 @@ +import { NodesComparator } from '../../types' +import { wildcardForAssignmentPatternOrDefaultParamValuesNodesComparator } from './nodeComparators/wildcardForAssignmentPatternOrDefaultParamValues' + +export const beforeWildcardsComparators: NodesComparator[] = [ + wildcardForAssignmentPatternOrDefaultParamValuesNodesComparator, +] diff --git a/packages/core/src/parserSettings/espreeParser/common.ts b/packages/core/src/parserSettings/espreeParser/common.ts new file mode 100644 index 0000000..f9ac9bc --- /dev/null +++ b/packages/core/src/parserSettings/espreeParser/common.ts @@ -0,0 +1,18 @@ +import { numericWildcard, wildcardChar } from '../_common/JSFamilyCommon' +import { createWildcardUtils } from '../../wildcardUtilsFactory' +import { PoorNodeType } from '../../types' +export const identifierNodeTypes = ['Identifier', 'JSXIdentifier'] +export const getIdentifierNodeName = (node: PoorNodeType) => node.name as string +export const setIdentifierNodeName = (node: PoorNodeType, name: string) => { + node.name = name +} + +export const getNodeType = (node: PoorNodeType) => node.type as string + +export const wildcardUtils = createWildcardUtils( + identifierNodeTypes, + numericWildcard, + wildcardChar, + getIdentifierNodeName, + getNodeType, +) diff --git a/packages/core/src/parserSettings/espreeParser/nodeComparators/matchDestructPropBeforeRename.ts b/packages/core/src/parserSettings/espreeParser/nodeComparators/matchDestructPropBeforeRename.ts new file mode 100644 index 0000000..1d522cf --- /dev/null +++ b/packages/core/src/parserSettings/espreeParser/nodeComparators/matchDestructPropBeforeRename.ts @@ -0,0 +1,6 @@ +import { createMatchDestructPropBeforeRenameNodesComparator } from '../../nodeComparatorFactories/JavaScriptSpecific/matchDestructPropBeforeRename' + +export const matchDestructPropBeforeRenameNodesComparator = + createMatchDestructPropBeforeRenameNodesComparator({ + objectPropertyNodeName: 'Property', + }) diff --git a/packages/core/src/parserSettings/espreeParser/nodeComparators/matchJsxElementRegardlessClosingTag.ts b/packages/core/src/parserSettings/espreeParser/nodeComparators/matchJsxElementRegardlessClosingTag.ts new file mode 100644 index 0000000..a60143b --- /dev/null +++ b/packages/core/src/parserSettings/espreeParser/nodeComparators/matchJsxElementRegardlessClosingTag.ts @@ -0,0 +1,4 @@ +import { createMatchJsxElementRegardlessClosingTagNodesComparator } from '../../nodeComparatorFactories/JsxSpecific/matchJsxElementRegardlessClosingTag' + +export const matchJsxElementRegardlessClosingTagNodesComparator = + createMatchJsxElementRegardlessClosingTagNodesComparator() diff --git a/packages/core/src/parserSettings/espreeParser/nodeComparators/matchJsxIdentifierUsingIdentifier.ts b/packages/core/src/parserSettings/espreeParser/nodeComparators/matchJsxIdentifierUsingIdentifier.ts new file mode 100644 index 0000000..7a21bf7 --- /dev/null +++ b/packages/core/src/parserSettings/espreeParser/nodeComparators/matchJsxIdentifierUsingIdentifier.ts @@ -0,0 +1,4 @@ +import { createMatchJsxIdentifierUsingIdentifierNodesComparator } from '../../nodeComparatorFactories/JsxSpecific/matchJsxIdentifierUsingIdentifier' + +export const matchJsxIdentifierUsingIdentifierNodesComparator = + createMatchJsxIdentifierUsingIdentifierNodesComparator() diff --git a/packages/core/src/parserSettings/espreeParser/nodeComparators/matchObjectPropertiesOfDifferentTypes.ts b/packages/core/src/parserSettings/espreeParser/nodeComparators/matchObjectPropertiesOfDifferentTypes.ts new file mode 100644 index 0000000..99bdf9b --- /dev/null +++ b/packages/core/src/parserSettings/espreeParser/nodeComparators/matchObjectPropertiesOfDifferentTypes.ts @@ -0,0 +1,6 @@ +import { createMatchObjectPropertiesOfDifferentTypesNodesComparator } from '../../nodeComparatorFactories/JavaScriptSpecific/matchObjectPropertiesOfDifferentTypes' + +export const matchObjectPropertiesOfDifferentTypesNodesComparator = + createMatchObjectPropertiesOfDifferentTypesNodesComparator({ + objectPropertyNodeName: 'Property', + }) diff --git a/packages/core/src/parserSettings/espreeParser/nodeComparators/matchOptionalFlagInMemberExpression.ts b/packages/core/src/parserSettings/espreeParser/nodeComparators/matchOptionalFlagInMemberExpression.ts new file mode 100644 index 0000000..2287657 --- /dev/null +++ b/packages/core/src/parserSettings/espreeParser/nodeComparators/matchOptionalFlagInMemberExpression.ts @@ -0,0 +1,4 @@ +import { createMatchOptionalFlagInMemberExpressionNodesComparator } from '../../nodeComparatorFactories/JavaScriptSpecific/matchOptionalFlagInMemberExpression' + +export const matchOptionalFlagInMemberExpressionNodesComparator = + createMatchOptionalFlagInMemberExpressionNodesComparator() diff --git a/packages/core/src/parserSettings/espreeParser/nodeComparators/partialMatchTemplateLiteral.ts b/packages/core/src/parserSettings/espreeParser/nodeComparators/partialMatchTemplateLiteral.ts new file mode 100644 index 0000000..3579c69 --- /dev/null +++ b/packages/core/src/parserSettings/espreeParser/nodeComparators/partialMatchTemplateLiteral.ts @@ -0,0 +1,4 @@ +import { createPartialMatchTemplateLiteralNodesComparator } from '../../nodeComparatorFactories/JavaScriptSpecific/partialMatchTemplateLiteral' + +export const partialMatchTemplateLiteralNodesComparator = + createPartialMatchTemplateLiteralNodesComparator() diff --git a/packages/core/src/parserSettings/espreeParser/nodeComparators/wildcardForAnyImport.ts b/packages/core/src/parserSettings/espreeParser/nodeComparators/wildcardForAnyImport.ts new file mode 100644 index 0000000..103a66d --- /dev/null +++ b/packages/core/src/parserSettings/espreeParser/nodeComparators/wildcardForAnyImport.ts @@ -0,0 +1,6 @@ +import { createWildcardForAnyImportNodesComparator } from '../../nodeComparatorFactories/JavaScriptSpecific/wildcardForAnyImport' +import { wildcardUtils } from '../common' + +export const wildcardForAnyImport = createWildcardForAnyImportNodesComparator({ + wildcardUtils, +}) diff --git a/packages/core/src/parserSettings/espreeParser/nodeComparators/wildcardForAssignmentPatternOrDefaultParamValues.ts b/packages/core/src/parserSettings/espreeParser/nodeComparators/wildcardForAssignmentPatternOrDefaultParamValues.ts new file mode 100644 index 0000000..6f1c78c --- /dev/null +++ b/packages/core/src/parserSettings/espreeParser/nodeComparators/wildcardForAssignmentPatternOrDefaultParamValues.ts @@ -0,0 +1,4 @@ +import { createWildcardForAssignmentPatternOrDefaultParamValuesNodesComparator } from '../../nodeComparatorFactories/JavaScriptSpecific/wildcardForAssignmentPatternOrDefaultParamValues' + +export const wildcardForAssignmentPatternOrDefaultParamValuesNodesComparator = + createWildcardForAssignmentPatternOrDefaultParamValuesNodesComparator() diff --git a/packages/core/src/parserSettings/espreeParser/settings.ts b/packages/core/src/parserSettings/espreeParser/settings.ts new file mode 100644 index 0000000..76ad0b6 --- /dev/null +++ b/packages/core/src/parserSettings/espreeParser/settings.ts @@ -0,0 +1,240 @@ +import { parse } from 'espree' + +import { + Location, + NodesComparatorParameters, + NumericLiteralUtils, + ParserSettings, + PoorNodeType, + ProgramNodeAndBlockNodeUtils, + StringLikeLiteralUtils, + MatchPosition, +} from '../../types' +import { normalizeText, runNodesComparators } from '../../utils' +import { supportedExtensions } from '../_common/JSFamilyCommon' +import { afterWildcardsComparators } from './afterWildcardsComparators' +import { beforeWildcardsComparators } from './beforeWildcardsComparators' +import { + getIdentifierNodeName, + getNodeType, + identifierNodeTypes, + wildcardUtils, + setIdentifierNodeName, +} from './common' + +const getProgramNodeFromRootNode = (rootNode: PoorNodeType) => rootNode // root node is program node + +const getProgramBodyFromRootNode = (rootNode: PoorNodeType) => { + return getProgramNodeFromRootNode(rootNode).body as PoorNodeType[] +} + +const unwrapExpressionStatement = (node: PoorNodeType) => { + if (typeof node !== 'object') { + return node + } + + if (node.type === 'ExpressionStatement') { + return node.expression as PoorNodeType + } + + return node as PoorNodeType +} + +const createBlockStatementNode = ( + body: PoorNodeType[], + position: MatchPosition, +) => + ({ + type: 'BlockStatement', + body, + loc: position.loc, + range: [position.start, position.end], + } as unknown as PoorNodeType) + +const isNode = (maybeNode: PoorNodeType) => { + return typeof maybeNode?.type === 'string' +} + +const astPropsToSkip = [ + 'start', + 'end', + 'loc', + 'range', + 'raw', + 'trailingComments', + 'leadingComments', + 'comments', + 'tail', // Support for partial matching of template literals + 'parent', // in eslint there is parent prop in node + { type: 'ArrowFunctionExpression', key: 'expression' }, // flag on ArrowFunctionExpression +] + +const parseCode = (code: string, filePath = '') => { + const options = { + range: true, + loc: true, + comment: false, + tokens: false, + ecmaVersion: 'latest', + sourceType: 'module', + ecmaFeatures: { + jsx: true, + globalReturn: true, + impliedStrict: false, + }, + } + + const maybeWrappedJSON = /\.json$/.test(filePath) ? `(${code})` : code + + const ast = parse(maybeWrappedJSON, options) + + return ast as unknown as PoorNodeType +} + +const sanitizeTemplateElementValue = ({ + raw, + cooked, +}: { + raw: string + cooked: string +}) => { + return { + raw: normalizeText(raw), + cooked: normalizeText(cooked), + } +} + +type NodeValueSanitizers = Record any>> + +const nodeValuesSanitizers: NodeValueSanitizers = { + ['JSXText']: { + value: normalizeText, + raw: normalizeText, + }, + ['TemplateElement']: { + value: sanitizeTemplateElementValue, + }, +} + +const getSanitizedNodeValue = ( + nodeType: string, + valueKey: string, + value: unknown, +) => { + const valueSanitizer = nodeValuesSanitizers?.[nodeType]?.[valueKey] + + if (valueSanitizer) { + return valueSanitizer(value) + } + + return value +} + +const shouldCompareNode = (node: PoorNodeType) => { + if (node.type === 'JSXText') { + const value: string = getSanitizedNodeValue('JSXText', 'value', node.value) + + return value.length > 0 + } + + return true +} + +const compareNodesBeforeWildcardsComparison = ( + ...nodeComparatorParams: NodesComparatorParameters +) => { + return runNodesComparators(beforeWildcardsComparators, nodeComparatorParams) +} + +const compareNodesAfterWildcardsComparison = ( + ...nodeComparatorParams: NodesComparatorParameters +) => { + return runNodesComparators(afterWildcardsComparators, nodeComparatorParams) +} + +const isIdentifierNode = (node: PoorNodeType) => + identifierNodeTypes.includes(getNodeType(node)) + +const isFirstCharStringStart = (str: string) => + str.charAt(0) === `'` || str.charAt(0) === `"` + +const stringLikeLiteralUtils: StringLikeLiteralUtils = { + isStringLikeLiteralNode: (node: PoorNodeType) => + (node.type === 'Literal' && isFirstCharStringStart(node.raw as string)) || + node.type === 'TemplateElement' || + node.type === 'JSXText', + getStringLikeLiteralValue: (node: PoorNodeType) => { + if (node.type === 'TemplateElement') { + const { raw } = sanitizeTemplateElementValue( + node.value as { raw: string; cooked: string }, + ) + + return raw + } + + // (node.type === 'Literal' || node.type === 'JSXText' + return normalizeText(node.value as string) + }, +} + +const numericLiteralUtils: NumericLiteralUtils = { + isNumericLiteralNode: (node: PoorNodeType) => + node.type === 'Literal' && !isFirstCharStringStart(node.raw as string), + getNumericLiteralValue: (node: PoorNodeType) => node.raw as string, +} + +const programNodeAndBlockNodeUtils: ProgramNodeAndBlockNodeUtils = { + isProgramNode: (node: PoorNodeType) => node.type === 'Program', + isBlockNode: (node: PoorNodeType) => node.type === 'BlockStatement', + programNodeBodyKey: 'body', + blockNodeBodyKey: 'body', +} + +const getNodePosition: ParserSettings['getNodePosition'] = ( + node: PoorNodeType, +) => ({ + start: (node.range as any)[0] as number, + end: (node.range as any)[1] as number, + loc: node.loc as unknown as Location, +}) + +const getParseErrorLocation = (e: any) => ({ + line: e.lineNumber ?? 0, + column: e.column ?? 0, +}) + +const alternativeNodeTypes = { + Identifier: identifierNodeTypes, + ChainExpression: ['MemberExpression'], + MemberExpression: ['ChainExpression'], + BlockStatement: ['Program'], +} +export const espreeParserSettings: ParserSettings = { + supportedExtensions, + parseCode, + isNode, + isIdentifierNode, + identifierNodeTypes, + astPropsToSkip, + getProgramBodyFromRootNode, + getProgramNodeFromRootNode, + getIdentifierNodeName, + setIdentifierNodeName, + getNodeType, + unwrapExpressionStatement, + createBlockStatementNode, + getSanitizedNodeValue, + shouldCompareNode, + wildcardUtils, + compareNodesBeforeWildcardsComparison, + compareNodesAfterWildcardsComparison, + identifierTypeAnnotationFieldName: 'typeAnnotation', + stringLikeLiteralUtils, + numericLiteralUtils, + programNodeAndBlockNodeUtils, + getNodePosition, + getParseErrorLocation, + alternativeNodeTypes, +} + +export default espreeParserSettings diff --git a/packages/core/src/parserSettings/esprimaParser/afterWildcardsComparators.ts b/packages/core/src/parserSettings/esprimaParser/afterWildcardsComparators.ts new file mode 100644 index 0000000..cdee906 --- /dev/null +++ b/packages/core/src/parserSettings/esprimaParser/afterWildcardsComparators.ts @@ -0,0 +1,19 @@ +import { NodesComparator } from '../../types' +import { wildcardForAnyImport } from './nodeComparators/wildcardForAnyImport' +import { matchJsxElementRegardlessClosingTagNodesComparator } from './nodeComparators/matchJsxElementRegardlessClosingTag' +import { matchJsxIdentifierUsingIdentifierNodesComparator } from './nodeComparators/matchJsxIdentifierUsingIdentifier' +import { partialMatchTemplateLiteralNodesComparator } from './nodeComparators/partialMatchTemplateLiteral' +import { matchOptionalFlagInMemberExpressionNodesComparator } from './nodeComparators/matchOptionalFlagInMemberExpression' +import { matchDestructPropBeforeRenameNodesComparator } from './nodeComparators/matchDestructPropBeforeRename' +import { matchObjectPropertiesOfDifferentTypesNodesComparator } from './nodeComparators/matchObjectPropertiesOfDifferentTypes' + +// Better keep this order +export const afterWildcardsComparators: NodesComparator[] = [ + wildcardForAnyImport, + matchDestructPropBeforeRenameNodesComparator, + matchObjectPropertiesOfDifferentTypesNodesComparator, + matchJsxElementRegardlessClosingTagNodesComparator, + matchJsxIdentifierUsingIdentifierNodesComparator, + partialMatchTemplateLiteralNodesComparator, + matchOptionalFlagInMemberExpressionNodesComparator, +] diff --git a/packages/core/src/parserSettings/esprimaParser/beforeWildcardsComparators.ts b/packages/core/src/parserSettings/esprimaParser/beforeWildcardsComparators.ts new file mode 100644 index 0000000..6203233 --- /dev/null +++ b/packages/core/src/parserSettings/esprimaParser/beforeWildcardsComparators.ts @@ -0,0 +1,6 @@ +import { NodesComparator } from '../../types' +import { wildcardForAssignmentPatternOrDefaultParamValuesNodesComparator } from './nodeComparators/wildcardForAssignmentPatternOrDefaultParamValues' + +export const beforeWildcardsComparators: NodesComparator[] = [ + wildcardForAssignmentPatternOrDefaultParamValuesNodesComparator, +] diff --git a/packages/core/src/parserSettings/esprimaParser/common.ts b/packages/core/src/parserSettings/esprimaParser/common.ts new file mode 100644 index 0000000..f9ac9bc --- /dev/null +++ b/packages/core/src/parserSettings/esprimaParser/common.ts @@ -0,0 +1,18 @@ +import { numericWildcard, wildcardChar } from '../_common/JSFamilyCommon' +import { createWildcardUtils } from '../../wildcardUtilsFactory' +import { PoorNodeType } from '../../types' +export const identifierNodeTypes = ['Identifier', 'JSXIdentifier'] +export const getIdentifierNodeName = (node: PoorNodeType) => node.name as string +export const setIdentifierNodeName = (node: PoorNodeType, name: string) => { + node.name = name +} + +export const getNodeType = (node: PoorNodeType) => node.type as string + +export const wildcardUtils = createWildcardUtils( + identifierNodeTypes, + numericWildcard, + wildcardChar, + getIdentifierNodeName, + getNodeType, +) diff --git a/packages/core/src/parserSettings/esprimaParser/nodeComparators/matchDestructPropBeforeRename.ts b/packages/core/src/parserSettings/esprimaParser/nodeComparators/matchDestructPropBeforeRename.ts new file mode 100644 index 0000000..1d522cf --- /dev/null +++ b/packages/core/src/parserSettings/esprimaParser/nodeComparators/matchDestructPropBeforeRename.ts @@ -0,0 +1,6 @@ +import { createMatchDestructPropBeforeRenameNodesComparator } from '../../nodeComparatorFactories/JavaScriptSpecific/matchDestructPropBeforeRename' + +export const matchDestructPropBeforeRenameNodesComparator = + createMatchDestructPropBeforeRenameNodesComparator({ + objectPropertyNodeName: 'Property', + }) diff --git a/packages/core/src/parserSettings/esprimaParser/nodeComparators/matchJsxElementRegardlessClosingTag.ts b/packages/core/src/parserSettings/esprimaParser/nodeComparators/matchJsxElementRegardlessClosingTag.ts new file mode 100644 index 0000000..a60143b --- /dev/null +++ b/packages/core/src/parserSettings/esprimaParser/nodeComparators/matchJsxElementRegardlessClosingTag.ts @@ -0,0 +1,4 @@ +import { createMatchJsxElementRegardlessClosingTagNodesComparator } from '../../nodeComparatorFactories/JsxSpecific/matchJsxElementRegardlessClosingTag' + +export const matchJsxElementRegardlessClosingTagNodesComparator = + createMatchJsxElementRegardlessClosingTagNodesComparator() diff --git a/packages/core/src/parserSettings/esprimaParser/nodeComparators/matchJsxIdentifierUsingIdentifier.ts b/packages/core/src/parserSettings/esprimaParser/nodeComparators/matchJsxIdentifierUsingIdentifier.ts new file mode 100644 index 0000000..7a21bf7 --- /dev/null +++ b/packages/core/src/parserSettings/esprimaParser/nodeComparators/matchJsxIdentifierUsingIdentifier.ts @@ -0,0 +1,4 @@ +import { createMatchJsxIdentifierUsingIdentifierNodesComparator } from '../../nodeComparatorFactories/JsxSpecific/matchJsxIdentifierUsingIdentifier' + +export const matchJsxIdentifierUsingIdentifierNodesComparator = + createMatchJsxIdentifierUsingIdentifierNodesComparator() diff --git a/packages/core/src/parserSettings/esprimaParser/nodeComparators/matchObjectPropertiesOfDifferentTypes.ts b/packages/core/src/parserSettings/esprimaParser/nodeComparators/matchObjectPropertiesOfDifferentTypes.ts new file mode 100644 index 0000000..99bdf9b --- /dev/null +++ b/packages/core/src/parserSettings/esprimaParser/nodeComparators/matchObjectPropertiesOfDifferentTypes.ts @@ -0,0 +1,6 @@ +import { createMatchObjectPropertiesOfDifferentTypesNodesComparator } from '../../nodeComparatorFactories/JavaScriptSpecific/matchObjectPropertiesOfDifferentTypes' + +export const matchObjectPropertiesOfDifferentTypesNodesComparator = + createMatchObjectPropertiesOfDifferentTypesNodesComparator({ + objectPropertyNodeName: 'Property', + }) diff --git a/packages/core/src/parserSettings/esprimaParser/nodeComparators/matchOptionalFlagInMemberExpression.ts b/packages/core/src/parserSettings/esprimaParser/nodeComparators/matchOptionalFlagInMemberExpression.ts new file mode 100644 index 0000000..2287657 --- /dev/null +++ b/packages/core/src/parserSettings/esprimaParser/nodeComparators/matchOptionalFlagInMemberExpression.ts @@ -0,0 +1,4 @@ +import { createMatchOptionalFlagInMemberExpressionNodesComparator } from '../../nodeComparatorFactories/JavaScriptSpecific/matchOptionalFlagInMemberExpression' + +export const matchOptionalFlagInMemberExpressionNodesComparator = + createMatchOptionalFlagInMemberExpressionNodesComparator() diff --git a/packages/core/src/parserSettings/esprimaParser/nodeComparators/partialMatchTemplateLiteral.ts b/packages/core/src/parserSettings/esprimaParser/nodeComparators/partialMatchTemplateLiteral.ts new file mode 100644 index 0000000..3579c69 --- /dev/null +++ b/packages/core/src/parserSettings/esprimaParser/nodeComparators/partialMatchTemplateLiteral.ts @@ -0,0 +1,4 @@ +import { createPartialMatchTemplateLiteralNodesComparator } from '../../nodeComparatorFactories/JavaScriptSpecific/partialMatchTemplateLiteral' + +export const partialMatchTemplateLiteralNodesComparator = + createPartialMatchTemplateLiteralNodesComparator() diff --git a/packages/core/src/parserSettings/esprimaParser/nodeComparators/wildcardForAnyImport.ts b/packages/core/src/parserSettings/esprimaParser/nodeComparators/wildcardForAnyImport.ts new file mode 100644 index 0000000..103a66d --- /dev/null +++ b/packages/core/src/parserSettings/esprimaParser/nodeComparators/wildcardForAnyImport.ts @@ -0,0 +1,6 @@ +import { createWildcardForAnyImportNodesComparator } from '../../nodeComparatorFactories/JavaScriptSpecific/wildcardForAnyImport' +import { wildcardUtils } from '../common' + +export const wildcardForAnyImport = createWildcardForAnyImportNodesComparator({ + wildcardUtils, +}) diff --git a/packages/core/src/parserSettings/esprimaParser/nodeComparators/wildcardForAssignmentPatternOrDefaultParamValues.ts b/packages/core/src/parserSettings/esprimaParser/nodeComparators/wildcardForAssignmentPatternOrDefaultParamValues.ts new file mode 100644 index 0000000..6f1c78c --- /dev/null +++ b/packages/core/src/parserSettings/esprimaParser/nodeComparators/wildcardForAssignmentPatternOrDefaultParamValues.ts @@ -0,0 +1,4 @@ +import { createWildcardForAssignmentPatternOrDefaultParamValuesNodesComparator } from '../../nodeComparatorFactories/JavaScriptSpecific/wildcardForAssignmentPatternOrDefaultParamValues' + +export const wildcardForAssignmentPatternOrDefaultParamValuesNodesComparator = + createWildcardForAssignmentPatternOrDefaultParamValuesNodesComparator() diff --git a/packages/core/src/parserSettings/esprimaParser/settings.ts b/packages/core/src/parserSettings/esprimaParser/settings.ts index b610495..558d15c 100644 --- a/packages/core/src/parserSettings/esprimaParser/settings.ts +++ b/packages/core/src/parserSettings/esprimaParser/settings.ts @@ -1,21 +1,230 @@ -import { typescriptEslintParserSettings } from '../typescriptEslintParser/settings' -import { ParserSettings, PoorNodeType } from '../../types' import esprima, { parseModule } from 'esprima' -export const esprimaParserSettings: ParserSettings = { - ...typescriptEslintParserSettings, - parseCode: (code, filePath = '') => { - const settings: esprima.ParseOptions = { - jsx: true, - range: true, - loc: true, - } - const maybeWrappedJSON = /\.json$/.test(filePath) ? `(${code})` : code +import { + Location, + NodesComparatorParameters, + NumericLiteralUtils, + ParserSettings, + PoorNodeType, + ProgramNodeAndBlockNodeUtils, + StringLikeLiteralUtils, + MatchPosition, +} from '../../types' +import { normalizeText, runNodesComparators } from '../../utils' +import { supportedExtensions } from '../_common/JSFamilyCommon' +import { afterWildcardsComparators } from './afterWildcardsComparators' +import { beforeWildcardsComparators } from './beforeWildcardsComparators' +import { + getIdentifierNodeName, + getNodeType, + identifierNodeTypes, + wildcardUtils, + setIdentifierNodeName, +} from './common' + +const getProgramNodeFromRootNode = (rootNode: PoorNodeType) => rootNode // root node is program node + +const getProgramBodyFromRootNode = (rootNode: PoorNodeType) => { + return getProgramNodeFromRootNode(rootNode).body as PoorNodeType[] +} + +const unwrapExpressionStatement = (node: PoorNodeType) => { + if (typeof node !== 'object') { + return node + } + + if (node.type === 'ExpressionStatement') { + return node.expression as PoorNodeType + } + + return node as PoorNodeType +} + +const createBlockStatementNode = ( + body: PoorNodeType[], + position: MatchPosition, +) => + ({ + type: 'BlockStatement', + body, + loc: position.loc, + range: [position.start, position.end], + } as unknown as PoorNodeType) + +const isNode = (maybeNode: PoorNodeType) => { + return typeof maybeNode?.type === 'string' +} + +const astPropsToSkip = [ + 'loc', + 'range', + 'raw', + 'trailingComments', + 'leadingComments', + 'comments', + 'tail', // Support for partial matching of template literals + 'parent', // in eslint there is parent prop in node + { type: 'ArrowFunctionExpression', key: 'expression' }, // flag on ArrowFunctionExpression +] - const ast = parseModule(maybeWrappedJSON, settings) +const parseCode = (code: string, filePath = '') => { + const settings: esprima.ParseOptions = { + jsx: true, + range: true, + loc: true, + } + const maybeWrappedJSON = /\.json$/.test(filePath) ? `(${code})` : code - return ast as unknown as PoorNodeType + const ast = parseModule(maybeWrappedJSON, settings) + + return ast as unknown as PoorNodeType +} + +const sanitizeTemplateElementValue = ({ + raw, + cooked, +}: { + raw: string + cooked: string +}) => { + return { + raw: normalizeText(raw), + cooked: normalizeText(cooked), + } +} + +type NodeValueSanitizers = Record any>> + +const nodeValuesSanitizers: NodeValueSanitizers = { + ['JSXText']: { + value: normalizeText, + raw: normalizeText, + }, + ['TemplateElement']: { + value: sanitizeTemplateElementValue, }, } +const getSanitizedNodeValue = ( + nodeType: string, + valueKey: string, + value: unknown, +) => { + const valueSanitizer = nodeValuesSanitizers?.[nodeType]?.[valueKey] + + if (valueSanitizer) { + return valueSanitizer(value) + } + + return value +} + +const shouldCompareNode = (node: PoorNodeType) => { + if (node.type === 'JSXText') { + const value: string = getSanitizedNodeValue('JSXText', 'value', node.value) + + return value.length > 0 + } + + return true +} + +const compareNodesBeforeWildcardsComparison = ( + ...nodeComparatorParams: NodesComparatorParameters +) => { + return runNodesComparators(beforeWildcardsComparators, nodeComparatorParams) +} + +const compareNodesAfterWildcardsComparison = ( + ...nodeComparatorParams: NodesComparatorParameters +) => { + return runNodesComparators(afterWildcardsComparators, nodeComparatorParams) +} + +const isIdentifierNode = (node: PoorNodeType) => + identifierNodeTypes.includes(getNodeType(node)) + +const isFirstCharStringStart = (str: string) => + str.charAt(0) === `'` || str.charAt(0) === `"` + +const stringLikeLiteralUtils: StringLikeLiteralUtils = { + isStringLikeLiteralNode: (node: PoorNodeType) => + (node.type === 'Literal' && isFirstCharStringStart(node.raw as string)) || + node.type === 'TemplateElement' || + node.type === 'JSXText', + getStringLikeLiteralValue: (node: PoorNodeType) => { + if (node.type === 'TemplateElement') { + const { raw } = sanitizeTemplateElementValue( + node.value as { raw: string; cooked: string }, + ) + + return raw + } + + // (node.type === 'Literal' || node.type === 'JSXText' + return normalizeText(node.value as string) + }, +} + +const numericLiteralUtils: NumericLiteralUtils = { + isNumericLiteralNode: (node: PoorNodeType) => + node.type === 'Literal' && !isFirstCharStringStart(node.raw as string), + getNumericLiteralValue: (node: PoorNodeType) => node.raw as string, +} + +const programNodeAndBlockNodeUtils: ProgramNodeAndBlockNodeUtils = { + isProgramNode: (node: PoorNodeType) => node.type === 'Program', + isBlockNode: (node: PoorNodeType) => node.type === 'BlockStatement', + programNodeBodyKey: 'body', + blockNodeBodyKey: 'body', +} + +const getNodePosition: ParserSettings['getNodePosition'] = ( + node: PoorNodeType, +) => ({ + start: (node.range as any)[0] as number, + end: (node.range as any)[1] as number, + loc: node.loc as unknown as Location, +}) + +const getParseErrorLocation = (e: any) => ({ + line: e.lineNumber ?? 0, + column: e.column ?? 0, +}) + +const alternativeNodeTypes = { + Identifier: identifierNodeTypes, + ChainExpression: ['MemberExpression'], + MemberExpression: ['ChainExpression'], + BlockStatement: ['Program'], +} + +export const esprimaParserSettings: ParserSettings = { + supportedExtensions, + parseCode, + isNode, + isIdentifierNode, + identifierNodeTypes, + astPropsToSkip, + getProgramBodyFromRootNode, + getProgramNodeFromRootNode, + getIdentifierNodeName, + setIdentifierNodeName, + getNodeType, + unwrapExpressionStatement, + createBlockStatementNode, + getSanitizedNodeValue, + shouldCompareNode, + wildcardUtils, + compareNodesBeforeWildcardsComparison, + compareNodesAfterWildcardsComparison, + identifierTypeAnnotationFieldName: 'typeAnnotation', + stringLikeLiteralUtils, + numericLiteralUtils, + programNodeAndBlockNodeUtils, + getNodePosition, + getParseErrorLocation, + alternativeNodeTypes, +} + export default esprimaParserSettings diff --git a/packages/core/src/parserSettings/index.ts b/packages/core/src/parserSettings/index.ts index d07c2ef..3cf8479 100644 --- a/packages/core/src/parserSettings/index.ts +++ b/packages/core/src/parserSettings/index.ts @@ -6,6 +6,15 @@ const resolveParserSettings = (parser: string) => () => { export const parserSettingsMap: Record ParserSettings> = { babel: resolveParserSettings('babelParser'), - 'typescript-eslint': resolveParserSettings('typescriptEslintParser'), + 'typescript-eslint-parser': resolveParserSettings('typescriptEslintParser'), + espree: resolveParserSettings('espreeParser'), esprima: resolveParserSettings('esprimaParser'), + 'babel-eslint-parser': resolveParserSettings('babelEslintParser'), + ['angular-eslint-template-parser']: resolveParserSettings( + 'angularEslintTemplateParser', + ), + ['css-tree']: resolveParserSettings('cssTree'), + ['python']: resolveParserSettings('python'), + ['lua']: resolveParserSettings('lua'), + ['csharp']: resolveParserSettings('csharp'), } diff --git a/packages/core/src/parserSettings/lua/common.ts b/packages/core/src/parserSettings/lua/common.ts new file mode 100644 index 0000000..c045f63 --- /dev/null +++ b/packages/core/src/parserSettings/lua/common.ts @@ -0,0 +1,24 @@ +import { createWildcardUtils } from '../../wildcardUtilsFactory' +import { PoorNodeType } from '../../types' + +export const identifierNodeTypes: string[] = ['identifier'] + +const wildcardChar = '$' +const numericWildcard = '0x0' + +export const getIdentifierNodeName = (node: PoorNodeType) => + node.rawValue as string + +export const setIdentifierNodeName = (node: PoorNodeType, name: string) => { + node.rawValue = name +} + +export const getNodeType = (node: PoorNodeType) => node.nodeType as string + +export const wildcardUtils = createWildcardUtils( + identifierNodeTypes, + numericWildcard, + wildcardChar, + getIdentifierNodeName, + getNodeType, +) diff --git a/packages/core/src/parserSettings/lua/parseCode.ts b/packages/core/src/parserSettings/lua/parseCode.ts new file mode 100644 index 0000000..6b7b3c9 --- /dev/null +++ b/packages/core/src/parserSettings/lua/parseCode.ts @@ -0,0 +1,193 @@ +import { treeSitterParserModuleFactory } from '../../treeSitterUtils' +import { PoorNodeType, Location } from '../../types' + +const defineRawValueForNodeTypes = ['identifier', 'number', 'string_content'] + +const postProcessNodes = { + function_declaration: (node: PoorNodeType) => { + /** + * Global scope function declaration without name should be same as anonymous function definition passed as argument + */ + if ((node.name as PoorNodeType)?.rawValue === '') { + node.nodeType = 'function_definition' + node.name = null + + return node + } + + return node + }, + function_definition: (node: PoorNodeType) => { + /** + * Anonymous function definition should have null name field to be compared with `function_declaration` changed into `function_definition` + * We could use delete operator on `function_declaration`, but it's slow. + */ + if (node.name === undefined) { + node.name = null + } + + return node + }, + binary_expression: (node: PoorNodeType, codeText: string) => { + const leftNodeLocationLoc = (node.left as PoorNodeType).loc as Location + + const leftNodeLocationEndIndex = leftNodeLocationLoc.end.index as number + + const rightNodeLocationLoc = (node.right as PoorNodeType).loc as Location + + const rightNodeLocationStartIndex = rightNodeLocationLoc.start + .index as number + + const operator = codeText.substring( + leftNodeLocationEndIndex, + rightNodeLocationStartIndex, + ) + + const operatorNode = { + nodeType: '__codeque__operator', + rawValue: operator.trim(), + loc: { + /** + * We could adjust location to be without spaces, but it does not matter actually + */ + start: leftNodeLocationLoc.end, + end: rightNodeLocationLoc.start, + }, + } + + node.operator = operatorNode + + return node + }, + function_call: (node: PoorNodeType) => { + if ((node.name as PoorNodeType).rawValue === '') { + const actualContent = ( + (node.arguments as PoorNodeType).children as PoorNodeType[] + )[0] + + return { + nodeType: 'expression_list', + loc: node.loc, + children: [actualContent], + } + } + + return node + }, + parenthesized_expression: (node: PoorNodeType) => { + const children = node.children as PoorNodeType[] + + // Remove empty identifiers from parenthesized_expression + if ( + children.length === 1 && + (children[0] as PoorNodeType)?.rawValue === '' + ) { + return { ...node, children: [] } + } + + return node + }, +} + +export const parserModule = treeSitterParserModuleFactory({ + treeSitterParserName: 'tree-sitter-lua', + postProcessNodes, + defineRawValueForNodeTypes, +}) + +const isRawString = (code: string) => { + const trimmedCode = code.trim() + + return ( + !trimmedCode.includes('\n') && + ((trimmedCode.startsWith("'") && trimmedCode.endsWith("'")) || + (trimmedCode.startsWith('"') && trimmedCode.endsWith('"'))) + ) +} + +export function parseCode(code: string) { + const trimmedCode = code.trim() + + if (isRawString(trimmedCode)) { + /** + * Just string literal query is strangely parsed, so we parse it manually + */ + const loc = { + start: { + line: 1, + column: 0, + index: 0, + }, + end: { + line: 1, + column: trimmedCode.length, + index: trimmedCode.length, + }, + } + + return { + nodeType: 'chunk', + loc, + children: [ + { + nodeType: 'expression_list', + loc, + children: [ + { + nodeType: 'string', + loc, + content: { + nodeType: 'string_content', + loc: { + start: { + line: 1, + column: loc.start.column + 1, + index: loc.start.index + 1, + }, + end: { + line: 1, + column: loc.end.column - 1, + index: loc.end.index - 1, + }, + }, + children: [], + rawValue: trimmedCode.substring(1, trimmedCode.length - 1), + }, + end: null, + start: null, + }, + ], + }, + ], + } + } + + try { + return parserModule.parse(code) + } catch (originalCodeParserError) { + /** + * Lua does not support standalone expressions in the code. + * + * To workaround that, when parser error occurs, we create variable assignment to extract expression_list + * + * we throw original error if that extraction fails, because our changes might produce also broken code. + */ + + try { + const modifiedCode = `__codeque = ${trimmedCode}` + + const modifiedCodeAst = parserModule.parse(modifiedCode) + + const expression_list = (modifiedCodeAst as any)?.children?.[0] + ?.children?.[1] as PoorNodeType + + return { + nodeType: 'chunk', + loc: expression_list.loc, + children: [expression_list], + } + } catch (e) { + throw originalCodeParserError + } + } +} diff --git a/packages/core/src/parserSettings/lua/settings.ts b/packages/core/src/parserSettings/lua/settings.ts new file mode 100644 index 0000000..02a349c --- /dev/null +++ b/packages/core/src/parserSettings/lua/settings.ts @@ -0,0 +1,197 @@ +import { traverseAst } from '../../searchStages/traverseAndMatch' +import { + Location, + MatchPosition, + NumericLiteralUtils, + ParserSettings, + PoorNodeType, + ProgramNodeAndBlockNodeUtils, + StringLikeLiteralUtils, +} from '../../types' + +import { + getIdentifierNodeName, + getNodeType, + identifierNodeTypes, + setIdentifierNodeName, + wildcardUtils, +} from './common' +import { parseCode, parserModule } from './parseCode' + +const supportedExtensions = ['lua'] + +const getProgramNodeFromRootNode = (rootNode: PoorNodeType) => rootNode // root node is program node + +const getProgramBodyFromRootNode = (fileNode: PoorNodeType) => { + return fileNode.children as PoorNodeType[] +} + +/** + * + * Lua has expression list, so there can be a tuple + * For single expression return it, for multiple expressions, return expression_list node, so the original node + */ + +const unwrapExpressionStatement = (node: PoorNodeType) => { + if (node.nodeType === 'expression_list' && node.children) { + const children = node.children as PoorNodeType[] + + if (children.length === 1) { + return children[0] + } + } + + return node +} + +const createBlockStatementNode = ( + children: PoorNodeType[], + position: MatchPosition, +) => ({ + nodeType: 'block', + children, + ...position, +}) + +const isNode = (maybeNode: PoorNodeType) => { + return typeof maybeNode?.nodeType === 'string' +} + +/* start and end is added by CQ in multiline queries */ +const astPropsToSkip = ['loc', 'start', 'end'] + +type NodeValueSanitizers = Record any>> + +const nodeValuesSanitizers: NodeValueSanitizers = {} + +const getSanitizedNodeValue = ( + nodeType: string, + valueKey: string, + value: unknown, +) => { + const valueSanitizer = nodeValuesSanitizers?.[nodeType]?.[valueKey] + + if (valueSanitizer) { + return valueSanitizer(value) + } + + return value +} + +const shouldCompareNode = (node: PoorNodeType) => { + return true +} + +const isIdentifierNode = (node: PoorNodeType) => + identifierNodeTypes.includes(getNodeType(node)) + +const stringLikeLiteralUtils: StringLikeLiteralUtils = { + isStringLikeLiteralNode: (node: PoorNodeType) => + node.nodeType === 'string_content', + getStringLikeLiteralValue: (node: PoorNodeType) => { + return node?.rawValue as string + }, +} + +const numericLiteralUtils: NumericLiteralUtils = { + isNumericLiteralNode: (node: PoorNodeType) => node.nodeType === 'number', + getNumericLiteralValue: (node: PoorNodeType) => node?.rawValue as string, +} + +const programNodeAndBlockNodeUtils: ProgramNodeAndBlockNodeUtils = { + isProgramNode: (node: PoorNodeType) => node.nodeType === 'chunk', + isBlockNode: (node: PoorNodeType) => node.nodeType === 'block', + programNodeBodyKey: 'children', + blockNodeBodyKey: 'children', +} + +const getNodePosition: ParserSettings['getNodePosition'] = ( + node: PoorNodeType, +) => ({ + start: ((node?.loc as any)?.start?.index as number) ?? 0, + end: ((node?.loc as any)?.end?.index as number) ?? 0, + loc: node.loc as unknown as Location, +}) + +const getParseErrorLocation = (e: any) => ({ + line: e.loc?.start?.line ?? 0, + column: e.loc?.start?.column ?? 0, +}) + +const alternativeNodeTypes = { + identifier: identifierNodeTypes, +} + +const encodedIdentifierWildcardSequence = 'a_x_2_x_a' +const encodedNodeWildcardSequence = 'a_x_3_x_a' + +const preprocessQueryCode = (code: string) => { + const queryCode = code + .replace(/(\$\$\$)/g, () => encodedNodeWildcardSequence) + .replace(/(\$\$)/g, () => encodedIdentifierWildcardSequence) + + return queryCode +} + +const replaceEncodedWildcards = (value: string) => + value.replace(/a_x_3_x_a/g, () => '$$$').replace(/a_x_2_x_a/g, () => '$$') + +const postprocessQueryNode = (queryNode: PoorNodeType) => { + traverseAst(queryNode, isNode, getNodeType, { + identifier: (node) => { + const nodeName = node.rawValue as string + + if ( + nodeName.includes(encodedNodeWildcardSequence) || + nodeName.includes(encodedIdentifierWildcardSequence) + ) { + node.rawValue = replaceEncodedWildcards(nodeName) + } + }, + string_content: (node) => { + const nodeName = node.rawValue as string + + if ( + nodeName.includes(encodedNodeWildcardSequence) || + nodeName.includes(encodedIdentifierWildcardSequence) + ) { + node.rawValue = replaceEncodedWildcards(nodeName) + } + }, + }) + + return queryNode +} + +export const pythonParser: ParserSettings = { + supportedExtensions, + parseCode, + isNode, + isIdentifierNode, + astPropsToSkip, + getProgramBodyFromRootNode, + getProgramNodeFromRootNode, + getIdentifierNodeName, + getNodeType, + unwrapExpressionStatement, + createBlockStatementNode, + getSanitizedNodeValue, + identifierNodeTypes, + setIdentifierNodeName, + shouldCompareNode, + wildcardUtils, + compareNodesBeforeWildcardsComparison: () => undefined, + compareNodesAfterWildcardsComparison: () => undefined, + identifierTypeAnnotationFieldName: '', + stringLikeLiteralUtils, + numericLiteralUtils, + programNodeAndBlockNodeUtils, + getNodePosition, + getParseErrorLocation, + alternativeNodeTypes, + preprocessQueryCode, + postprocessQueryNode, + init: parserModule.init, +} + +export default pythonParser diff --git a/packages/core/src/parserSettings/nodeComparatorFactories/JavaScriptSpecific/matchOptionalFlagInMemberExpression.ts b/packages/core/src/parserSettings/nodeComparatorFactories/JavaScriptSpecific/matchOptionalFlagInMemberExpression.ts index 29e4a5f..189a8b6 100644 --- a/packages/core/src/parserSettings/nodeComparatorFactories/JavaScriptSpecific/matchOptionalFlagInMemberExpression.ts +++ b/packages/core/src/parserSettings/nodeComparatorFactories/JavaScriptSpecific/matchOptionalFlagInMemberExpression.ts @@ -16,7 +16,7 @@ import { NodesComparator, PoorNodeType } from '../../../types' export const createMatchOptionalFlagInMemberExpressionNodesComparator = (): NodesComparator => ( - { queryNode, fileNode, searchSettings }, + { queryNode, fileNode, searchSettings, matchContext }, compareNodes, { fileKeysToTraverseForOtherMatches, queryKeysMapper, fileKeysMapper }, ) => { @@ -56,6 +56,7 @@ export const createMatchOptionalFlagInMemberExpressionNodesComparator = searchSettings, queryKeysPrefix: queryKeysMapper(''), fileKeysPrefix: fileKeysMapper('expression'), + matchContext, }) } else if ( queryNode.type === 'ChainExpression' && @@ -67,6 +68,7 @@ export const createMatchOptionalFlagInMemberExpressionNodesComparator = searchSettings, queryKeysPrefix: queryKeysMapper('expression'), fileKeysPrefix: fileKeysMapper(''), + matchContext, }) } } diff --git a/packages/core/src/parserSettings/nodeComparatorFactories/JavaScriptSpecific/wildcardForAssignmentPatternOrDefaultParamValues.ts b/packages/core/src/parserSettings/nodeComparatorFactories/JavaScriptSpecific/wildcardForAssignmentPatternOrDefaultParamValues.ts index 901e038..fafc2b0 100644 --- a/packages/core/src/parserSettings/nodeComparatorFactories/JavaScriptSpecific/wildcardForAssignmentPatternOrDefaultParamValues.ts +++ b/packages/core/src/parserSettings/nodeComparatorFactories/JavaScriptSpecific/wildcardForAssignmentPatternOrDefaultParamValues.ts @@ -13,7 +13,7 @@ export const createWildcardForAssignmentPatternOrDefaultParamValuesNodesComparat (): NodesComparator => ( - { fileNode, queryNode, searchSettings }, + { fileNode, queryNode, searchSettings, matchContext }, compareNodes, { queryKeysMapper, fileKeysMapper }, ) => { @@ -36,6 +36,7 @@ export const createWildcardForAssignmentPatternOrDefaultParamValuesNodesComparat searchSettings, queryKeysPrefix: queryKeysMapper(''), fileKeysPrefix: fileKeysMapper('left'), + matchContext, }) } } diff --git a/packages/core/src/parserSettings/nodeComparatorFactories/JsxSpecific/matchJsxElementRegardlessClosingTag.ts b/packages/core/src/parserSettings/nodeComparatorFactories/JsxSpecific/matchJsxElementRegardlessClosingTag.ts index 6feba32..282ac9b 100644 --- a/packages/core/src/parserSettings/nodeComparatorFactories/JsxSpecific/matchJsxElementRegardlessClosingTag.ts +++ b/packages/core/src/parserSettings/nodeComparatorFactories/JsxSpecific/matchJsxElementRegardlessClosingTag.ts @@ -27,6 +27,8 @@ export const createMatchJsxElementRegardlessClosingTagNodesComparator = (queryNode.children as []).length === 0 ) { measureCompare() + + // To skip matching closing element const keysToTraverse = ['openingElement'] return { diff --git a/packages/core/src/parserSettings/nodeComparatorFactories/Other/matchWildcardsInPropValueNodes.ts b/packages/core/src/parserSettings/nodeComparatorFactories/Other/matchWildcardsInPropValueNodes.ts new file mode 100644 index 0000000..9a23dc9 --- /dev/null +++ b/packages/core/src/parserSettings/nodeComparatorFactories/Other/matchWildcardsInPropValueNodes.ts @@ -0,0 +1,68 @@ +import { NodesComparator } from '../../../types' +import { matchStringOrIdentifierAliases } from '../../../searchStages/matchStringOrIdentifierAliases' + +type Settings = { + nodeType: string + keysWithWildcards: string[] + keysToTraverse: string[] +} +/* + * Supports matching string or identifier wildcards in nodes that contains prop and value in the same node. + * eg. TextAttribute in HTML which is { prop: 'propName', value: 'value', ...otherKeys } + */ +export const createMatchWildcardsInPropValueNodesComparator = + ({ + nodeType, + keysWithWildcards, + keysToTraverse, + }: Settings): NodesComparator => + ( + { queryNode, fileNode, searchSettings, matchContext }, + _, + { fileKeysToTraverseForOtherMatches, log }, + ) => { + if (queryNode?.type === nodeType && fileNode?.type === nodeType) { + log('Compare PropValue Node', nodeType) + const { wildcardUtils } = searchSettings.parserSettings + const { caseInsensitive } = searchSettings + + let levelMatch = true + + keysWithWildcards.forEach((key) => { + const queryNodeStringContent = queryNode[key] as string + + const fileNodeStringContent = fileNode[key] as string + + const wildcardsMeta = wildcardUtils.getStringWildcardsFromString( + queryNodeStringContent, + ) + + if (wildcardsMeta.length > 0) { + levelMatch = + levelMatch && + matchStringOrIdentifierAliases({ + queryValue: queryNodeStringContent, + fileValue: fileNodeStringContent, + wildcardsMeta, + matchContext, + wildcardUtils, + caseInsensitive, + }) + } else { + /** + * If there are no wildcards in given prop, compare prop values directly + */ + levelMatch = + levelMatch && queryNodeStringContent === fileNodeStringContent + } + }) + + // We always want to return here, otherwise generic string wildcard matching would take over and match incorrectly + return { + levelMatch, + queryKeysToTraverseForValidatingMatch: keysToTraverse, + fileKeysToTraverseForValidatingMatch: keysToTraverse, + fileKeysToTraverseForOtherMatches, + } + } + } diff --git a/packages/core/src/parserSettings/nodeComparatorFactories/TypeScriptSpecific/wildcardForAnyTypeAnnotation.ts b/packages/core/src/parserSettings/nodeComparatorFactories/TypeScriptSpecific/wildcardForAnyTypeAnnotation.ts index 672d7dd..27d30e8 100644 --- a/packages/core/src/parserSettings/nodeComparatorFactories/TypeScriptSpecific/wildcardForAnyTypeAnnotation.ts +++ b/packages/core/src/parserSettings/nodeComparatorFactories/TypeScriptSpecific/wildcardForAnyTypeAnnotation.ts @@ -11,7 +11,7 @@ export const createWildcardForAnyTypeAnnotationNodesComparator = if (queryNode) { if ( (queryNode.type as string) === 'TSTypeReference' && - wildcardUtils.removeIdentifierRefFromWildcard( + wildcardUtils.removeWildcardAliasesFromIdentifierName( (queryNode.typeName as PoorNodeType).name as string, ) === wildcardUtils.nodesTreeWildcard ) { diff --git a/packages/core/src/parserSettings/python/common.ts b/packages/core/src/parserSettings/python/common.ts new file mode 100644 index 0000000..c045f63 --- /dev/null +++ b/packages/core/src/parserSettings/python/common.ts @@ -0,0 +1,24 @@ +import { createWildcardUtils } from '../../wildcardUtilsFactory' +import { PoorNodeType } from '../../types' + +export const identifierNodeTypes: string[] = ['identifier'] + +const wildcardChar = '$' +const numericWildcard = '0x0' + +export const getIdentifierNodeName = (node: PoorNodeType) => + node.rawValue as string + +export const setIdentifierNodeName = (node: PoorNodeType, name: string) => { + node.rawValue = name +} + +export const getNodeType = (node: PoorNodeType) => node.nodeType as string + +export const wildcardUtils = createWildcardUtils( + identifierNodeTypes, + numericWildcard, + wildcardChar, + getIdentifierNodeName, + getNodeType, +) diff --git a/packages/core/src/parserSettings/python/parseCode.ts b/packages/core/src/parserSettings/python/parseCode.ts new file mode 100644 index 0000000..3c398b3 --- /dev/null +++ b/packages/core/src/parserSettings/python/parseCode.ts @@ -0,0 +1,17 @@ +import { treeSitterParserModuleFactory } from '../../treeSitterUtils' + +const defineRawValueForNodeTypes = [ + 'identifier', + 'integer', + 'string_content', + 'float', +] + +export const parserModule = treeSitterParserModuleFactory({ + treeSitterParserName: 'tree-sitter-python', + defineRawValueForNodeTypes, +}) + +export function parseCode(code: string) { + return parserModule.parse(code) +} diff --git a/packages/core/src/parserSettings/python/settings.ts b/packages/core/src/parserSettings/python/settings.ts new file mode 100644 index 0000000..e2fd6cc --- /dev/null +++ b/packages/core/src/parserSettings/python/settings.ts @@ -0,0 +1,186 @@ +import { traverseAst } from '../../searchStages/traverseAndMatch' +import { + Location, + MatchPosition, + NumericLiteralUtils, + ParserSettings, + PoorNodeType, + ProgramNodeAndBlockNodeUtils, + StringLikeLiteralUtils, +} from '../../types' + +import { + getIdentifierNodeName, + getNodeType, + identifierNodeTypes, + setIdentifierNodeName, + wildcardUtils, +} from './common' +import { parseCode, parserModule } from './parseCode' + +const supportedExtensions = ['py'] + +const getProgramNodeFromRootNode = (rootNode: PoorNodeType) => rootNode // root node is program node + +const getProgramBodyFromRootNode = (fileNode: PoorNodeType) => { + return fileNode.children as PoorNodeType[] +} + +const unwrapExpressionStatement = (node: PoorNodeType) => { + return node.nodeType === 'expression_statement' && node.children + ? ((node.children as PoorNodeType[])[0] as PoorNodeType) + : node +} + +const createBlockStatementNode = ( + children: PoorNodeType[], + position: MatchPosition, +) => ({ + nodeType: 'block', + children, + ...position, +}) + +const isNode = (maybeNode: PoorNodeType) => { + return typeof maybeNode?.nodeType === 'string' +} + +/* start and end is added by CQ in multiline queries */ +const astPropsToSkip = ['loc', 'start', 'end'] + +type NodeValueSanitizers = Record any>> + +const nodeValuesSanitizers: NodeValueSanitizers = {} + +const getSanitizedNodeValue = ( + nodeType: string, + valueKey: string, + value: unknown, +) => { + const valueSanitizer = nodeValuesSanitizers?.[nodeType]?.[valueKey] + + if (valueSanitizer) { + return valueSanitizer(value) + } + + return value +} + +const shouldCompareNode = (node: PoorNodeType) => { + return true +} + +const isIdentifierNode = (node: PoorNodeType) => + identifierNodeTypes.includes(getNodeType(node)) + +const stringLikeLiteralUtils: StringLikeLiteralUtils = { + isStringLikeLiteralNode: (node: PoorNodeType) => + node.nodeType === 'string_content', + getStringLikeLiteralValue: (node: PoorNodeType) => { + return node?.rawValue as string + }, +} + +const numericLiteralUtils: NumericLiteralUtils = { + isNumericLiteralNode: (node: PoorNodeType) => + node.nodeType === 'integer' || node.nodeType === 'float', + getNumericLiteralValue: (node: PoorNodeType) => node?.rawValue as string, +} + +const programNodeAndBlockNodeUtils: ProgramNodeAndBlockNodeUtils = { + isProgramNode: (node: PoorNodeType) => node.nodeType === 'module', + isBlockNode: (node: PoorNodeType) => node.nodeType === 'block', + programNodeBodyKey: 'children', + blockNodeBodyKey: 'children', +} + +const getNodePosition: ParserSettings['getNodePosition'] = ( + node: PoorNodeType, +) => ({ + start: ((node?.loc as any)?.start?.index as number) ?? 0, + end: ((node?.loc as any)?.end?.index as number) ?? 0, + loc: node.loc as unknown as Location, +}) + +const getParseErrorLocation = (e: any) => ({ + line: e.loc?.start?.line ?? 0, + column: e.loc?.start?.column ?? 0, +}) + +const alternativeNodeTypes = { + identifier: identifierNodeTypes, +} + +const encodedIdentifierWildcardSequence = 'a_x_2_x_a' +const encodedNodeWildcardSequence = 'a_x_3_x_a' + +const preprocessQueryCode = (code: string) => { + const queryCode = code + .replace(/(\$\$\$)/g, () => encodedNodeWildcardSequence) + .replace(/(\$\$)/g, () => encodedIdentifierWildcardSequence) + + return queryCode +} + +const replaceEncodedWildcards = (value: string) => + value.replace(/a_x_3_x_a/g, () => '$$$').replace(/a_x_2_x_a/g, () => '$$') + +const postprocessQueryNode = (queryNode: PoorNodeType) => { + traverseAst(queryNode, isNode, getNodeType, { + identifier: (node) => { + const nodeName = node.rawValue as string + + if ( + nodeName.includes(encodedNodeWildcardSequence) || + nodeName.includes(encodedIdentifierWildcardSequence) + ) { + node.rawValue = replaceEncodedWildcards(nodeName) + } + }, + string_content: (node) => { + const nodeName = node.rawValue as string + + if ( + nodeName.includes(encodedNodeWildcardSequence) || + nodeName.includes(encodedIdentifierWildcardSequence) + ) { + node.rawValue = replaceEncodedWildcards(nodeName) + } + }, + }) + + return queryNode +} + +export const pythonParser: ParserSettings = { + supportedExtensions, + parseCode, + isNode, + isIdentifierNode, + astPropsToSkip, + getProgramBodyFromRootNode, + getProgramNodeFromRootNode, + getIdentifierNodeName, + getNodeType, + unwrapExpressionStatement, + createBlockStatementNode, + getSanitizedNodeValue, + identifierNodeTypes, + setIdentifierNodeName, + shouldCompareNode, + wildcardUtils, + compareNodesBeforeWildcardsComparison: () => undefined, + compareNodesAfterWildcardsComparison: () => undefined, + identifierTypeAnnotationFieldName: '', + stringLikeLiteralUtils, + numericLiteralUtils, + programNodeAndBlockNodeUtils, + getNodePosition, + getParseErrorLocation, + alternativeNodeTypes, + preprocessQueryCode, + postprocessQueryNode, + init: parserModule.init, +} + +export default pythonParser diff --git a/packages/core/src/parserSettings/typescriptEslintParser/common.ts b/packages/core/src/parserSettings/typescriptEslintParser/common.ts index de4f612..f9ac9bc 100644 --- a/packages/core/src/parserSettings/typescriptEslintParser/common.ts +++ b/packages/core/src/parserSettings/typescriptEslintParser/common.ts @@ -1,9 +1,18 @@ import { numericWildcard, wildcardChar } from '../_common/JSFamilyCommon' import { createWildcardUtils } from '../../wildcardUtilsFactory' +import { PoorNodeType } from '../../types' export const identifierNodeTypes = ['Identifier', 'JSXIdentifier'] +export const getIdentifierNodeName = (node: PoorNodeType) => node.name as string +export const setIdentifierNodeName = (node: PoorNodeType, name: string) => { + node.name = name +} + +export const getNodeType = (node: PoorNodeType) => node.type as string export const wildcardUtils = createWildcardUtils( identifierNodeTypes, numericWildcard, wildcardChar, + getIdentifierNodeName, + getNodeType, ) diff --git a/packages/core/src/parserSettings/typescriptEslintParser/nodeComparators/wildcardForAnyTypeParameter.ts b/packages/core/src/parserSettings/typescriptEslintParser/nodeComparators/wildcardForAnyTypeParameter.ts index 53e82ef..1ed2a24 100644 --- a/packages/core/src/parserSettings/typescriptEslintParser/nodeComparators/wildcardForAnyTypeParameter.ts +++ b/packages/core/src/parserSettings/typescriptEslintParser/nodeComparators/wildcardForAnyTypeParameter.ts @@ -8,7 +8,7 @@ export const wildcardForAnyTypeParameter: NodesComparator = ( if (queryNode) { if ( (queryNode.type as string) === 'TSTypeParameter' && - wildcardUtils.removeIdentifierRefFromWildcard( + wildcardUtils.removeWildcardAliasesFromIdentifierName( (queryNode.name as PoorNodeType).name as string, ) === wildcardUtils.nodesTreeWildcard ) { diff --git a/packages/core/src/parserSettings/typescriptEslintParser/settings.ts b/packages/core/src/parserSettings/typescriptEslintParser/settings.ts index 2e637c5..0f6b7ae 100644 --- a/packages/core/src/parserSettings/typescriptEslintParser/settings.ts +++ b/packages/core/src/parserSettings/typescriptEslintParser/settings.ts @@ -1,19 +1,25 @@ import { parse, ParserOptions } from '@typescript-eslint/parser' import { Location, - Match, NodesComparatorParameters, NumericLiteralUtils, ParserSettings, PoorNodeType, ProgramNodeAndBlockNodeUtils, StringLikeLiteralUtils, + MatchPosition, } from '../../types' import { normalizeText, runNodesComparators } from '../../utils' import { supportedExtensions } from '../_common/JSFamilyCommon' import { afterWildcardsComparators } from './afterWildcardsComparators' import { beforeWildcardsComparators } from './beforeWildcardsComparators' -import { identifierNodeTypes, wildcardUtils } from './common' +import { + getIdentifierNodeName, + getNodeType, + identifierNodeTypes, + wildcardUtils, + setIdentifierNodeName, +} from './common' const getProgramNodeFromRootNode = (rootNode: PoorNodeType) => rootNode // root node is program node @@ -35,7 +41,7 @@ const unwrapExpressionStatement = (node: PoorNodeType) => { const createBlockStatementNode = ( body: PoorNodeType[], - position: Omit, + position: MatchPosition, ) => ({ type: 'BlockStatement', @@ -48,11 +54,6 @@ const isNode = (maybeNode: PoorNodeType) => { return typeof maybeNode?.type === 'string' } -const isNodeFieldOptional = (nodeType: string, nodeFieldKey: string) => { - // Eslint-typescript is about to remove optionality of properties https://github.com/typescript-eslint/typescript-eslint/pull/6274 - return true -} - const astPropsToSkip = [ 'loc', 'range', @@ -83,37 +84,50 @@ const parseCode = (code: string, filePath = '') => { return parse(maybeWrappedJSON, parseOptions) as unknown as PoorNodeType } -const generateCode = (node: PoorNodeType, options?: unknown) => { - return 'Not supported' +const sanitizeTemplateElementValue = ({ + raw, + cooked, +}: { + raw: string + cooked: string +}) => { + return { + raw: normalizeText(raw), + cooked: normalizeText(cooked), + } } -const sanitizeJSXText = (node: PoorNodeType) => { - //@ts-ignore - node.value = normalizeText(node.value) - //@ts-ignore - node.raw = normalizeText(node.raw) -} +type NodeValueSanitizers = Record any>> -const sanitizeTemplateElement = (node: PoorNodeType) => { - //@ts-ignore - node.value.raw = normalizeText(node.value.raw) - //@ts-ignore - node.value.cooked = normalizeText(node.value.cooked) +const nodeValuesSanitizers: NodeValueSanitizers = { + ['JSXText']: { + value: normalizeText, + raw: normalizeText, + }, + ['TemplateElement']: { + value: sanitizeTemplateElementValue, + }, } -const sanitizeNode = (node: PoorNodeType) => { - if (node?.type === 'TemplateElement') { - sanitizeTemplateElement(node) - } else if (node?.type === 'JSXText') { - sanitizeJSXText(node) +const getSanitizedNodeValue = ( + nodeType: string, + valueKey: string, + value: unknown, +) => { + const valueSanitizer = nodeValuesSanitizers?.[nodeType]?.[valueKey] + + if (valueSanitizer) { + return valueSanitizer(value) } + + return value } const shouldCompareNode = (node: PoorNodeType) => { if (node.type === 'JSXText') { - sanitizeJSXText(node) + const value: string = getSanitizedNodeValue('JSXText', 'value', node.value) - return (node.value as string).length > 0 + return value.length > 0 } return true @@ -131,9 +145,6 @@ const compareNodesAfterWildcardsComparison = ( return runNodesComparators(afterWildcardsComparators, nodeComparatorParams) } -const getIdentifierNodeName = (node: PoorNodeType) => node.name as string -const getNodeType = (node: PoorNodeType) => node.type as string - const isIdentifierNode = (node: PoorNodeType) => identifierNodeTypes.includes(getNodeType(node)) @@ -146,7 +157,16 @@ const stringLikeLiteralUtils: StringLikeLiteralUtils = { node.type === 'TemplateElement' || node.type === 'JSXText', getStringLikeLiteralValue: (node: PoorNodeType) => { - return ((node.value as any)?.raw as string) ?? (node?.value as string) + if (node.type === 'TemplateElement') { + const { raw } = sanitizeTemplateElementValue( + node.value as { raw: string; cooked: string }, + ) + + return raw + } + + // (node.type === 'Literal' || node.type === 'JSXText' + return normalizeText(node.value as string) }, } @@ -188,15 +208,16 @@ export const typescriptEslintParserSettings: ParserSettings = { parseCode, isNode, isIdentifierNode, + identifierNodeTypes, astPropsToSkip, - isNodeFieldOptional, getProgramBodyFromRootNode, getProgramNodeFromRootNode, getIdentifierNodeName, + setIdentifierNodeName, getNodeType, unwrapExpressionStatement, createBlockStatementNode, - sanitizeNode, + getSanitizedNodeValue, shouldCompareNode, wildcardUtils, compareNodesBeforeWildcardsComparison, diff --git a/packages/core/src/searchInFs.ts b/packages/core/src/searchInFs.ts index d80348c..8dff29f 100644 --- a/packages/core/src/searchInFs.ts +++ b/packages/core/src/searchInFs.ts @@ -9,12 +9,10 @@ import { SearchResults, NotNullParsedQuery, SearchSettings, - ParserType, } from './types' import { textSearch } from './textSearch' import { parserSettingsMap } from './parserSettings/index' - -const testParserTypeOverride = process?.env?.TEST_PARSER_TYPE as ParserType +import { testParserTypeOverride } from './testOnlyConfig' export const searchInFileSystem = ({ mode, @@ -37,7 +35,7 @@ export const searchInFileSystem = ({ getFileContent, filePaths, mode, - queryCodes, + queryCodes: queryCodes ?? [], caseInsensitive, onPartialResult, maxResultsLimit, @@ -65,10 +63,14 @@ export const searchInFileSystem = ({ ) if (!parseOk) { + log('Parse query failed') + + const errors = queries.filter((queryResult) => queryResult.error) + return { matches: [], hints: queries.map(({ hints }) => hints), - errors: queries.filter((queryResult) => queryResult.error), + errors, } } @@ -101,7 +103,7 @@ export const searchInFileSystem = ({ fileContent, ...settings, }) - const dedupedFileMatches = dedupMatches(fileMatches, log, debug) + const dedupedFileMatches = dedupMatches(fileMatches) if (onPartialResult && dedupedFileMatches.length > 0) { onPartialResult(dedupedFileMatches) @@ -130,7 +132,9 @@ export const searchInFileSystem = ({ } } - logMetrics() + if (debug) { + logMetrics() + } return { matches: allMatches, diff --git a/packages/core/src/searchInStrings.ts b/packages/core/src/searchInStrings.ts index 007c59f..b9371ca 100644 --- a/packages/core/src/searchInStrings.ts +++ b/packages/core/src/searchInStrings.ts @@ -9,11 +9,9 @@ import { SearchResults, NotNullParsedQuery, SearchSettings, - ParserType, } from './types' import { parserSettingsMap } from './parserSettings/index' - -const testParserTypeOverride = process?.env?.TEST_PARSER_TYPE as ParserType +import { testParserTypeOverride } from './testOnlyConfig' type StringsSearchArgs = Omit & { files: { @@ -66,6 +64,8 @@ export const searchInStrings = ({ ) if (!parseOk) { + log('Parse query failed') + return { matches: [], errors: queries.filter((queryResult) => queryResult.error), @@ -112,7 +112,7 @@ export const searchInStrings = ({ } return { - matches: dedupMatches(allMatches, log, debug), + matches: dedupMatches(allMatches), errors: searchErrors, hints: queries.map(({ hints }) => hints), } diff --git a/packages/core/src/searchMultiThread.ts b/packages/core/src/searchMultiThread.ts index 2f3103b..37bac5c 100644 --- a/packages/core/src/searchMultiThread.ts +++ b/packages/core/src/searchMultiThread.ts @@ -9,6 +9,7 @@ import { } from './types' import { measureStart, logMetrics } from './utils' import { searchInFileSystem } from './searchInFs' +import { parserSettingsMap } from './parserSettings' const coresCount = Math.round(cpus().length / 2) const singleThreadFilesCountLimitStructuralDefault = 350 @@ -21,6 +22,7 @@ export const searchMultiThread = async ({ maxResultsLimit, singleThreadFilesCountLimitStructural = singleThreadFilesCountLimitStructuralDefault, singleThreadFilesCountLimitText = singleThreadFilesCountLimitTextDefault, + debug, ...params }: FileSystemSearchArgs & { hardStopFlag?: HardStopFlag @@ -82,13 +84,28 @@ export const searchMultiThread = async ({ /** * Timeout to not block starting other searches */ - setTimeout(() => { - const results = searchInFileSystem({ - ...searchParams, - onPartialResult, - hardStopFlag, - }) - resolve(results) + setTimeout(async () => { + try { + if (params.parser) { + await parserSettingsMap[params.parser]().init?.( + params.parserFilesBasePath, + ) + } + + const results = searchInFileSystem({ + ...searchParams, + onPartialResult, + hardStopFlag, + }) + resolve(results) + } catch (e) { + const error = new Error( + 'Search on main thread failed with error ' + (e as Error).message, + ) + error.stack = (e as Error)?.stack + + reject(error) + } }, 0) } else { const measureWorkerReturnResult = measureStart('WorkerReturnResult') @@ -147,7 +164,9 @@ export const searchMultiThread = async ({ }, ) - logMetrics() + if (debug) { + logMetrics() + } return mergedResults } diff --git a/packages/core/src/searchStages/compareNodes.ts b/packages/core/src/searchStages/compareNodes.ts index 574107c..0db7618 100644 --- a/packages/core/src/searchStages/compareNodes.ts +++ b/packages/core/src/searchStages/compareNodes.ts @@ -1,14 +1,16 @@ import { + compareAst, getKeysWithNodes, getSetsOfKeysToCompare, isNodeArray, } from '../astUtils' import { - PoorNodeType, CompareNodesParams, CompareNodesReturnType, + PoorNodeType, } from '../types' import { measureStart, regExpTest } from '../utils' +import { matchStringOrIdentifierAliases } from './matchStringOrIdentifierAliases' const keyWithPrefix = (prefix: string) => (key: string) => prefix ? `${prefix}.${key}` : key @@ -20,6 +22,7 @@ export const compareNodes = ( fileNode, queryNode, searchSettings, + matchContext, /** Params used to support comparing nodes which are not on the same level */ queryKeysPrefix = '', fileKeysPrefix = '', @@ -30,8 +33,11 @@ export const compareNodes = ( caseInsensitive, logger: { log, logStepEnd, logStepStart }, parserSettings, + getCodeForNode = () => 'getCodeForNode not provided', } = searchSettings + const { getNodeType } = parserSettings + const queryKeysMapper = keyWithPrefix(queryKeysPrefix) const fileKeysMapper = keyWithPrefix(fileKeysPrefix) @@ -59,14 +65,14 @@ export const compareNodes = ( queryNode, isExact, parserSettings.astPropsToSkip, - parserSettings.isNodeFieldOptional, + parserSettings.getNodeType, ) log( 'compare: query node type', - queryNode.type, + getNodeType(queryNode), 'file node type', - fileNode.type, + getNodeType(fileNode), ) log('compare: queryKeys', queryKeys) @@ -88,11 +94,9 @@ export const compareNodes = ( fileKeysMapper, fileKeysToTraverseForOtherMatches, measureCompare, + log, } - parserSettings.sanitizeNode(fileNode) - parserSettings.sanitizeNode(queryNode) - const maybeCompareResult = parserSettings.compareNodesBeforeWildcardsComparison( compareParams, @@ -113,9 +117,9 @@ export const compareNodes = ( programNodeAndBlockNodeUtils, getIdentifierNodeName, wildcardUtils: { - getWildcardFromNode, + getIdentifierWildcardsFromNode, + getStringWildcardsFromString, anyStringWildcardRegExp, - patternToRegExp, numericWildcard, }, } = parserSettings @@ -129,21 +133,71 @@ export const compareNodes = ( * Support for wildcards in all nodes */ if (isIdentifierNode(queryNode)) { - const wildcardMeta = getWildcardFromNode(queryNode) + const wildcardsMeta = getIdentifierWildcardsFromNode(queryNode) - if (wildcardMeta !== null) { + if (wildcardsMeta.length > 0) { log('comparing wildcard') - const { wildcardType, wildcardWithoutRef } = wildcardMeta - let levelMatch - if (wildcardType === 'nodeTree') { + const firstWildcard = wildcardsMeta[0] + + const isNodesTreeWildcard = firstWildcard.wildcardType === 'nodeTree' + + let levelMatch: boolean + + log('First Wildcard type', firstWildcard.wildcardType) + log('wildcardWithoutAlias', firstWildcard.wildcardWithoutAlias) + log('MatchContext aliases', matchContext.getAllAliases()) + + if (isNodesTreeWildcard) { levelMatch = true + + const { wildcardAlias, wildcardWithAlias } = firstWildcard + + /** + * Check if alias has been already found + */ + const existingAlias = wildcardAlias + ? matchContext.getNodesTreeAlias(wildcardAlias) + : null + + const matchedNode = fileNode + + if (existingAlias !== null) { + /** + * If alias exist, compare file nodes tree with matched alias nodes tree + */ + const aliasMatches = compareAst( + matchedNode, + existingAlias.aliasNode, + parserSettings, + ) + + levelMatch = levelMatch && aliasMatches + } else if (wildcardAlias !== null) { + /** + * If alias not exist, add alias to match context + */ + matchContext.addNodesTreeAlias({ + alias: wildcardAlias, + wildcard: wildcardWithAlias, + aliasNode: matchedNode, + aliasValue: getCodeForNode(matchedNode, 'file'), + }) + } } else { - const regex = patternToRegExp(wildcardWithoutRef, caseInsensitive) + const queryValue = getIdentifierNodeName(queryNode) + const fileValue = getIdentifierNodeName(fileNode) levelMatch = isIdentifierNode(fileNode) && - regExpTest(regex, getIdentifierNodeName(fileNode)) + matchStringOrIdentifierAliases({ + queryValue, + fileValue, + wildcardsMeta, + matchContext, + wildcardUtils: parserSettings.wildcardUtils, + caseInsensitive, + }) if (isExact && identifierTypeAnnotationFieldName) { levelMatch = @@ -162,8 +216,9 @@ export const compareNodes = ( ) }) - const queryKeysToTraverseForValidatingMatch = - wildcardType !== 'nodeTree' ? queryKeysWithNodes : [] + const queryKeysToTraverseForValidatingMatch = isNodesTreeWildcard + ? [] + : queryKeysWithNodes measureCompare() @@ -181,7 +236,7 @@ export const compareNodes = ( const isStringWithWildcard = stringLikeLiteralUtils.isStringLikeLiteralNode(queryNode) && stringLikeLiteralUtils.isStringLikeLiteralNode(fileNode) && - queryNode.type === fileNode.type && // todo possibility to match string literals in other places (for include mode) + getNodeType(queryNode) === getNodeType(fileNode) && // todo possibility to match string literals in other places (for include mode) regExpTest( anyStringWildcardRegExp, stringLikeLiteralUtils.getStringLikeLiteralValue(queryNode), @@ -196,18 +251,27 @@ export const compareNodes = ( * Q: "some$$$string"; C: "someBLABLAstring"; // required wildcard * */ if (isStringWithWildcard) { - const regex = patternToRegExp( - stringLikeLiteralUtils.getStringLikeLiteralValue(queryNode), + const queryNodeStringContent = + stringLikeLiteralUtils.getStringLikeLiteralValue(queryNode) + + const fileNodeStringContent = + stringLikeLiteralUtils.getStringLikeLiteralValue(fileNode) + + const wildcardsMeta = getStringWildcardsFromString(queryNodeStringContent) + + const levelMatch = matchStringOrIdentifierAliases({ + queryValue: queryNodeStringContent, + fileValue: fileNodeStringContent, + wildcardsMeta, + matchContext, + wildcardUtils: parserSettings.wildcardUtils, caseInsensitive, - ) - const levelMatch = regExpTest( - regex, - stringLikeLiteralUtils.getStringLikeLiteralValue(fileNode), - ) + }) + measureCompare() return { - levelMatch: levelMatch, + levelMatch, fileKeysToTraverseForValidatingMatch: [], queryKeysToTraverseForValidatingMatch: [], fileKeysToTraverseForOtherMatches, @@ -276,7 +340,7 @@ export const compareNodes = ( if ( queryKeys.length !== fileKeys.length || - fileNode.type !== queryNode.type + getNodeType(fileNode) !== getNodeType(queryNode) ) { measureCompare() @@ -305,17 +369,32 @@ export const compareNodes = ( } else { primitivePropsCount++ + const sanitizedQueryValue = parserSettings.getSanitizedNodeValue( + getNodeType(queryNode), + key, + queryValue, + ) + const sanitizedFileValue = parserSettings.getSanitizedNodeValue( + getNodeType(fileNode), + key, + fileValue, + ) + if ( - typeof queryValue === 'string' && - typeof fileValue === 'string' && + typeof sanitizedQueryValue === 'string' && + typeof sanitizedFileValue === 'string' && caseInsensitive ) { - if (queryValue.toLocaleLowerCase() === fileValue.toLocaleLowerCase()) { + if ( + sanitizedQueryValue.toLocaleLowerCase() === + sanitizedFileValue.toLocaleLowerCase() + ) { matchingPrimitivePropsCount++ } } else if ( - queryValue === fileValue || - JSON.stringify(queryValue) === JSON.stringify(fileValue) + sanitizedQueryValue === sanitizedFileValue || + JSON.stringify(sanitizedQueryValue) === + JSON.stringify(sanitizedFileValue) ) { matchingPrimitivePropsCount++ } diff --git a/packages/core/src/searchStages/getLocationOfMultilineMatch.ts b/packages/core/src/searchStages/getLocationOfMultilineMatch.ts index 2bdb7c3..d79d286 100644 --- a/packages/core/src/searchStages/getLocationOfMultilineMatch.ts +++ b/packages/core/src/searchStages/getLocationOfMultilineMatch.ts @@ -12,7 +12,11 @@ export const getLocationOfMultilineMatch = ( const statements = queryNode[blockNodeBodyKey] as PoorNodeType[] - const alreadyPickedStatementNodes: PoorNodeType[] = [] + /** + * We initialise already picked statements with the top-level match node to filter it out in results. + * For some parses, like angular-eslint, top-level node can be matched by query statement node. + */ + const alreadyPickedStatementNodes: PoorNodeType[] = [match.node] const subMatches = statements .map((statement) => { @@ -39,12 +43,16 @@ export const getLocationOfMultilineMatch = ( const firstSubMatch = subMatches[0] const lastSubMatch = subMatches[subMatches.length - 1] - return { + const resultMatch: Match = { start: firstSubMatch.start, end: lastSubMatch.end, loc: { start: firstSubMatch.loc.start, end: lastSubMatch.loc.end, }, - } as Match + aliases: match.aliases, + node: match.node, + } + + return resultMatch } diff --git a/packages/core/src/searchStages/matchStringOrIdentifierAliases.ts b/packages/core/src/searchStages/matchStringOrIdentifierAliases.ts new file mode 100644 index 0000000..d41f681 --- /dev/null +++ b/packages/core/src/searchStages/matchStringOrIdentifierAliases.ts @@ -0,0 +1,144 @@ +import { WildcardUtils, WildcardMeta } from '../types' +import { regExpTest } from '../utils' +import { MatchContext } from '../matchContext' + +export const matchStringOrIdentifierAliases = ({ + queryValue, + fileValue, + matchContext, + wildcardsMeta, + wildcardUtils, + caseInsensitive, +}: { + queryValue: string + fileValue: string + wildcardsMeta: WildcardMeta[] + matchContext: MatchContext + wildcardUtils: WildcardUtils + caseInsensitive: boolean +}): boolean => { + const { patternToRegExp, removeWildcardAliasesFromIdentifierName } = + wildcardUtils + + const identifierNameWithWildcardsWithoutAliases = + removeWildcardAliasesFromIdentifierName(queryValue) + const regex = patternToRegExp( + identifierNameWithWildcardsWithoutAliases, + caseInsensitive, + ) + /** + * Check initial match of wildcards pattern + */ + + const wildcardMatch = regExpTest(regex, fileValue) + + let levelMatch = wildcardMatch + + if (wildcardMatch && wildcardsMeta.length > 0) { + /** + * If there are aliased wildcards, look for aliased values and match or assign new values + */ + const queryNodeIdentifierNameWithWildcard = queryValue + + const fileNodeIdentifierName = fileValue + + /** + * Creates named capturing group for alias, where alias is group name + */ + const createAliasedIdentifierWildcardRegExp = (alias: string) => + `(?<${alias}>(\\w|-)*)` + + const createAliasedStringWildcardRegExp = (alias: string) => + `(?<${alias}>(.)*)` + + const identifierWildcardRegExp = '(\\w|-)*' + const stringWildcardRegExp = '(.)*' + + /** + * Compose regex that represents identifier name with aliased and non aliased wildcards + */ + let wildcardValuesExtractionRegexText = queryNodeIdentifierNameWithWildcard + + wildcardsMeta.forEach( + ({ wildcardAlias, wildcardWithAlias, wildcardType }) => { + let regExpPart = identifierWildcardRegExp + + if (wildcardType === 'identifier' && wildcardAlias) { + regExpPart = createAliasedIdentifierWildcardRegExp(wildcardAlias) + } else if (wildcardType === 'string' && !wildcardAlias) { + regExpPart = stringWildcardRegExp + } else if (wildcardType === 'string' && wildcardAlias) { + regExpPart = createAliasedStringWildcardRegExp(wildcardAlias) + } + + wildcardValuesExtractionRegexText = + wildcardValuesExtractionRegexText.replace( + wildcardWithAlias, + regExpPart, + ) + }, + ) + + const wildcardValuesExtractionRegex = new RegExp( + wildcardValuesExtractionRegexText, + caseInsensitive ? 'i' : undefined, + ) + + /** + * Match file node content with wildcards regexp, so we can extract aliases values later + */ + const wildcardValuesExtractionMatch = fileNodeIdentifierName.match( + wildcardValuesExtractionRegex, + ) + + if (wildcardValuesExtractionMatch === null) { + throw new Error( + 'Wildcard alias extraction RegExp did not match, thus it was build incorrectly.', + ) + } + + /** + * Compare wildcard aliases with values extracted from file node + * - If alias value exist in match context, compare with value from file node + * - If alias value does not exist, add it's value to match context + */ + wildcardsMeta.forEach((wildcardMeta) => { + const { wildcardAlias, wildcardWithAlias, wildcardType } = wildcardMeta + + if (wildcardAlias !== null) { + const existingAlias = wildcardAlias + ? matchContext.getIdentifierAlias(wildcardAlias) || + matchContext.getStringAlias(wildcardAlias) + : null + + const aliasValue = + wildcardValuesExtractionMatch?.groups?.[wildcardAlias] ?? '' + + if (existingAlias !== null) { + const aliasMatches = caseInsensitive + ? existingAlias.aliasValue.toLocaleLowerCase() === + aliasValue.toLocaleLowerCase() + : existingAlias.aliasValue === aliasValue + + levelMatch = levelMatch && aliasMatches + } else { + if (wildcardType === 'identifier') { + matchContext.addIdentifierAlias({ + alias: wildcardAlias, + wildcard: wildcardWithAlias, + aliasValue: aliasValue, + }) + } else if (wildcardType === 'string') { + matchContext.addStringAlias({ + alias: wildcardAlias, + wildcard: wildcardWithAlias, + aliasValue: aliasValue, + }) + } + } + } + }) + } + + return levelMatch +} diff --git a/packages/core/src/searchStages/searchAst.ts b/packages/core/src/searchStages/searchAst.ts index f1372a2..7bb5ab5 100644 --- a/packages/core/src/searchStages/searchAst.ts +++ b/packages/core/src/searchStages/searchAst.ts @@ -8,21 +8,25 @@ import { traverseAndMatch, test_traverseAndMatchWithVisitors, } from './traverseAndMatch' -import { useTraverseApproachTestOnly } from '../config' +import { useTraverseApproachTestOnly } from '../testOnlyConfig' import { getLocationOfMultilineMatch } from './getLocationOfMultilineMatch' +import { MatchContextAliases } from '../matchContext' export type SearchAstSettings = SearchSettings & { queries: NotNullParsedQuery[] - getCodeForFileNode?: (node: PoorNodeType) => string + getCodeForFileNode: (node: PoorNodeType) => string } export const searchAst = ( fileNode: PoorNodeType, { queries, getCodeForFileNode, ...settings }: SearchAstSettings, + unwrapFileNode = true, + initialContext?: MatchContextAliases, ) => { const allMatches: { query: NotNullParsedQuery; matches: Match[] }[] = [] - const programNode = - settings.parserSettings.getProgramNodeFromRootNode(fileNode) + const nodeToSearch = unwrapFileNode + ? settings.parserSettings.getProgramNodeFromRootNode(fileNode) + : fileNode for (const query of queries) { const { queryNode, isMultistatement, queryCode } = query @@ -41,7 +45,7 @@ export const searchAst = ( return queryCode.substring(pos.start, pos.end) } catch { - console.log('Failed getting position for node', node) + console.error('Failed getting position for node', node) } return '' @@ -56,23 +60,26 @@ export const searchAst = ( ? test_traverseAndMatchWithVisitors : traverseAndMatch - const matches = traverseAndMatchFn(programNode, queryNode, newSettings).map( - (match) => { - if (!isMultistatement) { - return match - } + const matches = traverseAndMatchFn( + nodeToSearch, + queryNode, + newSettings, + initialContext, + ).map((match) => { + if (!isMultistatement) { + return match + } - /** - * For multi-statement queries we search where exactly statements are located within parent node - */ - return getLocationOfMultilineMatch( - match, - queryNode, - newSettings, - traverseAndMatchFn, - ) - }, - ) + /** + * For multi-statement queries we search where exactly statements are located within parent node + */ + return getLocationOfMultilineMatch( + match, + queryNode, + newSettings, + traverseAndMatchFn, + ) + }) allMatches.push({ query, diff --git a/packages/core/src/searchStages/searchFileContent.ts b/packages/core/src/searchStages/searchFileContent.ts index 1edfda6..1a560eb 100644 --- a/packages/core/src/searchStages/searchFileContent.ts +++ b/packages/core/src/searchStages/searchFileContent.ts @@ -37,6 +37,10 @@ export const searchFileContent = ({ if (shallowSearchPassed) { const measureParseFile = measureStart('parseFile') + /** + * + * Used for debugging + */ const getCodeForFileNode = (node: PoorNodeType) => { const pos = settings.parserSettings.getNodePosition(node) @@ -56,8 +60,11 @@ export const searchFileContent = ({ allMatches = results .map(({ query, matches }) => { - return matches.map((match) => { - const code = prepareCodeResult({ fileContent, ...match }) + return matches.map(({ node, ...match }) => { + const { code, indentationBase } = prepareCodeResult({ + fileContent, + ...match, + }) const [extendedCodeFrame, newStartLine] = getExtendedCodeFrame( match, @@ -73,6 +80,7 @@ export const searchFileContent = ({ code: extendedCodeFrame, startLine: match.loc.start.line + newStartLine, }, + indentationBase, } }) }) @@ -81,7 +89,5 @@ export const searchFileContent = ({ measureSearch() } - // console.log('all matches', allMatches) - return allMatches } diff --git a/packages/core/src/searchStages/traverseAndMatch.ts b/packages/core/src/searchStages/traverseAndMatch.ts index f1be5e5..92edb12 100644 --- a/packages/core/src/searchStages/traverseAndMatch.ts +++ b/packages/core/src/searchStages/traverseAndMatch.ts @@ -4,8 +4,9 @@ import { PoorNodeType, SearchSettings, SearchSettingsWithOptionalLogger, + ParserSettings, } from '../types' -import { measureStart, noopLogger } from '../utils' +import { measureStart, noopLogger, uniqueItems } from '../utils' import { compareNodes } from './compareNodes' import { validateMatch } from './validateMatch' import { @@ -13,6 +14,11 @@ import { getVisitorKeysForQueryNodeType, getKeysWithNodes, } from '../astUtils' +import { + MatchContext, + createMatchContext, + MatchContextAliases, +} from '../matchContext' /** * @@ -23,55 +29,121 @@ import { * VisitorKeys is a set of keys containing other nodes for each node type */ -const test_traverse_ast = ( +type ParentMeta = { + node: PoorNodeType + key: string + index?: number +} + +export const traverseAst = ( fileNode: PoorNodeType, - settings: SearchSettingsWithOptionalLogger, + isNode: ParserSettings['isNode'], + getNodeType: ParserSettings['getNodeType'], visitors: Record void>, + onNode?: (node: PoorNodeType, parentMeta?: ParentMeta) => void, + parentMeta?: { + node: PoorNodeType + key: string + index?: number + }, ) => { - const visitor = visitors[fileNode.type as string] + const visitor = visitors[getNodeType(fileNode)] visitor?.(fileNode) + onNode?.(fileNode, parentMeta) const keysWithNodes: string[] = getKeysWithNodes( fileNode, Object.keys(fileNode), - settings.parserSettings.isNode, + isNode, ) - keysWithNodes.forEach((key) => { + for (let i = 0; i < keysWithNodes.length; i++) { + const key = keysWithNodes[i] + if (fileNode[key] !== undefined) { - if (settings.parserSettings.isNode(fileNode[key] as PoorNodeType)) { - test_traverse_ast(fileNode[key] as PoorNodeType, settings, visitors) + if (isNode(fileNode[key] as PoorNodeType)) { + const parentMeta = { + node: fileNode, + key, + } + + traverseAst( + fileNode[key] as PoorNodeType, + isNode, + getNodeType, + visitors, + onNode, + parentMeta, + ) } else { const nestedNodesArray = fileNode[key] as PoorNodeType[] - nestedNodesArray.forEach((node) => - test_traverse_ast(node, settings, visitors), - ) + for (let j = 0; j < nestedNodesArray.length; j++) { + const parentMeta = { + node: fileNode, + key, + index: j, + } + const nestedNode = nestedNodesArray[j] + + traverseAst( + nestedNode, + isNode, + getNodeType, + visitors, + onNode, + parentMeta, + ) + } } } - }) + } } export const test_traverseAndMatchWithVisitors = ( fileNode: PoorNodeType, queryNode: PoorNodeType, settings: SearchSettingsWithOptionalLogger, + initialMatchContext?: MatchContextAliases, ) => { const matches: Match[] = [] const searchInPath = (node: PoorNodeType) => { - const match = validateMatch(node, queryNode, settings) + const matchContext = createMatchContext(initialMatchContext) + + const { match, matchContext: extendedMatchContext } = validateMatch( + node, + queryNode, + settings, + matchContext, + ) if (match) { - const matchData = getMatchFromNode(node, settings.parserSettings) + const matchData = getMatchFromNode( + node, + settings.parserSettings, + extendedMatchContext.getAllAliases(), + ) matches.push(matchData) } } - const visitorsMap = getVisitorKeysForQueryNodeType( - queryNode.type as string, - settings.parserSettings, + const visitorKeysForAliasedTreeWildcards = + initialMatchContext?.nodesTreeAliasesMap + ? Object.values(initialMatchContext?.nodesTreeAliasesMap).map((alias) => + settings.parserSettings.getNodeType(alias.aliasNode), + ) + : [] + + const visitorsMap = uniqueItems( + getVisitorKeysForQueryNodeType( + settings.parserSettings.getNodeType(queryNode), + settings.parserSettings, + ), + ...visitorKeysForAliasedTreeWildcards.map((nodeType) => + getVisitorKeysForQueryNodeType(nodeType, settings.parserSettings), + ), ).reduce( (map, visitorKey) => ({ ...map, @@ -80,7 +152,12 @@ export const test_traverseAndMatchWithVisitors = ( {}, ) - test_traverse_ast(fileNode, settings, visitorsMap) + traverseAst( + fileNode, + settings.parserSettings.isNode, + settings.parserSettings.getNodeType, + visitorsMap, + ) return matches } @@ -89,6 +166,7 @@ export const traverseAndMatch = ( fileNode: PoorNodeType, queryNode: PoorNodeType, settings: SearchSettingsWithOptionalLogger & GetCodeForNode, + initialMatchContext?: MatchContextAliases, ) => { const settingsWithLogger: SearchSettings & GetCodeForNode = { ...settings, @@ -97,12 +175,14 @@ export const traverseAndMatch = ( const { logger: { log, logStepEnd, logStepStart }, parserSettings, - getCodeForNode = () => '', + getCodeForNode = () => 'getCodeForNode not provided', } = settingsWithLogger logStepStart('traverse') const matches = [] + const localMatchContext = createMatchContext(initialMatchContext) + /** * LOOK FOR MATCH START */ @@ -110,6 +190,8 @@ export const traverseAndMatch = ( fileNode, queryNode, searchSettings: settingsWithLogger, + // To not bind ref in case rest of match is incorrect + matchContext: createMatchContext(initialMatchContext), }) const foundMatchStart = levelMatch @@ -122,18 +204,31 @@ export const traverseAndMatch = ( // We keep logs in IIFE to get the whole logic removed during build log( 'foundMatchStart:\n', - getCodeForNode(fileNode, 'file'), - '\n', getCodeForNode(queryNode, 'query'), + '\n', + getCodeForNode(fileNode, 'file'), '\n'.padEnd(10, '_'), ) const measureValidate = measureStart('validate') - const match = validateMatch(fileNode, queryNode, settings) + const { match, matchContext: extendedMatchContext } = validateMatch( + fileNode, + queryNode, + settings, + localMatchContext, + ) measureValidate() if (match) { - matches.push(getMatchFromNode(fileNode, parserSettings)) + matches.push( + getMatchFromNode( + fileNode, + parserSettings, + extendedMatchContext.getAllAliases(), + ), + ) + } else { + log('match not validated') } } @@ -149,10 +244,11 @@ export const traverseAndMatch = ( fileNode[key] as PoorNodeType, queryNode, settings, + initialMatchContext, ) } else { return (fileNode[key] as PoorNodeType[]).map((node) => - traverseAndMatch(node, queryNode, settings), + traverseAndMatch(node, queryNode, settings, initialMatchContext), ) } } diff --git a/packages/core/src/searchStages/validateMatch.ts b/packages/core/src/searchStages/validateMatch.ts index 56fb93b..db958fc 100644 --- a/packages/core/src/searchStages/validateMatch.ts +++ b/packages/core/src/searchStages/validateMatch.ts @@ -5,16 +5,19 @@ import { PoorNodeType, SearchSettings, SearchSettingsWithOptionalLogger, + ValidateMatchReturnType, } from '../types' import { getKeyFromObject, noopLogger } from '../utils' import { compareNodes } from './compareNodes' -import { Logger } from '../logger' +import { MatchContext, createMatchContext } from '../matchContext' export const validateMatch = ( fileNode: PoorNodeType, queryNode: PoorNodeType, settings: SearchSettingsWithOptionalLogger & GetCodeForNode, -) => { + matchContext: MatchContext, +): ValidateMatchReturnType => { + const localMatchContext = createMatchContext(matchContext.getAllAliases()) const settingsWithLogger: SearchSettings & GetCodeForNode = { ...settings, logger: settings.logger ?? noopLogger, @@ -22,7 +25,7 @@ export const validateMatch = ( const { mode, parserSettings, - getCodeForNode = () => '', + getCodeForNode = () => 'getCodeForNode not provided', logger: { log, logStepStart }, } = settingsWithLogger @@ -41,6 +44,7 @@ export const validateMatch = ( fileNode: fileNode, queryNode: queryNode, searchSettings: settingsWithLogger, + matchContext: localMatchContext, }) if ( @@ -48,7 +52,11 @@ export const validateMatch = ( queryKeysToTraverseForValidatingMatch.length ) { throw new Error( - `Count of keys to validate in query and file does not match for nodes ${fileNode.type}:${fileNode?.name} ${queryNode.type}:${queryNode?.name}, [${fileKeysToTraverseForValidatingMatch}] [${queryKeysToTraverseForValidatingMatch}]`, + `Count of keys to validate in query and file does not match for nodes ${parserSettings.getNodeType( + fileNode, + )}:${fileNode?.name} ${parserSettings.getNodeType(queryNode)}:${ + queryNode?.name + }, [${fileKeysToTraverseForValidatingMatch}] [${queryKeysToTraverseForValidatingMatch}]`, ) } @@ -81,7 +89,7 @@ export const validateMatch = ( log('nodes incompat:\n\n', 'invalid code') } - return false + return { match: false, matchContext: localMatchContext } } else { if (queryKeysToTraverseForValidatingMatch.length > 0) { for (let i = 0; i < queryKeysToTraverseForValidatingMatch.length; i++) { @@ -97,9 +105,12 @@ export const validateMatch = ( log('validate: query val', queryValue) log('validate: file val', fileValue) - if (Array.isArray(fileValue as PoorNodeType[])) { + if ( + Array.isArray(fileValue as PoorNodeType[]) && + Array.isArray(queryValue as PoorNodeType[]) + ) { log('validate: is array') - const nodesArr = (fileValue as PoorNodeType[]).filter( + const fileNodesArr = (fileValue as PoorNodeType[]).filter( parserSettings.shouldCompareNode, ) const queryNodesArr = (queryValue as PoorNodeType[]).filter( @@ -107,75 +118,118 @@ export const validateMatch = ( ) if (isExact) { - if (nodesArr.length !== queryNodesArr.length) { - return false + if (fileNodesArr.length !== queryNodesArr.length) { + return { match: false, matchContext: localMatchContext } } - for (let i = 0; i < nodesArr.length; i++) { - const newCurrentNode = nodesArr[i] + for (let i = 0; i < fileNodesArr.length; i++) { + const newCurrentNode = fileNodesArr[i] const newCurrentQueryNode = queryNodesArr[i] - if ( - !newCurrentNode || - !newCurrentQueryNode || - !validateMatch(newCurrentNode, newCurrentQueryNode, settings) - ) { - return false + if (!newCurrentNode || !newCurrentQueryNode) { + return { match: false, matchContext: localMatchContext } } + + const validateMatchResult = validateMatch( + newCurrentNode, + newCurrentQueryNode, + settings, + localMatchContext, + ) + + if (!validateMatchResult.match) { + return { match: false, matchContext: localMatchContext } + } + + localMatchContext.merge( + validateMatchResult.matchContext.getAllAliases(), + ) } } else { - if (queryNodesArr.length > nodesArr.length) { - return false + if (queryNodesArr.length > fileNodesArr.length) { + log('validate: more query nodes than array nodes') + + return { match: false, matchContext: localMatchContext } } - const matchedIndexes: number[] = [] + if (mode === 'include') { + const matchedIndexes: number[] = [] + + const queryNodesArrSorted = [...queryNodesArr].sort((a, b) => + sortByLeastIdentifierStrength( + a, + b, + parserSettings.wildcardUtils, + parserSettings.getIdentifierNodeName, + ), + ) - const queryNodesArrSorted = [...queryNodesArr].sort((a, b) => - sortByLeastIdentifierStrength(a, b, parserSettings.wildcardUtils), - ) + for (let i = 0; i < queryNodesArrSorted.length; i++) { + const newQueryNode = queryNodesArrSorted[i] - for (let i = 0; i < queryNodesArrSorted.length; i++) { - const newQueryNode = queryNodesArrSorted[i] + for (let j = 0; j < fileNodesArr.length; j++) { + const newFileNode = fileNodesArr[j] - for (let j = 0; j < nodesArr.length; j++) { - const newFileNode = nodesArr[j] + if (!matchedIndexes.includes(j)) { + const validateMatchResult = validateMatch( + newFileNode, + newQueryNode, + settings, + localMatchContext, + ) - if (!matchedIndexes.includes(j)) { - if (validateMatch(newFileNode, newQueryNode, settings)) { - matchedIndexes.push(j) - break + if (validateMatchResult.match) { + matchedIndexes.push(j) + + localMatchContext.merge( + validateMatchResult.matchContext.getAllAliases(), + ) + + break + } } } + + if (matchedIndexes.length !== i + 1) { + return { match: false, matchContext: localMatchContext } + } } - if (matchedIndexes.length !== i + 1) { - return false + if (matchedIndexes.length !== queryNodesArr.length) { + return { match: false, matchContext: localMatchContext } } - } + } else { + // mode is include-with-order - if (mode === ('include-with-order' as Mode)) { - const propsFoundInOrder = matchedIndexes.every( - (val, idx, arr) => { - if (idx + 1 === arr.length) { - return true - } else { - return val < arr[idx + 1] - } - }, - ) + let queryNodeIndexToMatch = 0 + + for (let j = 0; j < fileNodesArr.length; j++) { + const newFileNode = fileNodesArr[j] + const newQueryNode = queryNodesArr[queryNodeIndexToMatch] + + const validateMatchResult = validateMatch( + newFileNode, + newQueryNode, + settings, + localMatchContext, + ) - if ( - !propsFoundInOrder || - matchedIndexes.length !== queryNodesArr.length - ) { - return false + if (validateMatchResult.match) { + queryNodeIndexToMatch++ + + localMatchContext.merge( + validateMatchResult.matchContext.getAllAliases(), + ) + } } - } else { - if (matchedIndexes.length !== queryNodesArr.length) { - return false + + if (queryNodeIndexToMatch !== queryNodesArr.length) { + return { match: false, matchContext: localMatchContext } } } } + + log('validate: non boolean return result for comparing nodes array') } else { log('validate: is Node') @@ -184,19 +238,36 @@ export const validateMatch = ( log('validate: newFileNode', newFileNode) log('validate: newQueryNode', newQueryNode) - if ( - !newFileNode || - !newQueryNode || - !validateMatch(newFileNode, newQueryNode, settings) - ) { - return false + if (!newFileNode || !newQueryNode) { + return { match: false, matchContext: localMatchContext } } + + const validateMatchResult = validateMatch( + newFileNode, + newQueryNode, + settings, + localMatchContext, + ) + + if (!validateMatchResult.match) { + return { match: false, matchContext: localMatchContext } + } + + localMatchContext.merge( + validateMatchResult.matchContext.getAllAliases(), + ) } } - return true + return { + match: true, + matchContext: localMatchContext, + } } else { - return true + return { + match: true, + matchContext: localMatchContext, + } } } } diff --git a/packages/core/src/searchWorker.ts b/packages/core/src/searchWorker.ts index 83e466d..19ccdf4 100644 --- a/packages/core/src/searchWorker.ts +++ b/packages/core/src/searchWorker.ts @@ -1,12 +1,18 @@ import { parentPort, workerData as _workerData } from 'worker_threads' +import { parserSettingsMap } from './parserSettings' import { searchInFileSystem } from './searchInFs' import { Matches, SearchWorkerData, WorkerOutboundMessage } from './types' -// -;(async () => { +const searchWorkerRuntime = async () => { const workerData = _workerData as SearchWorkerData + const parser = workerData.parser + const parserFilesBasePath = workerData.parserFilesBasePath + + if (parser) { + await parserSettingsMap[parser]().init?.(parserFilesBasePath) + } const onPartialResult = workerData.reportEachMatch ? (partialResult: Matches) => { @@ -26,4 +32,6 @@ import { Matches, SearchWorkerData, WorkerOutboundMessage } from './types' type: 'ALL_RESULTS', data: results, } as WorkerOutboundMessage) -})() +} + +searchWorkerRuntime() diff --git a/packages/core/src/testOnlyConfig.ts b/packages/core/src/testOnlyConfig.ts new file mode 100644 index 0000000..3c6e925 --- /dev/null +++ b/packages/core/src/testOnlyConfig.ts @@ -0,0 +1,13 @@ +import { ParserType } from './types' +type TestSettings = { + parserType: ParserType + isTraversal: boolean +} + +declare global { + //eslint-disable-next-line no-var + var testSettings: TestSettings | undefined +} + +export const useTraverseApproachTestOnly = global?.testSettings?.isTraversal +export const testParserTypeOverride = global?.testSettings?.parserType diff --git a/packages/core/src/textSearch.ts b/packages/core/src/textSearch.ts index b3162a3..deb4a68 100644 --- a/packages/core/src/textSearch.ts +++ b/packages/core/src/textSearch.ts @@ -281,6 +281,12 @@ export function textSearch({ }, query: query.toString(), filePath, + // TODO: improve text search types, they do not provide aliases + aliases: { + identifierAliasesMap: {}, + nodesTreeAliasesMap: {}, + stringAliasesMap: {}, + }, } }) diff --git a/packages/core/src/treeSitterUtils.ts b/packages/core/src/treeSitterUtils.ts new file mode 100644 index 0000000..2a5e919 --- /dev/null +++ b/packages/core/src/treeSitterUtils.ts @@ -0,0 +1,295 @@ +import type { SyntaxNode, Tree } from 'web-tree-sitter' +import Parser from 'web-tree-sitter' +import { PoorNodeType, TreeSitterNodeFieldsMeta } from './types' + +export type PostProcessNodes = Record< + string, + (node: PoorNodeType, codeText: string) => PoorNodeType +> + +export function collectAstFromTree({ + tree, + codeText, + defineRawValueForNodeTypes, + nodeFieldsMeta, + postProcessNodes, +}: { + tree: Tree + codeText: string + defineRawValueForNodeTypes: string[] + nodeFieldsMeta: TreeSitterNodeFieldsMeta + postProcessNodes?: PostProcessNodes +}) { + const getPosition = (node: SyntaxNode) => { + const startPosition = node.startPosition + const endPosition = node.endPosition + const startIndex = node.startIndex + const endIndex = node.endIndex + + return { + start: { + line: startPosition.row + 1, + column: startPosition.column, + index: startIndex, + }, + end: { + line: endPosition.row + 1, + column: endPosition.column, + index: endIndex, + }, + } + } + + function collectAstFromTreeInner( + node: SyntaxNode, + level = 0, + nodeTypeFromParent?: string, + ) { + /** + * Receiving node type from parent is performance optimization for slow access to WASM memory + */ + const nodeType = nodeTypeFromParent ?? node.type + + if (nodeType === 'ERROR') { + const errorLocation = getPosition(node) + const error = Error( + `Parse error at ${errorLocation.start.line}:${errorLocation.start.column}-${errorLocation.end.line}:${errorLocation.end.column}`, + ) + + //@ts-ignore + error.loc = errorLocation + + throw error + } + + const nodeMeta = nodeFieldsMeta[nodeType] + + if (!nodeMeta) { + /** + * We don't care about node types that are not in meta mapping + */ + return null + } + + const fields = Object.fromEntries([ + ...nodeMeta.multipleOrChildrenFieldNames.map((fieldName) => [ + fieldName, + [], + ]), + ...nodeMeta.singleFieldNames.map((fieldName) => [fieldName, null]), + ]) + + const fieldNodes: SyntaxNode[] = [] + + nodeMeta.singleFieldNames.forEach((fieldName) => { + const childForName = node.childForFieldName(fieldName) + + if (childForName) { + fieldNodes.push(childForName) + + fields[fieldName] = collectAstFromTreeInner(childForName, level + 1) + } + }) + + const childCount = node.childCount + + for (let i = 0; i < childCount; i++) { + const childNode = node.child(i) + + if ( + childNode && + !fieldNodes.some((fieldNode) => fieldNode.equals(childNode)) + ) { + const collectedNodeType = childNode.type as string + + if (collectedNodeType === 'ERROR') { + collectAstFromTreeInner(childNode, level + 1, collectedNodeType) + } + + /** + * We ignore nodes with types that are not in mapping + */ + if (nodeFieldsMeta[collectedNodeType]) { + const collectedNode = collectAstFromTreeInner( + childNode, + level + 1, + collectedNodeType, + ) + + if (collectedNode) { + const field = + nodeMeta.nodeTypeToMultipleFieldName[collectedNodeType] + + if (field) { + if (fields[field]) { + fields[field].push(collectedNode) + } else { + console.error(`No field "${field}" for ${collectedNodeType}`) + } + } + + /** + * When node field was not found in mapping, it most likely mean that node was some language keyword that can be skipped + */ + } + } + } + } + + const rawNode = { + nodeType: nodeType, + loc: getPosition(node), + ...fields, + } as PoorNodeType + + const isLeaf = + nodeMeta.multipleOrChildrenFieldNames.length === 0 && + nodeMeta.singleFieldNames.length === 0 + + // Temporary disable check for leaf node, perhaps it's not needed. Now breaks stuff for string_content, which itself needs more adjustments to work properly + if (/*isLeaf && */ defineRawValueForNodeTypes.includes(nodeType)) { + rawNode.rawValue = codeText.substring( + //@ts-ignore + rawNode.loc.start.index, + //@ts-ignore + rawNode.loc.end.index, + ) + } + + if (postProcessNodes?.[nodeType]) { + return postProcessNodes[nodeType](rawNode, codeText) + } + + return rawNode + } + + return collectAstFromTreeInner(tree.rootNode) +} + +export const getFilePaths = (parserName: string) => { + return { + treeSitterWasm: `dist-tree-sitter/tree-sitter.wasm`, + parserWasm: `dist-tree-sitter/${parserName}/parser.wasm`, + fieldsMeta: `dist-tree-sitter/${parserName}/fields-meta.json`, + } +} + +export const getFieldsMeta = async ( + basePath: string, + path: string, +): Promise => { + if (typeof window !== 'undefined') { + return (await fetch(basePath + '/' + path)).json() + } + + return JSON.parse( + require('fs') + .readFileSync(sanitizeFsPath(basePath + '/' + path)) + .toString(), + ) +} + +export const getTreeSitterWasmPath = ( + basePath: string, + parserPath: string, +): string => { + if (typeof window !== 'undefined') { + return basePath + '/' + parserPath + } + + return sanitizeFsPath(basePath + '/' + parserPath) +} + +export const sanitizeFsPath = (fsPath: string) => { + const isWindows = process?.platform.includes('win32') + + // For some reason vscode return lowercased drive letters on windows :/ + if (isWindows) { + return fsPath.replace(/\//g, '\\') + } + + return fsPath.replace(/\\/g, '/') +} + +const getDefaultBasePath = () => { + return typeof process?.cwd !== 'undefined' ? process.cwd() : '/' +} + +export const treeSitterParserModuleFactory = ({ + treeSitterParserName, + defineRawValueForNodeTypes, + postProcessNodes, +}: { + treeSitterParserName: string + defineRawValueForNodeTypes: string[] + postProcessNodes?: PostProcessNodes +}) => { + let parser: Parser | null = null + let parserInitError: Error | null = null + let fieldsMeta: TreeSitterNodeFieldsMeta | null = null + + const filePaths = getFilePaths(treeSitterParserName) + + const init = async (basePathOption?: string | undefined) => { + if (parser) { + return + } + + const basePath = basePathOption ?? getDefaultBasePath() + + return Parser.init({ + locateFile: () => + getTreeSitterWasmPath(basePath, filePaths.treeSitterWasm), + }) + .then(async () => { + fieldsMeta = await getFieldsMeta(basePath, filePaths.fieldsMeta) + const Python = await Parser.Language.load( + getTreeSitterWasmPath(basePath, filePaths.parserWasm), + ) + + const localParser = new Parser() + + localParser.setLanguage(Python) + parser = localParser + }) + .catch((error) => { + console.error('Parser init error', error) + parser = null + parserInitError = error + }) + } + + const parse = (code: string) => { + if (parserInitError !== null) { + throw parserInitError + } + + if (parser === null) { + throw new Error('Parser not ready') + } + + if (fieldsMeta === null) { + throw new Error("Couldn't load fields meta") + } + + if (parserInitError) { + throw parserInitError + } + + const tree = parser.parse(code, undefined) + + const ast = collectAstFromTree({ + tree, + codeText: code, + defineRawValueForNodeTypes, + nodeFieldsMeta: fieldsMeta, + postProcessNodes, + }) + + tree.delete() + + return ast ?? { nodeType: 'empty' } // this is to make TS happy, won't happen in real life. + } + + return { init, parse } +} diff --git a/packages/core/src/types.ts b/packages/core/src/types.ts index 8e5b155..87e3d22 100644 --- a/packages/core/src/types.ts +++ b/packages/core/src/types.ts @@ -1,9 +1,11 @@ import { Logger } from './logger' +import { MatchContext, MatchContextAliases } from './matchContext' export type Mode = 'exact' | 'include' | 'include-with-order' | 'text' export type Position = { line: number column: number + index?: number } export type Location = { @@ -11,11 +13,15 @@ export type Location = { end: Position } -export type Match = { +export type MatchPosition = { start: number end: number loc: Location +} + +export type Match = MatchPosition & { node: PoorNodeType + aliases: MatchContextAliases } export type ExtendedCodeFrame = { @@ -28,6 +34,7 @@ export type MatchWithFileInfo = Omit & { code: string filePath: string extendedCodeFrame: ExtendedCodeFrame + indentationBase?: number } export type AstMatch = Omit & { @@ -37,8 +44,22 @@ export type AstMatch = Omit & { export type Matches = Array export type AstMatches = Array +export type PoorNodeTypeParentData = { + node: PoorNodeType + key: string + index?: number +} + export type PoorNodeType = { - [key: string]: string | number | boolean | PoorNodeType[] | PoorNodeType + [key: string]: + | string + | number + | boolean + | null + | PoorNodeType[] + | PoorNodeType +} & { + __parent?: PoorNodeTypeParentData } export type HardStopFlag = { @@ -47,7 +68,17 @@ export type HardStopFlag = { destroy: () => void } -export type ParserType = 'babel' | 'typescript-eslint' | 'esprima' +export type ParserType = + | 'babel' + | 'typescript-eslint-parser' + | 'espree' + | 'esprima' + | 'babel-eslint-parser' + | 'angular-eslint-template-parser' + | 'css-tree' + | 'python' + | 'lua' + | 'csharp' export type FileSystemSearchArgs = { filePaths: string[] @@ -59,6 +90,8 @@ export type FileSystemSearchArgs = { maxResultsLimit?: number hardStopFlag?: HardStopFlag parser?: ParserType + returnMatchedNodes?: boolean + parserFilesBasePath?: string } export type ParseError = { @@ -112,10 +145,10 @@ export type SearchWorkerData = FileSystemSearchArgs & { } export type SearchSettings = { + parserSettings: ParserSettings logger: Logger caseInsensitive: boolean mode: Mode - parserSettings: ParserSettings } export type SearchSettingsWithOptionalLogger = Omit< @@ -130,16 +163,17 @@ export type GetCodeForNode = { } export type WildcardMeta = { - wildcardType: 'identifier' | 'nodeTree' - wildcardWithRef: string - wildcardWithoutRef: string - wildcardRef: string | null + wildcardType: 'identifier' | 'nodeTree' | 'string' + wildcardWithAlias: string + wildcardWithoutAlias: string + wildcardAlias: string | null } export type CompareNodesParams = { fileNode: PoorNodeType | null queryNode: PoorNodeType | null - searchSettings: SearchSettings + searchSettings: SearchSettings & GetCodeForNode + matchContext: MatchContext /** Params used to support comparing nodes which are not on the same level */ queryKeysPrefix?: string fileKeysPrefix?: string @@ -160,6 +194,7 @@ export type NodesComparator = ( fileKeysMapper: (key: string) => string fileKeysToTraverseForOtherMatches: string[] measureCompare: () => void + log: (...text: string[]) => void }, ) => CompareNodesReturnType | undefined @@ -174,10 +209,12 @@ export type WildcardUtils = { numericWildcard: string disallowedWildcardSequence: string disallowedWildcardRegExp: RegExp - removeIdentifierRefFromWildcard: (identifier: string) => string - getWildcardRefFromIdentifierName: (name: string) => string | null - getWildcardFromString: (name: string) => WildcardMeta | null - getWildcardFromNode: (node: PoorNodeType) => WildcardMeta | null + removeWildcardAliasesFromIdentifierName: (identifier: string) => string + removeWildcardAliasesFromStringLiteral: (str: string) => string + getWildcardAliasFromWildcard: (name: string) => string | null + getIdentifierWildcardsFromString: (name: string) => WildcardMeta[] + getIdentifierWildcardsFromNode: (node: PoorNodeType) => WildcardMeta[] + getStringWildcardsFromString: (content: string) => WildcardMeta[] patternToRegExp: (string: string, caseInsensitive: boolean) => RegExp } @@ -198,28 +235,36 @@ export type ProgramNodeAndBlockNodeUtils = { blockNodeBodyKey: string } +export type GetUniqueTokensFromStringOrIdentifierNode = (arg: { + queryNode: PoorNodeType + caseInsensitive: boolean + parserSettings: Pick< + ParserSettings, + | 'isIdentifierNode' + | 'stringLikeLiteralUtils' + | 'getIdentifierNodeName' + | 'wildcardUtils' + > +}) => string[] + export type ParserSettings = { supportedExtensions: string[] parseCode: (code: string, filePath?: string) => PoorNodeType isNode: (maybeNode: PoorNodeType) => boolean + identifierNodeTypes: string[] isIdentifierNode: (node: PoorNodeType) => boolean astPropsToSkip: (string | { type: string; key: string })[] - /** - * This one is tricky, we use it to remove `null` or `undefined` properties from file node that are not present in query node - * Even if we use proper mapping for babel, we can also just `return true` to make it work - * Keeping this for now, but perhaps in future it could be removed, because we use it only in include mode, where everything should be optional - */ - isNodeFieldOptional: (nodeType: string, nodeFieldKey: string) => boolean getProgramBodyFromRootNode: (node: PoorNodeType) => PoorNodeType[] getProgramNodeFromRootNode: (node: PoorNodeType) => PoorNodeType getNodeType: (node: PoorNodeType) => string getIdentifierNodeName: (node: PoorNodeType) => string + setIdentifierNodeName: (node: PoorNodeType, name: string) => void unwrapExpressionStatement: (node: PoorNodeType) => PoorNodeType createBlockStatementNode: ( body: PoorNodeType[], - position: Omit, + position: MatchPosition, ) => PoorNodeType - sanitizeNode: (node: PoorNodeType) => void + getSanitizedNodeValue: (type: string, key: string, value: T) => T shouldCompareNode: (node: PoorNodeType) => void wildcardUtils: WildcardUtils compareNodesBeforeWildcardsComparison: NodesComparator @@ -228,11 +273,30 @@ export type ParserSettings = { stringLikeLiteralUtils: StringLikeLiteralUtils numericLiteralUtils: NumericLiteralUtils programNodeAndBlockNodeUtils: ProgramNodeAndBlockNodeUtils - getNodePosition: (node: PoorNodeType) => Omit + getNodePosition: (node: PoorNodeType) => MatchPosition getParseErrorLocation: (error: Error) => { line: number; column: number } /** * Alternative node types used to match while in traversal mode * eg. wildcard Identifier matcher should look in JSXIdentifier visitor */ alternativeNodeTypes: Record + preprocessQueryCode?: (code: string) => string + postprocessQueryNode?: (queryNode: PoorNodeType) => PoorNodeType + getUniqueTokensFromStringOrIdentifierNode?: GetUniqueTokensFromStringOrIdentifierNode + init?: (basePath?: string) => Promise +} + +export type TreeSitterNodeFieldsMeta = Record< + string, + { + type: string + singleFieldNames: string[] + nodeTypeToMultipleFieldName: Record + multipleOrChildrenFieldNames: string[] + } +> + +export type ValidateMatchReturnType = { + match: boolean + matchContext: MatchContext } diff --git a/packages/core/src/utils.ts b/packages/core/src/utils.ts index 8f98e2f..99bd7a3 100644 --- a/packages/core/src/utils.ts +++ b/packages/core/src/utils.ts @@ -9,6 +9,7 @@ import { Mode, NodesComparator, NodesComparatorParameters, + MatchPosition, } from './types' export const getMode = (mode: Mode = 'include') => { @@ -154,10 +155,8 @@ export const nonIdentifierOrKeywordGlobal = new RegExp( 'g', ) -export const dedupMatches = ( +export const dedupMatches = ( matches: M[], - log: (...args: any[]) => void, - debug = false, ): M[] => { const deduped: M[] = [] @@ -188,7 +187,7 @@ export const prepareCodeResult = ({ start, end, loc, -}: { fileContent: string } & Omit) => { +}: { fileContent: string } & MatchPosition) => { const frame = fileContent.substring(start - loc.start.column, end) const firstLineWhiteCharsCountRegExp = new RegExp(`^\\s*`) @@ -201,10 +200,12 @@ export const prepareCodeResult = ({ const replaceRegex = new RegExp(`^\\s{0,${firstLineWhiteCharsCount}}`) if (firstLineWhiteCharsCount > 0) { - return lines.map((line) => line.replace(replaceRegex, '')).join('\n') + const code = lines.map((line) => line.replace(replaceRegex, '')).join('\n') + + return { code, indentationBase: firstLineWhiteCharsCount } } - return lines.join('\n') + return { code: lines.join('\n'), indentationBase: 0 } } export const isNullOrUndef = (val: any) => val === null || val === undefined @@ -232,3 +233,23 @@ export const noopLogger: Logger = { logStepEnd: () => undefined, logStepStart: () => undefined, } + +export const groupBy = >( + arr: T[], + key: K, +) => { + return Object.values( + arr.reduce((grouped, duplicate) => { + const maybeExistingGroup = grouped[duplicate[key]] ?? [] + grouped[duplicate[key]] = [...maybeExistingGroup, duplicate] + + return grouped + }, {} as Record), + ) +} + +export const decomposeString = (str: string, splitter: RegExp) => + str + .split(splitter) + .map((part) => normalizeText(part).split(SPACE_CHAR)) + .flat(1) diff --git a/packages/core/src/wildcardUtilsFactory.ts b/packages/core/src/wildcardUtilsFactory.ts index ecea612..d35945d 100644 --- a/packages/core/src/wildcardUtilsFactory.ts +++ b/packages/core/src/wildcardUtilsFactory.ts @@ -1,86 +1,170 @@ import { PoorNodeType, WildcardMeta, WildcardUtils } from './types' -import { nonIdentifierOrKeywordGlobal } from './utils' +import { nonIdentifierOrKeywordGlobal, regExpTest } from './utils' export const createWildcardUtils = ( identifierNodeTypes: string[], numericWildcard: string, identifierWildcardBase: string, + getIdentifierNodeName: (node: PoorNodeType) => string, + getNodeType: (node: PoorNodeType) => string, stringWildcardBase = identifierWildcardBase, ): WildcardUtils => { const identifierWildcard = identifierWildcardBase.repeat(2) const nodesTreeWildcard = identifierWildcardBase.repeat(3) - const removeIdentifierRefFromWildcard = (name: string) => { - const containsWildcardRegExp = new RegExp(`^\\${identifierWildcardBase}`) - const removeIdRefRegExp = new RegExp( - `(?<=(\\${identifierWildcardBase}){2,3})_(\\w)+$`, - ) + const createRemoveWildcardAliasesFromIdentifierName = + (wildcardBase: string) => (name: string) => { + const containsWildcardWithInvalidRefRegExp = new RegExp( + `(\\${wildcardBase}){2,3}([a-zA-Z0-9])+_(\\${wildcardBase}){2,3}`, + ) + + const containsWildcardWithInvalidRefRegExp2 = new RegExp( + `(\\${wildcardBase}){2,3}_([a-zA-Z0-9])+(\\${wildcardBase}){2,3}`, + ) + + const containsWildcardWithInvalidRefRegExp3 = new RegExp( + `(\\${wildcardBase}){2,3}_([a-zA-Z0-9])+_(\\${wildcardBase}){2,3}`, + ) + + const removeIdRefRegExp = new RegExp( + `(?<=(\\${wildcardBase}){2,3})_([a-zA-Z0-9])+(_)?`, + ) + + let nameWithRemovedWildcardsAliases = name + + while ( + regExpTest(removeIdRefRegExp, nameWithRemovedWildcardsAliases) && + !regExpTest( + containsWildcardWithInvalidRefRegExp, + nameWithRemovedWildcardsAliases, + ) && + !regExpTest( + containsWildcardWithInvalidRefRegExp2, + nameWithRemovedWildcardsAliases, + ) && + !regExpTest( + containsWildcardWithInvalidRefRegExp3, + nameWithRemovedWildcardsAliases, + ) + ) { + nameWithRemovedWildcardsAliases = + nameWithRemovedWildcardsAliases.replace(removeIdRefRegExp, '') + } - if (containsWildcardRegExp.test(name)) { - return name.replace(removeIdRefRegExp, '') + return nameWithRemovedWildcardsAliases } - return name - } + const removeWildcardAliasesFromIdentifierName = + createRemoveWildcardAliasesFromIdentifierName(identifierWildcardBase) - const getWildcardRefFromIdentifierName = (name: string) => { + const removeWildcardAliasesFromStringLiteral = + createRemoveWildcardAliasesFromIdentifierName(stringWildcardBase) + + const getWildcardAliasFromWildcard = (name: string) => { const getRefRegExp = new RegExp( - `(?<=(\\${identifierWildcardBase}){2,3}_)(\\w)+$`, + `(?<=(\\${identifierWildcardBase}){2,3})_([a-zA-Z0-9])+(?=(_?))`, 'g', ) const matchedRef = name.match(getRefRegExp) - return matchedRef?.[0] || null + return matchedRef?.[0]?.replace(/_/g, '') || null } - const getWildcardFromString = ( - maybeWildcardString: string, - ): null | WildcardMeta => { + const getWildcardWithAliasFromIdentifierName = (name: string) => { + const getRefRegExp = new RegExp( + `((\\${identifierWildcardBase}){2,3}_([a-zA-Z0-9])+(_)?)|((\\${identifierWildcardBase}){2,3})`, + 'g', + ) + + const matchedWildcard = name.match(getRefRegExp) + + return matchedWildcard?.[0] as string + } + + const hasWildcard = (maybeWildcardString: string) => { const hasIdentifierWildcard = maybeWildcardString.includes(identifierWildcard) const hasNodeTreeWildcard = maybeWildcardString.includes(nodesTreeWildcard) - const hasWildcard = hasIdentifierWildcard || hasNodeTreeWildcard - if (hasWildcard) { - const wildcardWithoutRef = - removeIdentifierRefFromWildcard(maybeWildcardString) + return hasIdentifierWildcard || hasNodeTreeWildcard + } + + const getIdentifierWildcardsFromString = ( + maybeWildcardString: string, + ): WildcardMeta[] => { + let maybeWildcardStringToDecompose = maybeWildcardString + const wildcardsMeta: WildcardMeta[] = [] - return { + while (hasWildcard(maybeWildcardStringToDecompose)) { + const hasNodeTreeWildcard = + maybeWildcardStringToDecompose.includes(nodesTreeWildcard) + + const wildcardWithAlias = getWildcardWithAliasFromIdentifierName( + maybeWildcardStringToDecompose, + ) + + const wildcardAlias = getWildcardAliasFromWildcard(wildcardWithAlias) + + const wildcardWithoutAlias = + removeWildcardAliasesFromIdentifierName(wildcardWithAlias) + + wildcardsMeta.push({ wildcardType: hasNodeTreeWildcard ? 'nodeTree' : 'identifier', - wildcardWithRef: maybeWildcardString, - wildcardWithoutRef, - wildcardRef: getWildcardRefFromIdentifierName(maybeWildcardString), - } + wildcardWithAlias, + wildcardWithoutAlias, + wildcardAlias, + }) + + const wildcardEndIdx = + maybeWildcardStringToDecompose.indexOf(wildcardWithAlias) + + wildcardWithAlias.length + + maybeWildcardStringToDecompose = + maybeWildcardStringToDecompose.substring(wildcardEndIdx) } - return null + return wildcardsMeta } - const getWildcardFromNode = (node: PoorNodeType): null | WildcardMeta => { - if (typeof node.type !== 'string') { - return null + const getIdentifierWildcardsFromNode = ( + node: PoorNodeType, + ): WildcardMeta[] => { + const nodeType = getNodeType(node) + + if (typeof nodeType !== 'string') { + return [] } const isIdentifierNode = - typeof node.type === 'string' && identifierNodeTypes.includes(node.type) + typeof nodeType === 'string' && identifierNodeTypes.includes(nodeType) - if (isIdentifierNode && typeof node.name === 'string') { - return getWildcardFromString(node.name) + if (isIdentifierNode && typeof getIdentifierNodeName(node) === 'string') { + return getIdentifierWildcardsFromString(getIdentifierNodeName(node)) } - const isTypeReferenceNode = node.type === 'TSTypeReference' + /** + * TODO: make it generic + */ + const isTypeReferenceNode = nodeType === 'TSTypeReference' if (isTypeReferenceNode) { const maybeWildcardString = (node?.typeName as unknown as PoorNodeType) ?.name as string | undefined if (typeof maybeWildcardString === 'string') { - return getWildcardFromString(maybeWildcardString) + return getIdentifierWildcardsFromString(maybeWildcardString) } } - return null + return [] + } + + const getStringWildcardsFromString = (content: string): WildcardMeta[] => { + // Might need to have separate implementation for some other programming langs, good for now + return getIdentifierWildcardsFromString(content).map( + ({ wildcardType, ...rest }) => ({ wildcardType: 'string', ...rest }), + ) } const optionalStringWildcardRegExp = new RegExp( @@ -92,6 +176,11 @@ export const createWildcardUtils = ( 'g', ) + const anyStringWildcardRegExp = new RegExp( + `(\\${stringWildcardBase}){2,3}`, + 'g', + ) + const disallowedWildcardRegExp = new RegExp( `(\\${identifierWildcardBase}){4,}(?!\\{)`, ) @@ -128,16 +217,18 @@ export const createWildcardUtils = ( return { optionalStringWildcardRegExp, requiredStringWildcardRegExp, - anyStringWildcardRegExp: new RegExp(`(\\${stringWildcardBase}){2,3}`, 'g'), + anyStringWildcardRegExp, identifierWildcard, nodesTreeWildcard, numericWildcard, disallowedWildcardSequence: identifierWildcardBase.repeat(4), disallowedWildcardRegExp, - removeIdentifierRefFromWildcard, - getWildcardRefFromIdentifierName, - getWildcardFromString, - getWildcardFromNode, + removeWildcardAliasesFromIdentifierName, + removeWildcardAliasesFromStringLiteral, + getWildcardAliasFromWildcard, + getIdentifierWildcardsFromString, + getIdentifierWildcardsFromNode, + getStringWildcardsFromString, patternToRegExp, } } diff --git a/packages/core/tools/babel.plugins.js b/packages/core/tools/babel.plugins.js index 4959e3d..2a8dbcb 100644 --- a/packages/core/tools/babel.plugins.js +++ b/packages/core/tools/babel.plugins.js @@ -1,6 +1,5 @@ module.exports = function plugin() { const babelEnv = process.env.BABEL_ENV - console.log('BABEL_ENV', babelEnv) if (babelEnv !== 'production' && babelEnv !== 'test') { return {} diff --git a/packages/core/tools/getAstJson/index.js b/packages/core/tools/getAstJson/index.js new file mode 100644 index 0000000..9cfcb1a --- /dev/null +++ b/packages/core/tools/getAstJson/index.js @@ -0,0 +1,45 @@ +const fs = require('fs') + +const { __internal } = require('../../dist/index') + +const print = console.log + +const defaultParser = 'python' +const userParser = process.argv[2] + +if (userParser === undefined) { + print('Using default parser:', defaultParser) +} else { + print('Using parser:', userParser) +} + +const parserName = userParser ?? defaultParser + +const parserSettings = __internal.parserSettingsMap[parserName]() + +const runtime = async () => { + await parserSettings?.init() + print('Parser loaded') + + const filePathWithSourceCode = `${__dirname}/code.txt` + const jsonOutputFilePath = `${__dirname}/${parserName}.json` + + const parseFile = () => { + const content = fs.readFileSync(filePathWithSourceCode).toString() + + try { + const ast = parserSettings.parseCode(content) + + fs.writeFileSync(jsonOutputFilePath, JSON.stringify(ast, null, 2)) + print('Parsed correctly') + } catch (e) { + console.error('Parse error', e) + } + } + + fs.watchFile(filePathWithSourceCode, { interval: 500 }, parseFile) + + parseFile() +} + +runtime() diff --git a/packages/core/tsconfig.build.json b/packages/core/tsconfig.build.json index e15ccef..e0f99b7 100644 --- a/packages/core/tsconfig.build.json +++ b/packages/core/tsconfig.build.json @@ -1,11 +1,11 @@ { "extends": "./tsconfig.json", - "include": ["src"], + "include": ["src", "./declarations.d.ts"], "compilerOptions": { "paths": {}, "declaration": true, "outDir": "dist", "noEmit": false, "emitDeclarationOnly": true - }, -} \ No newline at end of file + } +} diff --git a/packages/core/tsconfig.json b/packages/core/tsconfig.json index 4e5ba5f..d4888f7 100644 --- a/packages/core/tsconfig.json +++ b/packages/core/tsconfig.json @@ -3,19 +3,18 @@ "compilerOptions": { "baseUrl": "./", "paths": { - "/*": [ - "src/*" - ] + "/*": ["src/*"] }, "skipLibCheck": true, "declaration": false, - "noEmit": true + "noEmit": true, + "resolveJsonModule": true }, "exclude": [ - "__tests__/**/__fixtures__", - "__tests__/**/__fixturesOther__", + "__tests__/__fixtures__", + "__tests__/__fixturesOther__", "dist", "node_modules", "tools/*.js" ] -} \ No newline at end of file +} diff --git a/packages/eslint/README.md b/packages/eslint/README.md index 192149a..cd5e365 100644 --- a/packages/eslint/README.md +++ b/packages/eslint/README.md @@ -1,34 +1,77 @@ + +

- - + +

- Home Page | - Docs | - Roadmap | - Mission | - Playground + Website  •   + Docs   •   + Roadmap  •   + Mission  •   + Playground

-# CodeQue - structural search engine for JavaScript and TypeScript +

Find and lint complex code patterns effortlessly

+ +--- + +# What is CodeQue? + +CodeQue is semantic code search engine that understands the code syntax. + +It matches code structurally which makes it excellent for more complex queries. + +Query language offers wildcards, partial matching and ignores code formatting. + +Structural code search is available for JavaScript, TypesScript, HTML, CSS, Python, Lua, C# and more soon. + +Text code search with handy wildcards is available for **every language** and covers common regex search use cases. + +

Give it a try in + playground

+ +

Just paste code snippet to start searching, no installation needed!

+ +**Integrations** + +CodeQue is available as: + +- [VSCode extension](https://marketplace.visualstudio.com/items?itemName=CodeQue.codeque) for delightful code search and navigation experience. +- [ESLint integration](https://www.npmjs.com/package/@codeque/eslint-plugin) for creating custom linting rules in zero time. +- [CLI tool](https://www.npmjs.com/package/@codeque/cli) for searching code and more including headless environments. + +

All CodeQue tools work offline hence code never leaves your local environment.

+ +**Coming soon** + +CodeQue will be soon available as: + +- Duplicated code identification +- Batch code refactoring +- Advanced ESLint rule creator + +

🔔 Get notified about updates 🔔

-CodeQue is code search engine that understands the code syntax. +
-It matches structure rather than plain text, which makes it very effective for complex queries. + -It's available as [CLI](https://www.npmjs.com/package/@codeque/cli), [Visual Studio Code Extension](https://codeque.co/r/vsc), and ESLint plugin. + -## ESLint plugin +## ESLint integration 💅 Using CodeQue ESLint plugin you can create your own custom linting rules in zero time. Custom ESLint rules can help execute on long-term refactors or prevent introducing codebase specific bugs or bad patterns. -Rules can replace your decision log and help standardizing coding conventions across the project. +Rules can replace your decision log and help standardizing coding conventions across the project or organization. -CodeQue is a no-brainier for any team willing to improve their codebase quality. +CodeQue ESLint integration is a no-brainier for any team willing to improve their codebase quality. + + ## Installation @@ -36,54 +79,65 @@ CodeQue is a no-brainier for any team willing to improve their codebase quality. yarn add --dev @codeque/eslint-plugin ``` -> `@codeque/eslint-plugin` is now in `beta` and only supports `@typescript-eslint/parser` parser. `@babel/eslint-parser` and `Esprima` will be supported soon. +CodeQue supports all parsers officially supported by ESLint + +- [Espree](https://github.com/eslint/espree) +- [Esprima](https://www.npmjs.com/package/esprima) +- [@babel/eslint-parser](https://www.npmjs.com/package/@babel/eslint-parser) +- [@typescript-eslint/parser](https://www.npmjs.com/package/@typescript-eslint/parser) -> `@codeque/eslint-plugin` was tested with `ESLint` in versions `7.x` and `8.x`. +👉 Open GitHub issue to [request parser support](https://github.com/codeque-co/codeque/issues) ## Configuration -Add `@codeque` plugin to plugins list in your `.eslintrc` +Add `@codeque` plugin to plugins list in your `.eslintrc`. -```js -{ - plugins: ["@codeque"] -} -``` +And then add a definition for one of the rules: -And add a definition for one of the rules: `@codeque/error` or `@codeque/warning`. +- `@codeque/error` +- `@codeque/warning` There are two rules, so you can configure them to report errors or warnings. Each rule should be provided with a list of rule config objects with CodeQue queries. -Learn mode about writing queries from [CodeQue docs](https://codeque.co/docs) +Learn more about writing queries from [CodeQue docs](https://codeque.co/docs) -```js +```json { - rules: [ - "@codeque/error": ["error", [ - { - "query": "throw new Error()", - mode: "include", - message: "Use only project defined error classes.", - }, - ]], - "@codeque/warning": ["warn", [ - { - "query": "import $$$ from 'lodash';", - mode: "include", - message: "Prefer to import lodash functions from separate packages like 'lodash.debounce'", - }, - ]] - ] + "plugins": ["@codeque"], + "rules": { + "@codeque/error": [ + "error", + [ + { + "query": "fetchData()", + "mode": "exact", + "message": "Using fetchData() without parameters causes app crash!" + } + ] + ], + "@codeque/warning": [ + "warn", + [ + { + "query": "import $$$ from 'lodash';", + "mode": "include", + "message": "Prefer to import lodash functions from separate packages like 'lodash.debounce'" + } + ] + ] + } } ``` +The above configuration + +- rises an error when `fetchData()` function is called without parameters +- reports a warning when utility is imported from main `lodash` package +

- -

-

- +

> If your config seems too big, feel free to extract your set of rules to a separate file. Change your `.eslintrc` to JS file and import rules from a separate file. @@ -93,12 +147,13 @@ Learn mode about writing queries from [CodeQue docs](https://codeque.co/docs) Minimal rule config object should contain just `query` key ```js -({ - query: "someCode" +;({ + query: 'someCode', }) ``` The default settings are: + - `mode` - `include` - `message` - `Restricted code pattern` - `caseInsensitive` - `true` @@ -111,19 +166,281 @@ All configuration options type RuleConfig = { query: string // Content of the query mode: 'exact' | 'include' | 'include-with-order' // CodeQue search mode - message: string // Error message to display + message: string // Error message to display caseInsensitive: boolean // Whether query should perform matches case insensitively includeFiles: string[] | undefined // list of glob patterns to indicate files against which given rule should be executed excludeFiles: string[] // list of glob patterns to indicate files against which given rule should not be executed } ``` -## Debugging performance +## Rule examples + +CodeQue is general purpose code search tool. The examples list could be endless. Here are some of them for you to get a glimpse of what's possible. Those are relatively simple, you will definitely find some more complex during day to day work. + +> Don't know how to write a query? [Open an issue on GitHub](https://github.com/codeque-co/codeque/issues) ! + +### All usages of `console` object + +Searching for console logs, warnings, errors is quite simple use-case and can be achieved using traditional tools, but It's for you to warm up 😁 + +This rule warns about all places in the code that can output some (usually unwanted) logs. + +
+Show configuration + +```json +{ + "rules": { + "@codeque/warning": [ + "warn", + [ + { + "query": "console.$$()", + "mode": "include", + "message": "Prefer to use 'Logger' util." + } + ] + ] + } +} +``` + +
+ +

+ +

+ +### Library specific issues + +Third party code not always work as expected. Whenever you spot a problem, add custom eslint rule to spread that knowledge across your team. + +The rule warns against using `disabled` property on `SomeLibComponent`, and suggest using not documented `isDisabled` prop instead. + +
+Show configuration + +```json +{ + "rules": { + "@codeque/warning": [ + "warn", + [ + { + "query": "", + "mode": "include", + "message": "'disabled' property does not work as expected. Use 'isDisabled' instead", + "includeFiles": ["**/*.tsx"] + } + ] + ] + } +} +``` + +
+ +

+ +

+ +### Unstable hook reference + +Some 3rd party hooks are not implemented correctly and return non-memoized variables. + +In this rule we rise an error when `confirm` callback from `useAsyncDialog` is used as an item of `useCallback` dependency array. + +This is interesting example that links together two statements in the same code block, that does not necessarily have to directly follow each other. + +
+Show configuration + +```json +{ + "rules": { + "@codeque/error": [ + "error", + [ + { + "query": "const { confirm } = useAsyncDialog(); const $$ = useCallback($$$, [confirm]);", + "mode": "include", + "message": "'confirm' is known to be unstable. Using it in hook dependency array can cause render loop" + } + ] + ] + } +} +``` + +
+ +

+ +

+ +### React bad patterns + +First rule restricts usage of object literal as a prop. Object literal could be extracted to variable in upper scope or memoized to avoid performance issues. + +Second rule restricts places where a given array is mapped directly in JSX. It could be memoized to make the array reference stable and reduce re-renders. + +
+Show configuration + +```json +{ + "rules": { + "@codeque/error": [ + "error", + [ + { + "query": "<$$ $$={{}} />", + "mode": "include", + "message": "Don't use object literal in JSX props", + "includeFiles": ["**/*.tsx"] + }, + { + "query": "<$$ $$={$$$.map(() => ($$$))} />", + "mode": "include", + "message": "Don't use map directly in JSX, memoize map result instead", + "includeFiles": ["**/*.tsx"] + } + ] + ] + } +} +``` + +
+ +

+ +

+ +## What about performance? + +Usually rules works very fast unless they are too generic. + +CodeQue performs shallow matching based on strings in query, so it can filter out most of the source files before AST comparison even starts. + +Query that contains wildcards only, eg. `$$.$$()` will be looked for in every file, as query does not include any specific characters sequence. + +However query `console.$$()` would match only on files that contains `console` string somewhere within it's contents. + +Remember to be as specific as possible and use file filtering options eg. to not run JSX rules on `*.js` files. + +CodeQue will run faster for more specific patterns that occurs rarely in the codebase. If pattern is very common, it would have to do more comparisons, hence would run longer. + +### Debugging performance You can check performance of your CodeQue ESLint rules by running ```sh -TIMING=1 CODEQUE_DEBUG=true yarn YOUR_LINT_SCRIPT +TIMING=ALL CODEQUE_DEBUG=true yarn YOUR_LINT_SCRIPT +``` + +All the tests below were run on Typescript codebase with ~4000 source files. + +It's not a benchmark, it's an example to give a reference. Results were not averaged. + +#### Linting specific code patterns + +Linting code pattern specific to your code base is what CodeQue eslint integration is build for. + +Rule from examples section that captures issue with unstable hook reference occurs rare in the codebase, but can prevent various important bugs. + +``` +const { confirm } = useAsyncDialog(); +const $$ = useCallback($$$, [confirm]); +``` + +For 12 occurrences it runs only ~60ms + +```sh +✖ 12 problems (0 errors, 12 warnings) + +Rule | Time (ms) | Relative +:----------------|----------:|--------: +@codeque/warning | 64.425 | 100.0% +``` + +You should strive to eliminate that pattern, so assuming you've fixed all places, the rule takes ~30ms + +```sh +0 problems (0 errors, 0 warnings) + +Rule | Time (ms) | Relative +:----------------|----------:|--------: +@codeque/warning | 28.286 | 100.0% +``` + +And for new introduction of the pattern while you code, it will be captures in ~30ms + +```sh +✖ 1 problems (0 errors, 1 warning) + +Rule | Time (ms) | Relative +:----------------|----------:|--------: +@codeque/warning | 30.553 | 100.0% +``` + +It's not much comparing to the value it gives. + +Consider how much time it would take to implement such rule with standard approach. No one has budget for that and instead time will be spend on fixing bugs. + +#### Capturing restricted imports + +Here is the comparison of `no-restricted-imports` ESLint rule with CodeQue rule. + +Both restricts importing `useCallback` from `react`. + +CodeQue query is using `include` mode. + +```ts +import { useCallback } from 'react' ``` -> Use TIMING=all to list all ESLint rules. \ No newline at end of file +Execution takes similar amount of time for both approaches. + +```sh +✖ 648 problems (0 errors, 648 warnings) + +Rule | Time (ms) | Relative +:---------------------|----------:|--------: +@codeque/warning | 38.700 | 56.1% +no-restricted-imports | 30.239 | 43.9% +``` + +#### Restricting console usage + +The performance result of ESLint `no-console` rule comparing to CodeQue `console.$$()` query on TypeScript codebase with ~4000 source files. + +CodeQue is 4 times slower for this use case, but it's still only ~80ms for ~4000 source files! + +```sh +✖ 404 problems (0 errors, 404 warnings) + +Rule | Time (ms) | Relative +:----------------|----------:|--------: +@codeque/warning | 81.267 | 79.1% +no-console | 21.482 | 20.9% +``` + +## Telemetry + +Plugin collects completely anonymous telemetry that helps me get insights about usage. + +It's implemented using `applicationinsights` and you can easily opt-out. + +Learn more about [telemetry](https://codeque.co/docs/telemetry#es-lint-plugin) + + + +## Support and feedback + +Feel free to use [Github Issues](https://github.com/codeque-co/codeque/issues) +to + +- ask for help with writing a query +- report a bug or doubt +- suggest feature or improvement diff --git a/packages/eslint/__tests__/JavaScript/javaScriptParsers.test.ts b/packages/eslint/__tests__/JavaScript/javaScriptParsers.test.ts new file mode 100644 index 0000000..04c581a --- /dev/null +++ b/packages/eslint/__tests__/JavaScript/javaScriptParsers.test.ts @@ -0,0 +1,436 @@ +import { rules } from '../../src/index' +import { RuleOption } from '../../src/types' + +const codeWithoutMatches = ` + // Different order of keys + const obj = { + prop10: 'World', + prop8: [ + { value: 'C', id: 3 }, + { id: 4, value: 'D' }, + ], + } + + { + const a = 1 === 0 + class A {} + } + + const Comp = () => (
not-test
); + + \`pre-not\${value} + + post\` +` + +const codeWithMatches = ` + const obj = { + prop1: 'Hello', + prop2: 42, + prop3: true, + prop4: [ + { name: 'John', age: 30 }, + { name: 'Jane', age: 25 }, + ], + prop5: { + prop6: [ + { id: 1, value: 'A' }, + { id: 2, value: 'B' }, + ], + prop7: { + prop8: [ + { id: 3, value: 'C' }, + { id: 4, value: 'D' }, + ], + prop10: 'World', + }, + }, + } + + { + const a = 1 === 0 + fn(obj) + fn(newobj) + class A {} + } + + const result = obj.key.fn() + + functionCall(obj.key.fn()) + + obj.prop1.prop2.prop3 + + const Comp = () => (
test
); + + \`pre\${value} + + post\` + +` + +describe('JavaScript code samples', () => { + describe('nested exact query', () => { + const options: [Array] = [ + [ + { + query: `({ + prop8: [ + { id: 3, value: 'C' }, + { id: 4, value: 'D' }, + ], + prop10: 'World', + })`, + mode: 'exact', + }, + ], + ] + + global.ruleTester.run('@codeque', rules.error, { + valid: [ + { + code: codeWithoutMatches, + options, + }, + ], + invalid: [ + { + code: codeWithMatches, + options, + errors: [ + { + message: 'Restricted code pattern', + line: 15, + column: 14, + endLine: 21, + endColumn: 8, + }, + ], + }, + ], + }) + }) + + describe('nested include query', () => { + const options: [Array] = [ + [ + { + query: `({ + prop3: true, + prop4: [ + { age: 25 }, + ], + prop5: { + prop7: { + prop8: [ + { id: 3, }, + ], + }, + }, + })`, + mode: 'include', + }, + ], + ] + + global.ruleTester.run('@codeque', rules.error, { + valid: [ + { + code: codeWithoutMatches, + options, + }, + ], + invalid: [ + { + code: codeWithMatches, + options, + errors: [ + { + message: 'Restricted code pattern', + line: 2, + column: 15, + endLine: 23, + endColumn: 4, + }, + ], + }, + ], + }) + }) + + describe('nested include-with-order query', () => { + const options: [Array] = [ + [ + { + query: `({ + prop8: [ + { id: 3, value: 'C' }, + { id: 4 }, + ], + prop10: 'World', + })`, + mode: 'include-with-order', + }, + ], + ] + + global.ruleTester.run('@codeque', rules.error, { + valid: [ + { + code: codeWithoutMatches, + options, + }, + ], + invalid: [ + { + code: codeWithMatches, + options, + errors: [ + { + message: 'Restricted code pattern', + line: 15, + column: 14, + endLine: 21, + endColumn: 8, + }, + ], + }, + ], + }) + }) + + describe('multiline include query', () => { + const options: [Array] = [ + [ + { + query: ` + const obj = {} + + { + fn(obj) + } + `, + mode: 'include', + }, + ], + ] + + global.ruleTester.run('@codeque', rules.error, { + valid: [ + { + code: codeWithoutMatches, + options, + }, + ], + invalid: [ + { + code: codeWithMatches, + options, + errors: [ + { + message: 'Restricted code pattern', + line: 2, + column: 3, + endLine: 30, + endColumn: 4, + }, + ], + }, + ], + }) + }) + + describe('multiline include query with identifier alias', () => { + const options: [Array] = [ + [ + { + query: ` + const $$_ref1 = {} + + { + fn(new$$_ref1_) + } + `, + mode: 'include', + }, + ], + ] + + global.ruleTester.run('@codeque', rules.error, { + valid: [ + { + code: codeWithoutMatches, + options, + }, + ], + invalid: [ + { + code: codeWithMatches, + options, + errors: [ + { + message: 'Restricted code pattern', + line: 2, + column: 3, + endLine: 30, + endColumn: 4, + }, + ], + }, + ], + }) + }) + + describe('multiline include query with nodes tree wildcard alias', () => { + const options: [Array] = [ + [ + { + query: ` + const result = $$$_chainExp + + functionCall($$$_chainExp) + `, + mode: 'include', + }, + ], + ] + + global.ruleTester.run('@codeque', rules.error, { + valid: [ + { + code: codeWithoutMatches, + options, + }, + ], + invalid: [ + { + code: codeWithMatches, + options, + errors: [ + { + message: 'Restricted code pattern', + line: 32, + column: 3, + endLine: 34, + endColumn: 29, + }, + ], + }, + ], + }) + }) + + describe('query with JSX', () => { + const options: [Array] = [ + [ + { + query: ` + const Comp = () =>
test
+ `, + mode: 'include', + }, + ], + ] + + global.ruleTester.run('@codeque', rules.error, { + valid: [ + { + code: codeWithoutMatches, + options, + }, + ], + invalid: [ + { + code: codeWithMatches, + options, + errors: [ + { + message: 'Restricted code pattern', + line: 38, + column: 3, + endLine: 38, + endColumn: 40, + }, + ], + }, + ], + }) + }) + + describe('query with multiline template string', () => { + const options: [Array] = [ + [ + { + query: `\`pre + \${value} + + post\` + `, + mode: 'include', + }, + ], + ] + + global.ruleTester.run('@codeque', rules.error, { + valid: [ + { + code: codeWithoutMatches, + options, + }, + ], + invalid: [ + { + code: codeWithMatches, + options, + errors: [ + { + message: 'Restricted code pattern', + line: 40, + column: 3, + endLine: 42, + endColumn: 8, + }, + ], + }, + ], + }) + }) + + if (!(global.ruleTester as any).testerConfig.parser.includes('esprima')) { + describe('optional chaining query that matches normal chaining', () => { + const options: [Array] = [ + [ + { + query: ` + obj?.prop1?.prop2?.prop3 + `, + mode: 'include', + }, + ], + ] + + global.ruleTester.run('@codeque', rules.error, { + valid: [ + { + code: codeWithoutMatches, + options, + }, + ], + invalid: [ + { + code: codeWithMatches, + options, + errors: [ + { + message: 'Restricted code pattern', + line: 36, + column: 3, + endLine: 36, + endColumn: 24, + }, + ], + }, + ], + }) + }) + } +}) diff --git a/packages/eslint/__tests__/TypeScript/typeScriptParsers.test.ts b/packages/eslint/__tests__/TypeScript/typeScriptParsers.test.ts new file mode 100644 index 0000000..e6226da --- /dev/null +++ b/packages/eslint/__tests__/TypeScript/typeScriptParsers.test.ts @@ -0,0 +1,156 @@ +import { rules } from '../../src/index' +import { RuleOption } from '../../src/types' + +const codeWithoutMatches = ` + +` + +const codeWithMatches = ` + const a: SomeType = ''; + let b: SomeType = ''; + let c: OtherType & { key: SomeType } = ''; + + interface B { + key: string; + key_2?: number; + } + + const getInitialValues = ( + assignment: AssignmentPopulated, + ): AssignmentFormValues => { + if (!assignment) { + return undefined; + } + }; +` + +describe('TypeScript code samples', () => { + describe('should match optional interface filed in include mode with query without optional interface', () => { + const options: [Array] = [ + [ + { + query: ` + interface B { + key_2: number; + } + `, + mode: 'include', + }, + ], + ] + + global.ruleTester.run('@codeque', rules.error, { + valid: [ + { + code: codeWithoutMatches, + options, + }, + ], + invalid: [ + { + code: codeWithMatches, + options, + errors: [ + { + message: 'Restricted code pattern', + line: 6, + column: 3, + endLine: 9, + endColumn: 4, + }, + ], + }, + ], + }) + }) + + describe('Should match type in variable type annotation', () => { + const options: [Array] = [ + [ + { + query: `SomeType`, + mode: 'include', + }, + ], + ] + + global.ruleTester.run('@codeque', rules.error, { + valid: [ + { + code: codeWithoutMatches, + options, + }, + ], + invalid: [ + { + code: codeWithMatches, + options, + errors: [ + { + message: 'Restricted code pattern', + line: 2, + column: 12, + endLine: 2, + endColumn: 20, + }, + { + message: 'Restricted code pattern', + line: 3, + column: 10, + endLine: 3, + endColumn: 18, + }, + { + message: 'Restricted code pattern', + line: 4, + column: 29, + endLine: 4, + endColumn: 37, + }, + ], + }, + ], + }) + }) + + describe('should match function declaration with types by query without types', () => { + const options: [Array] = [ + [ + { + query: ` + const getInitialValues = ( + assignment, + ) => { + + }; + `, + mode: 'include', + }, + ], + ] + + global.ruleTester.run('@codeque', rules.error, { + valid: [ + { + code: codeWithoutMatches, + options, + }, + ], + invalid: [ + { + code: codeWithMatches, + options, + errors: [ + { + message: 'Restricted code pattern', + line: 11, + column: 3, + endLine: 17, + endColumn: 5, + }, + ], + }, + ], + }) + }) +}) diff --git a/packages/eslint/__tests__/common/options.test.ts b/packages/eslint/__tests__/common/options.test.ts new file mode 100644 index 0000000..a04cb12 --- /dev/null +++ b/packages/eslint/__tests__/common/options.test.ts @@ -0,0 +1,332 @@ +import { rules } from '../../src/index' +import { RuleOption } from '../../src/types' + +describe('Rule options tests', () => { + const rootPath = process.cwd() + + describe('should run config with default error messages', () => { + const options: [Array] = [[{ query: `invalidIdentifier` }]] + + global.ruleTester.run('@codeque', rules.error, { + valid: [{ code: 'validIdentifier', options }], + invalid: [ + { + code: 'invalidIdentifier', + options, + errors: ['Restricted code pattern'], + }, + ], + }) + }) + + describe('should run config with multiple queries', () => { + const options: [Array] = [ + [{ query: `invalidIdentifier` }, { query: 'otherInvalidIdentifier' }], + ] + + global.ruleTester.run('@codeque', rules.error, { + valid: [{ code: 'validIdentifier', options }], + invalid: [ + { + code: 'invalidIdentifier;otherInvalidIdentifier', + options, + errors: ['Restricted code pattern', 'Restricted code pattern'], + }, + ], + }) + }) + + describe('should run config with custom error messages', () => { + const options: [Array] = [ + [ + { query: `invalidIdentifier` }, + { query: 'otherInvalidIdentifier', message: "Don't use this code" }, + ], + ] + + global.ruleTester.run('@codeque', rules.error, { + valid: [{ code: 'validIdentifier', options }], + invalid: [ + { + code: 'invalidIdentifier;otherInvalidIdentifier', + options, + errors: ['Restricted code pattern', "Don't use this code"], + }, + ], + }) + }) + + describe('should run config with include file paths filter', () => { + const options: [Array] = [ + [{ query: `invalidIdentifier`, includeFiles: ['**/includeDir/file.*'] }], + ] + + global.ruleTester.run('@codeque', rules.error, { + valid: [ + { + code: 'invalidIdentifier', + filename: rootPath + '/path/to/dir/file.js', + options, + }, + ], + invalid: [ + { + code: 'invalidIdentifier', + filename: rootPath + '/path/to/includeDir/file.js', + options, + errors: ['Restricted code pattern'], + }, + ], + }) + }) + + describe('should run config with exclude file paths filter', () => { + const options: [Array] = [ + [{ query: `invalidIdentifier`, excludeFiles: ['**/excludeDir/file.*'] }], + ] + + global.ruleTester.run('@codeque', rules.error, { + valid: [ + { + code: 'invalidIdentifier', + filename: rootPath + '/path/to/excludeDir/file.js', + options, + }, + ], + invalid: [ + { + code: 'invalidIdentifier', + filename: rootPath + '/path/to/dir/file.js', + options, + errors: ['Restricted code pattern'], + }, + ], + }) + }) + + describe('should run config with include and exclude file paths filter', () => { + const options: [Array] = [ + [ + { + query: `invalidIdentifier`, + excludeFiles: ['**/excludeDir/**/file.*'], + includeFiles: ['**/includeDir/**/file.*'], + }, + ], + ] + + global.ruleTester.run('@codeque', rules.error, { + valid: [ + { + code: 'invalidIdentifier', + filename: rootPath + '/includeDir/excludeDir/file.js', + options, + }, + { + code: 'invalidIdentifier', + filename: rootPath + '/otherDir/excludeDir/file.js', + options, + }, + ], + invalid: [ + { + code: 'invalidIdentifier', + filename: rootPath + '/includeDir/otherDir/file.js', + options, + errors: ['Restricted code pattern'], + }, + ], + }) + }) + + describe('should run config with queries with different search modes', () => { + describe('include mode', () => { + const options: [Array] = [ + [ + { + query: `({ key: 'value' })`, + mode: 'include', + }, + { + query: `({ key: 'value' })`, + // assert default include mode + }, + ], + ] + + global.ruleTester.run('@codeque', rules.error, { + valid: [ + { + code: `const obj = { kkk: 'value' }`, + options, + }, + ], + invalid: [ + { + code: `const obj = { key: 'value', key2: 0 }`, + options, + errors: ['Restricted code pattern', 'Restricted code pattern'], + }, + ], + }) + }) + + describe('exact mode', () => { + const options: [Array] = [ + [ + { + query: `({ key: 'value' })`, + mode: 'exact', + }, + ], + ] + + global.ruleTester.run('@codeque', rules.error, { + valid: [ + { + code: `const obj = { kkk: 'value' }`, + options, + }, + { + code: `const obj = { key: 'value', key2: 0 }`, + options, + }, + ], + invalid: [ + { + code: `const obj = { key: 'value' }`, + options, + errors: ['Restricted code pattern'], + }, + ], + }) + }) + + describe('include-with-order mode', () => { + const options: [Array] = [ + [ + { + query: `({ key1: 'value', key2: 'value2' })`, + mode: 'include-with-order', + }, + ], + ] + + global.ruleTester.run('@codeque', rules.error, { + valid: [ + { + code: `const obj = { key1: 'value' }`, + options, + }, + { + code: `const obj = { key2: 'value2', key1: 'value' }`, + options, + }, + ], + invalid: [ + { + code: `const obj = { key1: 'value', key2: 'value2' }`, + options, + errors: ['Restricted code pattern'], + }, + ], + }) + }) + + describe('text (unsupported)', () => { + const options: [Array] = [ + [ + { + query: `key1: 'value'`, + mode: 'text', + }, + ], + ] + + expect(() => { + // @ts-ignore we don't need all props for this test + rules.error.create({ + options: options, + // @ts-ignore we don't need all props for this test + getSourceCode: jest.fn(() => ({ text: '' })), + getPhysicalFilename: jest.fn(), + getCwd: jest.fn(), + parserPath: + 'project/node_modules/@typescript-eslint/parser/dist/index.js', + }) + }).toThrowError('"Text" search mode is not supported.') + }) + }) + + describe('should run config with case sensitive and insensitive queries', () => { + describe('case sensitive', () => { + const options: [Array] = [ + [ + { + query: `invalidIdentifier`, + mode: 'include', + caseInsensitive: false, + }, + ], + ] + + global.ruleTester.run('@codeque', rules.error, { + valid: [ + { + code: `validIdentifier`, + options, + }, + { + code: `invalididentifier`, + options, + }, + ], + invalid: [ + { + code: `invalidIdentifier`, + options, + errors: ['Restricted code pattern'], + }, + ], + }) + }) + + describe('case insensitive', () => { + const options: [Array] = [ + [ + { + query: `invalidIdentifier`, + mode: 'include', + caseInsensitive: true, + }, + { + query: `invalidIdentifier`, + mode: 'include', + // assert default case insensitive + }, + ], + ] + + global.ruleTester.run('@codeque', rules.error, { + valid: [ + { + code: `validIdentifier`, + options, + }, + ], + invalid: [ + { + code: `invalididentifier`, + options, + errors: ['Restricted code pattern', 'Restricted code pattern'], + }, + { + code: `invalidIdentifier`, + options, + errors: ['Restricted code pattern', 'Restricted code pattern'], + }, + ], + }) + }) + }) +}) diff --git a/packages/eslint/__tests__/utils.test.ts b/packages/eslint/__tests__/common/utils.test.ts similarity index 88% rename from packages/eslint/__tests__/utils.test.ts rename to packages/eslint/__tests__/common/utils.test.ts index f335825..a37c714 100644 --- a/packages/eslint/__tests__/utils.test.ts +++ b/packages/eslint/__tests__/common/utils.test.ts @@ -1,4 +1,4 @@ -import { extractParserNameFromResolvedPath } from '../src/utils' +import { extractParserNameFromResolvedPath } from '../../src/utils' it('should extract parser name from file path', () => { const parserWithOrg = '@typescript-eslint/parser' diff --git a/packages/eslint/declarations.d.ts b/packages/eslint/declarations.d.ts new file mode 100644 index 0000000..b1d2b07 --- /dev/null +++ b/packages/eslint/declarations.d.ts @@ -0,0 +1,7 @@ +import { RuleTester } from 'eslint' +declare global { + //eslint-disable-next-line no-var + var ruleTester: RuleTester +} + +declare module 'is-ci' diff --git a/packages/eslint/jest.config.js b/packages/eslint/jest.config.js index 58ef380..8a6f932 100644 --- a/packages/eslint/jest.config.js +++ b/packages/eslint/jest.config.js @@ -1,25 +1,74 @@ -const { pathsToModuleNameMapper } = require('ts-jest') -const fs = require('fs') - -const tsConfig = JSON.parse( - fs - .readFileSync(__dirname + '/tsconfig.json') - .toString() - .replace(/^(\s)*\/\//gm, '') - .replace(/\/\*.+?\*\//gm, ''), -) - -module.exports = { +const sharedConfig = { preset: 'ts-jest', testEnvironment: 'node', - moduleNameMapper: pathsToModuleNameMapper(tsConfig.compilerOptions.paths, { - prefix: '', - }), + setupFiles: ['/jest/jest.shared.setup.ts'], +} + +module.exports = { testPathIgnorePatterns: [ '__fixtures__', '__fixturesOther__', 'ts-dist', 'utils.ts', ], - setupFiles: ['./jest.setup.js'], + projects: [ + { + displayName: { name: 'common', color: 'white' }, + ...sharedConfig, + setupFiles: [ + ...sharedConfig.setupFiles, + '/jest/jest.common.setup.ts', + ], + testMatch: ['/__tests__/common/**/*.test.ts'], + }, + { + displayName: { name: 'typescript-eslint-parser', color: 'magenta' }, + ...sharedConfig, + setupFiles: [ + ...sharedConfig.setupFiles, + '/jest/jest.typescript-eslint-parser.setup.ts', + ], + testMatch: [ + '/__tests__/JavaScript/**/*.test.ts', + '/__tests__/TypeScript/**/*.test.ts', + '/__tests__/common/options.test.ts', + ], + }, + { + displayName: { name: 'babel-eslint-parser', color: 'yellow' }, + ...sharedConfig, + setupFiles: [ + ...sharedConfig.setupFiles, + '/jest/jest.babel-eslint-parser.setup.ts', + ], + testMatch: [ + '/__tests__/JavaScript/**/*.test.ts', + '/__tests__/common/options.test.ts', + ], + }, + { + displayName: { name: 'esprima', color: 'gray' }, + ...sharedConfig, + setupFiles: [ + ...sharedConfig.setupFiles, + '/jest/jest.esprima.setup.ts', + ], + testMatch: [ + '/__tests__/JavaScript/**/*.test.ts', + '/__tests__/common/options.test.ts', + ], + }, + { + displayName: { name: 'espree', color: 'blue' }, + ...sharedConfig, + setupFiles: [ + ...sharedConfig.setupFiles, + '/jest/jest.espree.setup.ts', + ], + testMatch: [ + '/__tests__/JavaScript/**/*.test.ts', + '/__tests__/common/options.test.ts', + ], + }, + ], } diff --git a/packages/eslint/jest.setup.js b/packages/eslint/jest.setup.js deleted file mode 100644 index b1e2bd6..0000000 --- a/packages/eslint/jest.setup.js +++ /dev/null @@ -1 +0,0 @@ -process.env.NODE_ENV = 'test' diff --git a/packages/eslint/jest/jest.babel-eslint-parser.setup.ts b/packages/eslint/jest/jest.babel-eslint-parser.setup.ts new file mode 100644 index 0000000..3b62c93 --- /dev/null +++ b/packages/eslint/jest/jest.babel-eslint-parser.setup.ts @@ -0,0 +1,17 @@ +import { RuleTester } from 'eslint' + +global.ruleTester = new RuleTester({ + parser: require.resolve('@babel/eslint-parser'), + plugins: ['@codeque'], + parserOptions: { + requireConfigFile: false, + allowImportExportEverywhere: true, + babelOptions: { + babelrc: false, + configFile: false, + parserOpts: { + plugins: ['jsx'], + }, + }, + }, +}) diff --git a/packages/eslint/jest/jest.common.setup.ts b/packages/eslint/jest/jest.common.setup.ts new file mode 100644 index 0000000..c2f3b6e --- /dev/null +++ b/packages/eslint/jest/jest.common.setup.ts @@ -0,0 +1,10 @@ +import { RuleTester } from 'eslint' + +global.ruleTester = new RuleTester({ + parser: require.resolve('@typescript-eslint/parser'), + plugins: ['@codeque'], + parserOptions: { + ecmaVersion: 2020, + sourceType: 'module', + }, +}) diff --git a/packages/eslint/jest/jest.espree.setup.ts b/packages/eslint/jest/jest.espree.setup.ts new file mode 100644 index 0000000..379d237 --- /dev/null +++ b/packages/eslint/jest/jest.espree.setup.ts @@ -0,0 +1,14 @@ +import { RuleTester } from 'eslint' + +global.ruleTester = new RuleTester({ + parser: require.resolve('espree'), + plugins: ['@codeque'], + parserOptions: { + ecmaVersion: 'latest', + ecmaFeatures: { + jsx: true, + globalReturn: true, + impliedStrict: false, + }, + }, +}) diff --git a/packages/eslint/jest/jest.esprima.setup.ts b/packages/eslint/jest/jest.esprima.setup.ts new file mode 100644 index 0000000..15efa8c --- /dev/null +++ b/packages/eslint/jest/jest.esprima.setup.ts @@ -0,0 +1,9 @@ +import { RuleTester } from 'eslint' + +global.ruleTester = new RuleTester({ + parser: require.resolve('esprima'), + plugins: ['@codeque'], + parserOptions: { + jsx: true, + }, +}) diff --git a/packages/eslint/jest/jest.shared.setup.ts b/packages/eslint/jest/jest.shared.setup.ts new file mode 100644 index 0000000..d95e487 --- /dev/null +++ b/packages/eslint/jest/jest.shared.setup.ts @@ -0,0 +1,16 @@ +process.env.NODE_ENV = 'test' + +global.performance = { + now: jest.fn().mockReturnValue(1), +} as unknown as Performance + +global.console = { + ...console, + warn: (...inputs) => { + if (typeof inputs[0] === 'string' && inputs[0].includes('Browserslist')) { + return + } else { + return console.log('console.warn', ...inputs) + } + }, +} diff --git a/packages/eslint/jest/jest.typescript-eslint-parser.setup.ts b/packages/eslint/jest/jest.typescript-eslint-parser.setup.ts new file mode 100644 index 0000000..5f4794b --- /dev/null +++ b/packages/eslint/jest/jest.typescript-eslint-parser.setup.ts @@ -0,0 +1,14 @@ +import { RuleTester } from 'eslint' + +global.ruleTester = new RuleTester({ + parser: require.resolve('@typescript-eslint/parser'), + plugins: ['@codeque'], + parserOptions: { + ecmaVersion: 2020, + sourceType: 'module', + ecmaFeatures: { + globalReturn: true, + jsx: true, + }, + }, +}) diff --git a/packages/eslint/package.json b/packages/eslint/package.json index 4f9c7ae..1a2bacc 100644 --- a/packages/eslint/package.json +++ b/packages/eslint/package.json @@ -1,7 +1,7 @@ { "name": "@codeque/eslint-plugin", - "version": "0.0.0-beta.0", - "description": "Create custom ESLint rules based on code sample(s). Utilizing CodeQue - structural code search engine.", + "version": "0.1.2", + "description": "Create custom ESLint rules based on code samples. Utilizing CodeQue - structural code search engine.", "author": "Jakub Mazurek (@jayu) ", "license": "Sustainable Use License", "engines": { @@ -21,14 +21,21 @@ "url": "https://github.com/codeque-co/codeque" }, "devDependencies": { + "@babel/eslint-parser": "^7.21.8", "@types/jest": "^27.0.3", + "@typescript-eslint/parser": "^5.53.0", "eslint": "^8.34.0", + "espree": "^9.5.2", + "esprima": "^4.0.1", "jest": "^27.4.4", "release-it": "^15.0.0", "ts-jest": "^27.1.1" }, "dependencies": { - "@codeque/core": "^0.4.0" + "@codeque/core": "^0.6.1", + "applicationinsights": "^2.7.0", + "is-ci": "^3.0.1", + "node-machine-id": "^1.1.12" }, "peerDependencies": { "eslint": "^8.34.0" @@ -42,7 +49,8 @@ "lint": "eslint --ext .js,.ts src", "lint:fix": "yarn lint --fix", "checks": "yarn lint && yarn typecheck && yarn test:circular && yarn test", - "release": "release-it" + "release": "release-it", + "postinstall": "node ./dist/scripts/postinstall.js" }, "keywords": [ "typescript", diff --git a/packages/eslint/readme-media/console-log.gif b/packages/eslint/readme-media/console-log.gif new file mode 100644 index 0000000..8cab2f2 Binary files /dev/null and b/packages/eslint/readme-media/console-log.gif differ diff --git a/packages/eslint/readme-media/disabled-prop.gif b/packages/eslint/readme-media/disabled-prop.gif new file mode 100644 index 0000000..832d2cf Binary files /dev/null and b/packages/eslint/readme-media/disabled-prop.gif differ diff --git a/packages/eslint/readme-media/error-example.png b/packages/eslint/readme-media/error-example.png deleted file mode 100644 index eec7d50..0000000 Binary files a/packages/eslint/readme-media/error-example.png and /dev/null differ diff --git a/packages/eslint/readme-media/getting-started.gif b/packages/eslint/readme-media/getting-started.gif new file mode 100644 index 0000000..d13a08f Binary files /dev/null and b/packages/eslint/readme-media/getting-started.gif differ diff --git a/packages/eslint/readme-media/object-literals.gif b/packages/eslint/readme-media/object-literals.gif new file mode 100644 index 0000000..2d67661 Binary files /dev/null and b/packages/eslint/readme-media/object-literals.gif differ diff --git a/packages/eslint/readme-media/unstable-hook.gif b/packages/eslint/readme-media/unstable-hook.gif new file mode 100644 index 0000000..3333671 Binary files /dev/null and b/packages/eslint/readme-media/unstable-hook.gif differ diff --git a/packages/eslint/readme-media/warning-example.png b/packages/eslint/readme-media/warning-example.png deleted file mode 100644 index adebe3d..0000000 Binary files a/packages/eslint/readme-media/warning-example.png and /dev/null differ diff --git a/packages/eslint/src/index.ts b/packages/eslint/src/index.ts index 4bfd65b..0a4b9be 100644 --- a/packages/eslint/src/index.ts +++ b/packages/eslint/src/index.ts @@ -1,6 +1,6 @@ import { createLintCode } from './lintCode' -const rules = { +export const rules = { error: createLintCode('problem'), warning: createLintCode('suggestion'), } diff --git a/packages/eslint/src/lintCode.ts b/packages/eslint/src/lintCode.ts index a3d75bc..9cb4385 100644 --- a/packages/eslint/src/lintCode.ts +++ b/packages/eslint/src/lintCode.ts @@ -1,6 +1,5 @@ import { Rule } from 'eslint' import { - Mode, parseQueries, __internal, NotNullParsedQuery, @@ -13,13 +12,16 @@ import { ParsedQueryWithSettings, VisitorsSearchArrayMap, VisitorsSearchMap, + RuleOption, } from './types' + import { formatQueryParseErrors, createMultipleSearchFunctionsExecutor, assertCompatibleParser, parserNamesMappingsToCodeQueInternal, } from './utils' +import { telemetryDisabled, createTelemetryInstance } from './telemetry' const queriesCache = {} as Record @@ -31,27 +33,37 @@ let filteringFilePathsTime = 0 const searchTimeForQueries = {} as Record process.on('beforeExit', () => { + const print = console.log const shouldPrintMetric = process.env.CODEQUE_DEBUG === 'true' if (shouldPrintMetric) { - console.log('\nCodeQue debug metrics:\n') - console.log('preparationTime', preparationTime) - console.log('shallowSearchTime', shallowSearchTime) - console.log('preparingVisitorsTime', preparingVisitorsTime) - console.log('preparingQueriesTime', preparingQueriesTime) - console.log('filteringFilePathsTime', filteringFilePathsTime) - console.log('searchTimeForQueries', searchTimeForQueries) - console.log('') + print('\nCodeQue debug metrics:\n') + print('preparationTime', preparationTime) + print('shallowSearchTime', shallowSearchTime) + print('preparingVisitorsTime', preparingVisitorsTime) + print('preparingQueriesTime', preparingQueriesTime) + print('filteringFilePathsTime', filteringFilePathsTime) + print('searchTimeForQueries', searchTimeForQueries) + print('') + print('Telemetry is', telemetryDisabled ? 'disabled' : 'enabled') } }) -export const createLintCode = (type: Rule.RuleMetaData['type']) => ({ +const telemetryReported = { + problem: false, + suggestion: false, + layout: false, +} + +export const createLintCode = ( + type: NonNullable, +) => ({ meta: { type: type, docs: { description: 'Lint anything based on code sample(s).', }, - fixable: 'code', + fixable: 'code' as const, schema: [ { type: 'array', @@ -88,19 +100,11 @@ export const createLintCode = (type: Rule.RuleMetaData['type']) => ({ ], }, create: function (context: Rule.RuleContext) { + const telemetry = createTelemetryInstance() const prepStart = performance.now() const parser = assertCompatibleParser(context.parserPath) - const settings = context.options[0] as - | Array<{ - mode?: Mode - query?: string - message?: string - caseInsensitive?: boolean - includeFiles?: string[] - excludeFiles?: string[] - }> - | undefined + const settings = context.options[0] as Array | undefined if (!settings || settings.length === 0) { return {} @@ -112,11 +116,16 @@ export const createLintCode = (type: Rule.RuleMetaData['type']) => ({ const defaultCaseInsensitive = true const queryCodes = settings.map(({ query }) => query) + const searchModes = settings.map(({ mode }) => mode).filter(Boolean) if (queryCodes.includes(undefined) || queryCodes.includes(null as any)) { throw new Error('Each setting has to have at least query defined.') } + if (searchModes.includes('text')) { + throw new Error('"Text" search mode is not supported.') + } + const parserSettings = __internal.parserSettingsMap[ parserNamesMappingsToCodeQueInternal[parser] @@ -168,6 +177,7 @@ export const createLintCode = (type: Rule.RuleMetaData['type']) => ({ })) as ParsedQueryWithSettings[] preparingQueriesTime += performance.now() - startPreparingQueries + const startFilteringFilePaths = performance.now() const queriesWithSettingsMatchedFilePath = parsedQueriesWithSettings.filter( ({ includeFiles, excludeFiles }) => { @@ -232,14 +242,20 @@ export const createLintCode = (type: Rule.RuleMetaData['type']) => ({ searchTimeForQueries[queryCode] = 0 } - const match = __internal.validateMatch( + const matchContext = __internal.createMatchContext() + const { match } = __internal.validateMatch( node, queryWithSettings.parsedQuery.queryNode, searchOptions, + matchContext, ) if (match) { - let matchData = __internal.getMatchFromNode(node, parserSettings) + let matchData = __internal.getMatchFromNode( + node, + parserSettings, + matchContext.getAllAliases(), + ) if (isMultistatement) { /** @@ -310,6 +326,26 @@ export const createLintCode = (type: Rule.RuleMetaData['type']) => ({ preparingVisitorsTime += performance.now() - preparingVisitorsStart preparationTime += performance.now() - prepStart - return visitors + if (!telemetryReported[type]) { + telemetry.reportConfig({ + ruleType: type === 'problem' ? 'error' : 'warning', + queriesCount: queryCodes.length, + mode_include: + searchModes.includes('include') || + settings.some(({ mode }) => mode === undefined), + mode_exact: searchModes.includes('exact'), + mode_include_w_order: searchModes.includes('include-with-order'), + fileFilters_include: settings.some( + ({ includeFiles }) => includeFiles !== undefined, + ), + fileFilters_exclude: settings.some( + ({ excludeFiles }) => excludeFiles !== undefined, + ), + }) + + telemetryReported[type] = true + } + + return visitors as unknown as Record void> }, }) diff --git a/packages/eslint/src/scripts/postinstall.ts b/packages/eslint/src/scripts/postinstall.ts new file mode 100644 index 0000000..6cb40b2 --- /dev/null +++ b/packages/eslint/src/scripts/postinstall.ts @@ -0,0 +1,7 @@ +import { createTelemetryInstance } from '../telemetry' + +const isCodeQueRepo = process.cwd().match(/packages(\/|\\)eslint$/g) !== null + +if (!isCodeQueRepo) { + createTelemetryInstance().reportInstall() +} diff --git a/packages/eslint/src/telemetry.ts b/packages/eslint/src/telemetry.ts new file mode 100644 index 0000000..e3c75e0 --- /dev/null +++ b/packages/eslint/src/telemetry.ts @@ -0,0 +1,118 @@ +import { machineIdSync } from 'node-machine-id' +import fs from 'fs' +import { createHash } from 'crypto' +import { defaultClient, setup, dispose } from 'applicationinsights' +//@ts-ignore +import isCI from 'is-ci' + +type TelemetryModule = { + reportConfig: (param: { + ruleType: 'error' | 'warning' + queriesCount: number + mode_include: boolean + mode_exact: boolean + mode_include_w_order: boolean + fileFilters_include: boolean + fileFilters_exclude: boolean + }) => void + reportInstall: () => void +} + +const disabledTelemetryInstance: TelemetryModule = { + reportConfig: () => undefined, + reportInstall: () => undefined, +} + +function hash(guid: string): string { + return createHash('sha256').update(guid).digest('hex') +} + +const getProjectId = () => { + const path = process.cwd() + '/package.json' + + try { + const packageJSON = fs.readFileSync(path).toString() + + const parsedPackageJSON = JSON.parse(packageJSON) + + return hash(parsedPackageJSON.name) + } catch (e) { + return null + } +} + +export const telemetryDisabled = + process.env.CQ_ESLINT_TELEMETRY_DISABLE === 'true' || + process.env.NODE_ENV === 'test' + +export const createTelemetryInstance = (): TelemetryModule => { + if (telemetryDisabled) { + return disabledTelemetryInstance + } + + setup( + 'InstrumentationKey=8f838c47-7173-4f6c-851a-b012d45d9ad8;IngestionEndpoint=https://eastus-8.in.applicationinsights.azure.com/;LiveEndpoint=https://eastus.livediagnostics.monitor.azure.com/', + ) + .setAutoDependencyCorrelation(false) + .setAutoCollectRequests(false) + .setAutoCollectPerformance(false, false) + .setAutoCollectExceptions(false) + .setAutoCollectDependencies(false) + .setAutoCollectConsole(false, false) + .setUseDiskRetryCaching(false) + .setAutoCollectPreAggregatedMetrics(false) + .setSendLiveMetrics(false) + .setAutoCollectHeartbeat(false) + .setAutoCollectIncomingRequestAzureFunctions(false) + .setInternalLogging(false, false) + .enableWebInstrumentation(false) + + const telemetryClient = defaultClient + + const machineId = machineIdSync() + const projectId = getProjectId() + const arch = process.arch + const platform = process.platform + const nodeVersion = process.version + + const commonProps = { + machineId, + projectId, + arch, + platform, + nodeVersion, + isCI, + } + + return { + reportConfig: (params) => { + try { + telemetryClient.trackEvent({ + name: 'eslint:config', + properties: { + ...commonProps, + ...params, + }, + }) + + telemetryClient.flush() + dispose() + } catch (e) { + console.error('Failed to report telemetry for config') + } + }, + reportInstall: () => { + try { + telemetryClient.trackEvent({ + name: 'eslint:install', + properties: commonProps, + }) + + telemetryClient.flush() + dispose() + } catch (e) { + console.error('Failed to report telemetry for install') + } + }, + } +} diff --git a/packages/eslint/src/types.ts b/packages/eslint/src/types.ts index 64b9b28..a29e31c 100644 --- a/packages/eslint/src/types.ts +++ b/packages/eslint/src/types.ts @@ -1,4 +1,5 @@ import { Mode, NotNullParsedQuery, PoorNodeType } from '@codeque/core' + export type ParsedQueryWithSettings = { parsedQuery: NotNullParsedQuery mode: Mode @@ -7,6 +8,16 @@ export type ParsedQueryWithSettings = { excludeFiles: string[] includeFiles: string[] | undefined } + +export type RuleOption = { + mode?: Mode + query?: string + message?: string + caseInsensitive?: boolean + includeFiles?: string[] + excludeFiles?: string[] +} + export type SearchFn = (node: PoorNodeType) => void export type VisitorsSearchMap = Record export type VisitorsSearchArrayMap = Record> diff --git a/packages/eslint/src/utils.ts b/packages/eslint/src/utils.ts index 9589f2e..582e9a9 100644 --- a/packages/eslint/src/utils.ts +++ b/packages/eslint/src/utils.ts @@ -1,4 +1,4 @@ -import { ParsedQuery, PoorNodeType } from '@codeque/core' +import { ParsedQuery, ParserType, PoorNodeType } from '@codeque/core' import { SearchFn } from './types' export const formatQueryParseErrors = (queries: [ParsedQuery[], boolean][]) => { @@ -19,12 +19,35 @@ export const createMultipleSearchFunctionsExecutor = } const typescriptEslintParser = '@typescript-eslint/parser' as const +const babelEslintParser = '@babel/eslint-parser' as const +const esprimaParser = 'esprima' as const +const espreeParser = 'espree' as const +const eslintParser = 'eslint' as const // in fact it's espree -type SupportedParsers = typeof typescriptEslintParser -export const supportedParsers = [typescriptEslintParser] +type SupportedParsers = + | typeof typescriptEslintParser + | typeof babelEslintParser + | typeof esprimaParser + | typeof espreeParser + | typeof eslintParser -export const parserNamesMappingsToCodeQueInternal = { - [typescriptEslintParser]: 'typescript-eslint', +export const supportedParsers = [ + typescriptEslintParser, + babelEslintParser, + esprimaParser, + espreeParser, + eslintParser, +] + +export const parserNamesMappingsToCodeQueInternal: Record< + SupportedParsers, + ParserType +> = { + [typescriptEslintParser]: 'typescript-eslint-parser', + [babelEslintParser]: 'babel-eslint-parser', + [esprimaParser]: 'esprima', + [espreeParser]: 'espree', + [eslintParser]: 'espree', } as const export const extractParserNameFromResolvedPath = (pathToParser: string) => { @@ -48,7 +71,7 @@ export const assertCompatibleParser = (parserPath: string) => { if (!supportedParsers.includes(parser as any)) { throw new Error( - `\nCodeQue does not support "${parser}" parser.\nSupported parsers are:\n -${supportedParsers.join( + `\nCodeQue does not support "${parser}" parser.\nSupported parsers are:\n- ${supportedParsers.join( '\n- ', )}\nPlease open an issue to request parser support.\nVisit https://github.com/codeque-co/codeque/issues\n`, ) diff --git a/packages/eslint/tsconfig.json b/packages/eslint/tsconfig.json index 58de4f4..6560fa6 100644 --- a/packages/eslint/tsconfig.json +++ b/packages/eslint/tsconfig.json @@ -3,20 +3,12 @@ "compilerOptions": { "baseUrl": "./src", "paths": { - "/*": [ - "src/*" - ] + "/*": ["src/*"] }, "outDir": "dist", "skipLibCheck": true, "declaration": false }, - "include": ["src"], - "exclude": [ - "__tests__/**/__fixtures__", - "__tests__/**/__fixturesOther__", - "dist", - "node_modules", - "tools/*.js" - ] -} \ No newline at end of file + "include": ["src", "declarations.d.ts"], + "exclude": ["dist", "node_modules", "tools/*.js", "jest/**", "jest.config.js"] +} diff --git a/packages/tree-sitter-port/.gitignore b/packages/tree-sitter-port/.gitignore new file mode 100644 index 0000000..770eab4 --- /dev/null +++ b/packages/tree-sitter-port/.gitignore @@ -0,0 +1 @@ +input \ No newline at end of file diff --git a/packages/tree-sitter-port/LICENSE.md b/packages/tree-sitter-port/LICENSE.md new file mode 100644 index 0000000..113cf00 --- /dev/null +++ b/packages/tree-sitter-port/LICENSE.md @@ -0,0 +1,54 @@ +# Sustainable Use License + +Version 1.0 + +## Acceptance + +By using the software, you agree to all of the terms and conditions below. + +## Copyright License + +The licensor grants you a non-exclusive, royalty-free, worldwide, non-sublicensable, non-transferable license to use, copy, distribute, make available, and prepare derivative works of the software, in each case subject to the limitations below. + +## Limitations + +You may use or modify the software only for your own internal business purposes or for non-commercial or personal use. +You may distribute the software or provide it to others only if you do so free of charge for non-commercial purposes. +You may not alter, remove, or obscure any licensing, copyright, or other notices of the licensor in the software. Any use of the licensor’s trademarks is subject to applicable law. + +## Patents + +The licensor grants you a license, under any patent claims the licensor can license, or becomes able to license, to make, have made, use, sell, offer for sale, import and have imported the software, in each case subject to the limitations and conditions in this license. This license does not cover any patent claims that you cause to be infringed by modifications or additions to the software. If you or your company make any written claim that the software infringes or contributes to infringement of any patent, your patent license for the software granted under these terms ends immediately. If your company makes such a claim, your patent license ends immediately for work on behalf of your company. + +## Notices + +You must ensure that anyone who gets a copy of any part of the software from you also gets a copy of these terms. +If you modify the software, you must include in any modified copies of the software a prominent notice stating that you have modified the software. + +## No Other Rights + +These terms do not imply any licenses other than those expressly granted in these terms. + +## Termination + +If you use the software in violation of these terms, such use is not licensed, and your license will automatically terminate. If the licensor provides you with a notice of your violation, and you cease all violation of this license no later than 30 days after you receive that notice, your license will be reinstated retroactively. However, if you violate these terms after such reinstatement, any additional violation of these terms will cause your license to terminate automatically and permanently. + +## No Liability + +As far as the law allows, the software comes as is, without any warranty or condition, and the licensor will not be liable to you for any damages arising out of these terms or the use or nature of the software, under any kind of legal claim. + +## Definitions + +The “licensor” is the entity offering these terms. + +The “software” is the software the licensor makes available under these terms, including any portion of it. + +“You” refers to the individual or entity agreeing to these terms. + +“Your company” is any legal entity, sole proprietorship, or other kind of organization that you work for, plus all organizations that have control over, are under the control of, or are under common control with that organization. Control means ownership of substantially all the assets of an entity, or the power to direct its management and policies by vote, contract, or otherwise. Control can be direct or indirect. + +“Your license” is the license granted to you for the software under these terms. + +“Use” means anything you do with the software requiring your license. + +“Trademark” means trademarks, service marks, and similar rights. \ No newline at end of file diff --git a/packages/tree-sitter-port/Readme.md b/packages/tree-sitter-port/Readme.md new file mode 100644 index 0000000..a4e7495 --- /dev/null +++ b/packages/tree-sitter-port/Readme.md @@ -0,0 +1,32 @@ +# CodeQue Tree Sitter port + +This repo is set of scripts to manage building files required for Tree Sitter parsers to work with CodeQue. + +It includes: +- Fetching newest versions of parsers +- Building WASM files +- Generating node type mappings +- Copying generated files into `vscode` and `core` directories + +## Usage + +Upgrade integrated parsers versions if needed + +`yarn parsers-upgrade` + +Build wasm files for parsers and generate mappings for node fields + +`yarn build-wasm-and-generate-mappings` + + +Copy generated files into `vscode` and `core` directories + +`yarn copy-files` + +## Preparation to generate wasm files + +Follow the requirements installation steps in [tree sitter readme](https://github.com/tree-sitter/tree-sitter/blob/master/lib/binding_web/README.md#generate-wasm-language-files) + +What's needed +- Emscripten (currently working with non-docker installation ) +- tree-sitter-cli diff --git a/packages/tree-sitter-port/output/tree-sitter-c-sharp/fields-meta.json b/packages/tree-sitter-port/output/tree-sitter-c-sharp/fields-meta.json new file mode 100644 index 0000000..b9a5289 --- /dev/null +++ b/packages/tree-sitter-port/output/tree-sitter-c-sharp/fields-meta.json @@ -0,0 +1,4827 @@ +{ + "accessor_declaration": { + "type": "accessor_declaration", + "singleFieldNames": ["body", "name"], + "nodeTypeToMultipleFieldName": { + "attribute_list": "children", + "modifier": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "accessor_list": { + "type": "accessor_list", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": { + "accessor_declaration": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "alias_qualified_name": { + "type": "alias_qualified_name", + "singleFieldNames": ["alias", "name"], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "and_pattern": { + "type": "and_pattern", + "singleFieldNames": ["left", "operator", "right"], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "anonymous_method_expression": { + "type": "anonymous_method_expression", + "singleFieldNames": ["parameters"], + "nodeTypeToMultipleFieldName": { + "block": "children", + "modifier": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "anonymous_object_creation_expression": { + "type": "anonymous_object_creation_expression", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": { + "element_access_expression": "children", + "element_binding_expression": "children", + "generic_name": "children", + "identifier": "children", + "member_access_expression": "children", + "parenthesized_expression": "children", + "prefix_unary_expression": "children", + "this": "children", + "tuple_expression": "children", + "anonymous_method_expression": "children", + "anonymous_object_creation_expression": "children", + "array_creation_expression": "children", + "as_expression": "children", + "assignment_expression": "children", + "await_expression": "children", + "base": "children", + "binary_expression": "children", + "cast_expression": "children", + "checked_expression": "children", + "conditional_access_expression": "children", + "conditional_expression": "children", + "default_expression": "children", + "implicit_array_creation_expression": "children", + "implicit_object_creation_expression": "children", + "implicit_stackalloc_expression": "children", + "initializer_expression": "children", + "interpolated_string_expression": "children", + "invocation_expression": "children", + "is_expression": "children", + "is_pattern_expression": "children", + "lambda_expression": "children", + "boolean_literal": "children", + "character_literal": "children", + "integer_literal": "children", + "null_literal": "children", + "raw_string_literal": "children", + "real_literal": "children", + "string_literal": "children", + "verbatim_string_literal": "children", + "makeref_expression": "children", + "object_creation_expression": "children", + "postfix_unary_expression": "children", + "preproc_if": "children", + "query_expression": "children", + "range_expression": "children", + "ref_expression": "children", + "reftype_expression": "children", + "refvalue_expression": "children", + "sizeof_expression": "children", + "stackalloc_expression": "children", + "switch_expression": "children", + "throw_expression": "children", + "typeof_expression": "children", + "with_expression": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "argument": { + "type": "argument", + "singleFieldNames": ["name"], + "nodeTypeToMultipleFieldName": { + "declaration_expression": "children", + "element_access_expression": "children", + "element_binding_expression": "children", + "generic_name": "children", + "identifier": "children", + "member_access_expression": "children", + "parenthesized_expression": "children", + "prefix_unary_expression": "children", + "this": "children", + "tuple_expression": "children", + "anonymous_method_expression": "children", + "anonymous_object_creation_expression": "children", + "array_creation_expression": "children", + "as_expression": "children", + "assignment_expression": "children", + "await_expression": "children", + "base": "children", + "binary_expression": "children", + "cast_expression": "children", + "checked_expression": "children", + "conditional_access_expression": "children", + "conditional_expression": "children", + "default_expression": "children", + "implicit_array_creation_expression": "children", + "implicit_object_creation_expression": "children", + "implicit_stackalloc_expression": "children", + "initializer_expression": "children", + "interpolated_string_expression": "children", + "invocation_expression": "children", + "is_expression": "children", + "is_pattern_expression": "children", + "lambda_expression": "children", + "boolean_literal": "children", + "character_literal": "children", + "integer_literal": "children", + "null_literal": "children", + "raw_string_literal": "children", + "real_literal": "children", + "string_literal": "children", + "verbatim_string_literal": "children", + "makeref_expression": "children", + "object_creation_expression": "children", + "postfix_unary_expression": "children", + "preproc_if": "children", + "query_expression": "children", + "range_expression": "children", + "ref_expression": "children", + "reftype_expression": "children", + "refvalue_expression": "children", + "sizeof_expression": "children", + "stackalloc_expression": "children", + "switch_expression": "children", + "throw_expression": "children", + "typeof_expression": "children", + "with_expression": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "argument_list": { + "type": "argument_list", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": { + "argument": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "array_creation_expression": { + "type": "array_creation_expression", + "singleFieldNames": ["type"], + "nodeTypeToMultipleFieldName": { + "initializer_expression": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "array_rank_specifier": { + "type": "array_rank_specifier", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": { + "element_access_expression": "children", + "element_binding_expression": "children", + "generic_name": "children", + "identifier": "children", + "member_access_expression": "children", + "parenthesized_expression": "children", + "prefix_unary_expression": "children", + "this": "children", + "tuple_expression": "children", + "anonymous_method_expression": "children", + "anonymous_object_creation_expression": "children", + "array_creation_expression": "children", + "as_expression": "children", + "assignment_expression": "children", + "await_expression": "children", + "base": "children", + "binary_expression": "children", + "cast_expression": "children", + "checked_expression": "children", + "conditional_access_expression": "children", + "conditional_expression": "children", + "default_expression": "children", + "implicit_array_creation_expression": "children", + "implicit_object_creation_expression": "children", + "implicit_stackalloc_expression": "children", + "initializer_expression": "children", + "interpolated_string_expression": "children", + "invocation_expression": "children", + "is_expression": "children", + "is_pattern_expression": "children", + "lambda_expression": "children", + "boolean_literal": "children", + "character_literal": "children", + "integer_literal": "children", + "null_literal": "children", + "raw_string_literal": "children", + "real_literal": "children", + "string_literal": "children", + "verbatim_string_literal": "children", + "makeref_expression": "children", + "object_creation_expression": "children", + "postfix_unary_expression": "children", + "preproc_if": "children", + "query_expression": "children", + "range_expression": "children", + "ref_expression": "children", + "reftype_expression": "children", + "refvalue_expression": "children", + "sizeof_expression": "children", + "stackalloc_expression": "children", + "switch_expression": "children", + "throw_expression": "children", + "typeof_expression": "children", + "with_expression": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "array_type": { + "type": "array_type", + "singleFieldNames": ["rank", "type"], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "arrow_expression_clause": { + "type": "arrow_expression_clause", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": { + "element_access_expression": "children", + "element_binding_expression": "children", + "generic_name": "children", + "identifier": "children", + "member_access_expression": "children", + "parenthesized_expression": "children", + "prefix_unary_expression": "children", + "this": "children", + "tuple_expression": "children", + "anonymous_method_expression": "children", + "anonymous_object_creation_expression": "children", + "array_creation_expression": "children", + "as_expression": "children", + "assignment_expression": "children", + "await_expression": "children", + "base": "children", + "binary_expression": "children", + "cast_expression": "children", + "checked_expression": "children", + "conditional_access_expression": "children", + "conditional_expression": "children", + "default_expression": "children", + "implicit_array_creation_expression": "children", + "implicit_object_creation_expression": "children", + "implicit_stackalloc_expression": "children", + "initializer_expression": "children", + "interpolated_string_expression": "children", + "invocation_expression": "children", + "is_expression": "children", + "is_pattern_expression": "children", + "lambda_expression": "children", + "boolean_literal": "children", + "character_literal": "children", + "integer_literal": "children", + "null_literal": "children", + "raw_string_literal": "children", + "real_literal": "children", + "string_literal": "children", + "verbatim_string_literal": "children", + "makeref_expression": "children", + "object_creation_expression": "children", + "postfix_unary_expression": "children", + "preproc_if": "children", + "query_expression": "children", + "range_expression": "children", + "ref_expression": "children", + "reftype_expression": "children", + "refvalue_expression": "children", + "sizeof_expression": "children", + "stackalloc_expression": "children", + "switch_expression": "children", + "throw_expression": "children", + "typeof_expression": "children", + "with_expression": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "as_expression": { + "type": "as_expression", + "singleFieldNames": ["left", "operator", "right"], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "assignment_expression": { + "type": "assignment_expression", + "singleFieldNames": ["left", "operator", "right"], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "attribute": { + "type": "attribute", + "singleFieldNames": ["name"], + "nodeTypeToMultipleFieldName": { + "attribute_argument_list": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "attribute_argument": { + "type": "attribute_argument", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": { + "element_access_expression": "children", + "element_binding_expression": "children", + "generic_name": "children", + "identifier": "children", + "member_access_expression": "children", + "parenthesized_expression": "children", + "prefix_unary_expression": "children", + "this": "children", + "tuple_expression": "children", + "anonymous_method_expression": "children", + "anonymous_object_creation_expression": "children", + "array_creation_expression": "children", + "as_expression": "children", + "assignment_expression": "children", + "await_expression": "children", + "base": "children", + "binary_expression": "children", + "cast_expression": "children", + "checked_expression": "children", + "conditional_access_expression": "children", + "conditional_expression": "children", + "default_expression": "children", + "implicit_array_creation_expression": "children", + "implicit_object_creation_expression": "children", + "implicit_stackalloc_expression": "children", + "initializer_expression": "children", + "interpolated_string_expression": "children", + "invocation_expression": "children", + "is_expression": "children", + "is_pattern_expression": "children", + "lambda_expression": "children", + "boolean_literal": "children", + "character_literal": "children", + "integer_literal": "children", + "null_literal": "children", + "raw_string_literal": "children", + "real_literal": "children", + "string_literal": "children", + "verbatim_string_literal": "children", + "makeref_expression": "children", + "object_creation_expression": "children", + "postfix_unary_expression": "children", + "preproc_if": "children", + "query_expression": "children", + "range_expression": "children", + "ref_expression": "children", + "reftype_expression": "children", + "refvalue_expression": "children", + "sizeof_expression": "children", + "stackalloc_expression": "children", + "switch_expression": "children", + "throw_expression": "children", + "typeof_expression": "children", + "with_expression": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "attribute_argument_list": { + "type": "attribute_argument_list", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": { + "attribute_argument": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "attribute_list": { + "type": "attribute_list", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": { + "attribute": "children", + "attribute_target_specifier": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "attribute_target_specifier": { + "type": "attribute_target_specifier", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "await_expression": { + "type": "await_expression", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": { + "element_access_expression": "children", + "element_binding_expression": "children", + "generic_name": "children", + "identifier": "children", + "member_access_expression": "children", + "parenthesized_expression": "children", + "prefix_unary_expression": "children", + "this": "children", + "tuple_expression": "children", + "anonymous_method_expression": "children", + "anonymous_object_creation_expression": "children", + "array_creation_expression": "children", + "as_expression": "children", + "assignment_expression": "children", + "await_expression": "children", + "base": "children", + "binary_expression": "children", + "cast_expression": "children", + "checked_expression": "children", + "conditional_access_expression": "children", + "conditional_expression": "children", + "default_expression": "children", + "implicit_array_creation_expression": "children", + "implicit_object_creation_expression": "children", + "implicit_stackalloc_expression": "children", + "initializer_expression": "children", + "interpolated_string_expression": "children", + "invocation_expression": "children", + "is_expression": "children", + "is_pattern_expression": "children", + "lambda_expression": "children", + "boolean_literal": "children", + "character_literal": "children", + "integer_literal": "children", + "null_literal": "children", + "raw_string_literal": "children", + "real_literal": "children", + "string_literal": "children", + "verbatim_string_literal": "children", + "makeref_expression": "children", + "object_creation_expression": "children", + "postfix_unary_expression": "children", + "preproc_if": "children", + "query_expression": "children", + "range_expression": "children", + "ref_expression": "children", + "reftype_expression": "children", + "refvalue_expression": "children", + "sizeof_expression": "children", + "stackalloc_expression": "children", + "switch_expression": "children", + "throw_expression": "children", + "typeof_expression": "children", + "with_expression": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "base_list": { + "type": "base_list", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": { + "argument_list": "children", + "primary_constructor_base_type": "children", + "alias_qualified_name": "children", + "array_type": "children", + "function_pointer_type": "children", + "generic_name": "children", + "identifier": "children", + "implicit_type": "children", + "nullable_type": "children", + "pointer_type": "children", + "predefined_type": "children", + "qualified_name": "children", + "ref_type": "children", + "scoped_type": "children", + "tuple_type": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "binary_expression": { + "type": "binary_expression", + "singleFieldNames": ["left", "operator", "right"], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "block": { + "type": "block", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": { + "block": "children", + "break_statement": "children", + "checked_statement": "children", + "continue_statement": "children", + "do_statement": "children", + "empty_statement": "children", + "expression_statement": "children", + "fixed_statement": "children", + "for_statement": "children", + "foreach_statement": "children", + "goto_statement": "children", + "if_statement": "children", + "labeled_statement": "children", + "local_declaration_statement": "children", + "local_function_statement": "children", + "lock_statement": "children", + "preproc_if": "children", + "return_statement": "children", + "switch_statement": "children", + "throw_statement": "children", + "try_statement": "children", + "unsafe_statement": "children", + "using_statement": "children", + "while_statement": "children", + "yield_statement": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "boolean_literal": { + "type": "boolean_literal", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "bracketed_argument_list": { + "type": "bracketed_argument_list", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": { + "argument": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "bracketed_parameter_list": { + "type": "bracketed_parameter_list", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": { + "identifier": "name", + "array_type": "type", + "nullable_type": "type", + "attribute_list": "children", + "parameter": "children" + }, + "multipleOrChildrenFieldNames": ["name", "type", "children"] + }, + "break_statement": { + "type": "break_statement", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "calling_convention": { + "type": "calling_convention", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": { + "identifier": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "cast_expression": { + "type": "cast_expression", + "singleFieldNames": ["type", "value"], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "catch_clause": { + "type": "catch_clause", + "singleFieldNames": ["body"], + "nodeTypeToMultipleFieldName": { + "catch_declaration": "children", + "catch_filter_clause": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "catch_declaration": { + "type": "catch_declaration", + "singleFieldNames": ["name", "type"], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "catch_filter_clause": { + "type": "catch_filter_clause", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": { + "element_access_expression": "children", + "element_binding_expression": "children", + "generic_name": "children", + "identifier": "children", + "member_access_expression": "children", + "parenthesized_expression": "children", + "prefix_unary_expression": "children", + "this": "children", + "tuple_expression": "children", + "anonymous_method_expression": "children", + "anonymous_object_creation_expression": "children", + "array_creation_expression": "children", + "as_expression": "children", + "assignment_expression": "children", + "await_expression": "children", + "base": "children", + "binary_expression": "children", + "cast_expression": "children", + "checked_expression": "children", + "conditional_access_expression": "children", + "conditional_expression": "children", + "default_expression": "children", + "implicit_array_creation_expression": "children", + "implicit_object_creation_expression": "children", + "implicit_stackalloc_expression": "children", + "initializer_expression": "children", + "interpolated_string_expression": "children", + "invocation_expression": "children", + "is_expression": "children", + "is_pattern_expression": "children", + "lambda_expression": "children", + "boolean_literal": "children", + "character_literal": "children", + "integer_literal": "children", + "null_literal": "children", + "raw_string_literal": "children", + "real_literal": "children", + "string_literal": "children", + "verbatim_string_literal": "children", + "makeref_expression": "children", + "object_creation_expression": "children", + "postfix_unary_expression": "children", + "preproc_if": "children", + "query_expression": "children", + "range_expression": "children", + "ref_expression": "children", + "reftype_expression": "children", + "refvalue_expression": "children", + "sizeof_expression": "children", + "stackalloc_expression": "children", + "switch_expression": "children", + "throw_expression": "children", + "typeof_expression": "children", + "with_expression": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "character_literal": { + "type": "character_literal", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": { + "character_literal_content": "children", + "escape_sequence": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "checked_expression": { + "type": "checked_expression", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": { + "element_access_expression": "children", + "element_binding_expression": "children", + "generic_name": "children", + "identifier": "children", + "member_access_expression": "children", + "parenthesized_expression": "children", + "prefix_unary_expression": "children", + "this": "children", + "tuple_expression": "children", + "anonymous_method_expression": "children", + "anonymous_object_creation_expression": "children", + "array_creation_expression": "children", + "as_expression": "children", + "assignment_expression": "children", + "await_expression": "children", + "base": "children", + "binary_expression": "children", + "cast_expression": "children", + "checked_expression": "children", + "conditional_access_expression": "children", + "conditional_expression": "children", + "default_expression": "children", + "implicit_array_creation_expression": "children", + "implicit_object_creation_expression": "children", + "implicit_stackalloc_expression": "children", + "initializer_expression": "children", + "interpolated_string_expression": "children", + "invocation_expression": "children", + "is_expression": "children", + "is_pattern_expression": "children", + "lambda_expression": "children", + "boolean_literal": "children", + "character_literal": "children", + "integer_literal": "children", + "null_literal": "children", + "raw_string_literal": "children", + "real_literal": "children", + "string_literal": "children", + "verbatim_string_literal": "children", + "makeref_expression": "children", + "object_creation_expression": "children", + "postfix_unary_expression": "children", + "preproc_if": "children", + "query_expression": "children", + "range_expression": "children", + "ref_expression": "children", + "reftype_expression": "children", + "refvalue_expression": "children", + "sizeof_expression": "children", + "stackalloc_expression": "children", + "switch_expression": "children", + "throw_expression": "children", + "typeof_expression": "children", + "with_expression": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "checked_statement": { + "type": "checked_statement", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": { + "block": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "class_declaration": { + "type": "class_declaration", + "singleFieldNames": ["body", "name"], + "nodeTypeToMultipleFieldName": { + "attribute_list": "children", + "base_list": "children", + "modifier": "children", + "parameter_list": "children", + "type_parameter_constraints_clause": "children", + "type_parameter_list": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "compilation_unit": { + "type": "compilation_unit", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": { + "extern_alias_directive": "children", + "file_scoped_namespace_declaration": "children", + "global_attribute": "children", + "global_statement": "children", + "namespace_declaration": "children", + "preproc_if": "children", + "shebang_directive": "children", + "class_declaration": "children", + "delegate_declaration": "children", + "enum_declaration": "children", + "interface_declaration": "children", + "record_declaration": "children", + "struct_declaration": "children", + "using_directive": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "conditional_access_expression": { + "type": "conditional_access_expression", + "singleFieldNames": ["condition"], + "nodeTypeToMultipleFieldName": { + "element_binding_expression": "children", + "member_binding_expression": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "conditional_expression": { + "type": "conditional_expression", + "singleFieldNames": ["alternative", "condition", "consequence"], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "constant_pattern": { + "type": "constant_pattern", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": { + "binary_expression": "children", + "cast_expression": "children", + "default_expression": "children", + "generic_name": "children", + "identifier": "children", + "interpolated_string_expression": "children", + "invocation_expression": "children", + "boolean_literal": "children", + "character_literal": "children", + "integer_literal": "children", + "null_literal": "children", + "raw_string_literal": "children", + "real_literal": "children", + "string_literal": "children", + "verbatim_string_literal": "children", + "member_access_expression": "children", + "parenthesized_expression": "children", + "postfix_unary_expression": "children", + "prefix_unary_expression": "children", + "sizeof_expression": "children", + "tuple_expression": "children", + "typeof_expression": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "constructor_constraint": { + "type": "constructor_constraint", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "constructor_declaration": { + "type": "constructor_declaration", + "singleFieldNames": ["body", "name", "parameters"], + "nodeTypeToMultipleFieldName": { + "attribute_list": "children", + "constructor_initializer": "children", + "modifier": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "constructor_initializer": { + "type": "constructor_initializer", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": { + "argument_list": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "continue_statement": { + "type": "continue_statement", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "conversion_operator_declaration": { + "type": "conversion_operator_declaration", + "singleFieldNames": ["body", "parameters", "type"], + "nodeTypeToMultipleFieldName": { + "attribute_list": "children", + "explicit_interface_specifier": "children", + "modifier": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "declaration_expression": { + "type": "declaration_expression", + "singleFieldNames": ["name", "type"], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "declaration_list": { + "type": "declaration_list", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": { + "class_declaration": "children", + "constructor_declaration": "children", + "conversion_operator_declaration": "children", + "delegate_declaration": "children", + "destructor_declaration": "children", + "enum_declaration": "children", + "event_declaration": "children", + "event_field_declaration": "children", + "field_declaration": "children", + "indexer_declaration": "children", + "interface_declaration": "children", + "method_declaration": "children", + "namespace_declaration": "children", + "operator_declaration": "children", + "preproc_if": "children", + "property_declaration": "children", + "record_declaration": "children", + "struct_declaration": "children", + "using_directive": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "declaration_pattern": { + "type": "declaration_pattern", + "singleFieldNames": ["name", "type"], + "nodeTypeToMultipleFieldName": { + "discard": "children", + "parenthesized_variable_designation": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "default_expression": { + "type": "default_expression", + "singleFieldNames": ["type"], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "delegate_declaration": { + "type": "delegate_declaration", + "singleFieldNames": ["name", "parameters", "type", "type_parameters"], + "nodeTypeToMultipleFieldName": { + "attribute_list": "children", + "modifier": "children", + "type_parameter_constraints_clause": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "destructor_declaration": { + "type": "destructor_declaration", + "singleFieldNames": ["body", "name", "parameters"], + "nodeTypeToMultipleFieldName": { + "attribute_list": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "do_statement": { + "type": "do_statement", + "singleFieldNames": ["body", "condition"], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "element_access_expression": { + "type": "element_access_expression", + "singleFieldNames": ["expression", "subscript"], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "element_binding_expression": { + "type": "element_binding_expression", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": { + "argument": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "empty_statement": { + "type": "empty_statement", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "enum_declaration": { + "type": "enum_declaration", + "singleFieldNames": ["body", "name"], + "nodeTypeToMultipleFieldName": { + "attribute_list": "children", + "base_list": "children", + "modifier": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "enum_member_declaration": { + "type": "enum_member_declaration", + "singleFieldNames": ["name", "value"], + "nodeTypeToMultipleFieldName": { + "attribute_list": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "enum_member_declaration_list": { + "type": "enum_member_declaration_list", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": { + "enum_member_declaration": "children", + "preproc_if": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "event_declaration": { + "type": "event_declaration", + "singleFieldNames": ["accessors", "name", "type"], + "nodeTypeToMultipleFieldName": { + "attribute_list": "children", + "explicit_interface_specifier": "children", + "modifier": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "event_field_declaration": { + "type": "event_field_declaration", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": { + "attribute_list": "children", + "modifier": "children", + "variable_declaration": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "explicit_interface_specifier": { + "type": "explicit_interface_specifier", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": { + "alias_qualified_name": "children", + "generic_name": "children", + "identifier": "children", + "qualified_name": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "expression_statement": { + "type": "expression_statement", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": { + "assignment_expression": "children", + "await_expression": "children", + "invocation_expression": "children", + "object_creation_expression": "children", + "parenthesized_expression": "children", + "postfix_unary_expression": "children", + "prefix_unary_expression": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "extern_alias_directive": { + "type": "extern_alias_directive", + "singleFieldNames": ["name"], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "field_declaration": { + "type": "field_declaration", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": { + "attribute_list": "children", + "modifier": "children", + "variable_declaration": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "file_scoped_namespace_declaration": { + "type": "file_scoped_namespace_declaration", + "singleFieldNames": ["name"], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "finally_clause": { + "type": "finally_clause", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": { + "block": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "fixed_statement": { + "type": "fixed_statement", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": { + "block": "children", + "break_statement": "children", + "checked_statement": "children", + "continue_statement": "children", + "do_statement": "children", + "empty_statement": "children", + "expression_statement": "children", + "fixed_statement": "children", + "for_statement": "children", + "foreach_statement": "children", + "goto_statement": "children", + "if_statement": "children", + "labeled_statement": "children", + "local_declaration_statement": "children", + "local_function_statement": "children", + "lock_statement": "children", + "preproc_if": "children", + "return_statement": "children", + "switch_statement": "children", + "throw_statement": "children", + "try_statement": "children", + "unsafe_statement": "children", + "using_statement": "children", + "while_statement": "children", + "yield_statement": "children", + "variable_declaration": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "for_statement": { + "type": "for_statement", + "singleFieldNames": ["body", "condition"], + "nodeTypeToMultipleFieldName": { + ",": "update", + "element_access_expression": "update", + "element_binding_expression": "update", + "generic_name": "update", + "identifier": "update", + "member_access_expression": "update", + "parenthesized_expression": "update", + "prefix_unary_expression": "update", + "this": "update", + "tuple_expression": "update", + "anonymous_method_expression": "update", + "anonymous_object_creation_expression": "update", + "array_creation_expression": "update", + "as_expression": "update", + "assignment_expression": "update", + "await_expression": "update", + "base": "update", + "binary_expression": "update", + "cast_expression": "update", + "checked_expression": "update", + "conditional_access_expression": "update", + "conditional_expression": "update", + "default_expression": "update", + "implicit_array_creation_expression": "update", + "implicit_object_creation_expression": "update", + "implicit_stackalloc_expression": "update", + "initializer_expression": "update", + "interpolated_string_expression": "update", + "invocation_expression": "update", + "is_expression": "update", + "is_pattern_expression": "update", + "lambda_expression": "update", + "boolean_literal": "update", + "character_literal": "update", + "integer_literal": "update", + "null_literal": "update", + "raw_string_literal": "update", + "real_literal": "update", + "string_literal": "update", + "verbatim_string_literal": "update", + "makeref_expression": "update", + "object_creation_expression": "update", + "postfix_unary_expression": "update", + "preproc_if": "update", + "query_expression": "update", + "range_expression": "update", + "ref_expression": "update", + "reftype_expression": "update", + "refvalue_expression": "update", + "sizeof_expression": "update", + "stackalloc_expression": "update", + "switch_expression": "update", + "throw_expression": "update", + "typeof_expression": "update", + "with_expression": "update", + "variable_declaration": "initializer" + }, + "multipleOrChildrenFieldNames": ["initializer", "update"] + }, + "foreach_statement": { + "type": "foreach_statement", + "singleFieldNames": ["body", "left", "right", "type"], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "from_clause": { + "type": "from_clause", + "singleFieldNames": ["name", "type"], + "nodeTypeToMultipleFieldName": { + "element_access_expression": "children", + "element_binding_expression": "children", + "generic_name": "children", + "identifier": "children", + "member_access_expression": "children", + "parenthesized_expression": "children", + "prefix_unary_expression": "children", + "this": "children", + "tuple_expression": "children", + "anonymous_method_expression": "children", + "anonymous_object_creation_expression": "children", + "array_creation_expression": "children", + "as_expression": "children", + "assignment_expression": "children", + "await_expression": "children", + "base": "children", + "binary_expression": "children", + "cast_expression": "children", + "checked_expression": "children", + "conditional_access_expression": "children", + "conditional_expression": "children", + "default_expression": "children", + "implicit_array_creation_expression": "children", + "implicit_object_creation_expression": "children", + "implicit_stackalloc_expression": "children", + "initializer_expression": "children", + "interpolated_string_expression": "children", + "invocation_expression": "children", + "is_expression": "children", + "is_pattern_expression": "children", + "lambda_expression": "children", + "boolean_literal": "children", + "character_literal": "children", + "integer_literal": "children", + "null_literal": "children", + "raw_string_literal": "children", + "real_literal": "children", + "string_literal": "children", + "verbatim_string_literal": "children", + "makeref_expression": "children", + "object_creation_expression": "children", + "postfix_unary_expression": "children", + "preproc_if": "children", + "query_expression": "children", + "range_expression": "children", + "ref_expression": "children", + "reftype_expression": "children", + "refvalue_expression": "children", + "sizeof_expression": "children", + "stackalloc_expression": "children", + "switch_expression": "children", + "throw_expression": "children", + "typeof_expression": "children", + "with_expression": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "function_pointer_parameter": { + "type": "function_pointer_parameter", + "singleFieldNames": ["type"], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "function_pointer_type": { + "type": "function_pointer_type", + "singleFieldNames": ["returns"], + "nodeTypeToMultipleFieldName": { + "calling_convention": "children", + "function_pointer_parameter": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "generic_name": { + "type": "generic_name", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": { + "identifier": "children", + "type_argument_list": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "global_attribute": { + "type": "global_attribute", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": { + "attribute": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "global_statement": { + "type": "global_statement", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": { + "block": "children", + "break_statement": "children", + "checked_statement": "children", + "continue_statement": "children", + "do_statement": "children", + "empty_statement": "children", + "expression_statement": "children", + "fixed_statement": "children", + "for_statement": "children", + "foreach_statement": "children", + "goto_statement": "children", + "if_statement": "children", + "labeled_statement": "children", + "local_declaration_statement": "children", + "local_function_statement": "children", + "lock_statement": "children", + "preproc_if": "children", + "return_statement": "children", + "switch_statement": "children", + "throw_statement": "children", + "try_statement": "children", + "unsafe_statement": "children", + "using_statement": "children", + "while_statement": "children", + "yield_statement": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "goto_statement": { + "type": "goto_statement", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": { + "element_access_expression": "children", + "element_binding_expression": "children", + "generic_name": "children", + "identifier": "children", + "member_access_expression": "children", + "parenthesized_expression": "children", + "prefix_unary_expression": "children", + "this": "children", + "tuple_expression": "children", + "anonymous_method_expression": "children", + "anonymous_object_creation_expression": "children", + "array_creation_expression": "children", + "as_expression": "children", + "assignment_expression": "children", + "await_expression": "children", + "base": "children", + "binary_expression": "children", + "cast_expression": "children", + "checked_expression": "children", + "conditional_access_expression": "children", + "conditional_expression": "children", + "default_expression": "children", + "implicit_array_creation_expression": "children", + "implicit_object_creation_expression": "children", + "implicit_stackalloc_expression": "children", + "initializer_expression": "children", + "interpolated_string_expression": "children", + "invocation_expression": "children", + "is_expression": "children", + "is_pattern_expression": "children", + "lambda_expression": "children", + "boolean_literal": "children", + "character_literal": "children", + "integer_literal": "children", + "null_literal": "children", + "raw_string_literal": "children", + "real_literal": "children", + "string_literal": "children", + "verbatim_string_literal": "children", + "makeref_expression": "children", + "object_creation_expression": "children", + "postfix_unary_expression": "children", + "preproc_if": "children", + "query_expression": "children", + "range_expression": "children", + "ref_expression": "children", + "reftype_expression": "children", + "refvalue_expression": "children", + "sizeof_expression": "children", + "stackalloc_expression": "children", + "switch_expression": "children", + "throw_expression": "children", + "typeof_expression": "children", + "with_expression": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "group_clause": { + "type": "group_clause", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": { + "element_access_expression": "children", + "element_binding_expression": "children", + "generic_name": "children", + "identifier": "children", + "member_access_expression": "children", + "parenthesized_expression": "children", + "prefix_unary_expression": "children", + "this": "children", + "tuple_expression": "children", + "anonymous_method_expression": "children", + "anonymous_object_creation_expression": "children", + "array_creation_expression": "children", + "as_expression": "children", + "assignment_expression": "children", + "await_expression": "children", + "base": "children", + "binary_expression": "children", + "cast_expression": "children", + "checked_expression": "children", + "conditional_access_expression": "children", + "conditional_expression": "children", + "default_expression": "children", + "implicit_array_creation_expression": "children", + "implicit_object_creation_expression": "children", + "implicit_stackalloc_expression": "children", + "initializer_expression": "children", + "interpolated_string_expression": "children", + "invocation_expression": "children", + "is_expression": "children", + "is_pattern_expression": "children", + "lambda_expression": "children", + "boolean_literal": "children", + "character_literal": "children", + "integer_literal": "children", + "null_literal": "children", + "raw_string_literal": "children", + "real_literal": "children", + "string_literal": "children", + "verbatim_string_literal": "children", + "makeref_expression": "children", + "object_creation_expression": "children", + "postfix_unary_expression": "children", + "preproc_if": "children", + "query_expression": "children", + "range_expression": "children", + "ref_expression": "children", + "reftype_expression": "children", + "refvalue_expression": "children", + "sizeof_expression": "children", + "stackalloc_expression": "children", + "switch_expression": "children", + "throw_expression": "children", + "typeof_expression": "children", + "with_expression": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "identifier": { + "type": "identifier", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "if_statement": { + "type": "if_statement", + "singleFieldNames": ["alternative", "condition", "consequence"], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "implicit_array_creation_expression": { + "type": "implicit_array_creation_expression", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": { + "initializer_expression": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "implicit_object_creation_expression": { + "type": "implicit_object_creation_expression", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": { + "argument_list": "children", + "initializer_expression": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "implicit_parameter": { + "type": "implicit_parameter", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "implicit_stackalloc_expression": { + "type": "implicit_stackalloc_expression", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": { + "initializer_expression": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "implicit_type": { + "type": "implicit_type", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "indexer_declaration": { + "type": "indexer_declaration", + "singleFieldNames": ["accessors", "parameters", "type", "value"], + "nodeTypeToMultipleFieldName": { + "attribute_list": "children", + "explicit_interface_specifier": "children", + "modifier": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "initializer_expression": { + "type": "initializer_expression", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": { + "element_access_expression": "children", + "element_binding_expression": "children", + "generic_name": "children", + "identifier": "children", + "member_access_expression": "children", + "parenthesized_expression": "children", + "prefix_unary_expression": "children", + "this": "children", + "tuple_expression": "children", + "anonymous_method_expression": "children", + "anonymous_object_creation_expression": "children", + "array_creation_expression": "children", + "as_expression": "children", + "assignment_expression": "children", + "await_expression": "children", + "base": "children", + "binary_expression": "children", + "cast_expression": "children", + "checked_expression": "children", + "conditional_access_expression": "children", + "conditional_expression": "children", + "default_expression": "children", + "implicit_array_creation_expression": "children", + "implicit_object_creation_expression": "children", + "implicit_stackalloc_expression": "children", + "initializer_expression": "children", + "interpolated_string_expression": "children", + "invocation_expression": "children", + "is_expression": "children", + "is_pattern_expression": "children", + "lambda_expression": "children", + "boolean_literal": "children", + "character_literal": "children", + "integer_literal": "children", + "null_literal": "children", + "raw_string_literal": "children", + "real_literal": "children", + "string_literal": "children", + "verbatim_string_literal": "children", + "makeref_expression": "children", + "object_creation_expression": "children", + "postfix_unary_expression": "children", + "preproc_if": "children", + "query_expression": "children", + "range_expression": "children", + "ref_expression": "children", + "reftype_expression": "children", + "refvalue_expression": "children", + "sizeof_expression": "children", + "stackalloc_expression": "children", + "switch_expression": "children", + "throw_expression": "children", + "typeof_expression": "children", + "with_expression": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "interface_declaration": { + "type": "interface_declaration", + "singleFieldNames": ["body", "name", "type_parameters"], + "nodeTypeToMultipleFieldName": { + "attribute_list": "children", + "base_list": "children", + "modifier": "children", + "type_parameter_constraints_clause": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "interpolated_string_expression": { + "type": "interpolated_string_expression", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": { + "escape_sequence": "children", + "interpolation": "children", + "interpolation_quote": "children", + "interpolation_start": "children", + "string_content": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "interpolation": { + "type": "interpolation", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": { + "element_access_expression": "children", + "element_binding_expression": "children", + "generic_name": "children", + "identifier": "children", + "member_access_expression": "children", + "parenthesized_expression": "children", + "prefix_unary_expression": "children", + "this": "children", + "tuple_expression": "children", + "anonymous_method_expression": "children", + "anonymous_object_creation_expression": "children", + "array_creation_expression": "children", + "as_expression": "children", + "assignment_expression": "children", + "await_expression": "children", + "base": "children", + "binary_expression": "children", + "cast_expression": "children", + "checked_expression": "children", + "conditional_access_expression": "children", + "conditional_expression": "children", + "default_expression": "children", + "implicit_array_creation_expression": "children", + "implicit_object_creation_expression": "children", + "implicit_stackalloc_expression": "children", + "initializer_expression": "children", + "interpolated_string_expression": "children", + "invocation_expression": "children", + "is_expression": "children", + "is_pattern_expression": "children", + "lambda_expression": "children", + "boolean_literal": "children", + "character_literal": "children", + "integer_literal": "children", + "null_literal": "children", + "raw_string_literal": "children", + "real_literal": "children", + "string_literal": "children", + "verbatim_string_literal": "children", + "makeref_expression": "children", + "object_creation_expression": "children", + "postfix_unary_expression": "children", + "preproc_if": "children", + "query_expression": "children", + "range_expression": "children", + "ref_expression": "children", + "reftype_expression": "children", + "refvalue_expression": "children", + "sizeof_expression": "children", + "stackalloc_expression": "children", + "switch_expression": "children", + "throw_expression": "children", + "typeof_expression": "children", + "with_expression": "children", + "interpolation_alignment_clause": "children", + "interpolation_brace": "children", + "interpolation_format_clause": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "interpolation_alignment_clause": { + "type": "interpolation_alignment_clause", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": { + "element_access_expression": "children", + "element_binding_expression": "children", + "generic_name": "children", + "identifier": "children", + "member_access_expression": "children", + "parenthesized_expression": "children", + "prefix_unary_expression": "children", + "this": "children", + "tuple_expression": "children", + "anonymous_method_expression": "children", + "anonymous_object_creation_expression": "children", + "array_creation_expression": "children", + "as_expression": "children", + "assignment_expression": "children", + "await_expression": "children", + "base": "children", + "binary_expression": "children", + "cast_expression": "children", + "checked_expression": "children", + "conditional_access_expression": "children", + "conditional_expression": "children", + "default_expression": "children", + "implicit_array_creation_expression": "children", + "implicit_object_creation_expression": "children", + "implicit_stackalloc_expression": "children", + "initializer_expression": "children", + "interpolated_string_expression": "children", + "invocation_expression": "children", + "is_expression": "children", + "is_pattern_expression": "children", + "lambda_expression": "children", + "boolean_literal": "children", + "character_literal": "children", + "integer_literal": "children", + "null_literal": "children", + "raw_string_literal": "children", + "real_literal": "children", + "string_literal": "children", + "verbatim_string_literal": "children", + "makeref_expression": "children", + "object_creation_expression": "children", + "postfix_unary_expression": "children", + "preproc_if": "children", + "query_expression": "children", + "range_expression": "children", + "ref_expression": "children", + "reftype_expression": "children", + "refvalue_expression": "children", + "sizeof_expression": "children", + "stackalloc_expression": "children", + "switch_expression": "children", + "throw_expression": "children", + "typeof_expression": "children", + "with_expression": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "interpolation_format_clause": { + "type": "interpolation_format_clause", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "invocation_expression": { + "type": "invocation_expression", + "singleFieldNames": ["arguments", "function"], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "is_expression": { + "type": "is_expression", + "singleFieldNames": ["left", "operator", "right"], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "is_pattern_expression": { + "type": "is_pattern_expression", + "singleFieldNames": ["expression", "pattern"], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "join_clause": { + "type": "join_clause", + "singleFieldNames": ["type"], + "nodeTypeToMultipleFieldName": { + "element_access_expression": "children", + "element_binding_expression": "children", + "generic_name": "children", + "identifier": "children", + "member_access_expression": "children", + "parenthesized_expression": "children", + "prefix_unary_expression": "children", + "this": "children", + "tuple_expression": "children", + "anonymous_method_expression": "children", + "anonymous_object_creation_expression": "children", + "array_creation_expression": "children", + "as_expression": "children", + "assignment_expression": "children", + "await_expression": "children", + "base": "children", + "binary_expression": "children", + "cast_expression": "children", + "checked_expression": "children", + "conditional_access_expression": "children", + "conditional_expression": "children", + "default_expression": "children", + "implicit_array_creation_expression": "children", + "implicit_object_creation_expression": "children", + "implicit_stackalloc_expression": "children", + "initializer_expression": "children", + "interpolated_string_expression": "children", + "invocation_expression": "children", + "is_expression": "children", + "is_pattern_expression": "children", + "lambda_expression": "children", + "boolean_literal": "children", + "character_literal": "children", + "integer_literal": "children", + "null_literal": "children", + "raw_string_literal": "children", + "real_literal": "children", + "string_literal": "children", + "verbatim_string_literal": "children", + "makeref_expression": "children", + "object_creation_expression": "children", + "postfix_unary_expression": "children", + "preproc_if": "children", + "query_expression": "children", + "range_expression": "children", + "ref_expression": "children", + "reftype_expression": "children", + "refvalue_expression": "children", + "sizeof_expression": "children", + "stackalloc_expression": "children", + "switch_expression": "children", + "throw_expression": "children", + "typeof_expression": "children", + "with_expression": "children", + "join_into_clause": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "join_into_clause": { + "type": "join_into_clause", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": { + "identifier": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "labeled_statement": { + "type": "labeled_statement", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": { + "identifier": "children", + "block": "children", + "break_statement": "children", + "checked_statement": "children", + "continue_statement": "children", + "do_statement": "children", + "empty_statement": "children", + "expression_statement": "children", + "fixed_statement": "children", + "for_statement": "children", + "foreach_statement": "children", + "goto_statement": "children", + "if_statement": "children", + "labeled_statement": "children", + "local_declaration_statement": "children", + "local_function_statement": "children", + "lock_statement": "children", + "preproc_if": "children", + "return_statement": "children", + "switch_statement": "children", + "throw_statement": "children", + "try_statement": "children", + "unsafe_statement": "children", + "using_statement": "children", + "while_statement": "children", + "yield_statement": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "lambda_expression": { + "type": "lambda_expression", + "singleFieldNames": ["body", "parameters", "type"], + "nodeTypeToMultipleFieldName": { + "attribute_list": "children", + "modifier": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "let_clause": { + "type": "let_clause", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": { + "element_access_expression": "children", + "element_binding_expression": "children", + "generic_name": "children", + "identifier": "children", + "member_access_expression": "children", + "parenthesized_expression": "children", + "prefix_unary_expression": "children", + "this": "children", + "tuple_expression": "children", + "anonymous_method_expression": "children", + "anonymous_object_creation_expression": "children", + "array_creation_expression": "children", + "as_expression": "children", + "assignment_expression": "children", + "await_expression": "children", + "base": "children", + "binary_expression": "children", + "cast_expression": "children", + "checked_expression": "children", + "conditional_access_expression": "children", + "conditional_expression": "children", + "default_expression": "children", + "implicit_array_creation_expression": "children", + "implicit_object_creation_expression": "children", + "implicit_stackalloc_expression": "children", + "initializer_expression": "children", + "interpolated_string_expression": "children", + "invocation_expression": "children", + "is_expression": "children", + "is_pattern_expression": "children", + "lambda_expression": "children", + "boolean_literal": "children", + "character_literal": "children", + "integer_literal": "children", + "null_literal": "children", + "raw_string_literal": "children", + "real_literal": "children", + "string_literal": "children", + "verbatim_string_literal": "children", + "makeref_expression": "children", + "object_creation_expression": "children", + "postfix_unary_expression": "children", + "preproc_if": "children", + "query_expression": "children", + "range_expression": "children", + "ref_expression": "children", + "reftype_expression": "children", + "refvalue_expression": "children", + "sizeof_expression": "children", + "stackalloc_expression": "children", + "switch_expression": "children", + "throw_expression": "children", + "typeof_expression": "children", + "with_expression": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "list_pattern": { + "type": "list_pattern", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": { + "and_pattern": "children", + "constant_pattern": "children", + "declaration_pattern": "children", + "discard": "children", + "list_pattern": "children", + "negated_pattern": "children", + "or_pattern": "children", + "parenthesized_pattern": "children", + "recursive_pattern": "children", + "relational_pattern": "children", + "type_pattern": "children", + "var_pattern": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "local_declaration_statement": { + "type": "local_declaration_statement", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": { + "modifier": "children", + "variable_declaration": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "local_function_statement": { + "type": "local_function_statement", + "singleFieldNames": [ + "body", + "name", + "parameters", + "type", + "type_parameters" + ], + "nodeTypeToMultipleFieldName": { + "attribute_list": "children", + "modifier": "children", + "type_parameter_constraints_clause": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "lock_statement": { + "type": "lock_statement", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": { + "element_access_expression": "children", + "element_binding_expression": "children", + "generic_name": "children", + "identifier": "children", + "member_access_expression": "children", + "parenthesized_expression": "children", + "prefix_unary_expression": "children", + "this": "children", + "tuple_expression": "children", + "anonymous_method_expression": "children", + "anonymous_object_creation_expression": "children", + "array_creation_expression": "children", + "as_expression": "children", + "assignment_expression": "children", + "await_expression": "children", + "base": "children", + "binary_expression": "children", + "cast_expression": "children", + "checked_expression": "children", + "conditional_access_expression": "children", + "conditional_expression": "children", + "default_expression": "children", + "implicit_array_creation_expression": "children", + "implicit_object_creation_expression": "children", + "implicit_stackalloc_expression": "children", + "initializer_expression": "children", + "interpolated_string_expression": "children", + "invocation_expression": "children", + "is_expression": "children", + "is_pattern_expression": "children", + "lambda_expression": "children", + "boolean_literal": "children", + "character_literal": "children", + "integer_literal": "children", + "null_literal": "children", + "raw_string_literal": "children", + "real_literal": "children", + "string_literal": "children", + "verbatim_string_literal": "children", + "makeref_expression": "children", + "object_creation_expression": "children", + "postfix_unary_expression": "children", + "preproc_if": "children", + "query_expression": "children", + "range_expression": "children", + "ref_expression": "children", + "reftype_expression": "children", + "refvalue_expression": "children", + "sizeof_expression": "children", + "stackalloc_expression": "children", + "switch_expression": "children", + "throw_expression": "children", + "typeof_expression": "children", + "with_expression": "children", + "block": "children", + "break_statement": "children", + "checked_statement": "children", + "continue_statement": "children", + "do_statement": "children", + "empty_statement": "children", + "expression_statement": "children", + "fixed_statement": "children", + "for_statement": "children", + "foreach_statement": "children", + "goto_statement": "children", + "if_statement": "children", + "labeled_statement": "children", + "local_declaration_statement": "children", + "local_function_statement": "children", + "lock_statement": "children", + "return_statement": "children", + "switch_statement": "children", + "throw_statement": "children", + "try_statement": "children", + "unsafe_statement": "children", + "using_statement": "children", + "while_statement": "children", + "yield_statement": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "makeref_expression": { + "type": "makeref_expression", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": { + "element_access_expression": "children", + "element_binding_expression": "children", + "generic_name": "children", + "identifier": "children", + "member_access_expression": "children", + "parenthesized_expression": "children", + "prefix_unary_expression": "children", + "this": "children", + "tuple_expression": "children", + "anonymous_method_expression": "children", + "anonymous_object_creation_expression": "children", + "array_creation_expression": "children", + "as_expression": "children", + "assignment_expression": "children", + "await_expression": "children", + "base": "children", + "binary_expression": "children", + "cast_expression": "children", + "checked_expression": "children", + "conditional_access_expression": "children", + "conditional_expression": "children", + "default_expression": "children", + "implicit_array_creation_expression": "children", + "implicit_object_creation_expression": "children", + "implicit_stackalloc_expression": "children", + "initializer_expression": "children", + "interpolated_string_expression": "children", + "invocation_expression": "children", + "is_expression": "children", + "is_pattern_expression": "children", + "lambda_expression": "children", + "boolean_literal": "children", + "character_literal": "children", + "integer_literal": "children", + "null_literal": "children", + "raw_string_literal": "children", + "real_literal": "children", + "string_literal": "children", + "verbatim_string_literal": "children", + "makeref_expression": "children", + "object_creation_expression": "children", + "postfix_unary_expression": "children", + "preproc_if": "children", + "query_expression": "children", + "range_expression": "children", + "ref_expression": "children", + "reftype_expression": "children", + "refvalue_expression": "children", + "sizeof_expression": "children", + "stackalloc_expression": "children", + "switch_expression": "children", + "throw_expression": "children", + "typeof_expression": "children", + "with_expression": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "member_access_expression": { + "type": "member_access_expression", + "singleFieldNames": ["expression", "name"], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "member_binding_expression": { + "type": "member_binding_expression", + "singleFieldNames": ["name"], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "method_declaration": { + "type": "method_declaration", + "singleFieldNames": [ + "body", + "name", + "parameters", + "returns", + "type_parameters" + ], + "nodeTypeToMultipleFieldName": { + "attribute_list": "children", + "explicit_interface_specifier": "children", + "modifier": "children", + "type_parameter_constraints_clause": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "modifier": { + "type": "modifier", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "namespace_declaration": { + "type": "namespace_declaration", + "singleFieldNames": ["body", "name"], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "negated_pattern": { + "type": "negated_pattern", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": { + "and_pattern": "children", + "constant_pattern": "children", + "declaration_pattern": "children", + "discard": "children", + "list_pattern": "children", + "negated_pattern": "children", + "or_pattern": "children", + "parenthesized_pattern": "children", + "recursive_pattern": "children", + "relational_pattern": "children", + "type_pattern": "children", + "var_pattern": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "nullable_type": { + "type": "nullable_type", + "singleFieldNames": ["type"], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "object_creation_expression": { + "type": "object_creation_expression", + "singleFieldNames": ["arguments", "initializer", "type"], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "operator_declaration": { + "type": "operator_declaration", + "singleFieldNames": ["body", "operator", "parameters", "type"], + "nodeTypeToMultipleFieldName": { + "attribute_list": "children", + "explicit_interface_specifier": "children", + "modifier": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "or_pattern": { + "type": "or_pattern", + "singleFieldNames": ["left", "operator", "right"], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "order_by_clause": { + "type": "order_by_clause", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": { + "element_access_expression": "children", + "element_binding_expression": "children", + "generic_name": "children", + "identifier": "children", + "member_access_expression": "children", + "parenthesized_expression": "children", + "prefix_unary_expression": "children", + "this": "children", + "tuple_expression": "children", + "anonymous_method_expression": "children", + "anonymous_object_creation_expression": "children", + "array_creation_expression": "children", + "as_expression": "children", + "assignment_expression": "children", + "await_expression": "children", + "base": "children", + "binary_expression": "children", + "cast_expression": "children", + "checked_expression": "children", + "conditional_access_expression": "children", + "conditional_expression": "children", + "default_expression": "children", + "implicit_array_creation_expression": "children", + "implicit_object_creation_expression": "children", + "implicit_stackalloc_expression": "children", + "initializer_expression": "children", + "interpolated_string_expression": "children", + "invocation_expression": "children", + "is_expression": "children", + "is_pattern_expression": "children", + "lambda_expression": "children", + "boolean_literal": "children", + "character_literal": "children", + "integer_literal": "children", + "null_literal": "children", + "raw_string_literal": "children", + "real_literal": "children", + "string_literal": "children", + "verbatim_string_literal": "children", + "makeref_expression": "children", + "object_creation_expression": "children", + "postfix_unary_expression": "children", + "preproc_if": "children", + "query_expression": "children", + "range_expression": "children", + "ref_expression": "children", + "reftype_expression": "children", + "refvalue_expression": "children", + "sizeof_expression": "children", + "stackalloc_expression": "children", + "switch_expression": "children", + "throw_expression": "children", + "typeof_expression": "children", + "with_expression": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "parameter": { + "type": "parameter", + "singleFieldNames": ["name", "type"], + "nodeTypeToMultipleFieldName": { + "attribute_list": "children", + "element_access_expression": "children", + "element_binding_expression": "children", + "generic_name": "children", + "identifier": "children", + "member_access_expression": "children", + "parenthesized_expression": "children", + "prefix_unary_expression": "children", + "this": "children", + "tuple_expression": "children", + "anonymous_method_expression": "children", + "anonymous_object_creation_expression": "children", + "array_creation_expression": "children", + "as_expression": "children", + "assignment_expression": "children", + "await_expression": "children", + "base": "children", + "binary_expression": "children", + "cast_expression": "children", + "checked_expression": "children", + "conditional_access_expression": "children", + "conditional_expression": "children", + "default_expression": "children", + "implicit_array_creation_expression": "children", + "implicit_object_creation_expression": "children", + "implicit_stackalloc_expression": "children", + "initializer_expression": "children", + "interpolated_string_expression": "children", + "invocation_expression": "children", + "is_expression": "children", + "is_pattern_expression": "children", + "lambda_expression": "children", + "boolean_literal": "children", + "character_literal": "children", + "integer_literal": "children", + "null_literal": "children", + "raw_string_literal": "children", + "real_literal": "children", + "string_literal": "children", + "verbatim_string_literal": "children", + "makeref_expression": "children", + "object_creation_expression": "children", + "postfix_unary_expression": "children", + "preproc_if": "children", + "query_expression": "children", + "range_expression": "children", + "ref_expression": "children", + "reftype_expression": "children", + "refvalue_expression": "children", + "sizeof_expression": "children", + "stackalloc_expression": "children", + "switch_expression": "children", + "throw_expression": "children", + "typeof_expression": "children", + "with_expression": "children", + "modifier": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "parameter_list": { + "type": "parameter_list", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": { + "identifier": "name", + "array_type": "type", + "nullable_type": "type", + "attribute_list": "children", + "parameter": "children" + }, + "multipleOrChildrenFieldNames": ["name", "type", "children"] + }, + "parenthesized_expression": { + "type": "parenthesized_expression", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": { + "boolean_literal": "children", + "character_literal": "children", + "integer_literal": "children", + "element_access_expression": "children", + "element_binding_expression": "children", + "generic_name": "children", + "identifier": "children", + "member_access_expression": "children", + "parenthesized_expression": "children", + "prefix_unary_expression": "children", + "this": "children", + "tuple_expression": "children", + "anonymous_method_expression": "children", + "anonymous_object_creation_expression": "children", + "array_creation_expression": "children", + "as_expression": "children", + "assignment_expression": "children", + "await_expression": "children", + "base": "children", + "binary_expression": "children", + "cast_expression": "children", + "checked_expression": "children", + "conditional_access_expression": "children", + "conditional_expression": "children", + "default_expression": "children", + "implicit_array_creation_expression": "children", + "implicit_object_creation_expression": "children", + "implicit_stackalloc_expression": "children", + "initializer_expression": "children", + "interpolated_string_expression": "children", + "invocation_expression": "children", + "is_expression": "children", + "is_pattern_expression": "children", + "lambda_expression": "children", + "null_literal": "children", + "raw_string_literal": "children", + "real_literal": "children", + "string_literal": "children", + "verbatim_string_literal": "children", + "makeref_expression": "children", + "object_creation_expression": "children", + "postfix_unary_expression": "children", + "preproc_if": "children", + "query_expression": "children", + "range_expression": "children", + "ref_expression": "children", + "reftype_expression": "children", + "refvalue_expression": "children", + "sizeof_expression": "children", + "stackalloc_expression": "children", + "switch_expression": "children", + "throw_expression": "children", + "typeof_expression": "children", + "with_expression": "children", + "unary_expression": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "parenthesized_pattern": { + "type": "parenthesized_pattern", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": { + "and_pattern": "children", + "constant_pattern": "children", + "declaration_pattern": "children", + "discard": "children", + "list_pattern": "children", + "negated_pattern": "children", + "or_pattern": "children", + "parenthesized_pattern": "children", + "recursive_pattern": "children", + "relational_pattern": "children", + "type_pattern": "children", + "var_pattern": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "parenthesized_variable_designation": { + "type": "parenthesized_variable_designation", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": { + "identifier": "name", + "discard": "children", + "parenthesized_variable_designation": "children" + }, + "multipleOrChildrenFieldNames": ["name", "children"] + }, + "pointer_type": { + "type": "pointer_type", + "singleFieldNames": ["type"], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "positional_pattern_clause": { + "type": "positional_pattern_clause", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": { + "subpattern": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "postfix_unary_expression": { + "type": "postfix_unary_expression", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": { + "element_access_expression": "children", + "element_binding_expression": "children", + "generic_name": "children", + "identifier": "children", + "member_access_expression": "children", + "parenthesized_expression": "children", + "prefix_unary_expression": "children", + "this": "children", + "tuple_expression": "children", + "anonymous_method_expression": "children", + "anonymous_object_creation_expression": "children", + "array_creation_expression": "children", + "as_expression": "children", + "assignment_expression": "children", + "await_expression": "children", + "base": "children", + "binary_expression": "children", + "cast_expression": "children", + "checked_expression": "children", + "conditional_access_expression": "children", + "conditional_expression": "children", + "default_expression": "children", + "implicit_array_creation_expression": "children", + "implicit_object_creation_expression": "children", + "implicit_stackalloc_expression": "children", + "initializer_expression": "children", + "interpolated_string_expression": "children", + "invocation_expression": "children", + "is_expression": "children", + "is_pattern_expression": "children", + "lambda_expression": "children", + "boolean_literal": "children", + "character_literal": "children", + "integer_literal": "children", + "null_literal": "children", + "raw_string_literal": "children", + "real_literal": "children", + "string_literal": "children", + "verbatim_string_literal": "children", + "makeref_expression": "children", + "object_creation_expression": "children", + "postfix_unary_expression": "children", + "preproc_if": "children", + "query_expression": "children", + "range_expression": "children", + "ref_expression": "children", + "reftype_expression": "children", + "refvalue_expression": "children", + "sizeof_expression": "children", + "stackalloc_expression": "children", + "switch_expression": "children", + "throw_expression": "children", + "typeof_expression": "children", + "with_expression": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "prefix_unary_expression": { + "type": "prefix_unary_expression", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": { + "element_access_expression": "children", + "element_binding_expression": "children", + "generic_name": "children", + "identifier": "children", + "member_access_expression": "children", + "parenthesized_expression": "children", + "prefix_unary_expression": "children", + "this": "children", + "tuple_expression": "children", + "anonymous_method_expression": "children", + "anonymous_object_creation_expression": "children", + "array_creation_expression": "children", + "as_expression": "children", + "assignment_expression": "children", + "await_expression": "children", + "base": "children", + "binary_expression": "children", + "cast_expression": "children", + "checked_expression": "children", + "conditional_access_expression": "children", + "conditional_expression": "children", + "default_expression": "children", + "implicit_array_creation_expression": "children", + "implicit_object_creation_expression": "children", + "implicit_stackalloc_expression": "children", + "initializer_expression": "children", + "interpolated_string_expression": "children", + "invocation_expression": "children", + "is_expression": "children", + "is_pattern_expression": "children", + "lambda_expression": "children", + "boolean_literal": "children", + "character_literal": "children", + "integer_literal": "children", + "null_literal": "children", + "raw_string_literal": "children", + "real_literal": "children", + "string_literal": "children", + "verbatim_string_literal": "children", + "makeref_expression": "children", + "object_creation_expression": "children", + "postfix_unary_expression": "children", + "preproc_if": "children", + "query_expression": "children", + "range_expression": "children", + "ref_expression": "children", + "reftype_expression": "children", + "refvalue_expression": "children", + "sizeof_expression": "children", + "stackalloc_expression": "children", + "switch_expression": "children", + "throw_expression": "children", + "typeof_expression": "children", + "with_expression": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "preproc_define": { + "type": "preproc_define", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": { + "preproc_arg": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "preproc_elif": { + "type": "preproc_elif", + "singleFieldNames": ["alternative", "condition"], + "nodeTypeToMultipleFieldName": { + "class_declaration": "children", + "constructor_declaration": "children", + "conversion_operator_declaration": "children", + "delegate_declaration": "children", + "destructor_declaration": "children", + "enum_declaration": "children", + "event_declaration": "children", + "event_field_declaration": "children", + "field_declaration": "children", + "indexer_declaration": "children", + "interface_declaration": "children", + "method_declaration": "children", + "namespace_declaration": "children", + "operator_declaration": "children", + "preproc_if": "children", + "property_declaration": "children", + "record_declaration": "children", + "struct_declaration": "children", + "using_directive": "children", + "enum_member_declaration": "children", + "element_access_expression": "children", + "element_binding_expression": "children", + "generic_name": "children", + "identifier": "children", + "member_access_expression": "children", + "parenthesized_expression": "children", + "prefix_unary_expression": "children", + "this": "children", + "tuple_expression": "children", + "anonymous_method_expression": "children", + "anonymous_object_creation_expression": "children", + "array_creation_expression": "children", + "as_expression": "children", + "assignment_expression": "children", + "await_expression": "children", + "base": "children", + "binary_expression": "children", + "cast_expression": "children", + "checked_expression": "children", + "conditional_access_expression": "children", + "conditional_expression": "children", + "default_expression": "children", + "implicit_array_creation_expression": "children", + "implicit_object_creation_expression": "children", + "implicit_stackalloc_expression": "children", + "initializer_expression": "children", + "interpolated_string_expression": "children", + "invocation_expression": "children", + "is_expression": "children", + "is_pattern_expression": "children", + "lambda_expression": "children", + "boolean_literal": "children", + "character_literal": "children", + "integer_literal": "children", + "null_literal": "children", + "raw_string_literal": "children", + "real_literal": "children", + "string_literal": "children", + "verbatim_string_literal": "children", + "makeref_expression": "children", + "object_creation_expression": "children", + "postfix_unary_expression": "children", + "query_expression": "children", + "range_expression": "children", + "ref_expression": "children", + "reftype_expression": "children", + "refvalue_expression": "children", + "sizeof_expression": "children", + "stackalloc_expression": "children", + "switch_expression": "children", + "throw_expression": "children", + "typeof_expression": "children", + "with_expression": "children", + "extern_alias_directive": "children", + "file_scoped_namespace_declaration": "children", + "global_attribute": "children", + "block": "children", + "break_statement": "children", + "checked_statement": "children", + "continue_statement": "children", + "do_statement": "children", + "empty_statement": "children", + "expression_statement": "children", + "fixed_statement": "children", + "for_statement": "children", + "foreach_statement": "children", + "goto_statement": "children", + "if_statement": "children", + "labeled_statement": "children", + "local_declaration_statement": "children", + "local_function_statement": "children", + "lock_statement": "children", + "return_statement": "children", + "switch_statement": "children", + "throw_statement": "children", + "try_statement": "children", + "unsafe_statement": "children", + "using_statement": "children", + "while_statement": "children", + "yield_statement": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "preproc_else": { + "type": "preproc_else", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": { + "class_declaration": "children", + "constructor_declaration": "children", + "conversion_operator_declaration": "children", + "delegate_declaration": "children", + "destructor_declaration": "children", + "enum_declaration": "children", + "event_declaration": "children", + "event_field_declaration": "children", + "field_declaration": "children", + "indexer_declaration": "children", + "interface_declaration": "children", + "method_declaration": "children", + "namespace_declaration": "children", + "operator_declaration": "children", + "preproc_if": "children", + "property_declaration": "children", + "record_declaration": "children", + "struct_declaration": "children", + "using_directive": "children", + "enum_member_declaration": "children", + "element_access_expression": "children", + "element_binding_expression": "children", + "generic_name": "children", + "identifier": "children", + "member_access_expression": "children", + "parenthesized_expression": "children", + "prefix_unary_expression": "children", + "this": "children", + "tuple_expression": "children", + "anonymous_method_expression": "children", + "anonymous_object_creation_expression": "children", + "array_creation_expression": "children", + "as_expression": "children", + "assignment_expression": "children", + "await_expression": "children", + "base": "children", + "binary_expression": "children", + "cast_expression": "children", + "checked_expression": "children", + "conditional_access_expression": "children", + "conditional_expression": "children", + "default_expression": "children", + "implicit_array_creation_expression": "children", + "implicit_object_creation_expression": "children", + "implicit_stackalloc_expression": "children", + "initializer_expression": "children", + "interpolated_string_expression": "children", + "invocation_expression": "children", + "is_expression": "children", + "is_pattern_expression": "children", + "lambda_expression": "children", + "boolean_literal": "children", + "character_literal": "children", + "integer_literal": "children", + "null_literal": "children", + "raw_string_literal": "children", + "real_literal": "children", + "string_literal": "children", + "verbatim_string_literal": "children", + "makeref_expression": "children", + "object_creation_expression": "children", + "postfix_unary_expression": "children", + "query_expression": "children", + "range_expression": "children", + "ref_expression": "children", + "reftype_expression": "children", + "refvalue_expression": "children", + "sizeof_expression": "children", + "stackalloc_expression": "children", + "switch_expression": "children", + "throw_expression": "children", + "typeof_expression": "children", + "with_expression": "children", + "extern_alias_directive": "children", + "file_scoped_namespace_declaration": "children", + "global_attribute": "children", + "block": "children", + "break_statement": "children", + "checked_statement": "children", + "continue_statement": "children", + "do_statement": "children", + "empty_statement": "children", + "expression_statement": "children", + "fixed_statement": "children", + "for_statement": "children", + "foreach_statement": "children", + "goto_statement": "children", + "if_statement": "children", + "labeled_statement": "children", + "local_declaration_statement": "children", + "local_function_statement": "children", + "lock_statement": "children", + "return_statement": "children", + "switch_statement": "children", + "throw_statement": "children", + "try_statement": "children", + "unsafe_statement": "children", + "using_statement": "children", + "while_statement": "children", + "yield_statement": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "preproc_endregion": { + "type": "preproc_endregion", + "singleFieldNames": ["content"], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "preproc_error": { + "type": "preproc_error", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": { + "preproc_arg": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "preproc_if": { + "type": "preproc_if", + "singleFieldNames": ["alternative", "condition"], + "nodeTypeToMultipleFieldName": { + "class_declaration": "children", + "constructor_declaration": "children", + "conversion_operator_declaration": "children", + "delegate_declaration": "children", + "destructor_declaration": "children", + "enum_declaration": "children", + "event_declaration": "children", + "event_field_declaration": "children", + "field_declaration": "children", + "indexer_declaration": "children", + "interface_declaration": "children", + "method_declaration": "children", + "namespace_declaration": "children", + "operator_declaration": "children", + "preproc_if": "children", + "property_declaration": "children", + "record_declaration": "children", + "struct_declaration": "children", + "using_directive": "children", + "enum_member_declaration": "children", + "element_access_expression": "children", + "element_binding_expression": "children", + "generic_name": "children", + "identifier": "children", + "member_access_expression": "children", + "parenthesized_expression": "children", + "prefix_unary_expression": "children", + "this": "children", + "tuple_expression": "children", + "anonymous_method_expression": "children", + "anonymous_object_creation_expression": "children", + "array_creation_expression": "children", + "as_expression": "children", + "assignment_expression": "children", + "await_expression": "children", + "base": "children", + "binary_expression": "children", + "cast_expression": "children", + "checked_expression": "children", + "conditional_access_expression": "children", + "conditional_expression": "children", + "default_expression": "children", + "implicit_array_creation_expression": "children", + "implicit_object_creation_expression": "children", + "implicit_stackalloc_expression": "children", + "initializer_expression": "children", + "interpolated_string_expression": "children", + "invocation_expression": "children", + "is_expression": "children", + "is_pattern_expression": "children", + "lambda_expression": "children", + "boolean_literal": "children", + "character_literal": "children", + "integer_literal": "children", + "null_literal": "children", + "raw_string_literal": "children", + "real_literal": "children", + "string_literal": "children", + "verbatim_string_literal": "children", + "makeref_expression": "children", + "object_creation_expression": "children", + "postfix_unary_expression": "children", + "query_expression": "children", + "range_expression": "children", + "ref_expression": "children", + "reftype_expression": "children", + "refvalue_expression": "children", + "sizeof_expression": "children", + "stackalloc_expression": "children", + "switch_expression": "children", + "throw_expression": "children", + "typeof_expression": "children", + "with_expression": "children", + "extern_alias_directive": "children", + "file_scoped_namespace_declaration": "children", + "global_attribute": "children", + "block": "children", + "break_statement": "children", + "checked_statement": "children", + "continue_statement": "children", + "do_statement": "children", + "empty_statement": "children", + "expression_statement": "children", + "fixed_statement": "children", + "for_statement": "children", + "foreach_statement": "children", + "goto_statement": "children", + "if_statement": "children", + "labeled_statement": "children", + "local_declaration_statement": "children", + "local_function_statement": "children", + "lock_statement": "children", + "return_statement": "children", + "switch_statement": "children", + "throw_statement": "children", + "try_statement": "children", + "unsafe_statement": "children", + "using_statement": "children", + "while_statement": "children", + "yield_statement": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "preproc_line": { + "type": "preproc_line", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": { + "integer_literal": "children", + "string_literal": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "preproc_nullable": { + "type": "preproc_nullable", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "preproc_pragma": { + "type": "preproc_pragma", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": { + "identifier": "children", + "integer_literal": "children", + "string_literal": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "preproc_region": { + "type": "preproc_region", + "singleFieldNames": ["content"], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "preproc_undef": { + "type": "preproc_undef", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": { + "preproc_arg": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "primary_constructor_base_type": { + "type": "primary_constructor_base_type", + "singleFieldNames": ["type"], + "nodeTypeToMultipleFieldName": { + "argument_list": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "property_declaration": { + "type": "property_declaration", + "singleFieldNames": ["accessors", "name", "type", "value"], + "nodeTypeToMultipleFieldName": { + "attribute_list": "children", + "explicit_interface_specifier": "children", + "modifier": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "property_pattern_clause": { + "type": "property_pattern_clause", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": { + "subpattern": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "qualified_name": { + "type": "qualified_name", + "singleFieldNames": ["name", "qualifier"], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "query_expression": { + "type": "query_expression", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": { + "from_clause": "children", + "group_clause": "children", + "identifier": "children", + "join_clause": "children", + "let_clause": "children", + "order_by_clause": "children", + "select_clause": "children", + "where_clause": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "range_expression": { + "type": "range_expression", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": { + "element_access_expression": "children", + "element_binding_expression": "children", + "generic_name": "children", + "identifier": "children", + "member_access_expression": "children", + "parenthesized_expression": "children", + "prefix_unary_expression": "children", + "this": "children", + "tuple_expression": "children", + "anonymous_method_expression": "children", + "anonymous_object_creation_expression": "children", + "array_creation_expression": "children", + "as_expression": "children", + "assignment_expression": "children", + "await_expression": "children", + "base": "children", + "binary_expression": "children", + "cast_expression": "children", + "checked_expression": "children", + "conditional_access_expression": "children", + "conditional_expression": "children", + "default_expression": "children", + "implicit_array_creation_expression": "children", + "implicit_object_creation_expression": "children", + "implicit_stackalloc_expression": "children", + "initializer_expression": "children", + "interpolated_string_expression": "children", + "invocation_expression": "children", + "is_expression": "children", + "is_pattern_expression": "children", + "lambda_expression": "children", + "boolean_literal": "children", + "character_literal": "children", + "integer_literal": "children", + "null_literal": "children", + "raw_string_literal": "children", + "real_literal": "children", + "string_literal": "children", + "verbatim_string_literal": "children", + "makeref_expression": "children", + "object_creation_expression": "children", + "postfix_unary_expression": "children", + "preproc_if": "children", + "query_expression": "children", + "range_expression": "children", + "ref_expression": "children", + "reftype_expression": "children", + "refvalue_expression": "children", + "sizeof_expression": "children", + "stackalloc_expression": "children", + "switch_expression": "children", + "throw_expression": "children", + "typeof_expression": "children", + "with_expression": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "raw_string_literal": { + "type": "raw_string_literal", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": { + "raw_string_content": "children", + "raw_string_end": "children", + "raw_string_start": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "record_declaration": { + "type": "record_declaration", + "singleFieldNames": ["body", "name"], + "nodeTypeToMultipleFieldName": { + "attribute_list": "children", + "base_list": "children", + "modifier": "children", + "parameter_list": "children", + "type_parameter_constraints_clause": "children", + "type_parameter_list": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "recursive_pattern": { + "type": "recursive_pattern", + "singleFieldNames": ["name", "type"], + "nodeTypeToMultipleFieldName": { + "discard": "children", + "parenthesized_variable_designation": "children", + "positional_pattern_clause": "children", + "property_pattern_clause": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "ref_expression": { + "type": "ref_expression", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": { + "element_access_expression": "children", + "element_binding_expression": "children", + "generic_name": "children", + "identifier": "children", + "member_access_expression": "children", + "parenthesized_expression": "children", + "prefix_unary_expression": "children", + "this": "children", + "tuple_expression": "children", + "anonymous_method_expression": "children", + "anonymous_object_creation_expression": "children", + "array_creation_expression": "children", + "as_expression": "children", + "assignment_expression": "children", + "await_expression": "children", + "base": "children", + "binary_expression": "children", + "cast_expression": "children", + "checked_expression": "children", + "conditional_access_expression": "children", + "conditional_expression": "children", + "default_expression": "children", + "implicit_array_creation_expression": "children", + "implicit_object_creation_expression": "children", + "implicit_stackalloc_expression": "children", + "initializer_expression": "children", + "interpolated_string_expression": "children", + "invocation_expression": "children", + "is_expression": "children", + "is_pattern_expression": "children", + "lambda_expression": "children", + "boolean_literal": "children", + "character_literal": "children", + "integer_literal": "children", + "null_literal": "children", + "raw_string_literal": "children", + "real_literal": "children", + "string_literal": "children", + "verbatim_string_literal": "children", + "makeref_expression": "children", + "object_creation_expression": "children", + "postfix_unary_expression": "children", + "preproc_if": "children", + "query_expression": "children", + "range_expression": "children", + "ref_expression": "children", + "reftype_expression": "children", + "refvalue_expression": "children", + "sizeof_expression": "children", + "stackalloc_expression": "children", + "switch_expression": "children", + "throw_expression": "children", + "typeof_expression": "children", + "with_expression": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "ref_type": { + "type": "ref_type", + "singleFieldNames": ["type"], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "reftype_expression": { + "type": "reftype_expression", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": { + "element_access_expression": "children", + "element_binding_expression": "children", + "generic_name": "children", + "identifier": "children", + "member_access_expression": "children", + "parenthesized_expression": "children", + "prefix_unary_expression": "children", + "this": "children", + "tuple_expression": "children", + "anonymous_method_expression": "children", + "anonymous_object_creation_expression": "children", + "array_creation_expression": "children", + "as_expression": "children", + "assignment_expression": "children", + "await_expression": "children", + "base": "children", + "binary_expression": "children", + "cast_expression": "children", + "checked_expression": "children", + "conditional_access_expression": "children", + "conditional_expression": "children", + "default_expression": "children", + "implicit_array_creation_expression": "children", + "implicit_object_creation_expression": "children", + "implicit_stackalloc_expression": "children", + "initializer_expression": "children", + "interpolated_string_expression": "children", + "invocation_expression": "children", + "is_expression": "children", + "is_pattern_expression": "children", + "lambda_expression": "children", + "boolean_literal": "children", + "character_literal": "children", + "integer_literal": "children", + "null_literal": "children", + "raw_string_literal": "children", + "real_literal": "children", + "string_literal": "children", + "verbatim_string_literal": "children", + "makeref_expression": "children", + "object_creation_expression": "children", + "postfix_unary_expression": "children", + "preproc_if": "children", + "query_expression": "children", + "range_expression": "children", + "ref_expression": "children", + "reftype_expression": "children", + "refvalue_expression": "children", + "sizeof_expression": "children", + "stackalloc_expression": "children", + "switch_expression": "children", + "throw_expression": "children", + "typeof_expression": "children", + "with_expression": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "refvalue_expression": { + "type": "refvalue_expression", + "singleFieldNames": ["type", "value"], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "relational_pattern": { + "type": "relational_pattern", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": { + "element_access_expression": "children", + "element_binding_expression": "children", + "generic_name": "children", + "identifier": "children", + "member_access_expression": "children", + "parenthesized_expression": "children", + "prefix_unary_expression": "children", + "this": "children", + "tuple_expression": "children", + "anonymous_method_expression": "children", + "anonymous_object_creation_expression": "children", + "array_creation_expression": "children", + "as_expression": "children", + "assignment_expression": "children", + "await_expression": "children", + "base": "children", + "binary_expression": "children", + "cast_expression": "children", + "checked_expression": "children", + "conditional_access_expression": "children", + "conditional_expression": "children", + "default_expression": "children", + "implicit_array_creation_expression": "children", + "implicit_object_creation_expression": "children", + "implicit_stackalloc_expression": "children", + "initializer_expression": "children", + "interpolated_string_expression": "children", + "invocation_expression": "children", + "is_expression": "children", + "is_pattern_expression": "children", + "lambda_expression": "children", + "boolean_literal": "children", + "character_literal": "children", + "integer_literal": "children", + "null_literal": "children", + "raw_string_literal": "children", + "real_literal": "children", + "string_literal": "children", + "verbatim_string_literal": "children", + "makeref_expression": "children", + "object_creation_expression": "children", + "postfix_unary_expression": "children", + "preproc_if": "children", + "query_expression": "children", + "range_expression": "children", + "ref_expression": "children", + "reftype_expression": "children", + "refvalue_expression": "children", + "sizeof_expression": "children", + "stackalloc_expression": "children", + "switch_expression": "children", + "throw_expression": "children", + "typeof_expression": "children", + "with_expression": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "return_statement": { + "type": "return_statement", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": { + "element_access_expression": "children", + "element_binding_expression": "children", + "generic_name": "children", + "identifier": "children", + "member_access_expression": "children", + "parenthesized_expression": "children", + "prefix_unary_expression": "children", + "this": "children", + "tuple_expression": "children", + "anonymous_method_expression": "children", + "anonymous_object_creation_expression": "children", + "array_creation_expression": "children", + "as_expression": "children", + "assignment_expression": "children", + "await_expression": "children", + "base": "children", + "binary_expression": "children", + "cast_expression": "children", + "checked_expression": "children", + "conditional_access_expression": "children", + "conditional_expression": "children", + "default_expression": "children", + "implicit_array_creation_expression": "children", + "implicit_object_creation_expression": "children", + "implicit_stackalloc_expression": "children", + "initializer_expression": "children", + "interpolated_string_expression": "children", + "invocation_expression": "children", + "is_expression": "children", + "is_pattern_expression": "children", + "lambda_expression": "children", + "boolean_literal": "children", + "character_literal": "children", + "integer_literal": "children", + "null_literal": "children", + "raw_string_literal": "children", + "real_literal": "children", + "string_literal": "children", + "verbatim_string_literal": "children", + "makeref_expression": "children", + "object_creation_expression": "children", + "postfix_unary_expression": "children", + "preproc_if": "children", + "query_expression": "children", + "range_expression": "children", + "ref_expression": "children", + "reftype_expression": "children", + "refvalue_expression": "children", + "sizeof_expression": "children", + "stackalloc_expression": "children", + "switch_expression": "children", + "throw_expression": "children", + "typeof_expression": "children", + "with_expression": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "scoped_type": { + "type": "scoped_type", + "singleFieldNames": ["type"], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "select_clause": { + "type": "select_clause", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": { + "element_access_expression": "children", + "element_binding_expression": "children", + "generic_name": "children", + "identifier": "children", + "member_access_expression": "children", + "parenthesized_expression": "children", + "prefix_unary_expression": "children", + "this": "children", + "tuple_expression": "children", + "anonymous_method_expression": "children", + "anonymous_object_creation_expression": "children", + "array_creation_expression": "children", + "as_expression": "children", + "assignment_expression": "children", + "await_expression": "children", + "base": "children", + "binary_expression": "children", + "cast_expression": "children", + "checked_expression": "children", + "conditional_access_expression": "children", + "conditional_expression": "children", + "default_expression": "children", + "implicit_array_creation_expression": "children", + "implicit_object_creation_expression": "children", + "implicit_stackalloc_expression": "children", + "initializer_expression": "children", + "interpolated_string_expression": "children", + "invocation_expression": "children", + "is_expression": "children", + "is_pattern_expression": "children", + "lambda_expression": "children", + "boolean_literal": "children", + "character_literal": "children", + "integer_literal": "children", + "null_literal": "children", + "raw_string_literal": "children", + "real_literal": "children", + "string_literal": "children", + "verbatim_string_literal": "children", + "makeref_expression": "children", + "object_creation_expression": "children", + "postfix_unary_expression": "children", + "preproc_if": "children", + "query_expression": "children", + "range_expression": "children", + "ref_expression": "children", + "reftype_expression": "children", + "refvalue_expression": "children", + "sizeof_expression": "children", + "stackalloc_expression": "children", + "switch_expression": "children", + "throw_expression": "children", + "typeof_expression": "children", + "with_expression": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "sizeof_expression": { + "type": "sizeof_expression", + "singleFieldNames": ["type"], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "stackalloc_expression": { + "type": "stackalloc_expression", + "singleFieldNames": ["type"], + "nodeTypeToMultipleFieldName": { + "initializer_expression": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "string_literal": { + "type": "string_literal", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": { + "escape_sequence": "children", + "string_literal_content": "children", + "string_literal_encoding": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "string_literal_content": { + "type": "string_literal_content", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "struct_declaration": { + "type": "struct_declaration", + "singleFieldNames": ["body", "name"], + "nodeTypeToMultipleFieldName": { + "attribute_list": "children", + "base_list": "children", + "modifier": "children", + "parameter_list": "children", + "type_parameter_constraints_clause": "children", + "type_parameter_list": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "subpattern": { + "type": "subpattern", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": { + "element_access_expression": "children", + "element_binding_expression": "children", + "generic_name": "children", + "identifier": "children", + "member_access_expression": "children", + "parenthesized_expression": "children", + "prefix_unary_expression": "children", + "this": "children", + "tuple_expression": "children", + "anonymous_method_expression": "children", + "anonymous_object_creation_expression": "children", + "array_creation_expression": "children", + "as_expression": "children", + "assignment_expression": "children", + "await_expression": "children", + "base": "children", + "binary_expression": "children", + "cast_expression": "children", + "checked_expression": "children", + "conditional_access_expression": "children", + "conditional_expression": "children", + "default_expression": "children", + "implicit_array_creation_expression": "children", + "implicit_object_creation_expression": "children", + "implicit_stackalloc_expression": "children", + "initializer_expression": "children", + "interpolated_string_expression": "children", + "invocation_expression": "children", + "is_expression": "children", + "is_pattern_expression": "children", + "lambda_expression": "children", + "boolean_literal": "children", + "character_literal": "children", + "integer_literal": "children", + "null_literal": "children", + "raw_string_literal": "children", + "real_literal": "children", + "string_literal": "children", + "verbatim_string_literal": "children", + "makeref_expression": "children", + "object_creation_expression": "children", + "postfix_unary_expression": "children", + "preproc_if": "children", + "query_expression": "children", + "range_expression": "children", + "ref_expression": "children", + "reftype_expression": "children", + "refvalue_expression": "children", + "sizeof_expression": "children", + "stackalloc_expression": "children", + "switch_expression": "children", + "throw_expression": "children", + "typeof_expression": "children", + "with_expression": "children", + "and_pattern": "children", + "constant_pattern": "children", + "declaration_pattern": "children", + "discard": "children", + "list_pattern": "children", + "negated_pattern": "children", + "or_pattern": "children", + "parenthesized_pattern": "children", + "recursive_pattern": "children", + "relational_pattern": "children", + "type_pattern": "children", + "var_pattern": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "switch_body": { + "type": "switch_body", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": { + "switch_section": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "switch_expression": { + "type": "switch_expression", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": { + "element_access_expression": "children", + "element_binding_expression": "children", + "generic_name": "children", + "identifier": "children", + "member_access_expression": "children", + "parenthesized_expression": "children", + "prefix_unary_expression": "children", + "this": "children", + "tuple_expression": "children", + "anonymous_method_expression": "children", + "anonymous_object_creation_expression": "children", + "array_creation_expression": "children", + "as_expression": "children", + "assignment_expression": "children", + "await_expression": "children", + "base": "children", + "binary_expression": "children", + "cast_expression": "children", + "checked_expression": "children", + "conditional_access_expression": "children", + "conditional_expression": "children", + "default_expression": "children", + "implicit_array_creation_expression": "children", + "implicit_object_creation_expression": "children", + "implicit_stackalloc_expression": "children", + "initializer_expression": "children", + "interpolated_string_expression": "children", + "invocation_expression": "children", + "is_expression": "children", + "is_pattern_expression": "children", + "lambda_expression": "children", + "boolean_literal": "children", + "character_literal": "children", + "integer_literal": "children", + "null_literal": "children", + "raw_string_literal": "children", + "real_literal": "children", + "string_literal": "children", + "verbatim_string_literal": "children", + "makeref_expression": "children", + "object_creation_expression": "children", + "postfix_unary_expression": "children", + "preproc_if": "children", + "query_expression": "children", + "range_expression": "children", + "ref_expression": "children", + "reftype_expression": "children", + "refvalue_expression": "children", + "sizeof_expression": "children", + "stackalloc_expression": "children", + "switch_expression": "children", + "throw_expression": "children", + "typeof_expression": "children", + "with_expression": "children", + "switch_expression_arm": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "switch_expression_arm": { + "type": "switch_expression_arm", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": { + "element_access_expression": "children", + "element_binding_expression": "children", + "generic_name": "children", + "identifier": "children", + "member_access_expression": "children", + "parenthesized_expression": "children", + "prefix_unary_expression": "children", + "this": "children", + "tuple_expression": "children", + "anonymous_method_expression": "children", + "anonymous_object_creation_expression": "children", + "array_creation_expression": "children", + "as_expression": "children", + "assignment_expression": "children", + "await_expression": "children", + "base": "children", + "binary_expression": "children", + "cast_expression": "children", + "checked_expression": "children", + "conditional_access_expression": "children", + "conditional_expression": "children", + "default_expression": "children", + "implicit_array_creation_expression": "children", + "implicit_object_creation_expression": "children", + "implicit_stackalloc_expression": "children", + "initializer_expression": "children", + "interpolated_string_expression": "children", + "invocation_expression": "children", + "is_expression": "children", + "is_pattern_expression": "children", + "lambda_expression": "children", + "boolean_literal": "children", + "character_literal": "children", + "integer_literal": "children", + "null_literal": "children", + "raw_string_literal": "children", + "real_literal": "children", + "string_literal": "children", + "verbatim_string_literal": "children", + "makeref_expression": "children", + "object_creation_expression": "children", + "postfix_unary_expression": "children", + "preproc_if": "children", + "query_expression": "children", + "range_expression": "children", + "ref_expression": "children", + "reftype_expression": "children", + "refvalue_expression": "children", + "sizeof_expression": "children", + "stackalloc_expression": "children", + "switch_expression": "children", + "throw_expression": "children", + "typeof_expression": "children", + "with_expression": "children", + "and_pattern": "children", + "constant_pattern": "children", + "declaration_pattern": "children", + "discard": "children", + "list_pattern": "children", + "negated_pattern": "children", + "or_pattern": "children", + "parenthesized_pattern": "children", + "recursive_pattern": "children", + "relational_pattern": "children", + "type_pattern": "children", + "var_pattern": "children", + "when_clause": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "switch_section": { + "type": "switch_section", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": { + "element_access_expression": "children", + "element_binding_expression": "children", + "generic_name": "children", + "identifier": "children", + "member_access_expression": "children", + "parenthesized_expression": "children", + "prefix_unary_expression": "children", + "this": "children", + "tuple_expression": "children", + "anonymous_method_expression": "children", + "anonymous_object_creation_expression": "children", + "array_creation_expression": "children", + "as_expression": "children", + "assignment_expression": "children", + "await_expression": "children", + "base": "children", + "binary_expression": "children", + "cast_expression": "children", + "checked_expression": "children", + "conditional_access_expression": "children", + "conditional_expression": "children", + "default_expression": "children", + "implicit_array_creation_expression": "children", + "implicit_object_creation_expression": "children", + "implicit_stackalloc_expression": "children", + "initializer_expression": "children", + "interpolated_string_expression": "children", + "invocation_expression": "children", + "is_expression": "children", + "is_pattern_expression": "children", + "lambda_expression": "children", + "boolean_literal": "children", + "character_literal": "children", + "integer_literal": "children", + "null_literal": "children", + "raw_string_literal": "children", + "real_literal": "children", + "string_literal": "children", + "verbatim_string_literal": "children", + "makeref_expression": "children", + "object_creation_expression": "children", + "postfix_unary_expression": "children", + "preproc_if": "children", + "query_expression": "children", + "range_expression": "children", + "ref_expression": "children", + "reftype_expression": "children", + "refvalue_expression": "children", + "sizeof_expression": "children", + "stackalloc_expression": "children", + "switch_expression": "children", + "throw_expression": "children", + "typeof_expression": "children", + "with_expression": "children", + "and_pattern": "children", + "constant_pattern": "children", + "declaration_pattern": "children", + "discard": "children", + "list_pattern": "children", + "negated_pattern": "children", + "or_pattern": "children", + "parenthesized_pattern": "children", + "recursive_pattern": "children", + "relational_pattern": "children", + "type_pattern": "children", + "var_pattern": "children", + "block": "children", + "break_statement": "children", + "checked_statement": "children", + "continue_statement": "children", + "do_statement": "children", + "empty_statement": "children", + "expression_statement": "children", + "fixed_statement": "children", + "for_statement": "children", + "foreach_statement": "children", + "goto_statement": "children", + "if_statement": "children", + "labeled_statement": "children", + "local_declaration_statement": "children", + "local_function_statement": "children", + "lock_statement": "children", + "return_statement": "children", + "switch_statement": "children", + "throw_statement": "children", + "try_statement": "children", + "unsafe_statement": "children", + "using_statement": "children", + "while_statement": "children", + "yield_statement": "children", + "when_clause": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "switch_statement": { + "type": "switch_statement", + "singleFieldNames": ["body", "value"], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "throw_expression": { + "type": "throw_expression", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": { + "element_access_expression": "children", + "element_binding_expression": "children", + "generic_name": "children", + "identifier": "children", + "member_access_expression": "children", + "parenthesized_expression": "children", + "prefix_unary_expression": "children", + "this": "children", + "tuple_expression": "children", + "anonymous_method_expression": "children", + "anonymous_object_creation_expression": "children", + "array_creation_expression": "children", + "as_expression": "children", + "assignment_expression": "children", + "await_expression": "children", + "base": "children", + "binary_expression": "children", + "cast_expression": "children", + "checked_expression": "children", + "conditional_access_expression": "children", + "conditional_expression": "children", + "default_expression": "children", + "implicit_array_creation_expression": "children", + "implicit_object_creation_expression": "children", + "implicit_stackalloc_expression": "children", + "initializer_expression": "children", + "interpolated_string_expression": "children", + "invocation_expression": "children", + "is_expression": "children", + "is_pattern_expression": "children", + "lambda_expression": "children", + "boolean_literal": "children", + "character_literal": "children", + "integer_literal": "children", + "null_literal": "children", + "raw_string_literal": "children", + "real_literal": "children", + "string_literal": "children", + "verbatim_string_literal": "children", + "makeref_expression": "children", + "object_creation_expression": "children", + "postfix_unary_expression": "children", + "preproc_if": "children", + "query_expression": "children", + "range_expression": "children", + "ref_expression": "children", + "reftype_expression": "children", + "refvalue_expression": "children", + "sizeof_expression": "children", + "stackalloc_expression": "children", + "switch_expression": "children", + "throw_expression": "children", + "typeof_expression": "children", + "with_expression": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "throw_statement": { + "type": "throw_statement", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": { + "element_access_expression": "children", + "element_binding_expression": "children", + "generic_name": "children", + "identifier": "children", + "member_access_expression": "children", + "parenthesized_expression": "children", + "prefix_unary_expression": "children", + "this": "children", + "tuple_expression": "children", + "anonymous_method_expression": "children", + "anonymous_object_creation_expression": "children", + "array_creation_expression": "children", + "as_expression": "children", + "assignment_expression": "children", + "await_expression": "children", + "base": "children", + "binary_expression": "children", + "cast_expression": "children", + "checked_expression": "children", + "conditional_access_expression": "children", + "conditional_expression": "children", + "default_expression": "children", + "implicit_array_creation_expression": "children", + "implicit_object_creation_expression": "children", + "implicit_stackalloc_expression": "children", + "initializer_expression": "children", + "interpolated_string_expression": "children", + "invocation_expression": "children", + "is_expression": "children", + "is_pattern_expression": "children", + "lambda_expression": "children", + "boolean_literal": "children", + "character_literal": "children", + "integer_literal": "children", + "null_literal": "children", + "raw_string_literal": "children", + "real_literal": "children", + "string_literal": "children", + "verbatim_string_literal": "children", + "makeref_expression": "children", + "object_creation_expression": "children", + "postfix_unary_expression": "children", + "preproc_if": "children", + "query_expression": "children", + "range_expression": "children", + "ref_expression": "children", + "reftype_expression": "children", + "refvalue_expression": "children", + "sizeof_expression": "children", + "stackalloc_expression": "children", + "switch_expression": "children", + "throw_expression": "children", + "typeof_expression": "children", + "with_expression": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "try_statement": { + "type": "try_statement", + "singleFieldNames": ["body"], + "nodeTypeToMultipleFieldName": { + "catch_clause": "children", + "finally_clause": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "tuple_element": { + "type": "tuple_element", + "singleFieldNames": ["name", "type"], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "tuple_expression": { + "type": "tuple_expression", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": { + "argument": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "tuple_pattern": { + "type": "tuple_pattern", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": { + "identifier": "name", + "discard": "children", + "tuple_pattern": "children" + }, + "multipleOrChildrenFieldNames": ["name", "children"] + }, + "tuple_type": { + "type": "tuple_type", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": { + "tuple_element": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "type_argument_list": { + "type": "type_argument_list", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": { + "alias_qualified_name": "children", + "array_type": "children", + "function_pointer_type": "children", + "generic_name": "children", + "identifier": "children", + "implicit_type": "children", + "nullable_type": "children", + "pointer_type": "children", + "predefined_type": "children", + "qualified_name": "children", + "ref_type": "children", + "scoped_type": "children", + "tuple_type": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "type_parameter": { + "type": "type_parameter", + "singleFieldNames": ["name"], + "nodeTypeToMultipleFieldName": { + "attribute_list": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "type_parameter_constraint": { + "type": "type_parameter_constraint", + "singleFieldNames": ["type"], + "nodeTypeToMultipleFieldName": { + "constructor_constraint": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "type_parameter_constraints_clause": { + "type": "type_parameter_constraints_clause", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": { + "identifier": "children", + "type_parameter_constraint": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "type_parameter_list": { + "type": "type_parameter_list", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": { + "type_parameter": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "type_pattern": { + "type": "type_pattern", + "singleFieldNames": ["type"], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "typeof_expression": { + "type": "typeof_expression", + "singleFieldNames": ["type"], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "unary_expression": { + "type": "unary_expression", + "singleFieldNames": ["argument", "operator"], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "unsafe_statement": { + "type": "unsafe_statement", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": { + "block": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "using_directive": { + "type": "using_directive", + "singleFieldNames": ["name"], + "nodeTypeToMultipleFieldName": { + "alias_qualified_name": "children", + "array_type": "children", + "function_pointer_type": "children", + "generic_name": "children", + "identifier": "children", + "implicit_type": "children", + "nullable_type": "children", + "pointer_type": "children", + "predefined_type": "children", + "qualified_name": "children", + "ref_type": "children", + "scoped_type": "children", + "tuple_type": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "using_statement": { + "type": "using_statement", + "singleFieldNames": ["body"], + "nodeTypeToMultipleFieldName": { + "element_access_expression": "children", + "element_binding_expression": "children", + "generic_name": "children", + "identifier": "children", + "member_access_expression": "children", + "parenthesized_expression": "children", + "prefix_unary_expression": "children", + "this": "children", + "tuple_expression": "children", + "anonymous_method_expression": "children", + "anonymous_object_creation_expression": "children", + "array_creation_expression": "children", + "as_expression": "children", + "assignment_expression": "children", + "await_expression": "children", + "base": "children", + "binary_expression": "children", + "cast_expression": "children", + "checked_expression": "children", + "conditional_access_expression": "children", + "conditional_expression": "children", + "default_expression": "children", + "implicit_array_creation_expression": "children", + "implicit_object_creation_expression": "children", + "implicit_stackalloc_expression": "children", + "initializer_expression": "children", + "interpolated_string_expression": "children", + "invocation_expression": "children", + "is_expression": "children", + "is_pattern_expression": "children", + "lambda_expression": "children", + "boolean_literal": "children", + "character_literal": "children", + "integer_literal": "children", + "null_literal": "children", + "raw_string_literal": "children", + "real_literal": "children", + "string_literal": "children", + "verbatim_string_literal": "children", + "makeref_expression": "children", + "object_creation_expression": "children", + "postfix_unary_expression": "children", + "preproc_if": "children", + "query_expression": "children", + "range_expression": "children", + "ref_expression": "children", + "reftype_expression": "children", + "refvalue_expression": "children", + "sizeof_expression": "children", + "stackalloc_expression": "children", + "switch_expression": "children", + "throw_expression": "children", + "typeof_expression": "children", + "with_expression": "children", + "variable_declaration": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "var_pattern": { + "type": "var_pattern", + "singleFieldNames": ["name"], + "nodeTypeToMultipleFieldName": { + "discard": "children", + "parenthesized_variable_designation": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "variable_declaration": { + "type": "variable_declaration", + "singleFieldNames": ["type"], + "nodeTypeToMultipleFieldName": { + "variable_declarator": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "variable_declarator": { + "type": "variable_declarator", + "singleFieldNames": ["name"], + "nodeTypeToMultipleFieldName": { + "bracketed_argument_list": "children", + "element_access_expression": "children", + "element_binding_expression": "children", + "generic_name": "children", + "identifier": "children", + "member_access_expression": "children", + "parenthesized_expression": "children", + "prefix_unary_expression": "children", + "this": "children", + "tuple_expression": "children", + "anonymous_method_expression": "children", + "anonymous_object_creation_expression": "children", + "array_creation_expression": "children", + "as_expression": "children", + "assignment_expression": "children", + "await_expression": "children", + "base": "children", + "binary_expression": "children", + "cast_expression": "children", + "checked_expression": "children", + "conditional_access_expression": "children", + "conditional_expression": "children", + "default_expression": "children", + "implicit_array_creation_expression": "children", + "implicit_object_creation_expression": "children", + "implicit_stackalloc_expression": "children", + "initializer_expression": "children", + "interpolated_string_expression": "children", + "invocation_expression": "children", + "is_expression": "children", + "is_pattern_expression": "children", + "lambda_expression": "children", + "boolean_literal": "children", + "character_literal": "children", + "integer_literal": "children", + "null_literal": "children", + "raw_string_literal": "children", + "real_literal": "children", + "string_literal": "children", + "verbatim_string_literal": "children", + "makeref_expression": "children", + "object_creation_expression": "children", + "postfix_unary_expression": "children", + "preproc_if": "children", + "query_expression": "children", + "range_expression": "children", + "ref_expression": "children", + "reftype_expression": "children", + "refvalue_expression": "children", + "sizeof_expression": "children", + "stackalloc_expression": "children", + "switch_expression": "children", + "throw_expression": "children", + "typeof_expression": "children", + "with_expression": "children", + "tuple_pattern": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "when_clause": { + "type": "when_clause", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": { + "element_access_expression": "children", + "element_binding_expression": "children", + "generic_name": "children", + "identifier": "children", + "member_access_expression": "children", + "parenthesized_expression": "children", + "prefix_unary_expression": "children", + "this": "children", + "tuple_expression": "children", + "anonymous_method_expression": "children", + "anonymous_object_creation_expression": "children", + "array_creation_expression": "children", + "as_expression": "children", + "assignment_expression": "children", + "await_expression": "children", + "base": "children", + "binary_expression": "children", + "cast_expression": "children", + "checked_expression": "children", + "conditional_access_expression": "children", + "conditional_expression": "children", + "default_expression": "children", + "implicit_array_creation_expression": "children", + "implicit_object_creation_expression": "children", + "implicit_stackalloc_expression": "children", + "initializer_expression": "children", + "interpolated_string_expression": "children", + "invocation_expression": "children", + "is_expression": "children", + "is_pattern_expression": "children", + "lambda_expression": "children", + "boolean_literal": "children", + "character_literal": "children", + "integer_literal": "children", + "null_literal": "children", + "raw_string_literal": "children", + "real_literal": "children", + "string_literal": "children", + "verbatim_string_literal": "children", + "makeref_expression": "children", + "object_creation_expression": "children", + "postfix_unary_expression": "children", + "preproc_if": "children", + "query_expression": "children", + "range_expression": "children", + "ref_expression": "children", + "reftype_expression": "children", + "refvalue_expression": "children", + "sizeof_expression": "children", + "stackalloc_expression": "children", + "switch_expression": "children", + "throw_expression": "children", + "typeof_expression": "children", + "with_expression": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "where_clause": { + "type": "where_clause", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": { + "element_access_expression": "children", + "element_binding_expression": "children", + "generic_name": "children", + "identifier": "children", + "member_access_expression": "children", + "parenthesized_expression": "children", + "prefix_unary_expression": "children", + "this": "children", + "tuple_expression": "children", + "anonymous_method_expression": "children", + "anonymous_object_creation_expression": "children", + "array_creation_expression": "children", + "as_expression": "children", + "assignment_expression": "children", + "await_expression": "children", + "base": "children", + "binary_expression": "children", + "cast_expression": "children", + "checked_expression": "children", + "conditional_access_expression": "children", + "conditional_expression": "children", + "default_expression": "children", + "implicit_array_creation_expression": "children", + "implicit_object_creation_expression": "children", + "implicit_stackalloc_expression": "children", + "initializer_expression": "children", + "interpolated_string_expression": "children", + "invocation_expression": "children", + "is_expression": "children", + "is_pattern_expression": "children", + "lambda_expression": "children", + "boolean_literal": "children", + "character_literal": "children", + "integer_literal": "children", + "null_literal": "children", + "raw_string_literal": "children", + "real_literal": "children", + "string_literal": "children", + "verbatim_string_literal": "children", + "makeref_expression": "children", + "object_creation_expression": "children", + "postfix_unary_expression": "children", + "preproc_if": "children", + "query_expression": "children", + "range_expression": "children", + "ref_expression": "children", + "reftype_expression": "children", + "refvalue_expression": "children", + "sizeof_expression": "children", + "stackalloc_expression": "children", + "switch_expression": "children", + "throw_expression": "children", + "typeof_expression": "children", + "with_expression": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "while_statement": { + "type": "while_statement", + "singleFieldNames": ["body", "condition"], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "with_expression": { + "type": "with_expression", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": { + "element_access_expression": "children", + "element_binding_expression": "children", + "generic_name": "children", + "identifier": "children", + "member_access_expression": "children", + "parenthesized_expression": "children", + "prefix_unary_expression": "children", + "this": "children", + "tuple_expression": "children", + "anonymous_method_expression": "children", + "anonymous_object_creation_expression": "children", + "array_creation_expression": "children", + "as_expression": "children", + "assignment_expression": "children", + "await_expression": "children", + "base": "children", + "binary_expression": "children", + "cast_expression": "children", + "checked_expression": "children", + "conditional_access_expression": "children", + "conditional_expression": "children", + "default_expression": "children", + "implicit_array_creation_expression": "children", + "implicit_object_creation_expression": "children", + "implicit_stackalloc_expression": "children", + "initializer_expression": "children", + "interpolated_string_expression": "children", + "invocation_expression": "children", + "is_expression": "children", + "is_pattern_expression": "children", + "lambda_expression": "children", + "boolean_literal": "children", + "character_literal": "children", + "integer_literal": "children", + "null_literal": "children", + "raw_string_literal": "children", + "real_literal": "children", + "string_literal": "children", + "verbatim_string_literal": "children", + "makeref_expression": "children", + "object_creation_expression": "children", + "postfix_unary_expression": "children", + "preproc_if": "children", + "query_expression": "children", + "range_expression": "children", + "ref_expression": "children", + "reftype_expression": "children", + "refvalue_expression": "children", + "sizeof_expression": "children", + "stackalloc_expression": "children", + "switch_expression": "children", + "throw_expression": "children", + "typeof_expression": "children", + "with_expression": "children", + "with_initializer": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "with_initializer": { + "type": "with_initializer", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": { + "element_access_expression": "children", + "element_binding_expression": "children", + "generic_name": "children", + "identifier": "children", + "member_access_expression": "children", + "parenthesized_expression": "children", + "prefix_unary_expression": "children", + "this": "children", + "tuple_expression": "children", + "anonymous_method_expression": "children", + "anonymous_object_creation_expression": "children", + "array_creation_expression": "children", + "as_expression": "children", + "assignment_expression": "children", + "await_expression": "children", + "base": "children", + "binary_expression": "children", + "cast_expression": "children", + "checked_expression": "children", + "conditional_access_expression": "children", + "conditional_expression": "children", + "default_expression": "children", + "implicit_array_creation_expression": "children", + "implicit_object_creation_expression": "children", + "implicit_stackalloc_expression": "children", + "initializer_expression": "children", + "interpolated_string_expression": "children", + "invocation_expression": "children", + "is_expression": "children", + "is_pattern_expression": "children", + "lambda_expression": "children", + "boolean_literal": "children", + "character_literal": "children", + "integer_literal": "children", + "null_literal": "children", + "raw_string_literal": "children", + "real_literal": "children", + "string_literal": "children", + "verbatim_string_literal": "children", + "makeref_expression": "children", + "object_creation_expression": "children", + "postfix_unary_expression": "children", + "preproc_if": "children", + "query_expression": "children", + "range_expression": "children", + "ref_expression": "children", + "reftype_expression": "children", + "refvalue_expression": "children", + "sizeof_expression": "children", + "stackalloc_expression": "children", + "switch_expression": "children", + "throw_expression": "children", + "typeof_expression": "children", + "with_expression": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "yield_statement": { + "type": "yield_statement", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": { + "element_access_expression": "children", + "element_binding_expression": "children", + "generic_name": "children", + "identifier": "children", + "member_access_expression": "children", + "parenthesized_expression": "children", + "prefix_unary_expression": "children", + "this": "children", + "tuple_expression": "children", + "anonymous_method_expression": "children", + "anonymous_object_creation_expression": "children", + "array_creation_expression": "children", + "as_expression": "children", + "assignment_expression": "children", + "await_expression": "children", + "base": "children", + "binary_expression": "children", + "cast_expression": "children", + "checked_expression": "children", + "conditional_access_expression": "children", + "conditional_expression": "children", + "default_expression": "children", + "implicit_array_creation_expression": "children", + "implicit_object_creation_expression": "children", + "implicit_stackalloc_expression": "children", + "initializer_expression": "children", + "interpolated_string_expression": "children", + "invocation_expression": "children", + "is_expression": "children", + "is_pattern_expression": "children", + "lambda_expression": "children", + "boolean_literal": "children", + "character_literal": "children", + "integer_literal": "children", + "null_literal": "children", + "raw_string_literal": "children", + "real_literal": "children", + "string_literal": "children", + "verbatim_string_literal": "children", + "makeref_expression": "children", + "object_creation_expression": "children", + "postfix_unary_expression": "children", + "preproc_if": "children", + "query_expression": "children", + "range_expression": "children", + "ref_expression": "children", + "reftype_expression": "children", + "refvalue_expression": "children", + "sizeof_expression": "children", + "stackalloc_expression": "children", + "switch_expression": "children", + "throw_expression": "children", + "typeof_expression": "children", + "with_expression": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "!": { + "type": "!", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "!=": { + "type": "!=", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "%": { + "type": "%", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "%=": { + "type": "%=", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "&": { + "type": "&", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "&&": { + "type": "&&", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "&=": { + "type": "&=", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "*": { + "type": "*", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "*=": { + "type": "*=", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "+": { + "type": "+", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "++": { + "type": "++", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "+=": { + "type": "+=", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "-": { + "type": "-", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "--": { + "type": "--", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "-=": { + "type": "-=", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "/": { + "type": "/", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "/=": { + "type": "/=", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "<": { + "type": "<", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "<<": { + "type": "<<", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "<<=": { + "type": "<<=", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "<=": { + "type": "<=", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "=": { + "type": "=", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "==": { + "type": "==", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + ">": { + "type": ">", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + ">=": { + "type": ">=", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + ">>": { + "type": ">>", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + ">>=": { + "type": ">>=", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + ">>>": { + "type": ">>>", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + ">>>=": { + "type": ">>>=", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "??": { + "type": "??", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "??=": { + "type": "??=", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "^": { + "type": "^", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "^=": { + "type": "^=", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "add": { + "type": "add", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "and": { + "type": "and", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "as": { + "type": "as", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "character_literal_content": { + "type": "character_literal_content", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "comment": { + "type": "comment", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "discard": { + "type": "discard", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "escape_sequence": { + "type": "escape_sequence", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "false": { + "type": "false", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "get": { + "type": "get", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "init": { + "type": "init", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "integer_literal": { + "type": "integer_literal", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "interpolation_brace": { + "type": "interpolation_brace", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "interpolation_quote": { + "type": "interpolation_quote", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "interpolation_start": { + "type": "interpolation_start", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "is": { + "type": "is", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "null_literal": { + "type": "null_literal", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "or": { + "type": "or", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "predefined_type": { + "type": "predefined_type", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "preproc_arg": { + "type": "preproc_arg", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "raw_string_content": { + "type": "raw_string_content", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "raw_string_end": { + "type": "raw_string_end", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "raw_string_start": { + "type": "raw_string_start", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "real_literal": { + "type": "real_literal", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "remove": { + "type": "remove", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "set": { + "type": "set", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "shebang_directive": { + "type": "shebang_directive", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "string_content": { + "type": "string_content", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "string_literal_encoding": { + "type": "string_literal_encoding", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "true": { + "type": "true", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "verbatim_string_literal": { + "type": "verbatim_string_literal", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "|": { + "type": "|", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "|=": { + "type": "|=", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "||": { + "type": "||", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "~": { + "type": "~", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + } +} diff --git a/packages/tree-sitter-port/output/tree-sitter-c-sharp/meta.json b/packages/tree-sitter-port/output/tree-sitter-c-sharp/meta.json new file mode 100644 index 0000000..61fdd02 --- /dev/null +++ b/packages/tree-sitter-port/output/tree-sitter-c-sharp/meta.json @@ -0,0 +1,5 @@ +{ + "name": "tree-sitter-c-sharp", + "version": "0.21.2", + "generatedAt": "2024-06-26T15:41:38.824Z" +} diff --git a/packages/tree-sitter-port/output/tree-sitter-c-sharp/parser.wasm b/packages/tree-sitter-port/output/tree-sitter-c-sharp/parser.wasm new file mode 100755 index 0000000..65e8511 Binary files /dev/null and b/packages/tree-sitter-port/output/tree-sitter-c-sharp/parser.wasm differ diff --git a/packages/tree-sitter-port/output/tree-sitter-c/fields-meta.json b/packages/tree-sitter-port/output/tree-sitter-c/fields-meta.json new file mode 100644 index 0000000..d28eed0 --- /dev/null +++ b/packages/tree-sitter-port/output/tree-sitter-c/fields-meta.json @@ -0,0 +1,1729 @@ +{ + "abstract_array_declarator": { + "type": "abstract_array_declarator", + "singleFieldNames": ["declarator", "size"], + "nodeTypeToMultipleFieldName": { + "type_qualifier": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "abstract_function_declarator": { + "type": "abstract_function_declarator", + "singleFieldNames": ["declarator", "parameters"], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "abstract_parenthesized_declarator": { + "type": "abstract_parenthesized_declarator", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": { + "abstract_array_declarator": "children", + "abstract_function_declarator": "children", + "abstract_parenthesized_declarator": "children", + "abstract_pointer_declarator": "children", + "ms_call_modifier": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "abstract_pointer_declarator": { + "type": "abstract_pointer_declarator", + "singleFieldNames": ["declarator"], + "nodeTypeToMultipleFieldName": { + "ms_pointer_modifier": "children", + "type_qualifier": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "alignas_qualifier": { + "type": "alignas_qualifier", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": { + "alignof_expression": "children", + "assignment_expression": "children", + "binary_expression": "children", + "call_expression": "children", + "cast_expression": "children", + "char_literal": "children", + "compound_literal_expression": "children", + "concatenated_string": "children", + "conditional_expression": "children", + "false": "children", + "field_expression": "children", + "generic_expression": "children", + "gnu_asm_expression": "children", + "identifier": "children", + "null": "children", + "number_literal": "children", + "offsetof_expression": "children", + "parenthesized_expression": "children", + "pointer_expression": "children", + "sizeof_expression": "children", + "string_literal": "children", + "subscript_expression": "children", + "true": "children", + "unary_expression": "children", + "update_expression": "children", + "type_descriptor": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "alignof_expression": { + "type": "alignof_expression", + "singleFieldNames": ["type"], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "argument_list": { + "type": "argument_list", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": { + "compound_statement": "children", + "alignof_expression": "children", + "assignment_expression": "children", + "binary_expression": "children", + "call_expression": "children", + "cast_expression": "children", + "char_literal": "children", + "compound_literal_expression": "children", + "concatenated_string": "children", + "conditional_expression": "children", + "false": "children", + "field_expression": "children", + "generic_expression": "children", + "gnu_asm_expression": "children", + "identifier": "children", + "null": "children", + "number_literal": "children", + "offsetof_expression": "children", + "parenthesized_expression": "children", + "pointer_expression": "children", + "sizeof_expression": "children", + "string_literal": "children", + "subscript_expression": "children", + "true": "children", + "unary_expression": "children", + "update_expression": "children", + "preproc_defined": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "array_declarator": { + "type": "array_declarator", + "singleFieldNames": ["declarator", "size"], + "nodeTypeToMultipleFieldName": { + "type_qualifier": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "assignment_expression": { + "type": "assignment_expression", + "singleFieldNames": ["left", "operator", "right"], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "attribute": { + "type": "attribute", + "singleFieldNames": ["name", "prefix"], + "nodeTypeToMultipleFieldName": { + "argument_list": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "attribute_declaration": { + "type": "attribute_declaration", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": { + "attribute": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "attribute_specifier": { + "type": "attribute_specifier", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": { + "argument_list": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "attributed_declarator": { + "type": "attributed_declarator", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": { + "array_declarator": "children", + "attributed_declarator": "children", + "function_declarator": "children", + "identifier": "children", + "parenthesized_declarator": "children", + "pointer_declarator": "children", + "field_identifier": "children", + "primitive_type": "children", + "type_identifier": "children", + "attribute_declaration": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "attributed_statement": { + "type": "attributed_statement", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": { + "attribute_declaration": "children", + "attributed_statement": "children", + "break_statement": "children", + "case_statement": "children", + "compound_statement": "children", + "continue_statement": "children", + "do_statement": "children", + "expression_statement": "children", + "for_statement": "children", + "goto_statement": "children", + "if_statement": "children", + "labeled_statement": "children", + "return_statement": "children", + "seh_leave_statement": "children", + "seh_try_statement": "children", + "switch_statement": "children", + "while_statement": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "binary_expression": { + "type": "binary_expression", + "singleFieldNames": ["left", "operator", "right"], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "bitfield_clause": { + "type": "bitfield_clause", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": { + "alignof_expression": "children", + "assignment_expression": "children", + "binary_expression": "children", + "call_expression": "children", + "cast_expression": "children", + "char_literal": "children", + "compound_literal_expression": "children", + "concatenated_string": "children", + "conditional_expression": "children", + "false": "children", + "field_expression": "children", + "generic_expression": "children", + "gnu_asm_expression": "children", + "identifier": "children", + "null": "children", + "number_literal": "children", + "offsetof_expression": "children", + "parenthesized_expression": "children", + "pointer_expression": "children", + "sizeof_expression": "children", + "string_literal": "children", + "subscript_expression": "children", + "true": "children", + "unary_expression": "children", + "update_expression": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "break_statement": { + "type": "break_statement", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "call_expression": { + "type": "call_expression", + "singleFieldNames": ["arguments", "function"], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "case_statement": { + "type": "case_statement", + "singleFieldNames": ["value"], + "nodeTypeToMultipleFieldName": { + "attributed_statement": "children", + "break_statement": "children", + "compound_statement": "children", + "continue_statement": "children", + "declaration": "children", + "do_statement": "children", + "expression_statement": "children", + "for_statement": "children", + "goto_statement": "children", + "if_statement": "children", + "labeled_statement": "children", + "return_statement": "children", + "seh_leave_statement": "children", + "seh_try_statement": "children", + "switch_statement": "children", + "type_definition": "children", + "while_statement": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "cast_expression": { + "type": "cast_expression", + "singleFieldNames": ["type", "value"], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "char_literal": { + "type": "char_literal", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": { + "character": "children", + "escape_sequence": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "comma_expression": { + "type": "comma_expression", + "singleFieldNames": ["left", "right"], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "compound_literal_expression": { + "type": "compound_literal_expression", + "singleFieldNames": ["type", "value"], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "compound_statement": { + "type": "compound_statement", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": { + "declaration": "children", + "function_definition": "children", + "linkage_specification": "children", + "preproc_call": "children", + "preproc_def": "children", + "preproc_function_def": "children", + "preproc_if": "children", + "preproc_ifdef": "children", + "preproc_include": "children", + "attributed_statement": "children", + "break_statement": "children", + "case_statement": "children", + "compound_statement": "children", + "continue_statement": "children", + "do_statement": "children", + "expression_statement": "children", + "for_statement": "children", + "goto_statement": "children", + "if_statement": "children", + "labeled_statement": "children", + "return_statement": "children", + "seh_leave_statement": "children", + "seh_try_statement": "children", + "switch_statement": "children", + "while_statement": "children", + "type_definition": "children", + "enum_specifier": "children", + "macro_type_specifier": "children", + "primitive_type": "children", + "sized_type_specifier": "children", + "struct_specifier": "children", + "type_identifier": "children", + "union_specifier": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "concatenated_string": { + "type": "concatenated_string", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": { + "identifier": "children", + "string_literal": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "conditional_expression": { + "type": "conditional_expression", + "singleFieldNames": ["alternative", "condition", "consequence"], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "continue_statement": { + "type": "continue_statement", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "declaration": { + "type": "declaration", + "singleFieldNames": ["type"], + "nodeTypeToMultipleFieldName": { + "array_declarator": "declarator", + "attributed_declarator": "declarator", + "function_declarator": "declarator", + "gnu_asm_expression": "declarator", + "identifier": "declarator", + "init_declarator": "declarator", + "ms_call_modifier": "declarator", + "parenthesized_declarator": "declarator", + "pointer_declarator": "declarator", + "attribute_declaration": "children", + "attribute_specifier": "children", + "ms_declspec_modifier": "children", + "storage_class_specifier": "children", + "type_qualifier": "children" + }, + "multipleOrChildrenFieldNames": ["declarator", "children"] + }, + "declaration_list": { + "type": "declaration_list", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": { + "declaration": "children", + "function_definition": "children", + "linkage_specification": "children", + "preproc_call": "children", + "preproc_def": "children", + "preproc_function_def": "children", + "preproc_if": "children", + "preproc_ifdef": "children", + "preproc_include": "children", + "attributed_statement": "children", + "break_statement": "children", + "case_statement": "children", + "compound_statement": "children", + "continue_statement": "children", + "do_statement": "children", + "expression_statement": "children", + "for_statement": "children", + "goto_statement": "children", + "if_statement": "children", + "labeled_statement": "children", + "return_statement": "children", + "seh_leave_statement": "children", + "seh_try_statement": "children", + "switch_statement": "children", + "while_statement": "children", + "type_definition": "children", + "enum_specifier": "children", + "macro_type_specifier": "children", + "primitive_type": "children", + "sized_type_specifier": "children", + "struct_specifier": "children", + "type_identifier": "children", + "union_specifier": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "do_statement": { + "type": "do_statement", + "singleFieldNames": ["body", "condition"], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "else_clause": { + "type": "else_clause", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": { + "attributed_statement": "children", + "break_statement": "children", + "case_statement": "children", + "compound_statement": "children", + "continue_statement": "children", + "do_statement": "children", + "expression_statement": "children", + "for_statement": "children", + "goto_statement": "children", + "if_statement": "children", + "labeled_statement": "children", + "return_statement": "children", + "seh_leave_statement": "children", + "seh_try_statement": "children", + "switch_statement": "children", + "while_statement": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "enum_specifier": { + "type": "enum_specifier", + "singleFieldNames": ["body", "name", "underlying_type"], + "nodeTypeToMultipleFieldName": { + "attribute_specifier": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "enumerator": { + "type": "enumerator", + "singleFieldNames": ["name", "value"], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "enumerator_list": { + "type": "enumerator_list", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": { + "enumerator": "children", + "preproc_call": "children", + "preproc_if": "children", + "preproc_ifdef": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "expression_statement": { + "type": "expression_statement", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": { + "comma_expression": "children", + "alignof_expression": "children", + "assignment_expression": "children", + "binary_expression": "children", + "call_expression": "children", + "cast_expression": "children", + "char_literal": "children", + "compound_literal_expression": "children", + "concatenated_string": "children", + "conditional_expression": "children", + "false": "children", + "field_expression": "children", + "generic_expression": "children", + "gnu_asm_expression": "children", + "identifier": "children", + "null": "children", + "number_literal": "children", + "offsetof_expression": "children", + "parenthesized_expression": "children", + "pointer_expression": "children", + "sizeof_expression": "children", + "string_literal": "children", + "subscript_expression": "children", + "true": "children", + "unary_expression": "children", + "update_expression": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "field_declaration": { + "type": "field_declaration", + "singleFieldNames": ["type"], + "nodeTypeToMultipleFieldName": { + "array_declarator": "declarator", + "attributed_declarator": "declarator", + "field_identifier": "declarator", + "function_declarator": "declarator", + "parenthesized_declarator": "declarator", + "pointer_declarator": "declarator", + "attribute_declaration": "children", + "attribute_specifier": "children", + "bitfield_clause": "children", + "ms_declspec_modifier": "children", + "storage_class_specifier": "children", + "type_qualifier": "children" + }, + "multipleOrChildrenFieldNames": ["declarator", "children"] + }, + "field_declaration_list": { + "type": "field_declaration_list", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": { + "field_declaration": "children", + "preproc_call": "children", + "preproc_def": "children", + "preproc_function_def": "children", + "preproc_if": "children", + "preproc_ifdef": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "field_designator": { + "type": "field_designator", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": { + "field_identifier": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "field_expression": { + "type": "field_expression", + "singleFieldNames": ["argument", "field", "operator"], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "for_statement": { + "type": "for_statement", + "singleFieldNames": ["body", "condition", "initializer", "update"], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "function_declarator": { + "type": "function_declarator", + "singleFieldNames": ["declarator", "parameters"], + "nodeTypeToMultipleFieldName": { + "attribute_specifier": "children", + "call_expression": "children", + "gnu_asm_expression": "children", + "identifier": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "function_definition": { + "type": "function_definition", + "singleFieldNames": ["body", "declarator", "type"], + "nodeTypeToMultipleFieldName": { + "attribute_declaration": "children", + "attribute_specifier": "children", + "declaration": "children", + "ms_call_modifier": "children", + "ms_declspec_modifier": "children", + "storage_class_specifier": "children", + "type_qualifier": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "generic_expression": { + "type": "generic_expression", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": { + "alignof_expression": "children", + "assignment_expression": "children", + "binary_expression": "children", + "call_expression": "children", + "cast_expression": "children", + "char_literal": "children", + "compound_literal_expression": "children", + "concatenated_string": "children", + "conditional_expression": "children", + "false": "children", + "field_expression": "children", + "generic_expression": "children", + "gnu_asm_expression": "children", + "identifier": "children", + "null": "children", + "number_literal": "children", + "offsetof_expression": "children", + "parenthesized_expression": "children", + "pointer_expression": "children", + "sizeof_expression": "children", + "string_literal": "children", + "subscript_expression": "children", + "true": "children", + "unary_expression": "children", + "update_expression": "children", + "type_descriptor": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "gnu_asm_clobber_list": { + "type": "gnu_asm_clobber_list", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": { + "concatenated_string": "register", + "string_literal": "register" + }, + "multipleOrChildrenFieldNames": ["register"] + }, + "gnu_asm_expression": { + "type": "gnu_asm_expression", + "singleFieldNames": [ + "assembly_code", + "clobbers", + "goto_labels", + "input_operands", + "output_operands" + ], + "nodeTypeToMultipleFieldName": { + "gnu_asm_qualifier": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "gnu_asm_goto_list": { + "type": "gnu_asm_goto_list", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": { + "identifier": "label" + }, + "multipleOrChildrenFieldNames": ["label"] + }, + "gnu_asm_input_operand": { + "type": "gnu_asm_input_operand", + "singleFieldNames": ["constraint", "symbol", "value"], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "gnu_asm_input_operand_list": { + "type": "gnu_asm_input_operand_list", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": { + "gnu_asm_input_operand": "operand" + }, + "multipleOrChildrenFieldNames": ["operand"] + }, + "gnu_asm_output_operand": { + "type": "gnu_asm_output_operand", + "singleFieldNames": ["constraint", "symbol", "value"], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "gnu_asm_output_operand_list": { + "type": "gnu_asm_output_operand_list", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": { + "gnu_asm_output_operand": "operand" + }, + "multipleOrChildrenFieldNames": ["operand"] + }, + "gnu_asm_qualifier": { + "type": "gnu_asm_qualifier", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "goto_statement": { + "type": "goto_statement", + "singleFieldNames": ["label"], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "if_statement": { + "type": "if_statement", + "singleFieldNames": ["alternative", "condition", "consequence"], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "init_declarator": { + "type": "init_declarator", + "singleFieldNames": ["declarator", "value"], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "initializer_list": { + "type": "initializer_list", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": { + "alignof_expression": "children", + "assignment_expression": "children", + "binary_expression": "children", + "call_expression": "children", + "cast_expression": "children", + "char_literal": "children", + "compound_literal_expression": "children", + "concatenated_string": "children", + "conditional_expression": "children", + "false": "children", + "field_expression": "children", + "generic_expression": "children", + "gnu_asm_expression": "children", + "identifier": "children", + "null": "children", + "number_literal": "children", + "offsetof_expression": "children", + "parenthesized_expression": "children", + "pointer_expression": "children", + "sizeof_expression": "children", + "string_literal": "children", + "subscript_expression": "children", + "true": "children", + "unary_expression": "children", + "update_expression": "children", + "initializer_list": "children", + "initializer_pair": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "initializer_pair": { + "type": "initializer_pair", + "singleFieldNames": ["value"], + "nodeTypeToMultipleFieldName": { + "field_designator": "designator", + "field_identifier": "designator", + "subscript_designator": "designator", + "subscript_range_designator": "designator" + }, + "multipleOrChildrenFieldNames": ["designator"] + }, + "labeled_statement": { + "type": "labeled_statement", + "singleFieldNames": ["label"], + "nodeTypeToMultipleFieldName": { + "attributed_statement": "children", + "break_statement": "children", + "case_statement": "children", + "compound_statement": "children", + "continue_statement": "children", + "do_statement": "children", + "expression_statement": "children", + "for_statement": "children", + "goto_statement": "children", + "if_statement": "children", + "labeled_statement": "children", + "return_statement": "children", + "seh_leave_statement": "children", + "seh_try_statement": "children", + "switch_statement": "children", + "while_statement": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "linkage_specification": { + "type": "linkage_specification", + "singleFieldNames": ["body", "value"], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "macro_type_specifier": { + "type": "macro_type_specifier", + "singleFieldNames": ["name", "type"], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "ms_based_modifier": { + "type": "ms_based_modifier", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": { + "argument_list": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "ms_call_modifier": { + "type": "ms_call_modifier", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "ms_declspec_modifier": { + "type": "ms_declspec_modifier", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": { + "identifier": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "ms_pointer_modifier": { + "type": "ms_pointer_modifier", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": { + "ms_restrict_modifier": "children", + "ms_signed_ptr_modifier": "children", + "ms_unaligned_ptr_modifier": "children", + "ms_unsigned_ptr_modifier": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "ms_unaligned_ptr_modifier": { + "type": "ms_unaligned_ptr_modifier", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "null": { + "type": "null", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "offsetof_expression": { + "type": "offsetof_expression", + "singleFieldNames": ["member", "type"], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "parameter_declaration": { + "type": "parameter_declaration", + "singleFieldNames": ["declarator", "type"], + "nodeTypeToMultipleFieldName": { + "attribute_declaration": "children", + "attribute_specifier": "children", + "ms_declspec_modifier": "children", + "storage_class_specifier": "children", + "type_qualifier": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "parameter_list": { + "type": "parameter_list", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": { + "identifier": "children", + "parameter_declaration": "children", + "variadic_parameter": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "parenthesized_declarator": { + "type": "parenthesized_declarator", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": { + "array_declarator": "children", + "attributed_declarator": "children", + "function_declarator": "children", + "identifier": "children", + "parenthesized_declarator": "children", + "pointer_declarator": "children", + "field_identifier": "children", + "primitive_type": "children", + "type_identifier": "children", + "ms_call_modifier": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "parenthesized_expression": { + "type": "parenthesized_expression", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": { + "comma_expression": "children", + "alignof_expression": "children", + "assignment_expression": "children", + "binary_expression": "children", + "call_expression": "children", + "cast_expression": "children", + "char_literal": "children", + "compound_literal_expression": "children", + "concatenated_string": "children", + "conditional_expression": "children", + "false": "children", + "field_expression": "children", + "generic_expression": "children", + "gnu_asm_expression": "children", + "identifier": "children", + "null": "children", + "number_literal": "children", + "offsetof_expression": "children", + "parenthesized_expression": "children", + "pointer_expression": "children", + "sizeof_expression": "children", + "string_literal": "children", + "subscript_expression": "children", + "true": "children", + "unary_expression": "children", + "update_expression": "children", + "preproc_defined": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "pointer_declarator": { + "type": "pointer_declarator", + "singleFieldNames": ["declarator"], + "nodeTypeToMultipleFieldName": { + "ms_based_modifier": "children", + "ms_pointer_modifier": "children", + "type_qualifier": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "pointer_expression": { + "type": "pointer_expression", + "singleFieldNames": ["argument", "operator"], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "preproc_call": { + "type": "preproc_call", + "singleFieldNames": ["argument", "directive"], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "preproc_def": { + "type": "preproc_def", + "singleFieldNames": ["name", "value"], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "preproc_defined": { + "type": "preproc_defined", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": { + "identifier": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "preproc_elif": { + "type": "preproc_elif", + "singleFieldNames": ["alternative", "condition"], + "nodeTypeToMultipleFieldName": { + "declaration": "children", + "enumerator": "children", + "field_declaration": "children", + "function_definition": "children", + "linkage_specification": "children", + "preproc_call": "children", + "preproc_def": "children", + "preproc_function_def": "children", + "preproc_if": "children", + "preproc_ifdef": "children", + "preproc_include": "children", + "attributed_statement": "children", + "break_statement": "children", + "case_statement": "children", + "compound_statement": "children", + "continue_statement": "children", + "do_statement": "children", + "expression_statement": "children", + "for_statement": "children", + "goto_statement": "children", + "if_statement": "children", + "labeled_statement": "children", + "return_statement": "children", + "seh_leave_statement": "children", + "seh_try_statement": "children", + "switch_statement": "children", + "while_statement": "children", + "type_definition": "children", + "enum_specifier": "children", + "macro_type_specifier": "children", + "primitive_type": "children", + "sized_type_specifier": "children", + "struct_specifier": "children", + "type_identifier": "children", + "union_specifier": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "preproc_elifdef": { + "type": "preproc_elifdef", + "singleFieldNames": ["alternative", "name"], + "nodeTypeToMultipleFieldName": { + "declaration": "children", + "enumerator": "children", + "field_declaration": "children", + "function_definition": "children", + "linkage_specification": "children", + "preproc_call": "children", + "preproc_def": "children", + "preproc_function_def": "children", + "preproc_if": "children", + "preproc_ifdef": "children", + "preproc_include": "children", + "attributed_statement": "children", + "break_statement": "children", + "case_statement": "children", + "compound_statement": "children", + "continue_statement": "children", + "do_statement": "children", + "expression_statement": "children", + "for_statement": "children", + "goto_statement": "children", + "if_statement": "children", + "labeled_statement": "children", + "return_statement": "children", + "seh_leave_statement": "children", + "seh_try_statement": "children", + "switch_statement": "children", + "while_statement": "children", + "type_definition": "children", + "enum_specifier": "children", + "macro_type_specifier": "children", + "primitive_type": "children", + "sized_type_specifier": "children", + "struct_specifier": "children", + "type_identifier": "children", + "union_specifier": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "preproc_else": { + "type": "preproc_else", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": { + "declaration": "children", + "enumerator": "children", + "field_declaration": "children", + "function_definition": "children", + "linkage_specification": "children", + "preproc_call": "children", + "preproc_def": "children", + "preproc_function_def": "children", + "preproc_if": "children", + "preproc_ifdef": "children", + "preproc_include": "children", + "attributed_statement": "children", + "break_statement": "children", + "case_statement": "children", + "compound_statement": "children", + "continue_statement": "children", + "do_statement": "children", + "expression_statement": "children", + "for_statement": "children", + "goto_statement": "children", + "if_statement": "children", + "labeled_statement": "children", + "return_statement": "children", + "seh_leave_statement": "children", + "seh_try_statement": "children", + "switch_statement": "children", + "while_statement": "children", + "type_definition": "children", + "enum_specifier": "children", + "macro_type_specifier": "children", + "primitive_type": "children", + "sized_type_specifier": "children", + "struct_specifier": "children", + "type_identifier": "children", + "union_specifier": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "preproc_function_def": { + "type": "preproc_function_def", + "singleFieldNames": ["name", "parameters", "value"], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "preproc_if": { + "type": "preproc_if", + "singleFieldNames": ["alternative", "condition"], + "nodeTypeToMultipleFieldName": { + "declaration": "children", + "enumerator": "children", + "field_declaration": "children", + "function_definition": "children", + "linkage_specification": "children", + "preproc_call": "children", + "preproc_def": "children", + "preproc_function_def": "children", + "preproc_if": "children", + "preproc_ifdef": "children", + "preproc_include": "children", + "attributed_statement": "children", + "break_statement": "children", + "case_statement": "children", + "compound_statement": "children", + "continue_statement": "children", + "do_statement": "children", + "expression_statement": "children", + "for_statement": "children", + "goto_statement": "children", + "if_statement": "children", + "labeled_statement": "children", + "return_statement": "children", + "seh_leave_statement": "children", + "seh_try_statement": "children", + "switch_statement": "children", + "while_statement": "children", + "type_definition": "children", + "enum_specifier": "children", + "macro_type_specifier": "children", + "primitive_type": "children", + "sized_type_specifier": "children", + "struct_specifier": "children", + "type_identifier": "children", + "union_specifier": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "preproc_ifdef": { + "type": "preproc_ifdef", + "singleFieldNames": ["alternative", "name"], + "nodeTypeToMultipleFieldName": { + "declaration": "children", + "enumerator": "children", + "field_declaration": "children", + "function_definition": "children", + "linkage_specification": "children", + "preproc_call": "children", + "preproc_def": "children", + "preproc_function_def": "children", + "preproc_if": "children", + "preproc_ifdef": "children", + "preproc_include": "children", + "attributed_statement": "children", + "break_statement": "children", + "case_statement": "children", + "compound_statement": "children", + "continue_statement": "children", + "do_statement": "children", + "expression_statement": "children", + "for_statement": "children", + "goto_statement": "children", + "if_statement": "children", + "labeled_statement": "children", + "return_statement": "children", + "seh_leave_statement": "children", + "seh_try_statement": "children", + "switch_statement": "children", + "while_statement": "children", + "type_definition": "children", + "enum_specifier": "children", + "macro_type_specifier": "children", + "primitive_type": "children", + "sized_type_specifier": "children", + "struct_specifier": "children", + "type_identifier": "children", + "union_specifier": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "preproc_include": { + "type": "preproc_include", + "singleFieldNames": ["path"], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "preproc_params": { + "type": "preproc_params", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": { + "identifier": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "return_statement": { + "type": "return_statement", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": { + "comma_expression": "children", + "alignof_expression": "children", + "assignment_expression": "children", + "binary_expression": "children", + "call_expression": "children", + "cast_expression": "children", + "char_literal": "children", + "compound_literal_expression": "children", + "concatenated_string": "children", + "conditional_expression": "children", + "false": "children", + "field_expression": "children", + "generic_expression": "children", + "gnu_asm_expression": "children", + "identifier": "children", + "null": "children", + "number_literal": "children", + "offsetof_expression": "children", + "parenthesized_expression": "children", + "pointer_expression": "children", + "sizeof_expression": "children", + "string_literal": "children", + "subscript_expression": "children", + "true": "children", + "unary_expression": "children", + "update_expression": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "seh_except_clause": { + "type": "seh_except_clause", + "singleFieldNames": ["body", "filter"], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "seh_finally_clause": { + "type": "seh_finally_clause", + "singleFieldNames": ["body"], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "seh_leave_statement": { + "type": "seh_leave_statement", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "seh_try_statement": { + "type": "seh_try_statement", + "singleFieldNames": ["body"], + "nodeTypeToMultipleFieldName": { + "seh_except_clause": "children", + "seh_finally_clause": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "sized_type_specifier": { + "type": "sized_type_specifier", + "singleFieldNames": ["type"], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "sizeof_expression": { + "type": "sizeof_expression", + "singleFieldNames": ["type", "value"], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "storage_class_specifier": { + "type": "storage_class_specifier", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "string_literal": { + "type": "string_literal", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": { + "escape_sequence": "children", + "string_content": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "struct_specifier": { + "type": "struct_specifier", + "singleFieldNames": ["body", "name"], + "nodeTypeToMultipleFieldName": { + "attribute_specifier": "children", + "ms_declspec_modifier": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "subscript_designator": { + "type": "subscript_designator", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": { + "alignof_expression": "children", + "assignment_expression": "children", + "binary_expression": "children", + "call_expression": "children", + "cast_expression": "children", + "char_literal": "children", + "compound_literal_expression": "children", + "concatenated_string": "children", + "conditional_expression": "children", + "false": "children", + "field_expression": "children", + "generic_expression": "children", + "gnu_asm_expression": "children", + "identifier": "children", + "null": "children", + "number_literal": "children", + "offsetof_expression": "children", + "parenthesized_expression": "children", + "pointer_expression": "children", + "sizeof_expression": "children", + "string_literal": "children", + "subscript_expression": "children", + "true": "children", + "unary_expression": "children", + "update_expression": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "subscript_expression": { + "type": "subscript_expression", + "singleFieldNames": ["argument", "index"], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "subscript_range_designator": { + "type": "subscript_range_designator", + "singleFieldNames": ["end", "start"], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "switch_statement": { + "type": "switch_statement", + "singleFieldNames": ["body", "condition"], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "translation_unit": { + "type": "translation_unit", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": { + "attributed_statement": "children", + "break_statement": "children", + "case_statement": "children", + "compound_statement": "children", + "continue_statement": "children", + "declaration": "children", + "do_statement": "children", + "expression_statement": "children", + "for_statement": "children", + "function_definition": "children", + "goto_statement": "children", + "if_statement": "children", + "labeled_statement": "children", + "linkage_specification": "children", + "preproc_call": "children", + "preproc_def": "children", + "preproc_function_def": "children", + "preproc_if": "children", + "preproc_ifdef": "children", + "preproc_include": "children", + "return_statement": "children", + "switch_statement": "children", + "type_definition": "children", + "enum_specifier": "children", + "macro_type_specifier": "children", + "primitive_type": "children", + "sized_type_specifier": "children", + "struct_specifier": "children", + "type_identifier": "children", + "union_specifier": "children", + "while_statement": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "type_definition": { + "type": "type_definition", + "singleFieldNames": ["type"], + "nodeTypeToMultipleFieldName": { + "array_declarator": "declarator", + "attributed_declarator": "declarator", + "function_declarator": "declarator", + "parenthesized_declarator": "declarator", + "pointer_declarator": "declarator", + "primitive_type": "declarator", + "type_identifier": "declarator", + "attribute_specifier": "children", + "type_qualifier": "children" + }, + "multipleOrChildrenFieldNames": ["declarator", "children"] + }, + "type_descriptor": { + "type": "type_descriptor", + "singleFieldNames": ["declarator", "type"], + "nodeTypeToMultipleFieldName": { + "type_qualifier": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "type_qualifier": { + "type": "type_qualifier", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": { + "alignas_qualifier": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "unary_expression": { + "type": "unary_expression", + "singleFieldNames": ["argument", "operator"], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "union_specifier": { + "type": "union_specifier", + "singleFieldNames": ["body", "name"], + "nodeTypeToMultipleFieldName": { + "attribute_specifier": "children", + "ms_declspec_modifier": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "update_expression": { + "type": "update_expression", + "singleFieldNames": ["argument", "operator"], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "variadic_parameter": { + "type": "variadic_parameter", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "while_statement": { + "type": "while_statement", + "singleFieldNames": ["body", "condition"], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "!": { + "type": "!", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "!=": { + "type": "!=", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "%": { + "type": "%", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "%=": { + "type": "%=", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "&": { + "type": "&", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "&&": { + "type": "&&", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "&=": { + "type": "&=", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "*": { + "type": "*", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "*=": { + "type": "*=", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "+": { + "type": "+", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "++": { + "type": "++", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "+=": { + "type": "+=", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "-": { + "type": "-", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "--": { + "type": "--", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "-=": { + "type": "-=", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "->": { + "type": "->", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + ".": { + "type": ".", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "/": { + "type": "/", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "/=": { + "type": "/=", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "<": { + "type": "<", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "<<": { + "type": "<<", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "<<=": { + "type": "<<=", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "<=": { + "type": "<=", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "=": { + "type": "=", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "==": { + "type": "==", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + ">": { + "type": ">", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + ">=": { + "type": ">=", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + ">>": { + "type": ">>", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + ">>=": { + "type": ">>=", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "^": { + "type": "^", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "^=": { + "type": "^=", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "character": { + "type": "character", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "comment": { + "type": "comment", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "escape_sequence": { + "type": "escape_sequence", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "false": { + "type": "false", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "field_identifier": { + "type": "field_identifier", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "identifier": { + "type": "identifier", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "ms_restrict_modifier": { + "type": "ms_restrict_modifier", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "ms_signed_ptr_modifier": { + "type": "ms_signed_ptr_modifier", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "ms_unsigned_ptr_modifier": { + "type": "ms_unsigned_ptr_modifier", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "number_literal": { + "type": "number_literal", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "preproc_arg": { + "type": "preproc_arg", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "preproc_directive": { + "type": "preproc_directive", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "primitive_type": { + "type": "primitive_type", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "statement_identifier": { + "type": "statement_identifier", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "string_content": { + "type": "string_content", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "system_lib_string": { + "type": "system_lib_string", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "true": { + "type": "true", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "type_identifier": { + "type": "type_identifier", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "|": { + "type": "|", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "|=": { + "type": "|=", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "||": { + "type": "||", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "~": { + "type": "~", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + } +} diff --git a/packages/tree-sitter-port/output/tree-sitter-c/meta.json b/packages/tree-sitter-port/output/tree-sitter-c/meta.json new file mode 100644 index 0000000..bba4b0f --- /dev/null +++ b/packages/tree-sitter-port/output/tree-sitter-c/meta.json @@ -0,0 +1,5 @@ +{ + "name": "tree-sitter-c", + "version": "0.21.4", + "generatedAt": "2024-06-26T15:41:27.820Z" +} diff --git a/packages/tree-sitter-port/output/tree-sitter-c/parser.wasm b/packages/tree-sitter-port/output/tree-sitter-c/parser.wasm new file mode 100755 index 0000000..0a801e0 Binary files /dev/null and b/packages/tree-sitter-port/output/tree-sitter-c/parser.wasm differ diff --git a/packages/tree-sitter-port/output/tree-sitter-lua/fields-meta.json b/packages/tree-sitter-port/output/tree-sitter-lua/fields-meta.json new file mode 100644 index 0000000..fabde99 --- /dev/null +++ b/packages/tree-sitter-port/output/tree-sitter-lua/fields-meta.json @@ -0,0 +1,407 @@ +{ + "arguments": { + "type": "arguments", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": { + "binary_expression": "children", + "false": "children", + "function_call": "children", + "function_definition": "children", + "nil": "children", + "number": "children", + "parenthesized_expression": "children", + "string": "children", + "table_constructor": "children", + "true": "children", + "unary_expression": "children", + "vararg_expression": "children", + "bracket_index_expression": "children", + "dot_index_expression": "children", + "identifier": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "assignment_statement": { + "type": "assignment_statement", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": { + "expression_list": "children", + "variable_list": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "attribute": { + "type": "attribute", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": { + "identifier": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "binary_expression": { + "type": "binary_expression", + "singleFieldNames": ["left", "right"], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "block": { + "type": "block", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": { + "return_statement": "children", + "assignment_statement": "children", + "break_statement": "children", + "function_declaration": "children", + "variable_declaration": "children", + "do_statement": "children", + "empty_statement": "children", + "for_statement": "children", + "function_call": "children", + "goto_statement": "children", + "if_statement": "children", + "label_statement": "children", + "repeat_statement": "children", + "while_statement": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "bracket_index_expression": { + "type": "bracket_index_expression", + "singleFieldNames": ["field", "table"], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "chunk": { + "type": "chunk", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": { + "hash_bang_line": "children", + "return_statement": "children", + "assignment_statement": "children", + "break_statement": "children", + "function_declaration": "children", + "variable_declaration": "children", + "do_statement": "children", + "empty_statement": "children", + "for_statement": "children", + "function_call": "children", + "goto_statement": "children", + "if_statement": "children", + "label_statement": "children", + "repeat_statement": "children", + "while_statement": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "comment": { + "type": "comment", + "singleFieldNames": ["content", "end", "start"], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "do_statement": { + "type": "do_statement", + "singleFieldNames": ["body"], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "dot_index_expression": { + "type": "dot_index_expression", + "singleFieldNames": ["field", "table"], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "else_statement": { + "type": "else_statement", + "singleFieldNames": ["body"], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "elseif_statement": { + "type": "elseif_statement", + "singleFieldNames": ["condition", "consequence"], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "empty_statement": { + "type": "empty_statement", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "expression_list": { + "type": "expression_list", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": { + "binary_expression": "children", + "false": "children", + "function_call": "children", + "function_definition": "children", + "nil": "children", + "number": "children", + "parenthesized_expression": "children", + "string": "children", + "table_constructor": "children", + "true": "children", + "unary_expression": "children", + "vararg_expression": "children", + "bracket_index_expression": "children", + "dot_index_expression": "children", + "identifier": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "field": { + "type": "field", + "singleFieldNames": ["name", "value"], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "for_generic_clause": { + "type": "for_generic_clause", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": { + "expression_list": "children", + "variable_list": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "for_numeric_clause": { + "type": "for_numeric_clause", + "singleFieldNames": ["end", "name", "start", "step"], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "for_statement": { + "type": "for_statement", + "singleFieldNames": ["body", "clause"], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "function_call": { + "type": "function_call", + "singleFieldNames": ["arguments", "name"], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "function_declaration": { + "type": "function_declaration", + "singleFieldNames": ["body", "name", "parameters"], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "function_definition": { + "type": "function_definition", + "singleFieldNames": ["body", "parameters"], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "goto_statement": { + "type": "goto_statement", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": { + "identifier": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "if_statement": { + "type": "if_statement", + "singleFieldNames": ["condition", "consequence"], + "nodeTypeToMultipleFieldName": { + "else_statement": "alternative", + "elseif_statement": "alternative" + }, + "multipleOrChildrenFieldNames": ["alternative"] + }, + "label_statement": { + "type": "label_statement", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": { + "identifier": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "method_index_expression": { + "type": "method_index_expression", + "singleFieldNames": ["method", "table"], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "parameters": { + "type": "parameters", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": { + "identifier": "name", + "vararg_expression": "children" + }, + "multipleOrChildrenFieldNames": ["name", "children"] + }, + "parenthesized_expression": { + "type": "parenthesized_expression", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": { + "binary_expression": "children", + "false": "children", + "function_call": "children", + "function_definition": "children", + "nil": "children", + "number": "children", + "parenthesized_expression": "children", + "string": "children", + "table_constructor": "children", + "true": "children", + "unary_expression": "children", + "vararg_expression": "children", + "bracket_index_expression": "children", + "dot_index_expression": "children", + "identifier": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "repeat_statement": { + "type": "repeat_statement", + "singleFieldNames": ["body", "condition"], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "return_statement": { + "type": "return_statement", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": { + "expression_list": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "string": { + "type": "string", + "singleFieldNames": ["content", "end", "start"], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "string_content": { + "type": "string_content", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": { + "escape_sequence": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "table_constructor": { + "type": "table_constructor", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": { + "field": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "unary_expression": { + "type": "unary_expression", + "singleFieldNames": ["operand"], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "variable_declaration": { + "type": "variable_declaration", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": { + "assignment_statement": "children", + "variable_list": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "variable_list": { + "type": "variable_list", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": { + "attribute": "attribute", + "bracket_index_expression": "name", + "dot_index_expression": "name", + "identifier": "name" + }, + "multipleOrChildrenFieldNames": ["attribute", "name"] + }, + "while_statement": { + "type": "while_statement", + "singleFieldNames": ["body", "condition"], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "--": { + "type": "--", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "[[": { + "type": "[[", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "]]": { + "type": "]]", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "break_statement": { + "type": "break_statement", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "comment_content": { + "type": "comment_content", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "escape_sequence": { + "type": "escape_sequence", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "false": { + "type": "false", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "hash_bang_line": { + "type": "hash_bang_line", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "identifier": { + "type": "identifier", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "nil": { + "type": "nil", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "number": { + "type": "number", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "true": { + "type": "true", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "vararg_expression": { + "type": "vararg_expression", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + } +} diff --git a/packages/tree-sitter-port/output/tree-sitter-lua/meta.json b/packages/tree-sitter-port/output/tree-sitter-lua/meta.json new file mode 100644 index 0000000..6b94e7d --- /dev/null +++ b/packages/tree-sitter-port/output/tree-sitter-lua/meta.json @@ -0,0 +1,5 @@ +{ + "name": "tree-sitter-lua", + "version": "0.1.0", + "generatedAt": "2024-06-26T15:41:59.042Z" +} diff --git a/packages/tree-sitter-port/output/tree-sitter-lua/parser.wasm b/packages/tree-sitter-port/output/tree-sitter-lua/parser.wasm new file mode 100755 index 0000000..9e28306 Binary files /dev/null and b/packages/tree-sitter-port/output/tree-sitter-lua/parser.wasm differ diff --git a/packages/tree-sitter-port/output/tree-sitter-php/meta.json b/packages/tree-sitter-port/output/tree-sitter-php/meta.json new file mode 100644 index 0000000..53dd175 --- /dev/null +++ b/packages/tree-sitter-port/output/tree-sitter-php/meta.json @@ -0,0 +1,5 @@ +{ + "name": "tree-sitter-php", + "version": "0.22.5", + "generatedAt": "2024-06-26T15:42:10.851Z" +} diff --git a/packages/tree-sitter-port/output/tree-sitter-python/fields-meta.json b/packages/tree-sitter-port/output/tree-sitter-python/fields-meta.json new file mode 100644 index 0000000..db41dd9 --- /dev/null +++ b/packages/tree-sitter-port/output/tree-sitter-python/fields-meta.json @@ -0,0 +1,2147 @@ +{ + "aliased_import": { + "type": "aliased_import", + "singleFieldNames": ["alias", "name"], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "argument_list": { + "type": "argument_list", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": { + "dictionary_splat": "children", + "as_pattern": "children", + "boolean_operator": "children", + "comparison_operator": "children", + "conditional_expression": "children", + "lambda": "children", + "named_expression": "children", + "not_operator": "children", + "attribute": "children", + "await": "children", + "binary_operator": "children", + "call": "children", + "concatenated_string": "children", + "dictionary": "children", + "dictionary_comprehension": "children", + "ellipsis": "children", + "false": "children", + "float": "children", + "generator_expression": "children", + "identifier": "children", + "integer": "children", + "list": "children", + "list_comprehension": "children", + "list_splat": "children", + "none": "children", + "parenthesized_expression": "children", + "set": "children", + "set_comprehension": "children", + "string": "children", + "subscript": "children", + "true": "children", + "tuple": "children", + "unary_operator": "children", + "keyword_argument": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "as_pattern": { + "type": "as_pattern", + "singleFieldNames": ["alias"], + "nodeTypeToMultipleFieldName": { + "case_pattern": "children", + "as_pattern": "children", + "boolean_operator": "children", + "comparison_operator": "children", + "conditional_expression": "children", + "lambda": "children", + "named_expression": "children", + "not_operator": "children", + "attribute": "children", + "await": "children", + "binary_operator": "children", + "call": "children", + "concatenated_string": "children", + "dictionary": "children", + "dictionary_comprehension": "children", + "ellipsis": "children", + "false": "children", + "float": "children", + "generator_expression": "children", + "identifier": "children", + "integer": "children", + "list": "children", + "list_comprehension": "children", + "list_splat": "children", + "none": "children", + "parenthesized_expression": "children", + "set": "children", + "set_comprehension": "children", + "string": "children", + "subscript": "children", + "true": "children", + "tuple": "children", + "unary_operator": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "assert_statement": { + "type": "assert_statement", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": { + "as_pattern": "children", + "boolean_operator": "children", + "comparison_operator": "children", + "conditional_expression": "children", + "lambda": "children", + "named_expression": "children", + "not_operator": "children", + "attribute": "children", + "await": "children", + "binary_operator": "children", + "call": "children", + "concatenated_string": "children", + "dictionary": "children", + "dictionary_comprehension": "children", + "ellipsis": "children", + "false": "children", + "float": "children", + "generator_expression": "children", + "identifier": "children", + "integer": "children", + "list": "children", + "list_comprehension": "children", + "list_splat": "children", + "none": "children", + "parenthesized_expression": "children", + "set": "children", + "set_comprehension": "children", + "string": "children", + "subscript": "children", + "true": "children", + "tuple": "children", + "unary_operator": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "assignment": { + "type": "assignment", + "singleFieldNames": ["left", "right", "type"], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "attribute": { + "type": "attribute", + "singleFieldNames": ["attribute", "object"], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "augmented_assignment": { + "type": "augmented_assignment", + "singleFieldNames": ["left", "operator", "right"], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "binary_operator": { + "type": "binary_operator", + "singleFieldNames": ["left", "operator", "right"], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "block": { + "type": "block", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": { + "case_clause": "alternative", + "class_definition": "children", + "decorated_definition": "children", + "for_statement": "children", + "function_definition": "children", + "if_statement": "children", + "match_statement": "children", + "try_statement": "children", + "while_statement": "children", + "with_statement": "children", + "assert_statement": "children", + "break_statement": "children", + "continue_statement": "children", + "delete_statement": "children", + "exec_statement": "children", + "expression_statement": "children", + "future_import_statement": "children", + "global_statement": "children", + "import_from_statement": "children", + "import_statement": "children", + "nonlocal_statement": "children", + "pass_statement": "children", + "print_statement": "children", + "raise_statement": "children", + "return_statement": "children", + "type_alias_statement": "children" + }, + "multipleOrChildrenFieldNames": ["alternative", "children"] + }, + "boolean_operator": { + "type": "boolean_operator", + "singleFieldNames": ["left", "operator", "right"], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "break_statement": { + "type": "break_statement", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "call": { + "type": "call", + "singleFieldNames": ["arguments", "function"], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "case_clause": { + "type": "case_clause", + "singleFieldNames": ["consequence", "guard"], + "nodeTypeToMultipleFieldName": { + "case_pattern": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "case_pattern": { + "type": "case_pattern", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": { + "as_pattern": "children", + "class_pattern": "children", + "complex_pattern": "children", + "concatenated_string": "children", + "dict_pattern": "children", + "dotted_name": "children", + "false": "children", + "float": "children", + "integer": "children", + "keyword_pattern": "children", + "list_pattern": "children", + "none": "children", + "splat_pattern": "children", + "string": "children", + "true": "children", + "tuple_pattern": "children", + "union_pattern": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "chevron": { + "type": "chevron", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": { + "as_pattern": "children", + "boolean_operator": "children", + "comparison_operator": "children", + "conditional_expression": "children", + "lambda": "children", + "named_expression": "children", + "not_operator": "children", + "attribute": "children", + "await": "children", + "binary_operator": "children", + "call": "children", + "concatenated_string": "children", + "dictionary": "children", + "dictionary_comprehension": "children", + "ellipsis": "children", + "false": "children", + "float": "children", + "generator_expression": "children", + "identifier": "children", + "integer": "children", + "list": "children", + "list_comprehension": "children", + "list_splat": "children", + "none": "children", + "parenthesized_expression": "children", + "set": "children", + "set_comprehension": "children", + "string": "children", + "subscript": "children", + "true": "children", + "tuple": "children", + "unary_operator": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "class_definition": { + "type": "class_definition", + "singleFieldNames": ["body", "name", "superclasses", "type_parameters"], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "class_pattern": { + "type": "class_pattern", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": { + "case_pattern": "children", + "dotted_name": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "comparison_operator": { + "type": "comparison_operator", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": { + "!=": "operators", + "<": "operators", + "<=": "operators", + "<>": "operators", + "==": "operators", + ">": "operators", + ">=": "operators", + "in": "operators", + "is": "operators", + "is not": "operators", + "not in": "operators", + "attribute": "children", + "await": "children", + "binary_operator": "children", + "call": "children", + "concatenated_string": "children", + "dictionary": "children", + "dictionary_comprehension": "children", + "ellipsis": "children", + "false": "children", + "float": "children", + "generator_expression": "children", + "identifier": "children", + "integer": "children", + "list": "children", + "list_comprehension": "children", + "list_splat": "children", + "none": "children", + "parenthesized_expression": "children", + "set": "children", + "set_comprehension": "children", + "string": "children", + "subscript": "children", + "true": "children", + "tuple": "children", + "unary_operator": "children" + }, + "multipleOrChildrenFieldNames": ["operators", "children"] + }, + "complex_pattern": { + "type": "complex_pattern", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": { + "float": "children", + "integer": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "concatenated_string": { + "type": "concatenated_string", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": { + "string": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "conditional_expression": { + "type": "conditional_expression", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": { + "as_pattern": "children", + "boolean_operator": "children", + "comparison_operator": "children", + "conditional_expression": "children", + "lambda": "children", + "named_expression": "children", + "not_operator": "children", + "attribute": "children", + "await": "children", + "binary_operator": "children", + "call": "children", + "concatenated_string": "children", + "dictionary": "children", + "dictionary_comprehension": "children", + "ellipsis": "children", + "false": "children", + "float": "children", + "generator_expression": "children", + "identifier": "children", + "integer": "children", + "list": "children", + "list_comprehension": "children", + "list_splat": "children", + "none": "children", + "parenthesized_expression": "children", + "set": "children", + "set_comprehension": "children", + "string": "children", + "subscript": "children", + "true": "children", + "tuple": "children", + "unary_operator": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "constrained_type": { + "type": "constrained_type", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": { + "type": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "continue_statement": { + "type": "continue_statement", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "decorated_definition": { + "type": "decorated_definition", + "singleFieldNames": ["definition"], + "nodeTypeToMultipleFieldName": { + "decorator": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "decorator": { + "type": "decorator", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": { + "as_pattern": "children", + "boolean_operator": "children", + "comparison_operator": "children", + "conditional_expression": "children", + "lambda": "children", + "named_expression": "children", + "not_operator": "children", + "attribute": "children", + "await": "children", + "binary_operator": "children", + "call": "children", + "concatenated_string": "children", + "dictionary": "children", + "dictionary_comprehension": "children", + "ellipsis": "children", + "false": "children", + "float": "children", + "generator_expression": "children", + "identifier": "children", + "integer": "children", + "list": "children", + "list_comprehension": "children", + "list_splat": "children", + "none": "children", + "parenthesized_expression": "children", + "set": "children", + "set_comprehension": "children", + "string": "children", + "subscript": "children", + "true": "children", + "tuple": "children", + "unary_operator": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "default_parameter": { + "type": "default_parameter", + "singleFieldNames": ["name", "value"], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "delete_statement": { + "type": "delete_statement", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": { + "as_pattern": "children", + "boolean_operator": "children", + "comparison_operator": "children", + "conditional_expression": "children", + "lambda": "children", + "named_expression": "children", + "not_operator": "children", + "attribute": "children", + "await": "children", + "binary_operator": "children", + "call": "children", + "concatenated_string": "children", + "dictionary": "children", + "dictionary_comprehension": "children", + "ellipsis": "children", + "false": "children", + "float": "children", + "generator_expression": "children", + "identifier": "children", + "integer": "children", + "list": "children", + "list_comprehension": "children", + "list_splat": "children", + "none": "children", + "parenthesized_expression": "children", + "set": "children", + "set_comprehension": "children", + "string": "children", + "subscript": "children", + "true": "children", + "tuple": "children", + "unary_operator": "children", + "expression_list": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "dict_pattern": { + "type": "dict_pattern", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": { + "case_pattern": "value", + "splat_pattern": "children", + "-": "children", + "_": "children", + "class_pattern": "children", + "complex_pattern": "children", + "concatenated_string": "children", + "dict_pattern": "children", + "dotted_name": "children", + "false": "children", + "float": "children", + "integer": "children", + "list_pattern": "children", + "none": "children", + "string": "children", + "true": "children", + "tuple_pattern": "children", + "union_pattern": "children" + }, + "multipleOrChildrenFieldNames": ["value", "children"] + }, + "dictionary": { + "type": "dictionary", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": { + "dictionary_splat": "children", + "pair": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "dictionary_comprehension": { + "type": "dictionary_comprehension", + "singleFieldNames": ["body"], + "nodeTypeToMultipleFieldName": { + "for_in_clause": "children", + "if_clause": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "dictionary_splat": { + "type": "dictionary_splat", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": { + "as_pattern": "children", + "boolean_operator": "children", + "comparison_operator": "children", + "conditional_expression": "children", + "lambda": "children", + "named_expression": "children", + "not_operator": "children", + "attribute": "children", + "await": "children", + "binary_operator": "children", + "call": "children", + "concatenated_string": "children", + "dictionary": "children", + "dictionary_comprehension": "children", + "ellipsis": "children", + "false": "children", + "float": "children", + "generator_expression": "children", + "identifier": "children", + "integer": "children", + "list": "children", + "list_comprehension": "children", + "list_splat": "children", + "none": "children", + "parenthesized_expression": "children", + "set": "children", + "set_comprehension": "children", + "string": "children", + "subscript": "children", + "true": "children", + "tuple": "children", + "unary_operator": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "dictionary_splat_pattern": { + "type": "dictionary_splat_pattern", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": { + "attribute": "children", + "identifier": "children", + "subscript": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "dotted_name": { + "type": "dotted_name", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": { + "identifier": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "elif_clause": { + "type": "elif_clause", + "singleFieldNames": ["condition", "consequence"], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "else_clause": { + "type": "else_clause", + "singleFieldNames": ["body"], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "except_clause": { + "type": "except_clause", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": { + "block": "children", + "as_pattern": "children", + "boolean_operator": "children", + "comparison_operator": "children", + "conditional_expression": "children", + "lambda": "children", + "named_expression": "children", + "not_operator": "children", + "attribute": "children", + "await": "children", + "binary_operator": "children", + "call": "children", + "concatenated_string": "children", + "dictionary": "children", + "dictionary_comprehension": "children", + "ellipsis": "children", + "false": "children", + "float": "children", + "generator_expression": "children", + "identifier": "children", + "integer": "children", + "list": "children", + "list_comprehension": "children", + "list_splat": "children", + "none": "children", + "parenthesized_expression": "children", + "set": "children", + "set_comprehension": "children", + "string": "children", + "subscript": "children", + "true": "children", + "tuple": "children", + "unary_operator": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "except_group_clause": { + "type": "except_group_clause", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": { + "block": "children", + "as_pattern": "children", + "boolean_operator": "children", + "comparison_operator": "children", + "conditional_expression": "children", + "lambda": "children", + "named_expression": "children", + "not_operator": "children", + "attribute": "children", + "await": "children", + "binary_operator": "children", + "call": "children", + "concatenated_string": "children", + "dictionary": "children", + "dictionary_comprehension": "children", + "ellipsis": "children", + "false": "children", + "float": "children", + "generator_expression": "children", + "identifier": "children", + "integer": "children", + "list": "children", + "list_comprehension": "children", + "list_splat": "children", + "none": "children", + "parenthesized_expression": "children", + "set": "children", + "set_comprehension": "children", + "string": "children", + "subscript": "children", + "true": "children", + "tuple": "children", + "unary_operator": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "exec_statement": { + "type": "exec_statement", + "singleFieldNames": ["code"], + "nodeTypeToMultipleFieldName": { + "as_pattern": "children", + "boolean_operator": "children", + "comparison_operator": "children", + "conditional_expression": "children", + "lambda": "children", + "named_expression": "children", + "not_operator": "children", + "attribute": "children", + "await": "children", + "binary_operator": "children", + "call": "children", + "concatenated_string": "children", + "dictionary": "children", + "dictionary_comprehension": "children", + "ellipsis": "children", + "false": "children", + "float": "children", + "generator_expression": "children", + "identifier": "children", + "integer": "children", + "list": "children", + "list_comprehension": "children", + "list_splat": "children", + "none": "children", + "parenthesized_expression": "children", + "set": "children", + "set_comprehension": "children", + "string": "children", + "subscript": "children", + "true": "children", + "tuple": "children", + "unary_operator": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "expression_list": { + "type": "expression_list", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": { + "as_pattern": "children", + "boolean_operator": "children", + "comparison_operator": "children", + "conditional_expression": "children", + "lambda": "children", + "named_expression": "children", + "not_operator": "children", + "attribute": "children", + "await": "children", + "binary_operator": "children", + "call": "children", + "concatenated_string": "children", + "dictionary": "children", + "dictionary_comprehension": "children", + "ellipsis": "children", + "false": "children", + "float": "children", + "generator_expression": "children", + "identifier": "children", + "integer": "children", + "list": "children", + "list_comprehension": "children", + "list_splat": "children", + "none": "children", + "parenthesized_expression": "children", + "set": "children", + "set_comprehension": "children", + "string": "children", + "subscript": "children", + "true": "children", + "tuple": "children", + "unary_operator": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "expression_statement": { + "type": "expression_statement", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": { + "assignment": "children", + "augmented_assignment": "children", + "as_pattern": "children", + "boolean_operator": "children", + "comparison_operator": "children", + "conditional_expression": "children", + "lambda": "children", + "named_expression": "children", + "not_operator": "children", + "attribute": "children", + "await": "children", + "binary_operator": "children", + "call": "children", + "concatenated_string": "children", + "dictionary": "children", + "dictionary_comprehension": "children", + "ellipsis": "children", + "false": "children", + "float": "children", + "generator_expression": "children", + "identifier": "children", + "integer": "children", + "list": "children", + "list_comprehension": "children", + "list_splat": "children", + "none": "children", + "parenthesized_expression": "children", + "set": "children", + "set_comprehension": "children", + "string": "children", + "subscript": "children", + "true": "children", + "tuple": "children", + "unary_operator": "children", + "yield": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "finally_clause": { + "type": "finally_clause", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": { + "block": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "for_in_clause": { + "type": "for_in_clause", + "singleFieldNames": ["left"], + "nodeTypeToMultipleFieldName": { + ",": "right", + "as_pattern": "right", + "boolean_operator": "right", + "comparison_operator": "right", + "conditional_expression": "right", + "lambda": "right", + "named_expression": "right", + "not_operator": "right", + "attribute": "right", + "await": "right", + "binary_operator": "right", + "call": "right", + "concatenated_string": "right", + "dictionary": "right", + "dictionary_comprehension": "right", + "ellipsis": "right", + "false": "right", + "float": "right", + "generator_expression": "right", + "identifier": "right", + "integer": "right", + "list": "right", + "list_comprehension": "right", + "list_splat": "right", + "none": "right", + "parenthesized_expression": "right", + "set": "right", + "set_comprehension": "right", + "string": "right", + "subscript": "right", + "true": "right", + "tuple": "right", + "unary_operator": "right" + }, + "multipleOrChildrenFieldNames": ["right"] + }, + "for_statement": { + "type": "for_statement", + "singleFieldNames": ["alternative", "body", "left", "right"], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "format_expression": { + "type": "format_expression", + "singleFieldNames": ["expression", "format_specifier", "type_conversion"], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "format_specifier": { + "type": "format_specifier", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": { + "format_expression": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "function_definition": { + "type": "function_definition", + "singleFieldNames": [ + "body", + "name", + "parameters", + "return_type", + "type_parameters" + ], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "future_import_statement": { + "type": "future_import_statement", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": { + "aliased_import": "name", + "dotted_name": "name" + }, + "multipleOrChildrenFieldNames": ["name"] + }, + "generator_expression": { + "type": "generator_expression", + "singleFieldNames": ["body"], + "nodeTypeToMultipleFieldName": { + "for_in_clause": "children", + "if_clause": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "generic_type": { + "type": "generic_type", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": { + "identifier": "children", + "type_parameter": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "global_statement": { + "type": "global_statement", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": { + "identifier": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "if_clause": { + "type": "if_clause", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": { + "as_pattern": "children", + "boolean_operator": "children", + "comparison_operator": "children", + "conditional_expression": "children", + "lambda": "children", + "named_expression": "children", + "not_operator": "children", + "attribute": "children", + "await": "children", + "binary_operator": "children", + "call": "children", + "concatenated_string": "children", + "dictionary": "children", + "dictionary_comprehension": "children", + "ellipsis": "children", + "false": "children", + "float": "children", + "generator_expression": "children", + "identifier": "children", + "integer": "children", + "list": "children", + "list_comprehension": "children", + "list_splat": "children", + "none": "children", + "parenthesized_expression": "children", + "set": "children", + "set_comprehension": "children", + "string": "children", + "subscript": "children", + "true": "children", + "tuple": "children", + "unary_operator": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "if_statement": { + "type": "if_statement", + "singleFieldNames": ["condition", "consequence"], + "nodeTypeToMultipleFieldName": { + "elif_clause": "alternative", + "else_clause": "alternative" + }, + "multipleOrChildrenFieldNames": ["alternative"] + }, + "import_from_statement": { + "type": "import_from_statement", + "singleFieldNames": ["module_name"], + "nodeTypeToMultipleFieldName": { + "aliased_import": "name", + "dotted_name": "name", + "wildcard_import": "children" + }, + "multipleOrChildrenFieldNames": ["name", "children"] + }, + "import_prefix": { + "type": "import_prefix", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "import_statement": { + "type": "import_statement", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": { + "aliased_import": "name", + "dotted_name": "name" + }, + "multipleOrChildrenFieldNames": ["name"] + }, + "interpolation": { + "type": "interpolation", + "singleFieldNames": ["expression", "format_specifier", "type_conversion"], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "keyword_argument": { + "type": "keyword_argument", + "singleFieldNames": ["name", "value"], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "keyword_pattern": { + "type": "keyword_pattern", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": { + "class_pattern": "children", + "complex_pattern": "children", + "concatenated_string": "children", + "dict_pattern": "children", + "dotted_name": "children", + "false": "children", + "float": "children", + "identifier": "children", + "integer": "children", + "list_pattern": "children", + "none": "children", + "splat_pattern": "children", + "string": "children", + "true": "children", + "tuple_pattern": "children", + "union_pattern": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "keyword_separator": { + "type": "keyword_separator", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "lambda_parameters": { + "type": "lambda_parameters", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": { + "default_parameter": "children", + "dictionary_splat_pattern": "children", + "identifier": "children", + "keyword_separator": "children", + "list_splat_pattern": "children", + "positional_separator": "children", + "tuple_pattern": "children", + "typed_default_parameter": "children", + "typed_parameter": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "list": { + "type": "list", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": { + "as_pattern": "children", + "boolean_operator": "children", + "comparison_operator": "children", + "conditional_expression": "children", + "lambda": "children", + "named_expression": "children", + "not_operator": "children", + "attribute": "children", + "await": "children", + "binary_operator": "children", + "call": "children", + "concatenated_string": "children", + "dictionary": "children", + "dictionary_comprehension": "children", + "ellipsis": "children", + "false": "children", + "float": "children", + "generator_expression": "children", + "identifier": "children", + "integer": "children", + "list": "children", + "list_comprehension": "children", + "list_splat": "children", + "none": "children", + "parenthesized_expression": "children", + "set": "children", + "set_comprehension": "children", + "string": "children", + "subscript": "children", + "true": "children", + "tuple": "children", + "unary_operator": "children", + "parenthesized_list_splat": "children", + "yield": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "list_comprehension": { + "type": "list_comprehension", + "singleFieldNames": ["body"], + "nodeTypeToMultipleFieldName": { + "for_in_clause": "children", + "if_clause": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "list_pattern": { + "type": "list_pattern", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": { + "case_pattern": "children", + "attribute": "children", + "identifier": "children", + "list_pattern": "children", + "list_splat_pattern": "children", + "subscript": "children", + "tuple_pattern": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "list_splat": { + "type": "list_splat", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": { + "attribute": "children", + "as_pattern": "children", + "boolean_operator": "children", + "comparison_operator": "children", + "conditional_expression": "children", + "lambda": "children", + "named_expression": "children", + "not_operator": "children", + "await": "children", + "binary_operator": "children", + "call": "children", + "concatenated_string": "children", + "dictionary": "children", + "dictionary_comprehension": "children", + "ellipsis": "children", + "false": "children", + "float": "children", + "generator_expression": "children", + "identifier": "children", + "integer": "children", + "list": "children", + "list_comprehension": "children", + "list_splat": "children", + "none": "children", + "parenthesized_expression": "children", + "set": "children", + "set_comprehension": "children", + "string": "children", + "subscript": "children", + "true": "children", + "tuple": "children", + "unary_operator": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "list_splat_pattern": { + "type": "list_splat_pattern", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": { + "attribute": "children", + "identifier": "children", + "subscript": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "match_statement": { + "type": "match_statement", + "singleFieldNames": ["body"], + "nodeTypeToMultipleFieldName": { + "as_pattern": "subject", + "boolean_operator": "subject", + "comparison_operator": "subject", + "conditional_expression": "subject", + "lambda": "subject", + "named_expression": "subject", + "not_operator": "subject", + "attribute": "subject", + "await": "subject", + "binary_operator": "subject", + "call": "subject", + "concatenated_string": "subject", + "dictionary": "subject", + "dictionary_comprehension": "subject", + "ellipsis": "subject", + "false": "subject", + "float": "subject", + "generator_expression": "subject", + "identifier": "subject", + "integer": "subject", + "list": "subject", + "list_comprehension": "subject", + "list_splat": "subject", + "none": "subject", + "parenthesized_expression": "subject", + "set": "subject", + "set_comprehension": "subject", + "string": "subject", + "subscript": "subject", + "true": "subject", + "tuple": "subject", + "unary_operator": "subject" + }, + "multipleOrChildrenFieldNames": ["subject"] + }, + "member_type": { + "type": "member_type", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": { + "identifier": "children", + "type": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "module": { + "type": "module", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": { + "class_definition": "children", + "decorated_definition": "children", + "for_statement": "children", + "function_definition": "children", + "if_statement": "children", + "match_statement": "children", + "try_statement": "children", + "while_statement": "children", + "with_statement": "children", + "assert_statement": "children", + "break_statement": "children", + "continue_statement": "children", + "delete_statement": "children", + "exec_statement": "children", + "expression_statement": "children", + "future_import_statement": "children", + "global_statement": "children", + "import_from_statement": "children", + "import_statement": "children", + "nonlocal_statement": "children", + "pass_statement": "children", + "print_statement": "children", + "raise_statement": "children", + "return_statement": "children", + "type_alias_statement": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "named_expression": { + "type": "named_expression", + "singleFieldNames": ["name", "value"], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "nonlocal_statement": { + "type": "nonlocal_statement", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": { + "identifier": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "not_operator": { + "type": "not_operator", + "singleFieldNames": ["argument"], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "pair": { + "type": "pair", + "singleFieldNames": ["key", "value"], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "parameters": { + "type": "parameters", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": { + "default_parameter": "children", + "dictionary_splat_pattern": "children", + "identifier": "children", + "keyword_separator": "children", + "list_splat_pattern": "children", + "positional_separator": "children", + "tuple_pattern": "children", + "typed_default_parameter": "children", + "typed_parameter": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "parenthesized_expression": { + "type": "parenthesized_expression", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": { + "as_pattern": "children", + "boolean_operator": "children", + "comparison_operator": "children", + "conditional_expression": "children", + "lambda": "children", + "named_expression": "children", + "not_operator": "children", + "attribute": "children", + "await": "children", + "binary_operator": "children", + "call": "children", + "concatenated_string": "children", + "dictionary": "children", + "dictionary_comprehension": "children", + "ellipsis": "children", + "false": "children", + "float": "children", + "generator_expression": "children", + "identifier": "children", + "integer": "children", + "list": "children", + "list_comprehension": "children", + "list_splat": "children", + "none": "children", + "parenthesized_expression": "children", + "set": "children", + "set_comprehension": "children", + "string": "children", + "subscript": "children", + "true": "children", + "tuple": "children", + "unary_operator": "children", + "yield": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "parenthesized_list_splat": { + "type": "parenthesized_list_splat", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": { + "list_splat": "children", + "parenthesized_expression": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "pass_statement": { + "type": "pass_statement", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "pattern_list": { + "type": "pattern_list", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": { + "attribute": "children", + "identifier": "children", + "list_pattern": "children", + "list_splat_pattern": "children", + "subscript": "children", + "tuple_pattern": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "positional_separator": { + "type": "positional_separator", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "print_statement": { + "type": "print_statement", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": { + "as_pattern": "argument", + "boolean_operator": "argument", + "comparison_operator": "argument", + "conditional_expression": "argument", + "lambda": "argument", + "named_expression": "argument", + "not_operator": "argument", + "attribute": "argument", + "await": "argument", + "binary_operator": "argument", + "call": "argument", + "concatenated_string": "argument", + "dictionary": "argument", + "dictionary_comprehension": "argument", + "ellipsis": "argument", + "false": "argument", + "float": "argument", + "generator_expression": "argument", + "identifier": "argument", + "integer": "argument", + "list": "argument", + "list_comprehension": "argument", + "list_splat": "argument", + "none": "argument", + "parenthesized_expression": "argument", + "set": "argument", + "set_comprehension": "argument", + "string": "argument", + "subscript": "argument", + "true": "argument", + "tuple": "argument", + "unary_operator": "argument", + "chevron": "children" + }, + "multipleOrChildrenFieldNames": ["argument", "children"] + }, + "raise_statement": { + "type": "raise_statement", + "singleFieldNames": ["cause"], + "nodeTypeToMultipleFieldName": { + "as_pattern": "children", + "boolean_operator": "children", + "comparison_operator": "children", + "conditional_expression": "children", + "lambda": "children", + "named_expression": "children", + "not_operator": "children", + "attribute": "children", + "await": "children", + "binary_operator": "children", + "call": "children", + "concatenated_string": "children", + "dictionary": "children", + "dictionary_comprehension": "children", + "ellipsis": "children", + "false": "children", + "float": "children", + "generator_expression": "children", + "identifier": "children", + "integer": "children", + "list": "children", + "list_comprehension": "children", + "list_splat": "children", + "none": "children", + "parenthesized_expression": "children", + "set": "children", + "set_comprehension": "children", + "string": "children", + "subscript": "children", + "true": "children", + "tuple": "children", + "unary_operator": "children", + "expression_list": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "relative_import": { + "type": "relative_import", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": { + "dotted_name": "children", + "import_prefix": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "return_statement": { + "type": "return_statement", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": { + "as_pattern": "children", + "boolean_operator": "children", + "comparison_operator": "children", + "conditional_expression": "children", + "lambda": "children", + "named_expression": "children", + "not_operator": "children", + "attribute": "children", + "await": "children", + "binary_operator": "children", + "call": "children", + "concatenated_string": "children", + "dictionary": "children", + "dictionary_comprehension": "children", + "ellipsis": "children", + "false": "children", + "float": "children", + "generator_expression": "children", + "identifier": "children", + "integer": "children", + "list": "children", + "list_comprehension": "children", + "list_splat": "children", + "none": "children", + "parenthesized_expression": "children", + "set": "children", + "set_comprehension": "children", + "string": "children", + "subscript": "children", + "true": "children", + "tuple": "children", + "unary_operator": "children", + "expression_list": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "set": { + "type": "set", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": { + "as_pattern": "children", + "boolean_operator": "children", + "comparison_operator": "children", + "conditional_expression": "children", + "lambda": "children", + "named_expression": "children", + "not_operator": "children", + "attribute": "children", + "await": "children", + "binary_operator": "children", + "call": "children", + "concatenated_string": "children", + "dictionary": "children", + "dictionary_comprehension": "children", + "ellipsis": "children", + "false": "children", + "float": "children", + "generator_expression": "children", + "identifier": "children", + "integer": "children", + "list": "children", + "list_comprehension": "children", + "list_splat": "children", + "none": "children", + "parenthesized_expression": "children", + "set": "children", + "set_comprehension": "children", + "string": "children", + "subscript": "children", + "true": "children", + "tuple": "children", + "unary_operator": "children", + "parenthesized_list_splat": "children", + "yield": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "set_comprehension": { + "type": "set_comprehension", + "singleFieldNames": ["body"], + "nodeTypeToMultipleFieldName": { + "for_in_clause": "children", + "if_clause": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "slice": { + "type": "slice", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": { + "as_pattern": "children", + "boolean_operator": "children", + "comparison_operator": "children", + "conditional_expression": "children", + "lambda": "children", + "named_expression": "children", + "not_operator": "children", + "attribute": "children", + "await": "children", + "binary_operator": "children", + "call": "children", + "concatenated_string": "children", + "dictionary": "children", + "dictionary_comprehension": "children", + "ellipsis": "children", + "false": "children", + "float": "children", + "generator_expression": "children", + "identifier": "children", + "integer": "children", + "list": "children", + "list_comprehension": "children", + "list_splat": "children", + "none": "children", + "parenthesized_expression": "children", + "set": "children", + "set_comprehension": "children", + "string": "children", + "subscript": "children", + "true": "children", + "tuple": "children", + "unary_operator": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "splat_pattern": { + "type": "splat_pattern", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": { + "identifier": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "splat_type": { + "type": "splat_type", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": { + "identifier": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "string": { + "type": "string", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": { + "interpolation": "children", + "string_content": "children", + "string_end": "children", + "string_start": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "string_content": { + "type": "string_content", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": { + "escape_interpolation": "children", + "escape_sequence": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "subscript": { + "type": "subscript", + "singleFieldNames": ["value"], + "nodeTypeToMultipleFieldName": { + "as_pattern": "subscript", + "boolean_operator": "subscript", + "comparison_operator": "subscript", + "conditional_expression": "subscript", + "lambda": "subscript", + "named_expression": "subscript", + "not_operator": "subscript", + "attribute": "subscript", + "await": "subscript", + "binary_operator": "subscript", + "call": "subscript", + "concatenated_string": "subscript", + "dictionary": "subscript", + "dictionary_comprehension": "subscript", + "ellipsis": "subscript", + "false": "subscript", + "float": "subscript", + "generator_expression": "subscript", + "identifier": "subscript", + "integer": "subscript", + "list": "subscript", + "list_comprehension": "subscript", + "list_splat": "subscript", + "none": "subscript", + "parenthesized_expression": "subscript", + "set": "subscript", + "set_comprehension": "subscript", + "string": "subscript", + "subscript": "subscript", + "true": "subscript", + "tuple": "subscript", + "unary_operator": "subscript", + "slice": "subscript" + }, + "multipleOrChildrenFieldNames": ["subscript"] + }, + "try_statement": { + "type": "try_statement", + "singleFieldNames": ["body"], + "nodeTypeToMultipleFieldName": { + "else_clause": "children", + "except_clause": "children", + "except_group_clause": "children", + "finally_clause": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "tuple": { + "type": "tuple", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": { + "as_pattern": "children", + "boolean_operator": "children", + "comparison_operator": "children", + "conditional_expression": "children", + "lambda": "children", + "named_expression": "children", + "not_operator": "children", + "attribute": "children", + "await": "children", + "binary_operator": "children", + "call": "children", + "concatenated_string": "children", + "dictionary": "children", + "dictionary_comprehension": "children", + "ellipsis": "children", + "false": "children", + "float": "children", + "generator_expression": "children", + "identifier": "children", + "integer": "children", + "list": "children", + "list_comprehension": "children", + "list_splat": "children", + "none": "children", + "parenthesized_expression": "children", + "set": "children", + "set_comprehension": "children", + "string": "children", + "subscript": "children", + "true": "children", + "tuple": "children", + "unary_operator": "children", + "parenthesized_list_splat": "children", + "yield": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "tuple_pattern": { + "type": "tuple_pattern", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": { + "case_pattern": "children", + "attribute": "children", + "identifier": "children", + "list_pattern": "children", + "list_splat_pattern": "children", + "subscript": "children", + "tuple_pattern": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "type_alias_statement": { + "type": "type_alias_statement", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": { + "type": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "type_parameter": { + "type": "type_parameter", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": { + "type": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "typed_default_parameter": { + "type": "typed_default_parameter", + "singleFieldNames": ["name", "type", "value"], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "typed_parameter": { + "type": "typed_parameter", + "singleFieldNames": ["type"], + "nodeTypeToMultipleFieldName": { + "dictionary_splat_pattern": "children", + "identifier": "children", + "list_splat_pattern": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "unary_operator": { + "type": "unary_operator", + "singleFieldNames": ["argument", "operator"], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "union_pattern": { + "type": "union_pattern", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": { + "class_pattern": "children", + "complex_pattern": "children", + "concatenated_string": "children", + "dict_pattern": "children", + "dotted_name": "children", + "false": "children", + "float": "children", + "integer": "children", + "list_pattern": "children", + "none": "children", + "splat_pattern": "children", + "string": "children", + "true": "children", + "tuple_pattern": "children", + "union_pattern": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "union_type": { + "type": "union_type", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": { + "type": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "while_statement": { + "type": "while_statement", + "singleFieldNames": ["alternative", "body", "condition"], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "wildcard_import": { + "type": "wildcard_import", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "with_clause": { + "type": "with_clause", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": { + "with_item": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "with_item": { + "type": "with_item", + "singleFieldNames": ["value"], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "with_statement": { + "type": "with_statement", + "singleFieldNames": ["body"], + "nodeTypeToMultipleFieldName": { + "with_clause": "children" + }, + "multipleOrChildrenFieldNames": ["children"] + }, + "!=": { + "type": "!=", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "%": { + "type": "%", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "%=": { + "type": "%=", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "&": { + "type": "&", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "&=": { + "type": "&=", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "*": { + "type": "*", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "**": { + "type": "**", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "**=": { + "type": "**=", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "*=": { + "type": "*=", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "+": { + "type": "+", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "+=": { + "type": "+=", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "-": { + "type": "-", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "-=": { + "type": "-=", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "/": { + "type": "/", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "//": { + "type": "//", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "//=": { + "type": "//=", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "/=": { + "type": "/=", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "<": { + "type": "<", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "<<": { + "type": "<<", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "<<=": { + "type": "<<=", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "<=": { + "type": "<=", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "<>": { + "type": "<>", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "==": { + "type": "==", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + ">": { + "type": ">", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + ">=": { + "type": ">=", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + ">>": { + "type": ">>", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + ">>=": { + "type": ">>=", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "@": { + "type": "@", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "@=": { + "type": "@=", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "^": { + "type": "^", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "^=": { + "type": "^=", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "_": { + "type": "_", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "and": { + "type": "and", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "ellipsis": { + "type": "ellipsis", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "escape_interpolation": { + "type": "escape_interpolation", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "escape_sequence": { + "type": "escape_sequence", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "false": { + "type": "false", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "float": { + "type": "float", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "identifier": { + "type": "identifier", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "in": { + "type": "in", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "integer": { + "type": "integer", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "is": { + "type": "is", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "is not": { + "type": "is not", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "line_continuation": { + "type": "line_continuation", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "none": { + "type": "none", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "not in": { + "type": "not in", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "or": { + "type": "or", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "string_end": { + "type": "string_end", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "string_start": { + "type": "string_start", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "true": { + "type": "true", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "type_conversion": { + "type": "type_conversion", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "|": { + "type": "|", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "|=": { + "type": "|=", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + }, + "~": { + "type": "~", + "singleFieldNames": [], + "nodeTypeToMultipleFieldName": {}, + "multipleOrChildrenFieldNames": [] + } +} diff --git a/packages/tree-sitter-port/output/tree-sitter-python/meta.json b/packages/tree-sitter-port/output/tree-sitter-python/meta.json new file mode 100644 index 0000000..82f0b62 --- /dev/null +++ b/packages/tree-sitter-port/output/tree-sitter-python/meta.json @@ -0,0 +1,5 @@ +{ + "name": "tree-sitter-python", + "version": "0.21.0", + "generatedAt": "2024-06-26T15:41:11.470Z" +} diff --git a/packages/tree-sitter-port/output/tree-sitter-python/parser.wasm b/packages/tree-sitter-port/output/tree-sitter-python/parser.wasm new file mode 100755 index 0000000..132a88c Binary files /dev/null and b/packages/tree-sitter-port/output/tree-sitter-python/parser.wasm differ diff --git a/packages/tree-sitter-port/package.json b/packages/tree-sitter-port/package.json new file mode 100644 index 0000000..237c5e7 --- /dev/null +++ b/packages/tree-sitter-port/package.json @@ -0,0 +1,32 @@ +{ + "name": "@codeque/tree-sitter-port", + "description": "Multiline code search for every language. Structural code search for JavaScript, TypeScript, HTML and CSS", + "repository": { + "type": "git", + "url": "https://github.com/codeque-co/codeque" + }, + "bugs": { + "url": "https://github.com/codeque-co/codeque/issues" + }, + "engines": { + "node": ">=14.0.0" + }, + "version": "0.1.0", + "scripts": { + "parsers-upgrade": "ts-node ./src/upgradeParsers.ts", + "build-wasm-and-generate-mappings": "ts-node ./src/buildWasmAndGenerateMappings.ts", + "copy-files": "ts-node ./src/copyFiles.ts", + "lint": "eslint src --ext ts && eslint output --ext json", + "test": "echo 0", + "typecheck": "tsc --project tsconfig.json --noEmit" + }, + "devDependencies": { + "@codeque/core": "^0.6.0", + "@types/node": "16.x", + "eslint": "^8.18.0", + "tree-sitter-cli": "^0.20.8", + "ts-node": "^10.9.1", + "typescript": "5.9.3" + }, + "dependencies": {} +} diff --git a/packages/tree-sitter-port/src/buildWasmAndGenerateMappings.ts b/packages/tree-sitter-port/src/buildWasmAndGenerateMappings.ts new file mode 100644 index 0000000..1b9ac05 --- /dev/null +++ b/packages/tree-sitter-port/src/buildWasmAndGenerateMappings.ts @@ -0,0 +1,91 @@ +import { parsersSettings, inputDirPath, outputDirPath } from './settings' +import { execSync } from 'child_process' +import path from 'path' +import { logger as log } from './log' + +import fs from 'fs' +import { prepareNodeTypes } from './prepareNodeTypes' + +const userSelectedParsers = process.argv.slice(2) + +const selectedParsers = + userSelectedParsers.length > 0 + ? parsersSettings.filter(({ parserType, parserName }) => { + return ( + userSelectedParsers.includes(parserType) || + userSelectedParsers.includes(parserName) + ) + }) + : parsersSettings + +for (const parserSettings of selectedParsers) { + const parserLogName = `${parserSettings.parserType} (${parserSettings.parserName})` + const localLogger = log.info('Generating for ' + parserLogName) + + try { + const parserInputsDir = path.join(inputDirPath, parserSettings.parserName) + + const parserOutputsDir = path.join(outputDirPath, parserSettings.parserName) + + execSync(`rm -rf ${parserOutputsDir}`) + + execSync(`mkdir ${parserOutputsDir}`) + + const packageJson = JSON.parse( + fs.readFileSync(path.join(parserInputsDir, 'package.json')).toString(), + ) + + fs.writeFileSync( + path.join(parserOutputsDir, 'meta.json'), + JSON.stringify( + { + name: parserSettings.parserName, + version: packageJson.version, + generatedAt: new Date(), + }, + null, + 2, + ) + '\n', + ) + + execSync(`npx tree-sitter build-wasm ${parserInputsDir}`) + + const parserGeneratedFileName = `tree-sitter-${parserSettings.parserName + .replace('tree-sitter-', '') + .replace(/-/g, '_')}.wasm` + + execSync( + `mv ${parserGeneratedFileName} ${path.join( + parserOutputsDir, + 'parser.wasm', + )}`, + ) + + localLogger.success('Wasm file generated') + + const typesGroupedByFiledType = prepareNodeTypes({ + nodeTypesToIgnore: parserSettings.nodeTypesToIgnore, + nodeTypesFilePath: path.join( + parserInputsDir, + parserSettings.nodeTypesLocation, + ), + conflictResolvers: parserSettings.conflictResolvers, + log: localLogger.info, + }) + + fs.writeFileSync( + path.join(parserOutputsDir, './fields-meta.json'), + JSON.stringify(typesGroupedByFiledType, null, 2) + '\n', + ) + + localLogger.success('fields meta file generated') + } catch (e: unknown) { + localLogger.error((e as Error)?.message) + } +} + +const nestedLogger = log.info('Linting files...') + +execSync(`yarn lint --fix`) + +nestedLogger.success('Done') diff --git a/packages/tree-sitter-port/src/copyFiles.ts b/packages/tree-sitter-port/src/copyFiles.ts new file mode 100644 index 0000000..939ad1f --- /dev/null +++ b/packages/tree-sitter-port/src/copyFiles.ts @@ -0,0 +1,54 @@ +import { parsersSettings, inputDirPath, outputDirPath } from './settings' +import { execSync } from 'child_process' +import path from 'path' +import { logger as log } from './log' +import { ParserType } from '@codeque/core' + +const parsersToCopy: Array = ['python', 'lua', 'csharp'] + +const filteredParserSettings = parsersSettings.filter(({ parserType }) => + parsersToCopy.includes(parserType), +) + +execSync(`rm -rf ../core/dist-tree-sitter`) +execSync(`rm -rf ../vscode/dist-tree-sitter`) + +execSync(`mkdir ../core/dist-tree-sitter`) +execSync(`mkdir ../vscode/dist-tree-sitter`) + +execSync( + `cp ../../node_modules/web-tree-sitter/tree-sitter.wasm ../core/dist-tree-sitter/tree-sitter.wasm`, +) + +execSync( + `cp ../../node_modules/web-tree-sitter/tree-sitter.wasm ../vscode/dist-tree-sitter/tree-sitter.wasm`, +) + +log.success('Prepared directories and copied tree-sitter.wasm') + +for (const parserSettings of filteredParserSettings) { + const parserLogName = `${parserSettings.parserType} (${parserSettings.parserName})` + const localLogger = log.info('Copying ' + parserLogName + ' parser') + const outputDir = path.join(outputDirPath, parserSettings.parserName) + + const coreDirName = path.join( + process.cwd(), + '../core/dist-tree-sitter', + parserSettings.parserName, + ) + const vscodeDirName = path.join( + process.cwd(), + '../vscode/dist-tree-sitter', + parserSettings.parserName, + ) + + try { + execSync(`cp -r ${outputDir} ${coreDirName}`) + + execSync(`cp -r ${outputDir} ${vscodeDirName}`) + + localLogger.success('Copied ' + parserLogName + ' parser') + } catch (e: unknown) { + localLogger.error((e as Error).message) + } +} diff --git a/packages/tree-sitter-port/src/log.ts b/packages/tree-sitter-port/src/log.ts new file mode 100644 index 0000000..c34f7c0 --- /dev/null +++ b/packages/tree-sitter-port/src/log.ts @@ -0,0 +1,31 @@ +const info = (msg: string, prefix = '') => { + process.stdout.write(prefix + 'ℹ️ ' + msg + '\n') +} + +const success = (msg: string, prefix = '') => { + process.stdout.write(prefix + '✅ ' + msg + '\n') +} + +const error = (msg: string, prefix = '') => { + process.stdout.write(prefix + '❌ ' + msg + '\n') +} + +const createLogger = (prefix: string) => ({ + info: (msg: string) => { + info(`${prefix}${msg}`, prefix) + + return createLogger(' ') + }, + success: (msg: string) => { + success(`${prefix}${msg}`, prefix) + + return createLogger(' ') + }, + error: (msg: string) => { + error(`${prefix}${msg}`, prefix) + + return createLogger(' ') + }, +}) + +export const logger = createLogger('') diff --git a/packages/tree-sitter-port/src/prepareNodeTypes.ts b/packages/tree-sitter-port/src/prepareNodeTypes.ts new file mode 100644 index 0000000..781e21f --- /dev/null +++ b/packages/tree-sitter-port/src/prepareNodeTypes.ts @@ -0,0 +1,297 @@ +const fs = require('fs') + +type NodeTypes = Array<{ + type: string + named: boolean + fields?: Record< + string, + { + multiple: boolean + required: boolean + types: Array<{ + type: string + named: boolean + }> + } + > + children?: { + multiple: boolean + required: boolean + types: Array<{ + type: string + named: boolean + }> + } + + subtypes?: Array<{ + type: string + named: boolean + }> +}> + +type FieldsMeta = { + type: string + singleFieldNames: string[] + nodeTypeToMultipleFieldName: Record + multipleOrChildrenFieldNames: string[] +} + +export type ConflictResolver = { + nodeType: string + fields: [string, string] + mergeToField: string +} + +export const prepareNodeTypes = ({ + nodeTypesToIgnore, + nodeTypesFilePath, + conflictResolvers = [], + log, +}: { + nodeTypesToIgnore: string[] + nodeTypesFilePath: string + conflictResolvers?: Array + log: (msg: string) => void +}) => { + const nodeTypes: NodeTypes = JSON.parse( + fs.readFileSync(nodeTypesFilePath).toString(), + ) + + const types: Record< + string, + { + named: boolean + type: string + fieldsMeta: null | Record< + string, + { + multipleOrChildren: boolean + nodeTypes: string[] + } + > + } + > = {} + + /** + * Not named types are usually some keywords or language constants. + * We don't care about them unless they two or more of not named types can occur in the same place in tree. + * We capture that when processing nested node types and filter out all Not named except those relevant. + */ + const notNamedNodeTypesToInclude: string[] = [] + + const nodeTypesWithSubTypes = nodeTypes.filter(({ subtypes }) => subtypes) + + const subTypesMap = Object.fromEntries( + nodeTypesWithSubTypes.map(({ type, subtypes }) => [ + type, + subtypes?.map(({ type }) => type), + ]), + ) + + /** + * Some subtypes are nested, hence recursion + * */ + const extrapolateBySubtype = (nodeType: string): string[] => + subTypesMap[nodeType] + ? subTypesMap[nodeType]?.map(extrapolateBySubtype)?.flat() ?? [] + : [nodeType] + + nodeTypes.forEach((nodeType) => { + const fieldsRaw = nodeType.fields ?? {} + + if (nodeType.children) { + fieldsRaw.children = nodeType.children + } + + const fieldsMeta = fieldsRaw + ? Object.fromEntries( + Object.entries(fieldsRaw).map(([fieldName, value]) => { + const notNamedNodeTypesInField = value.types + .filter(({ named }) => !named) + .map(({ type }) => type) + notNamedNodeTypesToInclude.push(...notNamedNodeTypesInField) + + return [ + fieldName, + { + multipleOrChildren: value.multiple || fieldName === 'children', + /* + * Some types are just aliases for types group, eg. "expression" can be "boolean_operator" "primary_expression" and others + * Some subtypes are nested so we do recursion, hence we have to flat. + */ + nodeTypes: value.types + .map(({ type }) => extrapolateBySubtype(type)) + .flat(1), + }, + ] + }), + ) + : null + + if (fieldsMeta) { + const entries = Object.entries(fieldsMeta) + + const multipleOrChildren = entries.filter( + ([, value]) => value.multipleOrChildren, + ) + let conflict: { + nodeType: string + fieldNameA: string + fieldNameB: string + conflictingNodeType: string + nodeTypesFilePath: string + } | null = null + + if (multipleOrChildren.length > 1) { + for (let a = 0; a < multipleOrChildren.length && !conflict; a++) { + for (let b = a + 1; b < multipleOrChildren.length && !conflict; b++) { + const fieldNameA = multipleOrChildren[a][0] + const fieldNameB = multipleOrChildren[b][0] + + const typesA = multipleOrChildren[a][1].nodeTypes + const typesB = multipleOrChildren[b][1].nodeTypes + + const hasConflict = typesA.some((typeA) => typesB.includes(typeA)) + + if (hasConflict) { + let conflictingNodeType = null + + for (let i = 0; i < typesA.length && !conflictingNodeType; i++) { + const typeAIndex = typesB.indexOf(typesA[i]) + + if (typeAIndex > -1) { + conflictingNodeType = typesA[i] + } + } + + if ( + conflictingNodeType && + !nodeTypesToIgnore.includes(conflictingNodeType) + ) { + /* + * Conflict is when two or more fields with multiple children can have node of the same type within. + * In this case we won't be able to properly assign nodes by type to given named field. + * If conflict would occur for other parsers, we should put all node types from conflicting fields into common "children" field, as we cannot do anything better in this case. + * + * We don't care about conflicts between ignored node types, as they will not be processed anyway + */ + conflict = { + nodeType: nodeType.type, + fieldNameA, + fieldNameB, + conflictingNodeType, + nodeTypesFilePath, + } + } + } + } + } + } + + if (conflict !== null) { + const conflictObj = conflict // TS is freaking out + + const conflictJsonString = JSON.stringify(conflictObj, null, 2) + const resolver = conflictResolvers.find(({ fields, nodeType }) => { + return ( + fields.includes(conflictObj.fieldNameA) && + fields.includes(conflictObj.fieldNameB) && + nodeType === conflictObj.nodeType + ) + }) + + if (resolver) { + const mergeSourceFieldName = [ + conflictObj.fieldNameA, + conflictObj.fieldNameB, + ].filter((fieldName) => fieldName !== resolver.mergeToField)[0] + + fieldsMeta[resolver.mergeToField].nodeTypes = uniq([ + ...fieldsMeta[resolver.mergeToField].nodeTypes, + ...fieldsMeta[mergeSourceFieldName].nodeTypes, + ]) + + delete fieldsMeta[mergeSourceFieldName] + + log('Resolved conflict ' + conflictJsonString) + } else { + throw new Error('Node types conflict: ' + conflictJsonString) + } + } + } + + const data = { + named: nodeType.named, + type: nodeType.type, + fieldsMeta, + } + + types[nodeType.type] = data + }) + + const uniqueNotNamedNodeTypesToInclude = new Set(notNamedNodeTypesToInclude) + + const typesEntriesWithoutSomeNotNamed = Object.entries(types).filter( + ([, { named, type }]) => + (named || uniqueNotNamedNodeTypesToInclude.has(type)) && + !subTypesMap[type], + ) + + const typesEntriesWithoutSomeNotNamedAndIgnoredTypes: typeof typesEntriesWithoutSomeNotNamed = + typesEntriesWithoutSomeNotNamed.filter( + ([, { named, type }]) => !nodeTypesToIgnore.includes(type), + ) + + const typesEntriesGroupedByFiledType: Array<[string, FieldsMeta]> = + typesEntriesWithoutSomeNotNamedAndIgnoredTypes.map( + ([nodeType, nodeData]) => { + if (nodeData.fieldsMeta === null) { + return [ + nodeType, + { + type: nodeData.type, + singleFieldNames: [], + nodeTypeToMultipleFieldName: {}, + multipleOrChildrenFieldNames: [], + }, + ] + } + + const fieldsMetaEntries = Object.entries(nodeData.fieldsMeta) + const multipleOrChildrenFieldEntries = fieldsMetaEntries.filter( + ([, fieldMeta]) => fieldMeta.multipleOrChildren, + ) + + const nodeTypeToMultipleFieldNameEntries = + multipleOrChildrenFieldEntries + .map(([fieldName, { nodeTypes }]) => + nodeTypes.map((nodeType) => [nodeType, fieldName]), + ) + .flat(1) + + return [ + nodeType, + { + type: nodeData.type, + singleFieldNames: fieldsMetaEntries + .filter(([, fieldMeta]) => !fieldMeta.multipleOrChildren) + .map(([fieldName]) => fieldName), + nodeTypeToMultipleFieldName: Object.fromEntries( + nodeTypeToMultipleFieldNameEntries, + ), + multipleOrChildrenFieldNames: multipleOrChildrenFieldEntries.map( + ([fieldName]) => fieldName, + ), + }, + ] + }, + ) + + const typesGroupedByFiledType = Object.fromEntries( + typesEntriesGroupedByFiledType, + ) + + return typesGroupedByFiledType +} + +const uniq = (arr: T[]) => [...new Set(arr)] diff --git a/packages/tree-sitter-port/src/settings.ts b/packages/tree-sitter-port/src/settings.ts new file mode 100644 index 0000000..a500e30 --- /dev/null +++ b/packages/tree-sitter-port/src/settings.ts @@ -0,0 +1,76 @@ +import { ParserType } from '@codeque/core' + +import path from 'path' +import { ConflictResolver } from './prepareNodeTypes' +type ParserSettings = { + parserType: ParserType + parserName: string + repoUrl: string + nodeTypesLocation: string + buildWasmCommand: string + nodeTypesToIgnore: string[] + conflictResolvers?: ConflictResolver[] +} + +export const inputDirPath = path.join(process.cwd(), 'input') +export const outputDirPath = path.join(process.cwd(), 'output') + +const getWasmBuildCommand = (treeSitterDirName: string) => + `npx tree-sitter build-wasm ${path.join(inputDirPath, treeSitterDirName)}` + +export const parsersSettings: ParserSettings[] = [ + { + parserType: 'python', + parserName: 'tree-sitter-python', + repoUrl: 'https://github.com/tree-sitter/tree-sitter-python.git', + nodeTypesLocation: 'src/node-types.json', + buildWasmCommand: getWasmBuildCommand('tree-sitter-python'), + nodeTypesToIgnore: [',', 'comment'], + conflictResolvers: [ + { + nodeType: 'dict_pattern', + fields: ['key', 'children'], + mergeToField: 'children', + }, + ], + }, + { + parserType: 'babel', // temporary for test + parserName: 'tree-sitter-c', + repoUrl: 'https://github.com/tree-sitter/tree-sitter-c.git', + nodeTypesLocation: 'src/node-types.json', + buildWasmCommand: getWasmBuildCommand('tree-sitter-c'), + nodeTypesToIgnore: [], + }, + { + parserType: 'csharp', + parserName: 'tree-sitter-c-sharp', + repoUrl: 'https://github.com/tree-sitter/tree-sitter-c-sharp.git', + nodeTypesLocation: 'src/node-types.json', + buildWasmCommand: getWasmBuildCommand('tree-sitter-c-sharp'), + nodeTypesToIgnore: [','], + }, + { + parserType: 'lua', // temporary for test + parserName: 'tree-sitter-lua', + repoUrl: 'https://github.com/MunifTanjim/tree-sitter-lua.git', + nodeTypesLocation: 'src/node-types.json', + buildWasmCommand: getWasmBuildCommand('tree-sitter-lua'), + nodeTypesToIgnore: ["'", '"'], + conflictResolvers: [ + { + nodeType: 'expression_list', + fields: ['value', 'children'], + mergeToField: 'children', + }, + ], + }, + { + parserType: 'babel', // temporary for test + parserName: 'tree-sitter-php', + repoUrl: 'https://github.com/tree-sitter/tree-sitter-php.git', + nodeTypesLocation: 'src/node-types.json', + buildWasmCommand: getWasmBuildCommand('tree-sitter-php'), + nodeTypesToIgnore: [], + }, +] diff --git a/packages/tree-sitter-port/src/upgradeParsers.ts b/packages/tree-sitter-port/src/upgradeParsers.ts new file mode 100644 index 0000000..a52c5a7 --- /dev/null +++ b/packages/tree-sitter-port/src/upgradeParsers.ts @@ -0,0 +1,37 @@ +import { parsersSettings, inputDirPath } from './settings' +import { execSync } from 'child_process' +import path from 'path' +import { logger as log } from './log' + +const userSelectedParsers = process.argv.slice(2) + +const selectedParsers = + userSelectedParsers.length > 0 + ? parsersSettings.filter(({ parserType, parserName }) => { + return ( + userSelectedParsers.includes(parserType) || + userSelectedParsers.includes(parserName) + ) + }) + : parsersSettings + +for (const parserSettings of selectedParsers) { + const parserLogName = `${parserSettings.parserType} (${parserSettings.parserName})` + const localLogger = log.info('Fetching ' + parserLogName + ' parser') + try { + const parserDir = path.join(inputDirPath, parserSettings.parserName) + + execSync(`rm -rf ${parserDir}`) + + execSync( + `git clone ${parserSettings.repoUrl} ${path.join( + inputDirPath, + parserSettings.parserName, + )}`, + ) + + localLogger.success('Fetched ' + parserLogName + ' parser') + } catch (e: unknown) { + localLogger.error((e as Error).message) + } +} diff --git a/packages/tree-sitter-port/tsconfig.json b/packages/tree-sitter-port/tsconfig.json new file mode 100644 index 0000000..10c6e50 --- /dev/null +++ b/packages/tree-sitter-port/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "baseUrl": "./src", + "skipLibCheck": true, + "declaration": false + }, + "exclude": ["dist", "node_modules", "output", "input"] +} diff --git a/packages/vscode/.gitignore b/packages/vscode/.gitignore index 41bcfe0..cdbfa64 100644 --- a/packages/vscode/.gitignore +++ b/packages/vscode/.gitignore @@ -1,2 +1,4 @@ dist-webviews -*.vsix \ No newline at end of file +*.vsix +builds +tsconfig.tsbuildinfo \ No newline at end of file diff --git a/packages/vscode/CHANGELOG.md b/packages/vscode/CHANGELOG.md deleted file mode 100644 index e593e9d..0000000 --- a/packages/vscode/CHANGELOG.md +++ /dev/null @@ -1,9 +0,0 @@ -# Change Log - -All notable changes to the "codeque" extension will be documented in this file. - -Check [Keep a Changelog](http://keepachangelog.com/) for recommendations on how to structure this file. - -## [Unreleased] - -- Initial release \ No newline at end of file diff --git a/packages/vscode/InternalReadme.md b/packages/vscode/InternalReadme.md index d201b54..7a06c88 100644 --- a/packages/vscode/InternalReadme.md +++ b/packages/vscode/InternalReadme.md @@ -1,5 +1,7 @@ ## Development +Make sure to build core package first `yarn workspace @codeque/core build` + Run `yarn watch:extension` and `yarn watch:webviews` Open Vscode and run `Run extension` configuration in debugger @@ -8,14 +10,35 @@ While in VSCode with extension host run `> Reload Extension` to refresh webview To refresh extension backed re-run debugger configuration. -## Publish +## Testing production build locally +Make sure to build core package first `yarn workspace @codeque/core build` + +Then change version in package.json to include `-local` suffix, eg. `0.35.1-local`. + +Otherwise vscode will confuse locally installed version and you won't be able to download published version with the same version code, unless you uninstall local version manually + +Package the extension into vsix file `cd packages/vscode && vsce package` + +Command runs checks, run webpack build and package extension into `vsix` (kind of archive file) + +You will get file `codeque-.vsix` + +The install extension from command pallette in vscode `cmd+p` -> `install from VSIX` -> select generated file -> Hit "Restart Extensions" button + +## Publish to official Visual Studio Code Marketplace Bump version manually in package.json And just run -`vsce publish` +`vsce publish -p ` `vsce` will automatically run pre-publish hooks from script `vscode:prepublish` to run checks and build package -You might be asked to [get new PAT](https://code.visualstudio.com/api/working-with-extensions/publishing-extension#get-a-personal-access-token) \ No newline at end of file +You might be asked to [get new PAT](https://code.visualstudio.com/api/working-with-extensions/publishing-extension#get-a-personal-access-token) + +## Publish to Open VSX registry + +Same procedure as above, but run + +`ovsx publish -p ` diff --git a/packages/vscode/README.md b/packages/vscode/README.md index 9e9010a..77078ef 100644 --- a/packages/vscode/README.md +++ b/packages/vscode/README.md @@ -1,57 +1,104 @@ + + + + +
+ + +

- - + +

- Home Page | - Docs | - Roadmap | - Mission | - Playground + Website  •   + Docs   •   + Roadmap  •   + Mission  •   + Playground

-# CodeQue - Multiline Structural Search for Visual Studio Code +

Find and lint complex code patterns effortlessly

-CodeQue is code search engine that understands the code syntax. +--- -It matches structure rather than plain text, which makes it very effective for complex queries. +# What is CodeQue? -VSCode extension aims to improve code search and navigation experience. +CodeQue is semantic code search engine that understands the code syntax. -Advanced search options and todo-like list of accurate search results makes it your new super power. +It matches code structurally which makes it excellent for more complex queries. -It's one of the tools in the ecosystem. There is also [CLI tool](https://www.npmjs.com/package/@codeque/cli) and [ESLint plugin](https://www.npmjs.com/package/@codeque/eslint-plugin) for creating custom rules in zero time. +Query language offers wildcards, partial matching and ignores code formatting. -CodeQue supports multiline search for any programming language and structural search for JavaScript and TypeScript. +Structural code search is available for JavaScript, TypesScript, HTML, CSS, Python, Lua, C# and more soon. -Structural search support for other programming languages will be added soon. +Text code search with handy wildcards is available for **every language** and covers common regex search use cases. -
+

Give it a try in + playground

-

Watch extension in action (external link) 👇

+

Just paste code snippet to start searching, no installation needed!

- - - +**Integrations** + +CodeQue is available as: + +- [VSCode extension](https://marketplace.visualstudio.com/items?itemName=CodeQue.codeque) for delightful code search and navigation experience. +- [ESLint integration](https://www.npmjs.com/package/@codeque/eslint-plugin) for creating custom linting rules in zero time. +- [CLI tool](https://www.npmjs.com/package/@codeque/cli) for searching code and more including headless environments. + +

All CodeQue tools work offline hence code never leaves your local environment.

+ +**Coming soon** + +CodeQue will be soon available as: -## Benefits of using CodeQue VSCode extension +- Duplicated code identification +- Batch code refactoring +- Advanced ESLint rule creator -CodeQue is more than just a search tool - it's a refactoring tool as well. +

🔔 Get notified about updates 🔔

-It addresses the problems of standard search by providing multiline support and an easy way to add gaps or use wildcards into the query. +
-You don't need to have any knowledge of Regular Expressions to query complex code patterns. + -It helps developers with code refactoring, speeds up project discovery, and makes it easy to find duplicated or similar code. patterns. + + +## Visual Studio Code Extension 🔮 + +VScode extension aims to make your workflow more efficient. + +It addresses the problems of standard search by providing multiline support and offers an easy way to add gaps or use wildcards in the query. + +You don't need to have any Regex knowledge to query complex code patterns. With CodeQue, you can easily navigate and modify your codebase, making your development process faster and more efficient. -## Overview +It will help you with code refactoring, speed up project discovery, and make it easy to find duplicated or similar code patterns. + +Advanced code search options and todo-like list of accurate search results will streamline your workflow. + +
+ +

Watch extension in action in 1 minute (external link) 👇

+ + + + + +
+ + + +## About + One of the main strengths of CodeQue is its easy-to-use query language, but it also offers several additional features that make it a great support tool for your daily work. **Features** + - [Query language](#query-language) - [Search modes](#search-modes) - [Searching by file imports](#searching-by-file-imports) @@ -62,6 +109,7 @@ One of the main strengths of CodeQue is its easy-to-use query language, but it a - [Search errors](#search-errors) **Example Queries** + - [All usages of `console` object](#all-usages-of-console-object) - [Object with given key and value](#object-with-given-key-and-value) - [React component with specific props combination](#react-component-with-specific-props-combination) @@ -75,7 +123,7 @@ One of the main strengths of CodeQue is its easy-to-use query language, but it a ### Query language The beauty of CodeQue query language is that the query has to be valid source code. -You don't have to learn anything new! +You don't have to learn anything new! Except the fact there are a few types of wildcards. @@ -85,7 +133,7 @@ It matches all identifiers, JSX identifiers, types identifiers, function names a `$$$` is an statement/expression wildcard. -It matches any statement or expression. Think of it as 'match anything'. +It matches any statement or expression. Think of it as 'match anything'. There a few quirks there. It's good to have general understanding of how code is represented in AST (Abstract Syntax Tree) for more complex queries. More technically `$$$` wildcard is matching any node with all it's children. @@ -100,7 +148,7 @@ Strings have their's own wildcards #### Number wildcard -`0x0`- matches any number +`0x0`- matches any number Here is an example of query which finds all types of logs which includes word `test` in parameter 👇 @@ -111,6 +159,7 @@ Here is an example of query which finds all types of logs which includes word `t ### Search modes CodeQue offers the following search modes + - include - text - exact @@ -118,13 +167,13 @@ CodeQue offers the following search modes
-The most useful mode is `include`. As the name suggest the matched code has to include the code from query, but it can also contain other statements. It performs structural comparison. +The most useful mode is `include`. As the name suggest the matched code has to include the code from query, but it can also contain other statements. It performs structural comparison. [Learn more about `include` search mode](https://codeque.co/docs#include-search-mode)
-`text` search mode is good replacement of build-in vscode search. It acts like a normal text search, but it's big advantage is that it allows for matching multiline statements. It also offers it's own types of wildcards. +`text` search mode is good replacement of build-in vscode search. It acts like a normal text search, but it's big advantage is that it allows for matching multiline statements. It also offers it's own types of wildcards. [Learn more about `text` search mode](https://codeque.co/docs#include-search-mode) @@ -136,13 +185,13 @@ Sometimes you might want to find the code that matches exactly your query. Here
-Last but not least, `include-with-order` search mode can be useful in some rare cases. Same like `include` mode it matches code structurally and allows for missing parts, but in addition, it require the order to match. +Last but not least, `include-with-order` search mode can be useful in some rare cases. Same like `include` mode it matches code structurally and allows for missing parts, but in addition, it require the order to match. [Learn more about `include-with-order` search mode](https://codeque.co/docs#include-w-ith-order-search-mode)
-Here is the example of `include` mode matching function body containing statements from query 👇 +Here is the example of `include` mode matching function body containing statements from query 👇 @@ -183,6 +232,7 @@ You can define glob patters to either include or exclude files from the list. By default CodeQue is not searching in files ignored by `.gitignore` Enable the following flags with caution, as they might significantly downgrade search performance for lager projects. + - Search ignored files - Search `node_modules` (for most projects to search node_modules you have to also enable searching by ignored files) - Search files above 100kb (these are usually bundled files which are structurally heavy due to their size and amount of information) @@ -195,7 +245,7 @@ Example files list settings 👇 ### Case sensitivity -You can choose whether to compare identifier persisting their original case or do case insensitive match. +You can choose whether to compare identifier persisting their original case or do case insensitive match. ### Search errors @@ -207,7 +257,7 @@ You can check search error details in tooltip available after click the error co ## Query examples -CodeQue is general purpose search tool. The examples list could be endless. Here are some of them for you to get a glimpse of what's possible. Those are relatively simple, you definitely would find some more complex during day to day work. +CodeQue is general purpose code search tool. The examples list could be endless. Here are some of them for you to get a glimpse of what's possible. Those are relatively simple, you will definitely find some more complex during day to day work. > Don't know how to write a query? [Open an issue on GitHub](https://github.com/codeque-co/codeque/issues) ! @@ -225,17 +275,18 @@ console.$$() CodeQue ability to match parts of the objects might be very useful, especially for searching patterns in large JSONs. -This query will match part of an object literal in file or JSON (depending on file extension) regardless of how deeply nested the object is in the structure. +This query will match part of an object literal in file or JSON (depending on file extension) regardless of how deeply nested the object is in the structure. More specifically it will match all objects with at least one address entry with country specified to `PL` and phone number which is non empty string. - ```ts -({ - addresses: [{ - country: 'PL' - }], - phoneNumber: "$$$" +;({ + addresses: [ + { + country: 'PL', + }, + ], + phoneNumber: '$$$', }) ``` @@ -243,14 +294,10 @@ More specifically it will match all objects with at least one address entry with ### React component with specific props combination -I found it very useful to find props with specific props combination. Sometimes props depends on each other and we might want to refactor some of them, but how do we determine whether they are used together? We can review long list of results for basic search, but who has time for that 😉 +I found it very useful to find props with specific props combination. Sometimes props depends on each other and we might want to refactor some of them, but how do we determine whether they are used together? We can review long list of results for basic code search, but who has time for that 😉 ```tsx -