feat(providence): oxc for main analyzers and traversal; parsers as peerDeps

This commit is contained in:
Thijs Louisse 2024-10-25 14:58:54 +02:00 committed by Thijs Louisse
parent 6f3137c963
commit 615472cc0a
41 changed files with 870 additions and 1818 deletions

View file

@ -0,0 +1,12 @@
---
'providence-analytics': minor
---
- use oxc for all analyzers (oxc is way smaller and more performant than swc, let alone babel)
- make swcTraverse compatible with oxc
- expand scope functionality of swcTraverse
BREAKING:
- make parsers peerDependencies (babel or swc should be loaded by external analyzers)
- rename `swcTraverse` to `oxcTraverse`

163
package-lock.json generated
View file

@ -4198,15 +4198,6 @@
"node": ">=18" "node": ">=18"
} }
}, },
"node_modules/@putout/babel": {
"version": "2.8.0",
"resolved": "https://registry.npmjs.org/@putout/babel/-/babel-2.8.0.tgz",
"integrity": "sha512-Vq4DgAR6Zfc0VXyspQndmgT4T7sTgJBm8kwigN2zPxtyTtz8R199qjxSrypY1P2d+iAGatG2imksrzlPOlombg==",
"license": "MIT",
"engines": {
"node": ">=16"
}
},
"node_modules/@rocket/blog": { "node_modules/@rocket/blog": {
"version": "0.4.0", "version": "0.4.0",
"resolved": "https://registry.npmjs.org/@rocket/blog/-/blog-0.4.0.tgz", "resolved": "https://registry.npmjs.org/@rocket/blog/-/blog-0.4.0.tgz",
@ -5525,6 +5516,7 @@
"version": "1.7.36", "version": "1.7.36",
"resolved": "https://registry.npmjs.org/@swc/core/-/core-1.7.36.tgz", "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.7.36.tgz",
"integrity": "sha512-bu7ymMX+LCJOSSrKank25Jaq66ymLVA9fOUuy4ck3/6rbXdLw+pIJPnIDKQ9uNcxww8KDxOuJk9Ui9pqR+aGFw==", "integrity": "sha512-bu7ymMX+LCJOSSrKank25Jaq66ymLVA9fOUuy4ck3/6rbXdLw+pIJPnIDKQ9uNcxww8KDxOuJk9Ui9pqR+aGFw==",
"dev": true,
"hasInstallScript": true, "hasInstallScript": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
@ -5566,6 +5558,7 @@
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
"dev": true,
"license": "Apache-2.0 AND MIT", "license": "Apache-2.0 AND MIT",
"optional": true, "optional": true,
"os": [ "os": [
@ -5582,6 +5575,7 @@
"cpu": [ "cpu": [
"x64" "x64"
], ],
"dev": true,
"license": "Apache-2.0 AND MIT", "license": "Apache-2.0 AND MIT",
"optional": true, "optional": true,
"os": [ "os": [
@ -5598,6 +5592,7 @@
"cpu": [ "cpu": [
"arm" "arm"
], ],
"dev": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"optional": true, "optional": true,
"os": [ "os": [
@ -5614,6 +5609,7 @@
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
"dev": true,
"license": "Apache-2.0 AND MIT", "license": "Apache-2.0 AND MIT",
"optional": true, "optional": true,
"os": [ "os": [
@ -5630,6 +5626,7 @@
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
"dev": true,
"license": "Apache-2.0 AND MIT", "license": "Apache-2.0 AND MIT",
"optional": true, "optional": true,
"os": [ "os": [
@ -5646,6 +5643,7 @@
"cpu": [ "cpu": [
"x64" "x64"
], ],
"dev": true,
"license": "Apache-2.0 AND MIT", "license": "Apache-2.0 AND MIT",
"optional": true, "optional": true,
"os": [ "os": [
@ -5662,6 +5660,7 @@
"cpu": [ "cpu": [
"x64" "x64"
], ],
"dev": true,
"license": "Apache-2.0 AND MIT", "license": "Apache-2.0 AND MIT",
"optional": true, "optional": true,
"os": [ "os": [
@ -5678,6 +5677,7 @@
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
"dev": true,
"license": "Apache-2.0 AND MIT", "license": "Apache-2.0 AND MIT",
"optional": true, "optional": true,
"os": [ "os": [
@ -5694,6 +5694,7 @@
"cpu": [ "cpu": [
"ia32" "ia32"
], ],
"dev": true,
"license": "Apache-2.0 AND MIT", "license": "Apache-2.0 AND MIT",
"optional": true, "optional": true,
"os": [ "os": [
@ -5710,6 +5711,7 @@
"cpu": [ "cpu": [
"x64" "x64"
], ],
"dev": true,
"license": "Apache-2.0 AND MIT", "license": "Apache-2.0 AND MIT",
"optional": true, "optional": true,
"os": [ "os": [
@ -5723,12 +5725,14 @@
"version": "0.1.3", "version": "0.1.3",
"resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz", "resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz",
"integrity": "sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==", "integrity": "sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==",
"dev": true,
"license": "Apache-2.0" "license": "Apache-2.0"
}, },
"node_modules/@swc/types": { "node_modules/@swc/types": {
"version": "0.1.13", "version": "0.1.13",
"resolved": "https://registry.npmjs.org/@swc/types/-/types-0.1.13.tgz", "resolved": "https://registry.npmjs.org/@swc/types/-/types-0.1.13.tgz",
"integrity": "sha512-JL7eeCk6zWCbiYQg2xQSdLXQJl8Qoc9rXmG2cEKvHe3CKwMHwHGpfOb8frzNLmbycOo6I51qxnLnn9ESf4I20Q==", "integrity": "sha512-JL7eeCk6zWCbiYQg2xQSdLXQJl8Qoc9rXmG2cEKvHe3CKwMHwHGpfOb8frzNLmbycOo6I51qxnLnn9ESf4I20Q==",
"dev": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"@swc/counter": "^0.1.3" "@swc/counter": "^0.1.3"
@ -28032,17 +28036,13 @@
"license": "MIT" "license": "MIT"
}, },
"packages-node/providence-analytics": { "packages-node/providence-analytics": {
"version": "0.16.5", "version": "0.16.8",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@babel/parser": "^7.25.8",
"@babel/plugin-syntax-import-assertions": "^7.25.7",
"@babel/traverse": "^7.25.7", "@babel/traverse": "^7.25.7",
"@babel/types": "^7.25.8",
"@putout/babel": "^2.8.0",
"@rollup/plugin-node-resolve": "^15.3.0", "@rollup/plugin-node-resolve": "^15.3.0",
"@swc/core": "^1.7.36",
"commander": "^2.20.3", "commander": "^2.20.3",
"oxc-parser": "^0.34.0",
"parse5": "^7.2.0", "parse5": "^7.2.0",
"semver": "^7.6.3" "semver": "^7.6.3"
}, },
@ -28050,6 +28050,9 @@
"providence": "src/cli/index.js" "providence": "src/cli/index.js"
}, },
"devDependencies": { "devDependencies": {
"@babel/parser": "^7.25.8",
"@babel/plugin-syntax-import-assertions": "^7.25.7",
"@swc/core": "^1.7.36",
"@types/inquirer": "^9.0.7", "@types/inquirer": "^9.0.7",
"@types/mocha": "^10.0.9", "@types/mocha": "^10.0.9",
"@web/dev-server": "^0.4.6", "@web/dev-server": "^0.4.6",
@ -28060,6 +28063,134 @@
}, },
"engines": { "engines": {
"node": ">=18.0.0" "node": ">=18.0.0"
},
"peerDependencies": {
"@babel/parser": "^7.25.8",
"@babel/plugin-syntax-import-assertions": "^7.25.7",
"@swc/core": "^1.7.36"
}
},
"packages-node/providence-analytics/node_modules/@oxc-parser/binding-darwin-arm64": {
"version": "0.34.0",
"resolved": "https://registry.npmjs.org/@oxc-parser/binding-darwin-arm64/-/binding-darwin-arm64-0.34.0.tgz",
"integrity": "sha512-1G99sWa40ylI1bX8VxUnvl3eEnT7C045t6HJ5UkYg+B6XoJouUgo/wrhFn53+kRBm9gT65YHBGs4gCLpk8b8dw==",
"cpu": [
"arm64"
],
"license": "MIT",
"optional": true,
"os": [
"darwin"
]
},
"packages-node/providence-analytics/node_modules/@oxc-parser/binding-darwin-x64": {
"version": "0.34.0",
"resolved": "https://registry.npmjs.org/@oxc-parser/binding-darwin-x64/-/binding-darwin-x64-0.34.0.tgz",
"integrity": "sha512-7+EcaPjG7PlnM/Oj2Cpf1tYFCXmABlO7P9uYNa/aiNVHv4oZklL2q1HT3fSsU/7nNa09IDKsl8yvQHhwfkNbWw==",
"cpu": [
"x64"
],
"license": "MIT",
"optional": true,
"os": [
"darwin"
]
},
"packages-node/providence-analytics/node_modules/@oxc-parser/binding-linux-arm64-gnu": {
"version": "0.34.0",
"resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-0.34.0.tgz",
"integrity": "sha512-uiQfQESB5WLxVUwWe+/wiaujoT0we5tDm7fz3EwpgqKDsqA3Y/IobLsymIPp5/dfOmOUElB9dMUZUaDTrQeWtA==",
"cpu": [
"arm64"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
]
},
"packages-node/providence-analytics/node_modules/@oxc-parser/binding-linux-arm64-musl": {
"version": "0.34.0",
"resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-arm64-musl/-/binding-linux-arm64-musl-0.34.0.tgz",
"integrity": "sha512-08ChBq0X4U60B6ervmNDdSIjxleeCLrztbhul/cFFRcqUNj10F7ZLgz7/rucfxD80hE8Cf2PsbVK5e89qY7Y/Q==",
"cpu": [
"arm64"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
]
},
"packages-node/providence-analytics/node_modules/@oxc-parser/binding-linux-x64-gnu": {
"version": "0.34.0",
"resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-x64-gnu/-/binding-linux-x64-gnu-0.34.0.tgz",
"integrity": "sha512-OVXEcQu9/FxUeSK1RGgvBzHNKQYjiYL536GahOzuptCyYjYUhrz+eopu7P0wIB/1irrMv33gCNt211inVnWaZQ==",
"cpu": [
"x64"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
]
},
"packages-node/providence-analytics/node_modules/@oxc-parser/binding-linux-x64-musl": {
"version": "0.34.0",
"resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-x64-musl/-/binding-linux-x64-musl-0.34.0.tgz",
"integrity": "sha512-/9WFKdTDKVRs2JJh4oxF1gEbnlJcZtoIAH6yToTqftghal7NFLoHBGdp1Jo8b2m0Vn4L3yiDE/sAbrB0XgIAsw==",
"cpu": [
"x64"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
]
},
"packages-node/providence-analytics/node_modules/@oxc-parser/binding-win32-arm64-msvc": {
"version": "0.34.0",
"resolved": "https://registry.npmjs.org/@oxc-parser/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-0.34.0.tgz",
"integrity": "sha512-eKO8BmgDSWl47SoKBtxj+XQjvn8SqXbpZ/NuZbcYkZVzpwWui4Ycqo76GRLrEhp+ykN3Nj9gl29nka3oenrk7A==",
"cpu": [
"arm64"
],
"license": "MIT",
"optional": true,
"os": [
"win32"
]
},
"packages-node/providence-analytics/node_modules/@oxc-parser/binding-win32-x64-msvc": {
"version": "0.34.0",
"resolved": "https://registry.npmjs.org/@oxc-parser/binding-win32-x64-msvc/-/binding-win32-x64-msvc-0.34.0.tgz",
"integrity": "sha512-7KWqCm7DmkFVd8MRMp14/HjxpYgWaj9k2E2pAQGOnExPCuzzF2Wji0qHcNzuMQMjTDKswjRkAOg6gk5b45JQRQ==",
"cpu": [
"x64"
],
"license": "MIT",
"optional": true,
"os": [
"win32"
]
},
"packages-node/providence-analytics/node_modules/oxc-parser": {
"version": "0.34.0",
"resolved": "https://registry.npmjs.org/oxc-parser/-/oxc-parser-0.34.0.tgz",
"integrity": "sha512-k9PJKDD4+U3VzLic2Pup+N4YNIlBhzUkEWZP6yVkXtwEVZn1gIp1ixAt1e9+9EagzzAiY/Kx6EPEsZxNb3d1fg==",
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/Boshen"
},
"optionalDependencies": {
"@oxc-parser/binding-darwin-arm64": "0.34.0",
"@oxc-parser/binding-darwin-x64": "0.34.0",
"@oxc-parser/binding-linux-arm64-gnu": "0.34.0",
"@oxc-parser/binding-linux-arm64-musl": "0.34.0",
"@oxc-parser/binding-linux-x64-gnu": "0.34.0",
"@oxc-parser/binding-linux-x64-musl": "0.34.0",
"@oxc-parser/binding-win32-arm64-msvc": "0.34.0",
"@oxc-parser/binding-win32-x64-msvc": "0.34.0"
} }
}, },
"packages-node/publish-docs": { "packages-node/publish-docs": {
@ -28164,7 +28295,7 @@
}, },
"packages/ui": { "packages/ui": {
"name": "@lion/ui", "name": "@lion/ui",
"version": "0.7.9", "version": "0.8.0",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@bundled-es-modules/message-format": "^6.2.4", "@bundled-es-modules/message-format": "^6.2.4",

View file

@ -89,7 +89,7 @@
- bdb038e1: Many improvements: - bdb038e1: Many improvements:
- rewritten from babel to swc - rewritten from babel to swc
- swcTraverse tool, compatible with babel traverse api - oxcTraverse tool, compatible with babel traverse api
- increased performance - increased performance
- better windows compatibility - better windows compatibility

View file

@ -1,266 +0,0 @@
2023.10.18, v3.0.1
fix:
- d37e664 types
2023.10.17, v3.0.0
feature:
- 7aefd72 types
- aa0ef7b babel v8
- be3c7a6 ImportAttributes
- f927c37 package: putout v32.2.2
- 4a8b9e3 package: eslint-plugin-putout v20.0.0
2023.07.18, v2.2.0
fix:
- ce8a51a incorrect line number calculation and fix bug with JSX elements not having loc keys (#20)
2023.07.12, v2.1.0
feature:
- 68dfe2d package: eslint-plugin-putout v18.1.0
- a303550 package: c8 v8.0.0
- 8e70e4c package: putout v30.4.0
- 8f5df0a package: eslint-plugin-n v16.0.1
- 19700e5 package: nodemon v3.0.1
2023.04.26, v2.0.0
fix:
- 606fd45 handle null element in holey arrays (#18)
feature:
- 6a1e3a3 swc-to-babel: drop support of node < 16
- ab3263e swc-to-babel: use @putout/printer
- d21f30e package: eslint-plugin-putout v17.5.1
- e14d18c package: check-dts v0.7.1
- a3cabd8 package: typescript v5.0.4
- 48a0b6c package: putout v29.3.0
2022.10.12, v1.26.0
fix:
- swc-to-babel: parenthesized const assertion error (#15)
feature:
- package: supertape v8.1.0
2022.08.24, v1.25.1
fix:
- getters/setters: make getters work properly and fix tests (#12)
2022.08.23, v1.25.0
feature:
- make source an optional parameter (#10)
2022.08.23, v1.24.0
feature:
- add type definitions (#8)
2022.08.23, v1.23.0
feature:
- add support for GetterProperty and SetterProperty (#7)
2022.08.21, v1.22.0
feature:
- add support of KeyValueProperty nodes
2022.08.21, v1.21.0
feature:
- package: eslint-plugin-putout v16.0.1
- package: putout v27.1.0
- add support of TSKeywordType (close #1)
2022.06.12, v1.20.1
fix:
- swc-to-babel: add directives
2022.06.12, v1.20.0
feature:
- swc-to-babel: add support of ExportDefaultDeclaration
2022.06.11, v1.19.0
feature:
- swc-to-babel: ObjectProperty instead of AssignmentPatternProperty
2022.06.11, v1.18.0
feature:
- swc-to-babel: add support of ClassExpression and ExportSpecifier
2022.06.11, v1.17.1
fix:
- swc-to-babl: ImportSpecifier
2022.06.11, v1.17.0
feature:
- swc-to-babel: add support of ObjectProperty
2022.06.11, v1.16.1
fix:
- swc-to-babel: ArrowFunctionExpression
2022.06.11, v1.16.0
feature:
- swc-to-babel: NewExpression: arguments field should always present
2022.06.11, v1.15.0
feature:
- swc-to-babel: add support of ArrayExpression
2022.06.11, v1.14.0
feature:
- swc-to-babel: add support of FunctionDeclaration
2022.06.10, v1.13.0
feature:
- swc-to-babel: TSAliasDeclaration
2022.06.10, v1.12.0
feature:
- swc-to-babel: handle typeParameters
2022.06.10, v1.11.2
feature:
- swc-to-babel: improve SpreadElement support
2022.06.10, v1.11.1
feature:
- swc-to-babel: improve support of SpreadElement
2022.06.10, v1.11.0
feature:
- swc-to-babel: add support of NewExpression
2022.06.10, v1.10.0
feature:
- swc-to-babel: improve support of MemberExpression
2022.06.10, v1.9.0
feature:
- swc-to-babel: add support of ClassDeclaration
2022.06.10, v1.8.0
feature:
- swc-to-babel: add support of ParenthesisExpression
2022.06.10, v1.7.1
feature:
- swc-to-babel: improve support of typeAnnotation in Identifiers
- swc-to-babel: add support of ExportNamedDeclaration and ExportDefaultDeclaration
2022.06.10, v1.7.0
feature:
- swc-to-babel: add support of ExportNamedDeclaration and ExportDefaultDeclaration
2022.06.09, v1.6.0
feature:
- swc-to-babel: CallExpression has no typeArguments
- swc-to-babel: TemplateElement
2022.06.09, v1.5.0
feature:
- swc-to-babel: TemplateElement
- package: eslint-plugin-putout v15.6.0
2022.06.09, v1.4.0
feature:
- swc-to-babel: add support of typescript
2022.06.09, v1.3.1
fix:
- swc-to-babel: position
2022.06.09, v1.3.0
feature:
- swc-to-babel: add support of BlockStatement
2022.06.09, v1.2.0
feature:
- swc-to-babel: CallExpression
2022.06.09, v1.1.1
fix:
- swc-to-babel: no type
2022.06.09, v1.1.0
feature:
- (package) supertape v7.3.0
- (package) putout v26.13.0
- (package) madrun v9.0.4
- swc-to-babel: add support of Identifier
2022.02.06, v1.0.2
feature:
- swc-to-babel: rm unused
2022.02.05, v1.0.1
fix:
- lint

View file

@ -1,21 +0,0 @@
The MIT License (MIT)
Copyright (c) coderaiser
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View file

@ -1,2 +0,0 @@
Temp inline version of swc-to-babel, as we need to change a few things for 100% compatibility with our analyzers.
"version": "3.0.1", [swc-to-babel](http://github.com/coderaiser/swc-to-babel)

View file

@ -1,17 +0,0 @@
'use strict';
module.exports = ({tokens, ...program}) => {
const ast = {
type: 'File',
program: {
...program,
directives: [],
},
comments: [],
tokens,
};
return ast;
};

View file

@ -1,134 +0,0 @@
'use strict';
const { types, traverse } = require('@putout/babel');
const {
convertModuleToProgram,
convertSpanToPosition,
convertVariableDeclarator,
convertStringLiteral,
convertIdentifier,
convertCallExpression,
convertObjectProperty,
BlockStatement,
TemplateElement,
convertTSTypeParameter,
convertExportDeclaration,
convertExportDefaultExpression,
convertParenthesisExpression,
convertGetterSetter,
ClassMethod,
ClassDeclaration,
ArrayExpression,
MemberExpression,
NewExpression,
Function,
ImportDeclaration,
ImportSpecifier,
ExportNamedDeclaration,
ExportDefaultDeclaration,
ExportSpecifier,
TSTypeAliasDeclaration,
TSMappedType,
TSTypeReference,
TSTypeOperator,
TSTypeParameter,
TSIndexedAccessType,
TSAsExpression,
JSXElement,
JSXFragment,
} = require('./swc/index.cjs');
const getAST = require('./get-ast.cjs');
const { isIdentifier } = types;
/**
* Convert an SWC ast to a babel ast
* @param ast {Module} SWC ast
* @param {string} [src=""] Source code
* @returns {ParseResult<File>} Babel ast
*/
function toBabel(node, source = '') {
const ast = getAST(node);
traverse(ast, {
noScope: true,
BlockStatement,
TemplateElement,
ClassMethod,
ClassDeclaration,
ClassExpression: ClassDeclaration,
ArrayExpression,
MemberExpression,
NewExpression,
Function,
ImportDeclaration,
ImportSpecifier,
ExportNamedDeclaration,
ExportSpecifier,
ExportDefaultDeclaration,
TSTypeAliasDeclaration,
TSMappedType,
TSTypeReference,
TSTypeOperator,
TSTypeParameter,
TSIndexedAccessType,
TSAsExpression,
JSXElement,
JSXFragment,
enter(path) {
const { node } = path;
const { type } = node;
if ('span' in path.node) convertSpanToPosition(path, source);
delete node.start;
delete node.end;
if (type?.startsWith('Ts')) node.type = type.replace('Ts', 'TS');
if (type?.endsWith('Literal')) setEsprimaRaw(node);
if (isIdentifier(path)) return convertIdentifier(path);
if (path.isStringLiteral()) return convertStringLiteral(path);
if (type === 'Module') return convertModuleToProgram(path);
if (path.isVariableDeclarator()) return convertVariableDeclarator(path);
if (path.isCallExpression()) return convertCallExpression(path);
if (path.isTSTypeParameter()) return convertTSTypeParameter(path);
if (path.type === 'ExportDeclaration') return convertExportDeclaration(path);
if (path.type === 'ExportDefaultExpression') return convertExportDefaultExpression(path);
if (path.type === 'ParenthesisExpression') return convertParenthesisExpression(path);
if (/^(KeyValue|KeyValuePattern|AssignmentPattern)Property$/.test(path.type))
return convertObjectProperty(path);
if (path.type === 'GetterProperty' || path.type === 'SetterProperty')
return convertGetterSetter(path);
},
});
return ast;
}
module.exports = toBabel;
function setEsprimaRaw(node) {
const { raw } = node;
node.raw = raw || node.extra?.raw;
node.extra = node.extra || {
raw,
};
}

View file

@ -1,24 +0,0 @@
'use strict';
module.exports.getPositionByOffset = (offset, source) => {
let line = 1;
let column = 0;
if (offset > source.length)
throw Error('end cannot be more then length ' + offset + ', ' + source.length);
for (let i = 0; i < offset; i++) {
if (source[i] === '\n' && i !== offset - 1) {
line++;
column = 0;
} else {
column++;
}
}
return {
line,
column,
index: offset - 1,
};
};

View file

@ -1,390 +0,0 @@
'use strict';
const { getPositionByOffset } = require('./get-position-by-offset.cjs');
const isNull = a => !a && typeof a === 'object';
const { assign } = Object;
module.exports.convertModuleToProgram = path => {
path.node.type = 'Program';
path.node.sourceType = 'module';
};
module.exports.convertSpanToPosition = (path, source) => {
const { start, end } = path.node.span;
delete path.node.span;
if (end > source.length)
return assign(path.node, {
start,
end,
});
const startPosition = getPositionByOffset(start, source);
const endPosition = getPositionByOffset(end, source);
assign(path.node, {
start: startPosition.index,
end: endPosition.index,
loc: {
start: startPosition,
end: endPosition,
},
});
};
module.exports.convertVariableDeclarator = path => {
delete path.parentPath.node.declare;
delete path.node.optional;
delete path.node.definite;
};
module.exports.convertStringLiteral = path => {
delete path.node.hasEscape;
delete path.node.kind;
};
module.exports.convertIdentifier = ({ node }) => {
convertIdentifier(node);
};
function convertIdentifier(node) {
const { typeAnnotation } = node;
node.name = node.value;
if (isNull(typeAnnotation)) {
delete node.typeAnnotation;
}
delete node.value;
delete node.optional;
delete node.span;
}
module.exports.convertCallExpression = path => {
const newArgs = [];
for (const arg of path.node.arguments) {
newArgs.push(arg.expression);
}
delete path.node.typeArguments;
path.node.arguments = newArgs;
};
module.exports.BlockStatement = path => {
path.node.body = path.node.stmts;
delete path.node.stmts;
path.node.directives = [];
};
module.exports.TSMappedType = path => {
path.node.typeParameter = path.node.typeParam;
if (!path.node.nameType) path.node.nameType = null;
if (!path.node.readonly) delete path.node.readonly;
if (!path.node.optional) delete path.node.optional;
delete path.node.typeParam;
};
module.exports.convertTSTypeParameter = path => {
convertIdentifier(path.node.name);
};
module.exports.TemplateElement = path => {
const { cooked, raw } = path.node;
path.node.value = {
cooked,
raw,
};
delete path.node.cooked;
delete path.node.raw;
delete path.node.tail;
};
module.exports.convertExportDeclaration = path => {
path.node.type = 'ExportNamedDeclaration';
};
module.exports.convertExportDefaultExpression = path => {
path.node.type = 'ExportDefaultDeclaration';
path.node.declaration = path.node.expression;
delete path.node.expression;
delete path.node.declare;
};
module.exports.convertParenthesisExpression = path => {
const expressionPath = path.get('expression');
if (expressionPath.type === 'TsAsExpression') convertTSAsExpression(expressionPath);
else if (expressionPath.type === 'TsConstAssertion') convertTSConstAssertion(expressionPath);
path.replaceWith(expressionPath.node);
};
module.exports.ClassMethod = path => {
const { node } = path;
const { key } = path.node;
Object.assign(node, {
...path.node.function,
key,
});
if (node.kind === 'getter') {
node.kind = 'get';
}
if (node.kind === 'setter') {
node.kind = 'set';
}
node.static = node.isStatic;
delete path.node.isStatic;
delete path.node.accessibility;
delete path.node.isAbstract;
delete path.node.isOptional;
delete path.node.isOverride;
delete path.node.optional;
delete path.node.function;
delete path.node.decorators;
delete path.node.typeParameters;
delete path.node.returnType;
delete path.node.span;
};
module.exports.ClassDeclaration = path => {
path.node.id = path.node.identifier;
path.node.body = {
type: 'ClassBody',
body: path.node.body,
};
delete path.node.identifier;
delete path.node.declare;
delete path.node.decorators;
delete path.node.isAbstract;
delete path.node.typeParams;
delete path.node.superTypeParams;
delete path.node.implements;
};
module.exports.MemberExpression = ({ node }) => {
node.computed = node.property.type === 'Computed';
if (node.computed) node.property = node.property.expression;
};
function convertSpreadElement(node) {
const { expression } = node;
assign(node, {
type: 'SpreadElement',
argument: expression,
});
delete node.spread;
delete node.expression;
}
function maybeConvertSpread(arg) {
if (arg === null) return;
const { spread } = arg;
if (spread) {
convertSpreadElement(arg);
return;
}
assign(arg, arg.expression);
delete arg.spread;
delete arg.expression;
}
module.exports.NewExpression = path => {
path.node.arguments = path.node.arguments || [];
path.node.arguments.forEach(maybeConvertSpread);
delete path.node.typeArguments;
};
module.exports.ArrayExpression = path => {
path.node.elements.forEach(maybeConvertSpread);
};
module.exports.Function = path => {
const { node } = path;
if (path.parentPath.isExportDefaultDeclaration()) path.node.type = 'FunctionDeclaration';
const { params, typeParameters } = node;
node.id = node.identifier || null;
delete node.identifier;
delete node.decorators;
if (!node.returnType) delete node.returnType;
for (const [index, param] of params.entries()) {
if (param.type === 'Parameter') params[index] = param.pat;
}
if (isNull(typeParameters)) delete node.typeParameters;
delete node.declare;
};
module.exports.TSTypeAliasDeclaration = path => {
delete path.node.declare;
delete path.node.typeParams;
};
module.exports.TSAsExpression = convertTSAsExpression;
function convertTSAsExpression({ node }) {
node.type = 'TSAsExpression';
if (node.typeAnnotation.kind === 'any')
assign(node.typeAnnotation, {
type: 'TSAnyKeyword',
});
}
module.exports.TSConstAssertion = convertTSConstAssertion;
function convertTSConstAssertion({ node }) {
assign(node, {
type: 'TSAsExpression',
extra: {
parenthesized: true,
parenStart: 0,
},
typeAnnotation: {
type: 'TSTypeReference',
typeName: {
type: 'Identifier',
name: 'const',
},
},
});
}
module.exports.TSTypeReference = path => {
delete path.node.typeParams;
};
module.exports.TSTypeOperator = path => {
path.node.operator = path.node.op;
delete path.node.op;
};
module.exports.TSTypeParameter = path => {
path.node.name = path.node.name.name;
delete path.node.in;
delete path.node.out;
delete path.node.default;
};
module.exports.TSIndexedAccessType = path => {
delete path.node.readonly;
};
module.exports.ImportDeclaration = ({ node }) => {
const { typeOnly } = node;
node.assertions = node.asserts?.properties || [];
node.importKind = typeOnly ? 'type' : 'value';
delete node.asserts;
delete node.typeOnly;
};
module.exports.ImportSpecifier = ({ node }) => {
if (!node.imported)
node.imported = {
...node.local,
};
delete node.isTypeOnly;
};
module.exports.convertObjectProperty = path => {
const { node } = path;
node.type = 'ObjectProperty';
node.shorthand = !node.value;
if (!node.value)
node.value = {
...node.key,
};
delete path.parentPath.node.optional;
};
module.exports.convertGetterSetter = ({ node }) => {
node.kind = node.type === 'GetterProperty' ? 'get' : 'set';
node.type = 'ObjectMethod';
node.params = node.param ? [node.param] : [];
delete node.param;
};
module.exports.ExportDefaultDeclaration = ({ node }) => {
// node.declaration may have been already provided by convertExportDefaultExpression
node.declaration = node.declaration || node.decl;
node.exportKind = 'value';
node.assertions = node.asserts?.properties || [];
delete node.decl;
};
module.exports.ExportNamedDeclaration = ({ node }) => {
const { typeOnly } = node;
node.assertions = node.asserts?.properties || [];
// node.source = null;
node.specifiers = node.specifiers || [];
node.exportKind = typeOnly ? 'type' : 'value';
delete node.asserts;
delete node.typeOnly;
};
module.exports.ExportSpecifier = ({ node }) => {
const { orig, exported } = node;
node.local = orig;
node.exported = exported || {
...orig,
};
delete node.isTypeOnly;
delete node.orig;
};
module.exports.JSXElement = path => {
path.node.openingElement = path.node.opening;
delete path.node.opening;
path.node.closingElement = path.node.closing;
delete path.node.closing;
};
module.exports.JSXFragment = path => {
path.node.openingFragment = path.node.opening;
delete path.node.opening;
path.node.closingFragment = path.node.closing;
delete path.node.closing;
};

View file

@ -23,7 +23,6 @@
"providence": "./src/cli/index.js" "providence": "./src/cli/index.js"
}, },
"files": [ "files": [
"inlined-swc-to-babel",
"src", "src",
"types" "types"
], ],
@ -38,18 +37,22 @@
"test:node:unit": "mocha './{test-node,src}/**/*.test.js'" "test:node:unit": "mocha './{test-node,src}/**/*.test.js'"
}, },
"dependencies": { "dependencies": {
"@babel/parser": "^7.25.8",
"@babel/plugin-syntax-import-assertions": "^7.25.7",
"@babel/traverse": "^7.25.7", "@babel/traverse": "^7.25.7",
"@babel/types": "^7.25.8",
"@putout/babel": "^2.8.0",
"@rollup/plugin-node-resolve": "^15.3.0", "@rollup/plugin-node-resolve": "^15.3.0",
"@swc/core": "^1.7.36",
"commander": "^2.20.3", "commander": "^2.20.3",
"oxc-parser": "^0.34.0",
"parse5": "^7.2.0", "parse5": "^7.2.0",
"semver": "^7.6.3" "semver": "^7.6.3"
}, },
"peerDependencies": {
"@babel/parser": "^7.25.8",
"@babel/plugin-syntax-import-assertions": "^7.25.7",
"@swc/core": "^1.7.36"
},
"devDependencies": { "devDependencies": {
"@babel/parser": "^7.25.8",
"@babel/plugin-syntax-import-assertions": "^7.25.7",
"@swc/core": "^1.7.36",
"@types/inquirer": "^9.0.7", "@types/inquirer": "^9.0.7",
"@types/mocha": "^10.0.9", "@types/mocha": "^10.0.9",
"@web/dev-server": "^0.4.6", "@web/dev-server": "^0.4.6",

View file

@ -1,10 +1,9 @@
/* eslint-disable no-shadow, no-param-reassign */ /* eslint-disable no-shadow, no-param-reassign */
import path from 'path'; import path from 'path';
import babelTraverse from '@babel/traverse'; import { oxcTraverse, isProperty } from '../utils/oxc-traverse.js';
import t from '@babel/types';
import { trackDownIdentifierFromScope } from '../utils/track-down-identifier--legacy.js'; import { trackDownIdentifierFromScope } from '../utils/track-down-identifier.js';
import { Analyzer } from '../core/Analyzer.js'; import { Analyzer } from '../core/Analyzer.js';
/** /**
@ -16,6 +15,7 @@ import { Analyzer } from '../core/Analyzer.js';
* @typedef {import('../../../types/index.js').FindClassesAnalyzerOutputFile} FindClassesAnalyzerOutputFile * @typedef {import('../../../types/index.js').FindClassesAnalyzerOutputFile} FindClassesAnalyzerOutputFile
* @typedef {import('../../../types/index.js').FindClassesAnalyzerEntry} FindClassesAnalyzerEntry * @typedef {import('../../../types/index.js').FindClassesAnalyzerEntry} FindClassesAnalyzerEntry
* @typedef {import('../../../types/index.js').FindClassesConfig} FindClassesConfig * @typedef {import('../../../types/index.js').FindClassesConfig} FindClassesConfig
* @typedef {import('../../../types/index.js').AnalyzerAst} AnalyzerAst
*/ */
/** /**
@ -132,8 +132,7 @@ async function findMembersPerAstEntry(babelAst, fullCurrentFilePath, projectPath
methods: [], methods: [],
}; };
astPath.traverse({ const handleMethodDefinitionOrClassMethod = astPath => {
ClassMethod(astPath) {
// if (isBlacklisted(astPath)) { // if (isBlacklisted(astPath)) {
// return; // return;
// } // }
@ -144,7 +143,7 @@ async function findMembersPerAstEntry(babelAst, fullCurrentFilePath, projectPath
if (hasFoundTopLvlObjExpr) return; if (hasFoundTopLvlObjExpr) return;
hasFoundTopLvlObjExpr = true; hasFoundTopLvlObjExpr = true;
astPath.node.properties.forEach(objectProperty => { astPath.node.properties.forEach(objectProperty => {
if (!t.isProperty(objectProperty)) { if (!isProperty(objectProperty)) {
// we can also have a SpreadElement // we can also have a SpreadElement
return; return;
} }
@ -180,7 +179,11 @@ async function findMembersPerAstEntry(babelAst, fullCurrentFilePath, projectPath
} else { } else {
classRes.members.methods.push(methodRes); classRes.members.methods.push(methodRes);
} }
}, };
astPath.traverse({
ClassMethod: handleMethodDefinitionOrClassMethod,
MethodDefinition: handleMethodDefinitionOrClassMethod,
}); });
classesFound.push(classRes); classesFound.push(classRes);
@ -188,7 +191,7 @@ async function findMembersPerAstEntry(babelAst, fullCurrentFilePath, projectPath
const classesToTraverse = []; const classesToTraverse = [];
babelTraverse.default(babelAst, { oxcTraverse(babelAst, {
ClassDeclaration(astPath) { ClassDeclaration(astPath) {
classesToTraverse.push({ astPath, isMixin: false }); classesToTraverse.push({ astPath, isMixin: false });
}, },
@ -228,8 +231,8 @@ export default class FindClassesAnalyzer extends Analyzer {
/** @type {AnalyzerName} */ /** @type {AnalyzerName} */
static analyzerName = 'find-classes'; static analyzerName = 'find-classes';
/** @type {'babel'|'swc-to-babel'} */ /** @type {AnalyzerAst} */
static requiredAst = 'babel'; static requiredAst = 'oxc';
/** /**
* Will find all public members (properties (incl. getter/setters)/functions) of a class and * Will find all public members (properties (incl. getter/setters)/functions) of a class and

View file

@ -1,9 +1,9 @@
import path from 'path'; import path from 'path';
import babelTraverse from '@babel/traverse'; // import babelTraverse from '@babel/traverse';
import t from '@babel/types'; import { oxcTraverse } from '../utils/oxc-traverse.js';
import { trackDownIdentifierFromScope } from '../utils/track-down-identifier--legacy.js'; import { trackDownIdentifierFromScope } from '../utils/track-down-identifier.js';
import { Analyzer } from '../core/Analyzer.js'; import { Analyzer } from '../core/Analyzer.js';
/** /**
@ -40,27 +40,29 @@ async function trackdownRoot(transformedEntry, relativePath, projectPath) {
/** /**
* Finds import specifiers and sources * Finds import specifiers and sources
* @param {File} babelAst * @param {File} oxcAst
*/ */
function findCustomElementsPerAstFile(babelAst) { function findCustomElementsPerAstFile(oxcAst) {
const definitions = []; const definitions = [];
babelTraverse.default(babelAst, { oxcTraverse(oxcAst, {
CallExpression(astPath) { CallExpression(astPath) {
let found = false; let found = false;
// Doing it like this we detect 'customElements.define()', // Doing it like this we detect 'customElements.define()',
// but also 'window.customElements.define()' // but also 'window.customElements.define()'
astPath.traverse({ astPath.traverse({
MemberExpression(memberPath) { // MemberExpression in babel
if (memberPath.parentPath !== astPath) { StaticMemberExpression(memberPath) {
if (memberPath.node !== astPath.node.callee) {
return; return;
} }
const { node } = memberPath; const { node } = memberPath;
if (node.object.name === 'customElements' && node.property.name === 'define') { if (node.object.name === 'customElements' && node.property.name === 'define') {
found = true; found = true;
} }
if ( if (
node.object.object && node.object.object?.name === 'window' &&
node.object.object.name === 'window' &&
node.object.property.name === 'customElements' && node.object.property.name === 'customElements' &&
node.property.name === 'define' node.property.name === 'define'
) { ) {
@ -72,7 +74,7 @@ function findCustomElementsPerAstFile(babelAst) {
let tagName; let tagName;
let constructorIdentifier; let constructorIdentifier;
if (t.isLiteral(astPath.node.arguments[0])) { if (astPath.node.arguments[0].type === 'StringLiteral') {
tagName = astPath.node.arguments[0].value; tagName = astPath.node.arguments[0].value;
} else { } else {
// No Literal found. For now, we only mark them as '[variable]' // No Literal found. For now, we only mark them as '[variable]'
@ -95,8 +97,8 @@ export default class FindCustomelementsAnalyzer extends Analyzer {
/** @type {AnalyzerName} */ /** @type {AnalyzerName} */
static analyzerName = 'find-customelements'; static analyzerName = 'find-customelements';
/** @type {'babel'|'swc-to-babel'} */ /** @type {AnalyzerAst} */
static requiredAst = 'swc-to-babel'; static requiredAst = 'oxc';
/** /**
* Finds export specifiers and sources * Finds export specifiers and sources

View file

@ -5,7 +5,7 @@ import { getReferencedDeclaration } from '../utils/get-source-code-fragment-of-d
import { normalizeSourcePaths } from './helpers/normalize-source-paths.js'; import { normalizeSourcePaths } from './helpers/normalize-source-paths.js';
import { trackDownIdentifier } from '../utils/track-down-identifier.js'; import { trackDownIdentifier } from '../utils/track-down-identifier.js';
import { getAssertionType } from '../utils/get-assertion-type.js'; import { getAssertionType } from '../utils/get-assertion-type.js';
import { swcTraverse } from '../utils/swc-traverse.js'; import { oxcTraverse } from '../utils/oxc-traverse.js';
import { LogService } from '../core/LogService.js'; import { LogService } from '../core/LogService.js';
import { Analyzer } from '../core/Analyzer.js'; import { Analyzer } from '../core/Analyzer.js';
@ -110,20 +110,25 @@ function cleanup(transformedFile) {
function getExportSpecifiers(node) { function getExportSpecifiers(node) {
// handles default [export const g = 4]; // handles default [export const g = 4];
if (node.declaration?.declarations) { if (node.declaration?.declarations) {
return [node.declaration.declarations[0].id.value]; return [node.declaration.declarations[0].id.value || node.declaration.declarations[0].id.name];
} }
if (node.declaration?.identifier) { if (node.declaration?.identifier) {
return [node.declaration.identifier.value]; return [node.declaration.identifier.value || node.declaration.identifier.name];
}
if (node.declaration?.id) {
return [node.declaration.id.value || node.declaration.id.name];
} }
// handles (re)named specifiers [export { x (as y)} from 'y']; // handles (re)named specifiers [export { x (as y)} from 'y'];
return (node.specifiers || []).map(s => { return (node.specifiers || []).map(s => {
if (s.exported) { if (s.exported) {
// { x as y } // { x as y }
return s.exported.value === 'default' ? '[default]' : s.exported.value; return (s.exported.value || s.exported.name) === 'default'
? '[default]'
: s.exported.value || s.exported.name;
} }
// { x } // { x }
return s.orig.value; return s.orig.value || s.local.name;
}); });
} }
@ -133,11 +138,18 @@ function getExportSpecifiers(node) {
function getLocalNameSpecifiers(node) { function getLocalNameSpecifiers(node) {
return (node.declaration?.declarations || node.specifiers || []) return (node.declaration?.declarations || node.specifiers || [])
.map(s => { .map(s => {
if (s.exported && s.orig && s.exported.value !== s.orig.value) { if (
s.exported &&
(s.orig || s.local) &&
(s.exported.value || s.exported.name) !== (s.orig?.value || s.local?.name)
) {
return { return {
// if reserved keyword 'default' is used, translate it into 'providence keyword' // if reserved keyword 'default' is used, translate it into 'providence keyword'
local: s.orig.value === 'default' ? '[default]' : s.orig.value, local:
exported: s.exported.value, (s.orig?.value || s.local?.name) === 'default'
? '[default]'
: s.orig?.value || s.local?.name,
exported: s.exported.value || s.exported.name,
}; };
} }
return undefined; return undefined;
@ -150,10 +162,10 @@ const isImportingSpecifier = pathOrNode =>
/** /**
* Finds import specifiers and sources for a given ast result * Finds import specifiers and sources for a given ast result
* @param {SwcAstModule} swcAst * @param {SwcAstModule} oxcAst
* @param {FindExportsConfig} config * @param {FindExportsConfig} config
*/ */
function findExportsPerAstFile(swcAst, { skipFileImports }) { function findExportsPerAstFile(oxcAst, { skipFileImports }) {
LogService.debug(`Analyzer "find-exports": started findExportsPerAstFile method`); LogService.debug(`Analyzer "find-exports": started findExportsPerAstFile method`);
// Visit AST... // Visit AST...
@ -169,7 +181,7 @@ function findExportsPerAstFile(swcAst, { skipFileImports }) {
const exportHandler = (/** @type {SwcPath} */ astPath) => { const exportHandler = (/** @type {SwcPath} */ astPath) => {
const exportSpecifiers = getExportSpecifiers(astPath.node); const exportSpecifiers = getExportSpecifiers(astPath.node);
const localMap = getLocalNameSpecifiers(astPath.node); const localMap = getLocalNameSpecifiers(astPath.node);
const source = astPath.node.source?.value; const source = astPath.node.source?.value || astPath.node.source?.name;
const entry = { exportSpecifiers, localMap, source, __tmp: { astPath } }; const entry = { exportSpecifiers, localMap, source, __tmp: { astPath } };
const assertionType = getAssertionType(astPath.node); const assertionType = getAssertionType(astPath.node);
if (assertionType) { if (assertionType) {
@ -180,15 +192,18 @@ function findExportsPerAstFile(swcAst, { skipFileImports }) {
const exportDefaultHandler = (/** @type {SwcPath} */ astPath) => { const exportDefaultHandler = (/** @type {SwcPath} */ astPath) => {
const exportSpecifiers = ['[default]']; const exportSpecifiers = ['[default]'];
const { node } = astPath;
let source; let source;
// Is it an inline declaration like "export default class X {};" ? // Is it an inline declaration like "export default class X {};" ?
if ( if (
astPath.node.decl?.type === 'Identifier' || node.decl?.type === 'Identifier' ||
astPath.node.expression?.type === 'Identifier' node.expression?.type === 'Identifier' ||
node.declaration?.type === 'Identifier'
) { ) {
// It is a reference to an identifier like "export { x } from 'y';" // It is a reference to an identifier like "export { x } from 'y';"
const importOrDeclPath = getReferencedDeclaration({ const importOrDeclPath = getReferencedDeclaration({
referencedIdentifierName: astPath.node.decl?.value || astPath.node.expression.value, referencedIdentifierName:
node.decl?.value || node.expression?.value || node.declaration?.name,
globalScopeBindings, globalScopeBindings,
}); });
if (isImportingSpecifier(importOrDeclPath)) { if (isImportingSpecifier(importOrDeclPath)) {
@ -198,18 +213,23 @@ function findExportsPerAstFile(swcAst, { skipFileImports }) {
transformedFile.push({ exportSpecifiers, source, __tmp: { astPath } }); transformedFile.push({ exportSpecifiers, source, __tmp: { astPath } });
}; };
const globalScopeHandler = ({ scope }) => {
globalScopeBindings = scope.bindings;
};
/** @type {SwcVisitor} */ /** @type {SwcVisitor} */
const visitor = { const visitor = {
Module({ scope }) { // for swc
globalScopeBindings = scope.bindings; Module: globalScopeHandler,
}, // for oxc and babel
Program: globalScopeHandler,
ExportDeclaration: exportHandler, ExportDeclaration: exportHandler,
ExportNamedDeclaration: exportHandler, ExportNamedDeclaration: exportHandler,
ExportDefaultDeclaration: exportDefaultHandler, ExportDefaultDeclaration: exportDefaultHandler,
ExportDefaultExpression: exportDefaultHandler, ExportDefaultExpression: exportDefaultHandler,
}; };
swcTraverse(swcAst, visitor, { needsAdvancedPaths: true }); oxcTraverse(oxcAst, visitor, { needsAdvancedPaths: true });
if (!skipFileImports) { if (!skipFileImports) {
// Always add an entry for just the file 'relativePath' // Always add an entry for just the file 'relativePath'
@ -226,7 +246,7 @@ function findExportsPerAstFile(swcAst, { skipFileImports }) {
export default class FindExportsAnalyzer extends Analyzer { export default class FindExportsAnalyzer extends Analyzer {
static analyzerName = /** @type {AnalyzerName} */ ('find-exports'); static analyzerName = /** @type {AnalyzerName} */ ('find-exports');
static requiredAst = /** @type {AnalyzerAst} */ ('swc'); static requiredAst = /** @type {AnalyzerAst} */ ('oxc');
/** /**
* @typedef FindExportsConfig * @typedef FindExportsConfig

View file

@ -2,7 +2,7 @@
import { normalizeSourcePaths } from './helpers/normalize-source-paths.js'; import { normalizeSourcePaths } from './helpers/normalize-source-paths.js';
import { isRelativeSourcePath } from '../utils/relative-source-path.js'; import { isRelativeSourcePath } from '../utils/relative-source-path.js';
import { getAssertionType } from '../utils/get-assertion-type.js'; import { getAssertionType } from '../utils/get-assertion-type.js';
import { swcTraverse } from '../utils/swc-traverse.js'; import { oxcTraverse } from '../utils/oxc-traverse.js';
import { LogService } from '../core/LogService.js'; import { LogService } from '../core/LogService.js';
import { Analyzer } from '../core/Analyzer.js'; import { Analyzer } from '../core/Analyzer.js';
@ -13,49 +13,80 @@ import { Analyzer } from '../core/Analyzer.js';
* @typedef {import('../../../types/index.js').AnalyzerConfig} AnalyzerConfig * @typedef {import('../../../types/index.js').AnalyzerConfig} AnalyzerConfig
* @typedef {import('../../../types/index.js').AnalyzerName} AnalyzerName * @typedef {import('../../../types/index.js').AnalyzerName} AnalyzerName
* @typedef {import('../../../types/index.js').AnalyzerAst} AnalyzerAst * @typedef {import('../../../types/index.js').AnalyzerAst} AnalyzerAst
* @typedef {import("@swc/core").Module} SwcAstModule * @typedef {import("@swc/core").Module} oxcAstModule
* @typedef {import("@swc/core").Node} SwcNode * @typedef {import("@swc/core").Node} SwcNode
*/ */
// const exportedNames = ['exported'];
// const importedNames = ['orig', 'imported', 'local'];
// const valueNames = ['name', 'value'];
/**
* Intends to work for oxc, swc, and babel asts
*/
function getSpecifierValue(s) {
// for (const exportedorImportedName of [...exportedNames, ...importedNames]) {
// for (const valueName of valueNames) {
// const result = s[exportedorImportedName][valueName];
// if (result) return result;
// }
// }
// return undefined;
return (
// These are regular import values and must be checked first
s.imported?.value ||
s.imported?.name ||
s.orig?.value ||
s.orig?.name ||
s.local?.value ||
s.local?.name ||
// Re-export
s.exported?.value ||
s.exported?.name
);
}
/** /**
* @param {SwcNode} node * @param {SwcNode} node
*/ */
function getImportOrReexportsSpecifiers(node) { function getImportOrReexportsSpecifiers(node) {
// @ts-expect-error // @ts-expect-error
return node.specifiers.map(s => { return (node.specifiers || []).map(s => {
if ( if (
s.type === 'ImportDefaultSpecifier' || s.type === 'ImportDefaultSpecifier' ||
s.type === 'ExportDefaultSpecifier' || s.type === 'ExportDefaultSpecifier' ||
(s.type === 'ExportSpecifier' && s.exported?.value === 'default') (s.type === 'ExportSpecifier' &&
(s.exported?.value === 'default' || s.exported?.name === 'default'))
) { ) {
return '[default]'; return '[default]';
} }
if (s.type === 'ImportNamespaceSpecifier' || s.type === 'ExportNamespaceSpecifier') { if (s.type === 'ImportNamespaceSpecifier' || s.type === 'ExportNamespaceSpecifier') {
return '[*]'; return '[*]';
} }
const importedValue = s.imported?.value || s.orig?.value || s.exported?.value || s.local?.value; const importedValue = getSpecifierValue(s);
return importedValue; return importedValue;
}); });
} }
/** /**
* Finds import specifiers and sources * Finds import specifiers and sources
* @param {SwcAstModule} swcAst * @param {oxcAstModule} oxcAst
*/ */
function findImportsPerAstFile(swcAst) { function findImportsPerAstFile(oxcAst) {
LogService.debug(`Analyzer "find-imports": started findImportsPerAstFile method`); LogService.debug(`Analyzer "find-imports": started findImportsPerAstFile method`);
// https://github.com/babel/babel/blob/672a58660f0b15691c44582f1f3fdcdac0fa0d2f/packages/babel-core/src/transformation/index.ts#L110 // https://github.com/babel/babel/blob/672a58660f0b15691c44582f1f3fdcdac0fa0d2f/packages/babel-core/src/transformation/index.ts#L110
// Visit AST... // Visit AST...
/** @type {Partial<FindImportsAnalyzerEntry>[]} */ /** @type {Partial<FindImportsAnalyzerEntry>[]} */
const transformedFile = []; const transformedFile = [];
oxcTraverse(oxcAst, {
swcTraverse(swcAst, {
ImportDeclaration({ node }) { ImportDeclaration({ node }) {
const importSpecifiers = getImportOrReexportsSpecifiers(node); const importSpecifiers = getImportOrReexportsSpecifiers(node);
if (!importSpecifiers.length) { if (!importSpecifiers.length) {
importSpecifiers.push('[file]'); // apparently, there was just a file import importSpecifiers.push('[file]'); // apparently, there was just a file import
} }
const source = node.source.value; const source = node.source.value;
const entry = /** @type {Partial<FindImportsAnalyzerEntry>} */ ({ importSpecifiers, source }); const entry = /** @type {Partial<FindImportsAnalyzerEntry>} */ ({ importSpecifiers, source });
const assertionType = getAssertionType(node); const assertionType = getAssertionType(node);
@ -65,9 +96,9 @@ function findImportsPerAstFile(swcAst) {
transformedFile.push(entry); transformedFile.push(entry);
}, },
ExportNamedDeclaration({ node }) { ExportNamedDeclaration({ node }) {
if (!node.source) { // Are we dealing with a regular export, not a re-export?
return; // we are dealing with a regular export, not a reexport if (!node.source) return;
}
const importSpecifiers = getImportOrReexportsSpecifiers(node); const importSpecifiers = getImportOrReexportsSpecifiers(node);
const source = node.source.value; const source = node.source.value;
const entry = /** @type {Partial<FindImportsAnalyzerEntry>} */ ({ importSpecifiers, source }); const entry = /** @type {Partial<FindImportsAnalyzerEntry>} */ ({ importSpecifiers, source });
@ -77,7 +108,22 @@ function findImportsPerAstFile(swcAst) {
} }
transformedFile.push(entry); transformedFile.push(entry);
}, },
// Dynamic imports ExportAllDeclaration({ node }) {
// Are we dealing with a regular export, not a re-export?
if (!node.source) return;
const importSpecifiers = ['[*]'];
const source = node.source.value;
const entry = /** @type {Partial<FindImportsAnalyzerEntry>} */ ({ importSpecifiers, source });
const assertionType = getAssertionType(node);
if (assertionType) {
entry.assertionType = assertionType;
}
transformedFile.push(entry);
},
// Dynamic imports for swc
// TODO: remove if swc is completely phased out
CallExpression({ node }) { CallExpression({ node }) {
if (node.callee?.type !== 'Import') { if (node.callee?.type !== 'Import') {
return; return;
@ -92,6 +138,21 @@ function findImportsPerAstFile(swcAst) {
: '[variable]'; : '[variable]';
transformedFile.push({ importSpecifiers, source }); transformedFile.push({ importSpecifiers, source });
}, },
// Dynamic imports for oxc
ExpressionStatement({ node }) {
if (node.expression.type !== 'ImportExpression') return;
// TODO: check for specifiers catched via obj destructuring?
// TODO: also check for ['file']
const importSpecifiers = ['[default]'];
const dynamicImportExpression = node.expression;
const source =
dynamicImportExpression.source?.type === 'StringLiteral'
? dynamicImportExpression.source.value
: '[variable]';
transformedFile.push({ importSpecifiers, source });
},
}); });
return transformedFile; return transformedFile;
@ -100,7 +161,7 @@ function findImportsPerAstFile(swcAst) {
export default class FindImportsSwcAnalyzer extends Analyzer { export default class FindImportsSwcAnalyzer extends Analyzer {
static analyzerName = /** @type {AnalyzerName} */ ('find-imports'); static analyzerName = /** @type {AnalyzerName} */ ('find-imports');
static requiredAst = /** @type {AnalyzerAst} */ ('swc'); static requiredAst = /** @type {AnalyzerAst} */ ('oxc');
/** /**
* Finds import specifiers and sources * Finds import specifiers and sources
@ -132,9 +193,9 @@ export default class FindImportsSwcAnalyzer extends Analyzer {
/** /**
* Traverse * Traverse
*/ */
const queryOutput = await this._traverse(async (swcAst, context) => { const queryOutput = await this._traverse(async (oxcAst, context) => {
// @ts-expect-error // @ts-expect-error
let transformedFile = findImportsPerAstFile(swcAst); let transformedFile = findImportsPerAstFile(oxcAst);
// Post processing based on configuration... // Post processing based on configuration...
transformedFile = await normalizeSourcePaths( transformedFile = await normalizeSourcePaths(
transformedFile, transformedFile,

View file

@ -159,7 +159,8 @@ async function matchImportsPostprocess(exportsAnalyzerResult, importsAnalyzerRes
export default class MatchImportsAnalyzer extends Analyzer { export default class MatchImportsAnalyzer extends Analyzer {
static analyzerName = /** @type {AnalyzerName} */ ('match-imports'); static analyzerName = /** @type {AnalyzerName} */ ('match-imports');
static requiredAst = /** @type {AnalyzerAst} */ ('swc'); // N.B. implicit
static requiredAst = /** @type {AnalyzerAst} */ ('oxc');
static requiresReference = true; static requiresReference = true;

View file

@ -74,7 +74,7 @@ function storeResult(resultsObj, exportId, filteredList, meta) {
* @param {FindClassesAnalyzerResult} targetClassesAnalyzerResult * @param {FindClassesAnalyzerResult} targetClassesAnalyzerResult
* @param {FindClassesAnalyzerResult} refClassesAResult * @param {FindClassesAnalyzerResult} refClassesAResult
* @param {MatchSubclassesConfig} customConfig * @param {MatchSubclassesConfig} customConfig
* @returns {AnalyzerQueryResult} * @returns {Promise<AnalyzerQueryResult>}
*/ */
async function matchSubclassesPostprocess( async function matchSubclassesPostprocess(
refExportsAnalyzerResult, refExportsAnalyzerResult,
@ -281,7 +281,7 @@ export default class MatchSubclassesAnalyzer extends Analyzer {
/** @type {AnalyzerName} */ /** @type {AnalyzerName} */
static analyzerName = 'match-subclasses'; static analyzerName = 'match-subclasses';
static requiredAst = /** @type {AnalyzerAst} */ ('babel'); static requiredAst = /** @type {AnalyzerAst} */ ('oxc');
static requiresReference = true; static requiresReference = true;

View file

@ -1,15 +1,21 @@
import babelParser from '@babel/parser';
import * as parse5 from 'parse5'; import * as parse5 from 'parse5';
import swc from '@swc/core';
import { traverseHtml } from '../utils/traverse-html.js'; import { traverseHtml } from '../utils/traverse-html.js';
import { LogService } from './LogService.js'; import { LogService } from './LogService.js';
import { guardedSwcToBabel } from '../utils/guarded-swc-to-babel.js';
/** @type {import('@babel/parser')} */
let babelParser;
/** @type {import('@swc/core')} */
let swcParser;
/** @type {import('oxc-parser')} */
let oxcParser;
/** /**
* @typedef {import("@babel/types").File} File
* @typedef {import("@swc/core").Module} SwcAstModule
* @typedef {import("@babel/parser").ParserOptions} ParserOptions
* @typedef {import('../../../types/index.js').PathFromSystemRoot} PathFromSystemRoot * @typedef {import('../../../types/index.js').PathFromSystemRoot} PathFromSystemRoot
* @typedef {import('../../../types/index.js').AnalyzerAst} AnalyzerAst
* @typedef {import("oxc-parser").ParseResult} OxcParseResult
* @typedef {import("@babel/parser").ParserOptions} ParserOptions
* @typedef {import("@swc/core").Module} SwcAstModule
* @typedef {import("@babel/types").File} File
*/ */
export class AstService { export class AstService {
@ -17,9 +23,13 @@ export class AstService {
* Compiles an array of file paths using Babel. * Compiles an array of file paths using Babel.
* @param {string} code * @param {string} code
* @param {ParserOptions} parserOptions * @param {ParserOptions} parserOptions
* @returns {File} * @returns {Promise<File>}
*/ */
static _getBabelAst(code, parserOptions = {}) { static async _getBabelAst(code, parserOptions = {}) {
if (!babelParser) {
babelParser = (await import('@babel/parser')).default;
}
const ast = babelParser.parse(code, { const ast = babelParser.parse(code, {
sourceType: 'module', sourceType: 'module',
plugins: [ plugins: [
@ -34,32 +44,18 @@ export class AstService {
return ast; return ast;
} }
/**
* Compiles an array of file paths using Babel.
* @param {string} code
* @param {ParserOptions} parserOptions
* @returns {File}
*/
static _getSwcToBabelAst(code, parserOptions = {}) {
if (this.fallbackToBabel) {
return this._getBabelAst(code, parserOptions);
}
const ast = swc.parseSync(code, {
syntax: 'typescript',
// importAssertions: true,
...parserOptions,
});
return guardedSwcToBabel(ast, code);
}
/** /**
* Compiles an array of file paths using swc. * Compiles an array of file paths using swc.
* @param {string} code * @param {string} code
* @param {ParserOptions} parserOptions * @param {ParserOptions} parserOptions
* @returns {SwcAstModule} * @returns {Promise<SwcAstModule>}
*/ */
static _getSwcAst(code, parserOptions = {}) { static async _getSwcAst(code, parserOptions = {}) {
const ast = swc.parseSync(code, { if (!swcParser) {
swcParser = (await import('@swc/core')).default;
}
const ast = swcParser.parseSync(code, {
syntax: 'typescript', syntax: 'typescript',
target: 'es2022', target: 'es2022',
...parserOptions, ...parserOptions,
@ -72,7 +68,22 @@ export class AstService {
* @returns {number} * @returns {number}
*/ */
static _getSwcOffset() { static _getSwcOffset() {
return swc.parseSync('').span.end; return swcParser.parseSync('').span.end;
}
/**
* Compiles an array of file paths using swc.
* @param {string} code
* @param {ParserOptions} parserOptions
* @returns {Promise<OxcParseResult>}
*/
static async _getOxcAst(code, parserOptions = {}) {
if (!oxcParser) {
// eslint-disable-next-line import/no-extraneous-dependencies
oxcParser = (await import('oxc-parser')).default;
}
return oxcParser.parseSync(code, parserOptions).program;
} }
/** /**
@ -100,22 +111,22 @@ export class AstService {
/** /**
* Returns the Babel AST * Returns the Babel AST
* @param { string } code * @param { string } code
* @param { 'babel'|'swc-to-babel'|'swc'} astType * @param {AnalyzerAst} astType
* @param { {filePath?: PathFromSystemRoot} } options * @param { {filePath?: PathFromSystemRoot} } options
* @returns {File|undefined|SwcAstModule} * @returns {Promise<File|undefined|SwcAstModule|OxcParseResult>}
*/ */
// eslint-disable-next-line consistent-return // eslint-disable-next-line consistent-return
static getAst(code, astType, { filePath } = {}) { static async getAst(code, astType, { filePath } = {}) {
// eslint-disable-next-line default-case // eslint-disable-next-line default-case
try { try {
if (astType === 'babel') { if (astType === 'babel') {
return this._getBabelAst(code); return await this._getBabelAst(code);
}
if (astType === 'swc-to-babel') {
return this._getSwcToBabelAst(code);
} }
if (astType === 'swc') { if (astType === 'swc') {
return this._getSwcAst(code); return await this._getSwcAst(code);
}
if (astType === 'oxc') {
return await this._getOxcAst(code);
} }
throw new Error(`astType "${astType}" not supported.`); throw new Error(`astType "${astType}" not supported.`);
} catch (e) { } catch (e) {

View file

@ -107,20 +107,27 @@ export class QueryService {
* @param {AnalyzerAst} requiredAst * @param {AnalyzerAst} requiredAst
*/ */
static async addAstToProjectsData(projectsData, requiredAst) { static async addAstToProjectsData(projectsData, requiredAst) {
return projectsData.map(projectData => { const resultWithAsts = [];
for (const projectData of projectsData) {
const cachedData = astProjectsDataCache.get(projectData.project.path); const cachedData = astProjectsDataCache.get(projectData.project.path);
if (cachedData) { if (cachedData) {
return cachedData; return cachedData;
} }
const resultEntries = projectData.entries.map(entry => { const resultEntries = [];
const ast = AstService.getAst(entry.context.code, requiredAst, { filePath: entry.file }); for (const entry of projectData.entries) {
return { ...entry, ast }; const ast = await AstService.getAst(entry.context.code, requiredAst, {
filePath: entry.file,
}); });
resultEntries.push({ ...entry, ast });
}
const astData = { ...projectData, entries: resultEntries }; const astData = { ...projectData, entries: resultEntries };
this._addToProjectsDataCache(`${projectData.project.path}#${requiredAst}`, astData); this._addToProjectsDataCache(`${projectData.project.path}#${requiredAst}`, astData);
return astData; resultWithAsts.push(astData);
}); }
return resultWithAsts;
} }
/** /**

View file

@ -10,5 +10,9 @@ export function getAssertionType(node) {
if (node.assertions) { if (node.assertions) {
return node.assertions.properties[0].value?.value; return node.assertions.properties[0].value?.value;
} }
// oxc
if (node.withClause) {
return node.withClause.withEntries[0].value?.name;
}
return undefined; return undefined;
} }

View file

@ -1,190 +0,0 @@
import path from 'path';
import babelTraversePkg from '@babel/traverse';
import { trackDownIdentifier } from './track-down-identifier.js';
import { AstService } from '../core/AstService.js';
import { toPosixPath } from './to-posix-path.js';
import { fsAdapter } from './fs-adapter.js';
/**
* @typedef {import('../../../types/index.js').PathRelativeFromProjectRoot} PathRelativeFromProjectRoot
* @typedef {import('../../../types/index.js').PathFromSystemRoot} PathFromSystemRoot
* @typedef {import('@babel/traverse').NodePath} NodePath
* @typedef {import('@babel/types').Node} Node
*/
/**
* @param {{rootPath:PathFromSystemRoot; localPath:PathRelativeFromProjectRoot}} opts
* @returns
*/
export function getFilePathOrExternalSource({ rootPath, localPath }) {
if (!localPath.startsWith('.')) {
// We are not resolving external files like '@lion/input-amount/x.js',
// but we give a 100% score if from and to are same here..
return localPath;
}
return toPosixPath(path.resolve(rootPath, localPath));
}
/**
* Assume we had:
* ```js
* const x = 88;
* const y = x;
* export const myIdentifier = y;
* ```
* - We started in getSourceCodeFragmentOfDeclaration (looking for 'myIdentifier'), which found VariableDeclarator of export myIdentifier
* - getReferencedDeclaration is called with { referencedIdentifierName: 'y', ... }
* - now we will look in globalScopeBindings, till we find declaration of 'y'
* - Is it a ref? Call ourselves with referencedIdentifierName ('x' in example above)
* - is it a non ref declaration? Return the path of the node
* @param {{ referencedIdentifierName:string, globalScopeBindings:BabelBinding; }} opts
* @returns {NodePath}
*/
export function getReferencedDeclaration({ referencedIdentifierName, globalScopeBindings }) {
const [, refDeclaratorBinding] = Object.entries(globalScopeBindings).find(
([key]) => key === referencedIdentifierName,
);
if (
refDeclaratorBinding.path.type === 'ImportSpecifier' ||
refDeclaratorBinding.path.type === 'ImportDefaultSpecifier'
) {
return refDeclaratorBinding.path;
}
if (refDeclaratorBinding.path.node.init.type === 'Identifier') {
return getReferencedDeclaration({
referencedIdentifierName: refDeclaratorBinding.path.node.init.name,
globalScopeBindings,
});
}
return refDeclaratorBinding.path.get('init');
}
/**
* @example
* ```js
* // ------ input file --------
* const x = 88;
* const y = x;
* export const myIdentifier = y;
* // --------------------------
*
* await getSourceCodeFragmentOfDeclaration(code) // finds "88"
* ```
*
* @param {{ filePath: PathFromSystemRoot; exportedIdentifier: string; projectRootPath: PathFromSystemRoot }} opts
* @returns {Promise<{ sourceNodePath: string; sourceFragment: string|null; externalImportSource: string; }>}
*/
export async function getSourceCodeFragmentOfDeclaration({
filePath,
exportedIdentifier,
projectRootPath,
}) {
const code = fsAdapter.fs.readFileSync(filePath, 'utf8');
// TODO: fix swc-to-babel lib to make this compatible with 'swc-to-babel' mode of getAst
const babelAst = AstService.getAst(code, 'babel', { filePath });
/** @type {NodePath} */
let finalNodePath;
babelTraversePkg.default(babelAst, {
Program(astPath) {
astPath.stop();
// Situations
// - Identifier is part of default export (in this case 'exportedIdentifier' is '[default]' )
// - declared right away (for instance a class)
// - referenced (possibly recursively) by other declaration
// - Identifier is part of a named export
// - declared right away
// - referenced (possibly recursively) by other declaration
const globalScopeBindings = astPath.get('body')[0].scope.bindings;
if (exportedIdentifier === '[default]') {
const defaultExportPath = astPath
.get('body')
.find(child => child.node.type === 'ExportDefaultDeclaration');
// @ts-expect-error
const isReferenced = defaultExportPath?.node.declaration?.type === 'Identifier';
if (!isReferenced) {
finalNodePath = defaultExportPath.get('declaration');
} else {
finalNodePath = getReferencedDeclaration({
referencedIdentifierName: defaultExportPath.node.declaration.name,
globalScopeBindings,
});
}
} else {
const variableDeclaratorPath = astPath.scope.getBinding(exportedIdentifier).path;
const varDeclNode = variableDeclaratorPath.node;
const isReferenced = varDeclNode.init?.type === 'Identifier';
const contentPath = varDeclNode.init
? variableDeclaratorPath.get('init')
: variableDeclaratorPath;
const name = varDeclNode.init
? varDeclNode.init.name
: varDeclNode.id?.name || varDeclNode.imported.name;
if (!isReferenced) {
// it must be an exported declaration
finalNodePath = contentPath;
} else {
finalNodePath = getReferencedDeclaration({
referencedIdentifierName: name,
globalScopeBindings,
});
}
}
},
});
if (finalNodePath.type === 'ImportSpecifier') {
const importDeclNode = finalNodePath.parentPath.node;
const source = importDeclNode.source.value;
const identifierName = finalNodePath.node.imported.name;
const currentFilePath = filePath;
const rootFile = await trackDownIdentifier(
source,
identifierName,
currentFilePath,
projectRootPath,
);
const filePathOrSrc = getFilePathOrExternalSource({
rootPath: projectRootPath,
localPath: rootFile.file,
});
// TODO: allow resolving external project file paths
if (!filePathOrSrc.startsWith('/')) {
// So we have external project; smth like '@lion/input/x.js'
return {
sourceNodePath: finalNodePath,
sourceFragment: null,
externalImportSource: filePathOrSrc,
};
}
return getSourceCodeFragmentOfDeclaration({
filePath: filePathOrSrc,
exportedIdentifier: rootFile.specifier,
projectRootPath,
});
}
return {
sourceNodePath: finalNodePath,
sourceFragment: code.slice(
finalNodePath.node?.loc?.start.index,
finalNodePath.node?.loc?.end.index,
),
externalImportSource: null,
};
}

View file

@ -1,7 +1,7 @@
import path from 'path'; import path from 'path';
import { oxcTraverse, getPathFromNode, nameOf } from './oxc-traverse.js';
import { trackDownIdentifier } from './track-down-identifier.js'; import { trackDownIdentifier } from './track-down-identifier.js';
import { swcTraverse, getPathFromNode } from './swc-traverse.js';
import { AstService } from '../core/AstService.js'; import { AstService } from '../core/AstService.js';
import { toPosixPath } from './to-posix-path.js'; import { toPosixPath } from './to-posix-path.js';
import { fsAdapter } from './fs-adapter.js'; import { fsAdapter } from './fs-adapter.js';
@ -9,6 +9,7 @@ import { fsAdapter } from './fs-adapter.js';
/** /**
* @typedef {import('../../../types/index.js').PathRelativeFromProjectRoot} PathRelativeFromProjectRoot * @typedef {import('../../../types/index.js').PathRelativeFromProjectRoot} PathRelativeFromProjectRoot
* @typedef {import('../../../types/index.js').PathFromSystemRoot} PathFromSystemRoot * @typedef {import('../../../types/index.js').PathFromSystemRoot} PathFromSystemRoot
* @typedef {import('../../../types/index.js').AnalyzerAst} AnalyzerAst
* @typedef {import('../../../types/index.js').SwcBinding} SwcBinding * @typedef {import('../../../types/index.js').SwcBinding} SwcBinding
* @typedef {import('../../../types/index.js').SwcPath} SwcPath * @typedef {import('../../../types/index.js').SwcPath} SwcPath
* @typedef {import('@swc/core').Node} SwcNode * @typedef {import('@swc/core').Node} SwcNode
@ -57,9 +58,9 @@ export function getReferencedDeclaration({ referencedIdentifierName, globalScope
return refDeclaratorBinding.path; return refDeclaratorBinding.path;
} }
if (refDeclaratorBinding.identifier.init.type === 'Identifier') { if (refDeclaratorBinding.path.node.init.type === 'Identifier') {
return getReferencedDeclaration({ return getReferencedDeclaration({
referencedIdentifierName: refDeclaratorBinding.identifier.init.value, referencedIdentifierName: nameOf(refDeclaratorBinding.path.node.init),
globalScopeBindings, globalScopeBindings,
}); });
} }
@ -79,28 +80,25 @@ export function getReferencedDeclaration({ referencedIdentifierName, globalScope
* await getSourceCodeFragmentOfDeclaration(code) // finds "88" * await getSourceCodeFragmentOfDeclaration(code) // finds "88"
* ``` * ```
* *
* @param {{ filePath: PathFromSystemRoot; exportedIdentifier: string; projectRootPath: PathFromSystemRoot }} opts * @param {{ filePath: PathFromSystemRoot; exportedIdentifier: string; projectRootPath: PathFromSystemRoot; parser: AnalyzerAst }} opts
* @returns {Promise<{ sourceNodePath: SwcPath; sourceFragment: string|null; externalImportSource: string|null; }>} * @returns {Promise<{ sourceNodePath: SwcPath; sourceFragment: string|null; externalImportSource: string|null; }>}
*/ */
export async function getSourceCodeFragmentOfDeclaration({ export async function getSourceCodeFragmentOfDeclaration({
exportedIdentifier, exportedIdentifier,
projectRootPath, projectRootPath,
parser = 'oxc',
filePath, filePath,
}) { }) {
const code = fsAdapter.fs.readFileSync(filePath, 'utf8'); const code = await fsAdapter.fs.promises.readFile(filePath, 'utf8');
// compensate for swc span bug: https://github.com/swc-project/swc/issues/1366#issuecomment-1516539812 // compensate for swc span bug: https://github.com/swc-project/swc/issues/1366#issuecomment-1516539812
const offset = AstService._getSwcOffset(); const offset = parser === 'swc' ? await AstService._getSwcOffset() : -1;
// TODO: fix swc-to-babel lib to make this compatible with 'swc-to-babel' mode of getAst const ast = await AstService.getAst(code, parser);
const swcAst = AstService._getSwcAst(code);
/** @type {SwcPath} */ /** @type {SwcPath} */
let finalNodePath; let finalNodePath;
swcTraverse( const moduleOrProgramHandler = astPath => {
swcAst,
{
Module(astPath) {
astPath.stop(); astPath.stop();
// Situations // Situations
@ -121,14 +119,22 @@ export async function getSourceCodeFragmentOfDeclaration({
), ),
) )
); );
const isReferenced = defaultExportPath?.node.expression?.type === 'Identifier';
const isReferenced =
(defaultExportPath?.node.declaration?.type || defaultExportPath?.node.expression?.type) ===
'Identifier';
if (!isReferenced) { if (!isReferenced) {
finalNodePath = defaultExportPath.get('decl') || defaultExportPath.get('expression'); finalNodePath =
defaultExportPath.get('declaration') ||
defaultExportPath.get('decl') ||
defaultExportPath.get('expression');
} else { } else {
finalNodePath = /** @type {SwcPath} */ ( finalNodePath = /** @type {SwcPath} */ (
getReferencedDeclaration({ getReferencedDeclaration({
referencedIdentifierName: defaultExportPath.node.expression.value, referencedIdentifierName: nameOf(
defaultExportPath.node.declaration || defaultExportPath.node.expression,
),
// @ts-expect-error // @ts-expect-error
globalScopeBindings, globalScopeBindings,
}) })
@ -143,8 +149,8 @@ export async function getSourceCodeFragmentOfDeclaration({
: variableDeclaratorPath; : variableDeclaratorPath;
const name = varDeclNode.init const name = varDeclNode.init
? varDeclNode.init.value ? nameOf(varDeclNode.init)
: varDeclNode.id?.value || varDeclNode.imported?.value || varDeclNode.orig?.value; : nameOf(varDeclNode.id) || nameOf(varDeclNode.imported) || nameOf(varDeclNode.orig);
if (!isReferenced) { if (!isReferenced) {
// it must be an exported declaration // it must be an exported declaration
@ -159,7 +165,13 @@ export async function getSourceCodeFragmentOfDeclaration({
); );
} }
} }
}, };
oxcTraverse(
ast,
{
Module: moduleOrProgramHandler,
Program: moduleOrProgramHandler,
}, },
{ needsAdvancedPaths: true }, { needsAdvancedPaths: true },
); );
@ -168,9 +180,9 @@ export async function getSourceCodeFragmentOfDeclaration({
if (finalNodePath.type === 'ImportSpecifier') { if (finalNodePath.type === 'ImportSpecifier') {
// @ts-expect-error // @ts-expect-error
const importDeclNode = finalNodePath.parentPath.node; const importDeclNode = finalNodePath.parentPath.node;
const source = importDeclNode.source.value; const source = nameOf(importDeclNode.source);
// @ts-expect-error // @ts-expect-error
const identifierName = finalNodePath.node.imported?.value || finalNodePath.node.local?.value; const identifierName = nameOf(finalNodePath.node.imported) || nameOf(finalNodePath.node.local);
const currentFilePath = filePath; const currentFilePath = filePath;
const rootFile = await trackDownIdentifier( const rootFile = await trackDownIdentifier(
@ -199,17 +211,20 @@ export async function getSourceCodeFragmentOfDeclaration({
filePath: /** @type {PathFromSystemRoot} */ (filePathOrSrc), filePath: /** @type {PathFromSystemRoot} */ (filePathOrSrc),
exportedIdentifier: rootFile.specifier, exportedIdentifier: rootFile.specifier,
projectRootPath, projectRootPath,
parser,
}); });
} }
const startOf = node => node.start || node.span.start;
const endOf = node => node.end || node.span.end;
return { return {
// @ts-expect-error // @ts-expect-error
sourceNodePath: finalNodePath, sourceNodePath: finalNodePath,
sourceFragment: code.slice( sourceFragment: code.slice(
// @ts-expect-error // @ts-expect-error
finalNodePath.node.span.start - 1 - offset, startOf(finalNodePath.node) - 1 - offset,
// @ts-expect-error // @ts-expect-error
finalNodePath.node.span.end - 1 - offset, endOf(finalNodePath.node) - 1 - offset,
), ),
// sourceFragment: finalNodePath.node?.raw || finalNodePath.node?.value, // sourceFragment: finalNodePath.node?.raw || finalNodePath.node?.value,
externalImportSource: null, externalImportSource: null,

View file

@ -1,23 +0,0 @@
import toBabel from '../../../inlined-swc-to-babel/lib/swc-to-babel.cjs';
/**
* @typedef {import('@babel/types').File} File
*/
/**
* Internal wrapper around swc-to-babel...
* Allows to easily switch all swc based analyzers to Babel in case
* they turn out to be not stable yet (for instance printing a transformed ast with @babel/generator)
* Checks first whether it gets a Babel ast provided or not...
* @param {*} swcOrBabelAst
* @param {string} source
* @returns {File}
*/
export function guardedSwcToBabel(swcOrBabelAst, source) {
const isSwcAst = swcOrBabelAst.type === 'Module';
if (isSwcAst) {
// @ts-ignore
return toBabel(swcOrBabelAst, source);
}
return swcOrBabelAst;
}

View file

@ -5,7 +5,7 @@ export {
getFilePathOrExternalSource, getFilePathOrExternalSource,
} from './get-source-code-fragment-of-declaration.js'; } from './get-source-code-fragment-of-declaration.js';
export { optimisedGlob } from './optimised-glob.js'; export { optimisedGlob } from './optimised-glob.js';
export { swcTraverse } from './swc-traverse.js'; export { oxcTraverse } from './oxc-traverse.js';
export { fsAdapter } from './fs-adapter.js'; export { fsAdapter } from './fs-adapter.js';
export { memoize } from './memoize.js'; export { memoize } from './memoize.js';
export { hash } from './hash.js'; export { hash } from './hash.js';

View file

@ -1,13 +1,14 @@
/** /**
* @typedef {import('@swc/core').Module} SwcAstModule * @typedef {import('../../../types/index.js').SwcTraversalContext} SwcTraversalContext
* @typedef {import('@swc/core').Node} SwcNode
* @typedef {import('@swc/core').VariableDeclarator} SwcVariableDeclarator * @typedef {import('@swc/core').VariableDeclarator} SwcVariableDeclarator
* @typedef {import('@swc/core').Identifier} SwcIdentifierNode * @typedef {import('@swc/core').Identifier} SwcIdentifierNode
* @typedef {import('@swc/core').Module} SwcAstModule
* @typedef {import('@swc/core').Node} SwcNode
* @typedef {import('../../../types/index.js').SwcPath} SwcPath * @typedef {import('../../../types/index.js').SwcPath} SwcPath
* @typedef {import('../../../types/index.js').SwcScope} SwcScope * @typedef {import('../../../types/index.js').SwcScope} SwcScope
* @typedef {import('../../../types/index.js').SwcVisitor} SwcVisitor * @typedef {import('../../../types/index.js').SwcVisitor} SwcVisitor
* @typedef {import('../../../types/index.js').SwcBinding} SwcBinding * @typedef {import('../../../types/index.js').SwcBinding} SwcBinding
* @typedef {import('../../../types/index.js').SwcTraversalContext} SwcTraversalContext * @typedef {import('oxc-parser').ParseResult} OxcNode
*/ */
/** /**
@ -26,6 +27,42 @@ const fnTypes = [
const nonBlockParentTypes = [...fnTypes, 'SwitchStatement', 'ClassDeclaration']; const nonBlockParentTypes = [...fnTypes, 'SwitchStatement', 'ClassDeclaration'];
/**
* @param {SwcNode|OxcNode} node
*/
export function nameOf(node) {
// @ts-expect-error
return node.value || node.name;
}
/**
* @param {SwcNode|OxcNode} node
*/
export function importedOf(node) {
// @ts-expect-error
// babel/oxc vs swc
return node?.imported || node?.orig || node?.local;
}
/**
* @param {SwcNode|OxcNode} node
*/
export function isProperty(node) {
if (!node) return false;
switch (node.type) {
case 'ObjectProperty':
case 'ClassProperty':
case 'ClassAccessorProperty':
case 'ClassPrivateProperty':
break;
default:
return false;
}
return false;
}
/** /**
* @param {SwcPath} swcPath * @param {SwcPath} swcPath
* @param {SwcScope} currentScope * @param {SwcScope} currentScope
@ -48,6 +85,15 @@ function getNewScope(swcPath, currentScope, traversalContext) {
parentScope: currentScope, parentScope: currentScope,
path: swcPath, path: swcPath,
bindings: {}, bindings: {},
getBinding(identifierName) {
let parentScope = currentScope;
let foundBinding;
while (!foundBinding && parentScope) {
foundBinding = parentScope.bindings[identifierName];
parentScope = parentScope.parentScope;
}
return foundBinding;
},
_pendingRefsWithoutBinding: [], _pendingRefsWithoutBinding: [],
_isIsolatedBlockStatement: isIsolatedBlockStatement, _isIsolatedBlockStatement: isIsolatedBlockStatement,
}; };
@ -83,7 +129,7 @@ function createSwcPath(node, parent, stop, scope) {
const swcPathForNode = getPathFromNode(node[id]); const swcPathForNode = getPathFromNode(node[id]);
if (node[id] && !swcPathForNode) { if (node[id] && !swcPathForNode) {
// throw new Error( // throw new Error(
// `[swcTraverse]: Use {needsAdvancedPaths: true} to find path for node: ${node[name]}`, // `[oxcTraverse]: Use {needsAdvancedPaths: true} to find path for node: ${node[name]}`,
// ); // );
// TODO: "pre-traverse" the missing path parts instead // TODO: "pre-traverse" the missing path parts instead
} }
@ -92,6 +138,10 @@ function createSwcPath(node, parent, stop, scope) {
get type() { get type() {
return node.type; return node.type;
}, },
traverse(visitor) {
// eslint-disable-next-line no-use-before-define
return oxcTraverse(node, visitor);
},
}; };
swcPathCache.set(node, swcPath); swcPathCache.set(node, swcPath);
return swcPath; return swcPath;
@ -103,19 +153,18 @@ function createSwcPath(node, parent, stop, scope) {
* - an import specifier (like "import { a } from 'b'")? * - an import specifier (like "import { a } from 'b'")?
* Handy to know if the parents of Identifiers mark a binding * Handy to know if the parents of Identifiers mark a binding
* @param {SwcNode} parent * @param {SwcNode} parent
* @param {string} identifierValue * @param {string} identifierName
*/ */
function isBindingNode(parent, identifierValue) { function isBindingNode(parent, identifierName) {
if (parent.type === 'VariableDeclarator') { if (['VariableDeclarator', 'ClassDeclaration'].includes(parent.type)) {
// @ts-expect-error // @ts-expect-error
return parent.id.value === identifierValue; return nameOf(parent.id) === identifierName;
} }
return [ return [
'ClassDeclaration',
'FunctionDeclaration',
'ArrowFunctionExpression', 'ArrowFunctionExpression',
'ImportSpecifier',
'ImportDefaultSpecifier', 'ImportDefaultSpecifier',
'FunctionDeclaration',
'ImportSpecifier',
].includes(parent.type); ].includes(parent.type);
} }
@ -141,15 +190,13 @@ function isBindingRefNode(parent) {
function addPotentialBindingOrRefToScope(swcPathForIdentifier) { function addPotentialBindingOrRefToScope(swcPathForIdentifier) {
const { node, parent, scope, parentPath } = swcPathForIdentifier; const { node, parent, scope, parentPath } = swcPathForIdentifier;
if (node.type !== 'Identifier') { if (node.type !== 'Identifier') return;
return;
}
// const parentPath = getPathFromNode(parent); // const parentPath = getPathFromNode(parent);
if (isBindingNode(parent, node.value)) { if (isBindingNode(parent, nameOf(node))) {
/** @type {SwcBinding} */ /** @type {SwcBinding} */
const binding = { const binding = {
identifier: parent, identifier: parent?.id || parent?.identifier,
// kind: 'var', // kind: 'var',
refs: [], refs: [],
path: swcPathForIdentifier.parentPath, path: swcPathForIdentifier.parentPath,
@ -170,19 +217,20 @@ function addPotentialBindingOrRefToScope(swcPathForIdentifier) {
1, 1,
); );
} }
const idName = node.value || node.local?.value || node.orig?.value; const idName = nameOf(node) || nameOf(node.local) || nameOf(node.orig || node.imported);
// eslint-disable-next-line no-param-reassign // eslint-disable-next-line no-param-reassign
scopeBindingBelongsTo.bindings[idName] = binding; scopeBindingBelongsTo.bindings[idName] = binding;
// Align with Babel... => in example `class Q {}`, Q has binding to root scope and ClassDeclaration scope // Align with Babel... => in example `class Q {}`, Q has binding to root scope and ClassDeclaration scope
if (parent.type === 'ClassDeclaration') { if (parent.type === 'ClassDeclaration' && (parent.id || parent.identifier) === node) {
scope.bindings[idName] = binding; scope.bindings[idName] = binding;
} }
} }
// In other cases, we are dealing with a reference that must be bound to a binding // In other cases, we are dealing with a reference that must be bound to a binding
else if (isBindingRefNode(parent)) { else if (isBindingRefNode(parent)) {
// eslint-disable-next-line no-prototype-builtins // eslint-disable-next-line no-prototype-builtins
const binding = scope.bindings.hasOwnProperty(node.value) && scope.bindings[node.value]; const binding = scope.bindings.hasOwnProperty(nameOf(node)) && scope.bindings[nameOf(node)];
if (binding) { if (binding) {
binding.refs.push(parentPath); binding.refs.push(parentPath);
} else { } else {
@ -200,7 +248,7 @@ function addPotentialBindingOrRefToScope(swcPathForIdentifier) {
* @returns {boolean} * @returns {boolean}
*/ */
function isRootNode(node) { function isRootNode(node) {
return node.type === 'Module' || node.type === 'Script'; return node.type === 'Program' || node.type === 'Module' || node.type === 'Script';
} }
/** /**
@ -260,12 +308,12 @@ function visit(swcPath, visitor, traversalContext) {
/** /**
* Simple traversal for swc ast. * Simple traversal for swc ast.
* @param {SwcAstModule} swcAst * @param {SwcAstModule|SwcNode} oxcAst
* @param {SwcVisitor} visitor * @param {SwcVisitor} visitor
* @param {object} config * @param {object} config
* @param {boolean} [config.needsAdvancedPaths] needs a full traversal before starting the visitor, which is less performant. Only enable when path.get() is used * @param {boolean} [config.needsAdvancedPaths] needs a full traversal before starting the visitor, which is less performant. Only enable when path.get() is used
*/ */
export function swcTraverse(swcAst, visitor, { needsAdvancedPaths = false } = {}) { export function oxcTraverse(oxcAst, visitor, { needsAdvancedPaths = false } = {}) {
/** /**
* For performance, the author of a visitor can call this to stop further traversal * For performance, the author of a visitor can call this to stop further traversal
*/ */
@ -344,15 +392,18 @@ export function swcTraverse(swcAst, visitor, { needsAdvancedPaths = false } = {}
id: traversalContext.scopeId, id: traversalContext.scopeId,
bindings: {}, bindings: {},
path: null, path: null,
getBinding(/** @type {string} */ identifierName) {
return initialScope.bindings[identifierName];
},
_pendingRefsWithoutBinding: [], _pendingRefsWithoutBinding: [],
_isIsolatedBlockStatement: false, _isIsolatedBlockStatement: false,
}; };
if (needsAdvancedPaths) { if (needsAdvancedPaths) {
// Do one full traversal to prepare advanced path functionality like path.get() and path.scope.bindings // Do one full traversal to prepare advanced path functionality like path.get() and path.scope.bindings
// TODO: improve with on the fly, partial tree traversal for best performance // TODO: improve with on the fly, partial tree traversal for best performance
prepareTree(swcAst, null, initialScope, traversalContext); prepareTree(oxcAst, null, initialScope, traversalContext);
} }
visitTree(swcAst, null, initialScope, { hasPreparedTree: needsAdvancedPaths }, traversalContext); visitTree(oxcAst, null, initialScope, { hasPreparedTree: needsAdvancedPaths }, traversalContext);
// @ts-expect-error // @ts-expect-error
traversalContext.visitOnExitFns.reverse().forEach(fn => fn()); traversalContext.visitOnExitFns.reverse().forEach(fn => fn());
} }

View file

@ -1,334 +0,0 @@
/* eslint-disable no-shadow */
// @ts-nocheck
import path from 'path';
import babelTraverse from '@babel/traverse';
import { isRelativeSourcePath, toRelativeSourcePath } from './relative-source-path.js';
import { InputDataService } from '../core/InputDataService.js';
import { resolveImportPath } from './resolve-import-path.js';
import { AstService } from '../core/AstService.js';
import { LogService } from '../core/LogService.js';
import { fsAdapter } from './fs-adapter.js';
import { memoize } from './memoize.js';
/**
* @typedef {import('../../../../types/index.js').PathFromSystemRoot} PathFromSystemRoot
* @typedef {import('../../../../types/index.js').SpecifierSource} SpecifierSource
* @typedef {import('../../../../types/index.js').IdentifierName} IdentifierName
* @typedef {import('../../../../types/index.js').RootFile} RootFile
* @typedef {import('@babel/traverse').NodePath} NodePath
*/
/**
* @param {string} source
* @param {string} projectName
*/
function isSelfReferencingProject(source, projectName) {
return source.split('/')[0] === projectName;
}
/**
* @param {string} source
* @param {string} projectName
*/
function isExternalProject(source, projectName) {
return (
!source.startsWith('#') &&
!isRelativeSourcePath(source) &&
!isSelfReferencingProject(source, projectName)
);
}
/**
* Other than with import, no binding is created for MyClass by Babel(?)
* This means 'path.scope.getBinding('MyClass')' returns undefined
* and we have to find a different way to retrieve this value.
* @param {NodePath} astPath Babel ast traversal path
* @param {IdentifierName} identifierName the name that should be tracked (and that exists inside scope of astPath)
*/
function getBindingAndSourceReexports(astPath, identifierName) {
// Get to root node of file and look for exports like `export { identifierName } from 'src';`
let source;
let bindingType;
let bindingPath;
let curPath = astPath;
while (curPath.parentPath) {
curPath = curPath.parentPath;
}
const rootPath = curPath;
rootPath.traverse({
ExportSpecifier(astPath) {
// eslint-disable-next-line arrow-body-style
const found =
astPath.node.exported.name === identifierName || astPath.node.local.name === identifierName;
if (found) {
bindingPath = astPath;
bindingType = 'ExportSpecifier';
source = astPath.parentPath.node.source
? astPath.parentPath.node.source.value
: '[current]';
astPath.stop();
}
},
});
return [source, bindingType, bindingPath];
}
/**
* Retrieves source (like '@lion/core') and importedIdentifierName (like 'lit') from ast for
* current file.
* We might be an import that was locally renamed.
* Since we are traversing, we are interested in the imported name. Or in case of a re-export,
* the local name.
* @param {NodePath} astPath Babel ast traversal path
* @param {string} identifierName the name that should be tracked (and that exists inside scope of astPath)
* @returns {{ source:string, importedIdentifierName:string }}
*/
export function getImportSourceFromAst(astPath, identifierName) {
let source;
let importedIdentifierName;
const binding = astPath.scope.getBinding(identifierName);
let bindingType = binding?.path.type;
let bindingPath = binding?.path;
const matchingTypes = ['ImportSpecifier', 'ImportDefaultSpecifier', 'ExportSpecifier'];
if (bindingType && matchingTypes.includes(bindingType)) {
source = binding?.path?.parentPath?.node?.source?.value;
} else {
// no binding
[source, bindingType, bindingPath] = getBindingAndSourceReexports(astPath, identifierName);
}
const shouldLookForDefaultExport = bindingType === 'ImportDefaultSpecifier';
if (shouldLookForDefaultExport) {
importedIdentifierName = '[default]';
} else if (source) {
const { node } = bindingPath;
importedIdentifierName = (node.imported && node.imported.name) || node.local.name;
}
return { source, importedIdentifierName };
}
/**
* @typedef {(source:SpecifierSource,identifierName:IdentifierName,currentFilePath:PathFromSystemRoot,rootPath:PathFromSystemRoot,projectName?: string,depth?:number) => Promise<RootFile>} TrackDownIdentifierFn
*/
/**
* Follows the full path of an Identifier until its declaration ('root file') is found.
* @example
*```js
* // 1. Starting point
* // target-proj/my-comp-import.js
* import { MyComp as TargetComp } from 'ref-proj';
*
* // 2. Intermediate stop: a re-export
* // ref-proj/exportsIndex.js (package.json has main: './exportsIndex.js')
* export { RefComp as MyComp } from './src/RefComp.js';
*
* // 3. End point: our declaration
* // ref-proj/src/RefComp.js
* export class RefComp extends LitElement {...}
*```
*
* -param {SpecifierSource} source an importSpecifier source, like 'ref-proj' or '../file'
* -param {IdentifierName} identifierName imported reference/Identifier name, like 'MyComp'
* -param {PathFromSystemRoot} currentFilePath file path, like '/path/to/target-proj/my-comp-import.js'
* -param {PathFromSystemRoot} rootPath dir path, like '/path/to/target-proj'
* -param {string} [projectName] like 'target-proj' or '@lion/input'
* -returns {Promise<RootFile>} file: path of file containing the binding (exported declaration),
* like '/path/to/ref-proj/src/RefComp.js'
*/
/** @type {TrackDownIdentifierFn} */
// eslint-disable-next-line import/no-mutable-exports
export let trackDownIdentifier;
/** @type {TrackDownIdentifierFn} */
async function trackDownIdentifierFn(
source,
identifierName,
currentFilePath,
rootPath,
projectName,
depth = 0,
) {
let rootFilePath; // our result path
let rootSpecifier; // the name under which it was imported
if (!projectName) {
// eslint-disable-next-line no-param-reassign
projectName = InputDataService.getPackageJson(rootPath)?.name;
}
if (projectName && isExternalProject(source, projectName)) {
// So, it is an external ref like '@lion/core' or '@open-wc/scoped-elements/index.js'
// At this moment in time, we don't know if we have file system access to this particular
// project. Therefore, we limit ourselves to tracking down local references.
// In case this helper is used inside an analyzer like 'match-subclasses', the external
// (search-target) project can be accessed and paths can be resolved to local ones,
// just like in 'match-imports' analyzer.
/** @type {RootFile} */
const result = { file: source, specifier: identifierName };
return result;
}
const resolvedSourcePath = await resolveImportPath(source, currentFilePath);
LogService.debug(`[trackDownIdentifier] ${resolvedSourcePath}`);
const allowedJsModuleExtensions = ['.mjs', '.js'];
if (!allowedJsModuleExtensions.includes(path.extname(resolvedSourcePath))) {
// We have an import assertion
return /** @type { RootFile } */ {
file: toRelativeSourcePath(resolvedSourcePath, rootPath),
specifier: '[default]',
};
}
const code = fsAdapter.fs.readFileSync(resolvedSourcePath, 'utf8');
const babelAst = AstService.getAst(code, 'swc-to-babel', { filePath: resolvedSourcePath });
const shouldLookForDefaultExport = identifierName === '[default]';
let reexportMatch = false; // named specifier declaration
let exportMatch;
let pendingTrackDownPromise;
babelTraverse.default(babelAst, {
ExportDefaultDeclaration(astPath) {
if (!shouldLookForDefaultExport) {
return;
}
let newSource;
if (astPath.node.declaration.type === 'Identifier') {
newSource = getImportSourceFromAst(astPath, astPath.node.declaration.name).source;
}
if (newSource) {
pendingTrackDownPromise = trackDownIdentifier(
newSource,
'[default]',
resolvedSourcePath,
rootPath,
projectName,
depth + 1,
);
} else {
// We found our file!
rootSpecifier = identifierName;
rootFilePath = toRelativeSourcePath(resolvedSourcePath, rootPath);
}
astPath.stop();
},
ExportNamedDeclaration: {
enter(astPath) {
if (reexportMatch || shouldLookForDefaultExport) {
return;
}
// Are we dealing with a re-export ?
if (astPath.node.specifiers?.length) {
exportMatch = astPath.node.specifiers.find(s => s.exported.name === identifierName);
if (exportMatch) {
const localName = exportMatch.local.name;
let newSource;
if (astPath.node.source) {
/**
* @example
* export { x } from 'y'
*/
newSource = astPath.node.source.value;
} else {
/**
* @example
* import { x } from 'y'
* export { x }
*/
newSource = getImportSourceFromAst(astPath, identifierName).source;
if (!newSource || newSource === '[current]') {
/**
* @example
* const x = 12;
* export { x }
*/
return;
}
}
reexportMatch = true;
pendingTrackDownPromise = trackDownIdentifier(
newSource,
localName,
resolvedSourcePath,
rootPath,
projectName,
depth + 1,
);
astPath.stop();
}
}
},
exit(astPath) {
if (!reexportMatch) {
// We didn't find a re-exported Identifier, that means the reference is declared
// in current file...
rootSpecifier = identifierName;
rootFilePath = toRelativeSourcePath(resolvedSourcePath, rootPath);
if (exportMatch) {
astPath.stop();
}
}
},
},
});
if (pendingTrackDownPromise) {
// We can't handle promises inside Babel traverse, so we do it here...
const resObj = await pendingTrackDownPromise;
rootFilePath = resObj.file;
rootSpecifier = resObj.specifier;
}
return /** @type { RootFile } */ { file: rootFilePath, specifier: rootSpecifier };
}
trackDownIdentifier = memoize(trackDownIdentifierFn);
/**
* @param {NodePath} astPath
* @param {string} identifierNameInScope
* @param {PathFromSystemRoot} fullCurrentFilePath
* @param {PathFromSystemRoot} projectPath
* @param {string} [projectName]
*/
async function trackDownIdentifierFromScopeFn(
astPath,
identifierNameInScope,
fullCurrentFilePath,
projectPath,
projectName,
) {
const sourceObj = getImportSourceFromAst(astPath, identifierNameInScope);
/** @type {RootFile} */
let rootFile;
if (sourceObj.source) {
rootFile = await trackDownIdentifier(
sourceObj.source,
sourceObj.importedIdentifierName,
fullCurrentFilePath,
projectPath,
projectName,
);
} else {
const specifier = sourceObj.importedIdentifierName || identifierNameInScope;
rootFile = { file: '[current]', specifier };
}
return rootFile;
}
export const trackDownIdentifierFromScope = memoize(trackDownIdentifierFromScopeFn);

View file

@ -4,7 +4,7 @@ import { isRelativeSourcePath, toRelativeSourcePath } from './relative-source-pa
import { InputDataService } from '../core/InputDataService.js'; import { InputDataService } from '../core/InputDataService.js';
import { resolveImportPath } from './resolve-import-path.js'; import { resolveImportPath } from './resolve-import-path.js';
import { AstService } from '../core/AstService.js'; import { AstService } from '../core/AstService.js';
import { swcTraverse } from './swc-traverse.js'; import { oxcTraverse, nameOf, importedOf } from './oxc-traverse.js';
import { fsAdapter } from './fs-adapter.js'; import { fsAdapter } from './fs-adapter.js';
import { memoize } from './memoize.js'; import { memoize } from './memoize.js';
@ -55,18 +55,19 @@ function getBindingAndSourceReexports(swcPath, identifierName) {
} }
const rootPath = curPath; const rootPath = curPath;
swcTraverse(rootPath.node, { oxcTraverse(rootPath.node, {
ExportSpecifier(astPath) { ExportSpecifier(astPath) {
const { node } = astPath;
// eslint-disable-next-line arrow-body-style // eslint-disable-next-line arrow-body-style
const found = const found =
astPath.node.orig?.value === identifierName || nameOf(importedOf(node)) === identifierName ||
astPath.node.exported?.value === identifierName || nameOf(node.exported) === identifierName ||
astPath.node.local?.value === identifierName; nameOf(node.local) === identifierName;
if (found) { if (found) {
bindingPath = astPath; bindingPath = astPath;
bindingType = 'ExportSpecifier'; bindingType = 'ExportSpecifier';
source = astPath.parentPath.node.source source = astPath.parentPath.node.source
? astPath.parentPath.node.source.value ? nameOf(astPath.parentPath.node.source)
: '[current]'; : '[current]';
astPath.stop(); astPath.stop();
} }
@ -89,13 +90,15 @@ export function getImportSourceFromAst(astPath, identifierName) {
let source; let source;
let importedIdentifierName; let importedIdentifierName;
const binding = astPath.scope.bindings[identifierName]; // TODO: use (smth like) getReferencedDeclaration if we want to catch renamed variables
const binding = astPath.scope.getBinding(identifierName);
let bindingType = binding?.path.type; let bindingType = binding?.path.type;
let bindingPath = binding?.path; let bindingPath = binding?.path;
const matchingTypes = ['ImportSpecifier', 'ImportDefaultSpecifier', 'ExportSpecifier']; const matchingTypes = ['ImportSpecifier', 'ImportDefaultSpecifier', 'ExportSpecifier'];
if (bindingType && matchingTypes.includes(bindingType)) { if (bindingType && matchingTypes.includes(bindingType)) {
source = binding?.path?.parentPath?.node?.source?.value; source = nameOf(binding?.path?.parentPath?.node?.source);
} else { } else {
// no binding // no binding
[source, bindingType, bindingPath] = getBindingAndSourceReexports(astPath, identifierName); [source, bindingType, bindingPath] = getBindingAndSourceReexports(astPath, identifierName);
@ -106,7 +109,7 @@ export function getImportSourceFromAst(astPath, identifierName) {
importedIdentifierName = '[default]'; importedIdentifierName = '[default]';
} else if (source) { } else if (source) {
const { node } = bindingPath; const { node } = bindingPath;
importedIdentifierName = node.orig?.value || node.imported?.value || node.local?.value; importedIdentifierName = nameOf(importedOf(node)) || nameOf(node.local);
} }
return { source, importedIdentifierName }; return { source, importedIdentifierName };
@ -194,8 +197,11 @@ async function trackDownIdentifierFn(
specifier: '[default]', specifier: '[default]',
}; };
} }
const code = fsAdapter.fs.readFileSync(/** @type {string} */ (resolvedSourcePath), 'utf8'); const code = await fsAdapter.fs.promises.readFile(
const swcAst = AstService._getSwcAst(code); /** @type {string} */ (resolvedSourcePath),
'utf8',
);
const oxcAst = await AstService._getOxcAst(code);
const shouldLookForDefaultExport = identifierName === '[default]'; const shouldLookForDefaultExport = identifierName === '[default]';
@ -204,16 +210,16 @@ async function trackDownIdentifierFn(
let pendingTrackDownPromise; let pendingTrackDownPromise;
const handleExportDefaultDeclOrExpr = astPath => { const handleExportDefaultDeclOrExpr = astPath => {
if (!shouldLookForDefaultExport) { if (!shouldLookForDefaultExport) return;
return;
} const { node } = astPath;
let newSource; let newSource;
if ( if (node.expression?.type === 'Identifier' || node.declaration?.type === 'Identifier') {
astPath.node.expression?.type === 'Identifier' || newSource = getImportSourceFromAst(
astPath.node.declaration?.type === 'Identifier' astPath,
) { nameOf(node.expression || node.declaration),
newSource = getImportSourceFromAst(astPath, astPath.node.expression.value).source; ).source;
} }
if (newSource) { if (newSource) {
@ -237,25 +243,27 @@ async function trackDownIdentifierFn(
}; };
const handleExportDeclOrNamedDecl = { const handleExportDeclOrNamedDecl = {
enter(astPath) { enter(astPath) {
if (reexportMatch || shouldLookForDefaultExport) { if (reexportMatch || shouldLookForDefaultExport) return;
return;
} const { node } = astPath;
// Are we dealing with a re-export ? // Are we dealing with a re-export ?
if (astPath.node.specifiers?.length) { if (!node.specifiers?.length) return;
exportMatch = astPath.node.specifiers.find(
s => s.orig?.value === identifierName || s.exported?.value === identifierName, exportMatch = node.specifiers.find(
s => nameOf(importedOf(s)) === identifierName || nameOf(s.exported) === identifierName,
); );
if (exportMatch) { if (!exportMatch) return;
const localName = exportMatch.orig.value;
const localName = nameOf(importedOf(exportMatch));
let newSource; let newSource;
if (astPath.node.source) { if (node.source) {
/** /**
* @example * @example
* export { x } from 'y' * export { x } from 'y'
*/ */
newSource = astPath.node.source.value; newSource = nameOf(node.source);
} else { } else {
/** /**
* @example * @example
@ -283,8 +291,6 @@ async function trackDownIdentifierFn(
depth + 1, depth + 1,
); );
astPath.stop(); astPath.stop();
}
}
}, },
exit(astPath) { exit(astPath) {
if (!reexportMatch) { if (!reexportMatch) {
@ -307,7 +313,7 @@ async function trackDownIdentifierFn(
ExportDeclaration: handleExportDeclOrNamedDecl, ExportDeclaration: handleExportDeclOrNamedDecl,
}; };
swcTraverse(swcAst, visitor, { needsAdvancedPaths: true }); oxcTraverse(oxcAst, visitor, { needsAdvancedPaths: true });
if (pendingTrackDownPromise) { if (pendingTrackDownPromise) {
// We can't handle promises inside Babel traverse, so we do it here... // We can't handle promises inside Babel traverse, so we do it here...
@ -351,6 +357,7 @@ async function trackDownIdentifierFromScopeFn(
const specifier = sourceObj.importedIdentifierName || identifierNameInScope; const specifier = sourceObj.importedIdentifierName || identifierNameInScope;
rootFile = { file: '[current]', specifier }; rootFile = { file: '[current]', specifier };
} }
return rootFile; return rootFile;
} }

View file

@ -1,3 +1,4 @@
import module from 'module';
import path from 'path'; import path from 'path';
// eslint-disable-next-line import/no-extraneous-dependencies // eslint-disable-next-line import/no-extraneous-dependencies
import mockFs from 'mock-fs'; import mockFs from 'mock-fs';
@ -59,6 +60,46 @@ function getMockObjectForProject(files, cfg = {}, existingMock = {}) {
return totalMock; return totalMock;
} }
/**
* @param {string} resolvedPath
* @param {string} dynamicImport
* @returns {string}
*/
function getPackageRootFromNodeModulesPath(resolvedPath, dynamicImport) {
const scope = dynamicImport.startsWith('@') ? dynamicImport.split('/')[0] : dynamicImport;
const tailOfRootPath = `${path.sep}node_modules${path.sep}${scope}`;
const lio = resolvedPath.lastIndexOf(tailOfRootPath);
return resolvedPath.slice(0, lio + tailOfRootPath.length);
}
function incorporateDynamicImports(
dynamicImports = [
{
name: 'oxc-parser',
siblings: [
'@oxc-parser',
// '@oxc-resolver'
],
},
{ name: '@babel/parser' },
{ name: '@swc/core' },
],
) {
const require = module.createRequire(import.meta.url);
const importablePaths = [];
for (const dynamicImport of dynamicImports) {
const resolvedPath = require.resolve(dynamicImport.name);
const rootPath = getPackageRootFromNodeModulesPath(resolvedPath, dynamicImport.name);
importablePaths.push(rootPath);
for (const sibling of dynamicImport.siblings || []) {
const siblingPath = `${rootPath.split(path.sep).slice(0, -1).join(path.sep)}${path.sep}${sibling}`;
importablePaths.push(siblingPath);
}
}
return importablePaths;
}
const importablePaths = incorporateDynamicImports();
/** /**
* Makes sure that, whenever the main program (providence) calls * Makes sure that, whenever the main program (providence) calls
* "InputDataService.createDataObject", it gives back a mocked response. * "InputDataService.createDataObject", it gives back a mocked response.
@ -72,7 +113,7 @@ function getMockObjectForProject(files, cfg = {}, existingMock = {}) {
*/ */
export function mockProject(files, cfg = {}, existingMock = {}) { export function mockProject(files, cfg = {}, existingMock = {}) {
const obj = getMockObjectForProject(files, cfg, existingMock); const obj = getMockObjectForProject(files, cfg, existingMock);
mockFs(obj); mockFs({ ...obj, ...Object.fromEntries(importablePaths.map(p => [p, mockFs.load(p)])) });
return obj; return obj;
} }

View file

@ -3,7 +3,7 @@
"searchType": "ast-analyzer", "searchType": "ast-analyzer",
"analyzerMeta": { "analyzerMeta": {
"name": "find-classes", "name": "find-classes",
"requiredAst": "babel", "requiredAst": "oxc",
"identifier": "importing-target-project_0.0.2-target-mock__-905964591", "identifier": "importing-target-project_0.0.2-target-mock__-905964591",
"targetProject": { "targetProject": {
"mainEntry": "./target-src/match-imports/root-level-imports.js", "mainEntry": "./target-src/match-imports/root-level-imports.js",

View file

@ -3,7 +3,7 @@
"searchType": "ast-analyzer", "searchType": "ast-analyzer",
"analyzerMeta": { "analyzerMeta": {
"name": "find-customelements", "name": "find-customelements",
"requiredAst": "swc-to-babel", "requiredAst": "oxc",
"identifier": "importing-target-project_0.0.2-target-mock__61665553", "identifier": "importing-target-project_0.0.2-target-mock__61665553",
"targetProject": { "targetProject": {
"mainEntry": "./target-src/match-imports/root-level-imports.js", "mainEntry": "./target-src/match-imports/root-level-imports.js",

View file

@ -3,7 +3,7 @@
"searchType": "ast-analyzer", "searchType": "ast-analyzer",
"analyzerMeta": { "analyzerMeta": {
"name": "find-exports", "name": "find-exports",
"requiredAst": "swc", "requiredAst": "oxc",
"identifier": "exporting-ref-project_1.0.0__-42206859", "identifier": "exporting-ref-project_1.0.0__-42206859",
"targetProject": { "targetProject": {
"mainEntry": "./index.js", "mainEntry": "./index.js",

View file

@ -3,7 +3,7 @@
"searchType": "ast-analyzer", "searchType": "ast-analyzer",
"analyzerMeta": { "analyzerMeta": {
"name": "find-imports", "name": "find-imports",
"requiredAst": "swc", "requiredAst": "oxc",
"identifier": "importing-target-project_0.0.2-target-mock__349742630", "identifier": "importing-target-project_0.0.2-target-mock__349742630",
"targetProject": { "targetProject": {
"mainEntry": "./target-src/match-imports/root-level-imports.js", "mainEntry": "./target-src/match-imports/root-level-imports.js",

View file

@ -3,7 +3,7 @@
"searchType": "ast-analyzer", "searchType": "ast-analyzer",
"analyzerMeta": { "analyzerMeta": {
"name": "match-imports", "name": "match-imports",
"requiredAst": "swc", "requiredAst": "oxc",
"identifier": "importing-target-project_0.0.2-target-mock_+_exporting-ref-project_1.0.0__1789378150", "identifier": "importing-target-project_0.0.2-target-mock_+_exporting-ref-project_1.0.0__1789378150",
"targetProject": { "targetProject": {
"mainEntry": "./target-src/match-imports/root-level-imports.js", "mainEntry": "./target-src/match-imports/root-level-imports.js",

View file

@ -3,7 +3,7 @@
"searchType": "ast-analyzer", "searchType": "ast-analyzer",
"analyzerMeta": { "analyzerMeta": {
"name": "match-subclasses", "name": "match-subclasses",
"requiredAst": "babel", "requiredAst": "oxc",
"identifier": "importing-target-project_0.0.2-target-mock_+_exporting-ref-project_1.0.0__1982316146", "identifier": "importing-target-project_0.0.2-target-mock_+_exporting-ref-project_1.0.0__1982316146",
"targetProject": { "targetProject": {
"mainEntry": "./target-src/match-imports/root-level-imports.js", "mainEntry": "./target-src/match-imports/root-level-imports.js",

View file

@ -87,7 +87,7 @@ describe('Analyzer "find-imports"', async () => {
expect(firstEntry.result[0].importSpecifiers[0]).to.equal('[*]'); expect(firstEntry.result[0].importSpecifiers[0]).to.equal('[*]');
}); });
describe('Reexports', () => { describe('Re-exports', () => {
it(`supports [export { x } from 'imported/source'] (re-exported named specifiers)`, async () => { it(`supports [export { x } from 'imported/source'] (re-exported named specifiers)`, async () => {
mockProject([`export { x } from 'imported/source'`]); mockProject([`export { x } from 'imported/source'`]);
const queryResults = await providence(findImportsQueryConfig, _providenceCfg); const queryResults = await providence(findImportsQueryConfig, _providenceCfg);

View file

@ -2,9 +2,13 @@ import { expect } from 'chai';
import { it } from 'mocha'; import { it } from 'mocha';
import { getSourceCodeFragmentOfDeclaration } from '../../../src/program/utils/index.js'; import { getSourceCodeFragmentOfDeclaration } from '../../../src/program/utils/index.js';
import { mock } from '../../../test-helpers/mock-project-helpers.js'; import { mockProject } from '../../../test-helpers/mock-project-helpers.js';
import { memoize } from '../../../src/program/utils/memoize.js'; import { memoize } from '../../../src/program/utils/memoize.js';
/**
* @typedef {import('../../../types/index.js').PathFromSystemRoot} PathFromSystemRoot
*/
describe('getSourceCodeFragmentOfDeclaration', () => { describe('getSourceCodeFragmentOfDeclaration', () => {
const initialMemoizeCacheEnabled = memoize.isCacheEnabled; const initialMemoizeCacheEnabled = memoize.isCacheEnabled;
before(() => { before(() => {
@ -19,10 +23,10 @@ describe('getSourceCodeFragmentOfDeclaration', () => {
const fakeFs = { const fakeFs = {
'/my/proj/exports/file.js': 'export const x = 0;', '/my/proj/exports/file.js': 'export const x = 0;',
}; };
mock(fakeFs); mockProject(fakeFs);
const { sourceFragment } = await getSourceCodeFragmentOfDeclaration({ const { sourceFragment } = await getSourceCodeFragmentOfDeclaration({
filePath: '/my/proj/exports/file.js', filePath: /** @type {PathFromSystemRoot} */ ('/my/proj/exports/file.js'),
exportedIdentifier: 'x', exportedIdentifier: 'x',
}); });
@ -36,7 +40,7 @@ describe('getSourceCodeFragmentOfDeclaration', () => {
export const x = y; export const x = y;
`, `,
}; };
mock(fakeFs); mockProject(fakeFs);
const { sourceFragment } = await getSourceCodeFragmentOfDeclaration({ const { sourceFragment } = await getSourceCodeFragmentOfDeclaration({
filePath: '/my/proj/exports/file.js', filePath: '/my/proj/exports/file.js',
@ -54,7 +58,7 @@ describe('getSourceCodeFragmentOfDeclaration', () => {
export const myIdentifier = y; export const myIdentifier = y;
`, `,
}; };
mock(fakeFs); mockProject(fakeFs);
const { sourceFragment } = await getSourceCodeFragmentOfDeclaration({ const { sourceFragment } = await getSourceCodeFragmentOfDeclaration({
filePath: '/my/proj/exports/file.js', filePath: '/my/proj/exports/file.js',
@ -74,7 +78,7 @@ describe('getSourceCodeFragmentOfDeclaration', () => {
export const black67 = black59; export const black67 = black59;
`, `,
}; };
mock(fakeFs); mockProject(fakeFs);
const { sourceFragment } = await getSourceCodeFragmentOfDeclaration({ const { sourceFragment } = await getSourceCodeFragmentOfDeclaration({
filePath: '/my/proj/exports/file-2.js', filePath: '/my/proj/exports/file-2.js',
@ -93,7 +97,7 @@ describe('getSourceCodeFragmentOfDeclaration', () => {
export class AjaxClass extends LionAjaxClass {} export class AjaxClass extends LionAjaxClass {}
`, `,
}; };
mock(fakeFs); mockProject(fakeFs);
const { sourceFragment } = await getSourceCodeFragmentOfDeclaration({ const { sourceFragment } = await getSourceCodeFragmentOfDeclaration({
filePath: '/my/proj/exports/ajax.js', filePath: '/my/proj/exports/ajax.js',
@ -109,7 +113,7 @@ describe('getSourceCodeFragmentOfDeclaration', () => {
export function myFn() {} export function myFn() {}
`, `,
}; };
mock(fakeFs); mockProject(fakeFs);
const { sourceFragment } = await getSourceCodeFragmentOfDeclaration({ const { sourceFragment } = await getSourceCodeFragmentOfDeclaration({
filePath: '/my/proj/exports/myFn.js', filePath: '/my/proj/exports/myFn.js',
@ -126,7 +130,7 @@ describe('getSourceCodeFragmentOfDeclaration', () => {
const fakeFs = { const fakeFs = {
'/my/proj/exports/file.js': 'export default class {};', '/my/proj/exports/file.js': 'export default class {};',
}; };
mock(fakeFs); mockProject(fakeFs);
const { sourceFragment } = await getSourceCodeFragmentOfDeclaration({ const { sourceFragment } = await getSourceCodeFragmentOfDeclaration({
filePath: '/my/proj/exports/file.js', filePath: '/my/proj/exports/file.js',
@ -143,7 +147,7 @@ describe('getSourceCodeFragmentOfDeclaration', () => {
export default myIdentifier; export default myIdentifier;
`, `,
}; };
mock(fakeFs); mockProject(fakeFs);
const { sourceFragment } = await getSourceCodeFragmentOfDeclaration({ const { sourceFragment } = await getSourceCodeFragmentOfDeclaration({
filePath: '/my/proj/exports/file.js', filePath: '/my/proj/exports/file.js',
@ -161,7 +165,7 @@ describe('getSourceCodeFragmentOfDeclaration', () => {
export default myIdentifier; export default myIdentifier;
`, `,
}; };
mock(fakeFs); mockProject(fakeFs);
const { sourceFragment } = await getSourceCodeFragmentOfDeclaration({ const { sourceFragment } = await getSourceCodeFragmentOfDeclaration({
filePath: '/my/proj/exports/file.js', filePath: '/my/proj/exports/file.js',
@ -180,7 +184,7 @@ describe('getSourceCodeFragmentOfDeclaration', () => {
export default class AjaxClass extends LionAjaxClass {} export default class AjaxClass extends LionAjaxClass {}
`, `,
}; };
mock(fakeFs); mockProject(fakeFs);
const { sourceFragment } = await getSourceCodeFragmentOfDeclaration({ const { sourceFragment } = await getSourceCodeFragmentOfDeclaration({
filePath: '/my/proj/exports/ajax.js', filePath: '/my/proj/exports/ajax.js',
@ -196,7 +200,7 @@ describe('getSourceCodeFragmentOfDeclaration', () => {
export default function myFn() {} export default function myFn() {}
`, `,
}; };
mock(fakeFs); mockProject(fakeFs);
const { sourceFragment } = await getSourceCodeFragmentOfDeclaration({ const { sourceFragment } = await getSourceCodeFragmentOfDeclaration({
filePath: '/my/proj/exports/myFn.js', filePath: '/my/proj/exports/myFn.js',

View file

@ -2,7 +2,7 @@ import { expect } from 'chai';
import { it } from 'mocha'; import { it } from 'mocha';
// @ts-ignore // @ts-ignore
import babelTraversePkg from '@babel/traverse'; import babelTraversePkg from '@babel/traverse';
import { swcTraverse } from '../../../src/program/utils/swc-traverse.js'; import { oxcTraverse } from '../../../src/program/utils/oxc-traverse.js';
import { AstService } from '../../../src/program/core/AstService.js'; import { AstService } from '../../../src/program/core/AstService.js';
/** /**
@ -12,12 +12,12 @@ import { AstService } from '../../../src/program/core/AstService.js';
*/ */
/** /**
* @param {SwcAstModule} swcAst * @param {SwcAstModule} oxcAst
*/ */
function gatherAllScopes(swcAst) { function gatherAllScopes(oxcAst) {
/** @type {SwcScope[]} */ /** @type {SwcScope[]} */
const swcScopes = []; const swcScopes = [];
swcTraverse(swcAst, { oxcTraverse(oxcAst, {
enter({ scope }) { enter({ scope }) {
if (!swcScopes.includes(scope)) { if (!swcScopes.includes(scope)) {
swcScopes.push(scope); swcScopes.push(scope);
@ -27,11 +27,18 @@ function gatherAllScopes(swcAst) {
return swcScopes; return swcScopes;
} }
describe('swcTraverse', () => { /**
* @param {*} node
*/
function nameOf(node) {
return node.value || node.name;
}
describe('oxcTraverse', () => {
describe('Visitor', () => { describe('Visitor', () => {
it('traverses an swc AST based on <Node.type> visitor', async () => { it('traverses an swc AST based on <Node.type> visitor', async () => {
const code = `import x from 'y';`; const code = `import x from 'y';`;
const swcAst = await AstService._getSwcAst(code); const oxcAst = await AstService._getOxcAst(code);
let foundImportDeclarationPath; let foundImportDeclarationPath;
const visitor = { const visitor = {
@ -39,14 +46,14 @@ describe('swcTraverse', () => {
foundImportDeclarationPath = path; foundImportDeclarationPath = path;
}, },
}; };
swcTraverse(swcAst, visitor); oxcTraverse(oxcAst, visitor);
expect(foundImportDeclarationPath).to.not.be.undefined; expect(foundImportDeclarationPath).to.not.be.undefined;
}); });
it('supports "enter" as a generic arrival handler', async () => { it('supports "enter" as a generic arrival handler', async () => {
const code = `import x from 'y';`; const code = `import x from 'y';`;
const swcAst = await AstService._getSwcAst(code); const oxcAst = await AstService._getOxcAst(code);
/** @type {string[]} */ /** @type {string[]} */
const foundTypes = []; const foundTypes = [];
@ -58,10 +65,11 @@ describe('swcTraverse', () => {
foundTypes.push(path.node.type); foundTypes.push(path.node.type);
}, },
}; };
swcTraverse(swcAst, visitor); oxcTraverse(oxcAst, visitor);
expect(foundTypes).to.deep.equal([ expect(foundTypes).to.deep.equal([
'Module', // 'Module',
'Program',
'ImportDeclaration', 'ImportDeclaration',
'ImportDefaultSpecifier', 'ImportDefaultSpecifier',
'Identifier', 'Identifier',
@ -71,7 +79,7 @@ describe('swcTraverse', () => {
it('supports "enter" and "exit" as generic handlers inside <Node.type> handlers', async () => { it('supports "enter" and "exit" as generic handlers inside <Node.type> handlers', async () => {
const code = `import x from 'y';`; const code = `import x from 'y';`;
const swcAst = await AstService._getSwcAst(code); const oxcAst = await AstService._getOxcAst(code);
/** @type {string[]} */ /** @type {string[]} */
const visitedPaths = []; const visitedPaths = [];
@ -88,7 +96,7 @@ describe('swcTraverse', () => {
}, },
}, },
}; };
swcTraverse(swcAst, visitor); oxcTraverse(oxcAst, visitor);
expect(visitedPaths[0].path).to.equal(visitedPaths[1].path); expect(visitedPaths[0].path).to.equal(visitedPaths[1].path);
expect(visitedPaths[0].phase).to.equal('enter'); expect(visitedPaths[0].phase).to.equal('enter');
@ -97,7 +105,7 @@ describe('swcTraverse', () => {
it('supports "root" as alternative for Program', async () => { it('supports "root" as alternative for Program', async () => {
const code = `import x from 'y';`; const code = `import x from 'y';`;
const swcAst = await AstService._getSwcAst(code); const oxcAst = await AstService._getOxcAst(code);
let rootPath; let rootPath;
const visitor = { const visitor = {
@ -108,17 +116,17 @@ describe('swcTraverse', () => {
rootPath = path; rootPath = path;
}, },
}; };
swcTraverse(swcAst, visitor); oxcTraverse(oxcAst, visitor);
// TODO: also add case for Script // TODO: also add case for Script
expect(rootPath.node.type).to.equal('Module'); expect(rootPath.node.type).to.equal('Program');
}); });
it('does not fail on object prototype built-ins (like "toString")', async () => { it('does not fail on object prototype built-ins (like "toString")', async () => {
const code = `const { toString } = x;`; const code = `const { toString } = x;`;
const swcAst = await AstService._getSwcAst(code); const oxcAst = await AstService._getOxcAst(code);
expect(swcTraverse(swcAst, {})).to.not.throw; expect(oxcTraverse(oxcAst, {})).to.not.throw;
}); });
}); });
@ -147,7 +155,7 @@ describe('swcTraverse', () => {
} }
const alsoGlobalScope = 3; const alsoGlobalScope = 3;
`; `;
const swcAst = await AstService._getSwcAst(code); const oxcAst = await AstService._getOxcAst(code);
/** @type {SwcPath[]} */ /** @type {SwcPath[]} */
const declaratorPaths = []; const declaratorPaths = [];
@ -159,13 +167,13 @@ describe('swcTraverse', () => {
declaratorPaths.push(path); declaratorPaths.push(path);
}, },
}; };
swcTraverse(swcAst, visitor, { needsAdvancedPaths: true }); oxcTraverse(oxcAst, visitor, { needsAdvancedPaths: true });
expect(declaratorPaths[0].scope.id).to.equal(0); expect(declaratorPaths[0].scope.id).to.equal(0);
expect(declaratorPaths[1].scope.id).to.equal(1); expect(declaratorPaths[1].scope.id).to.equal(1);
expect(declaratorPaths[2].scope.id).to.equal(2); expect(declaratorPaths[2].scope.id).to.equal(2);
expect(declaratorPaths[0].node.id.value).to.equal('globalScope'); expect(nameOf(declaratorPaths[0].node.id)).to.equal('globalScope');
expect(Object.keys(declaratorPaths[0].scope.bindings)).to.deep.equal([ expect(Object.keys(declaratorPaths[0].scope.bindings)).to.deep.equal([
'globalScope', 'globalScope',
'alsoGlobalScope', 'alsoGlobalScope',
@ -173,10 +181,10 @@ describe('swcTraverse', () => {
// 0 and 3 are the same scope // 0 and 3 are the same scope
expect(declaratorPaths[0].scope).to.equal(declaratorPaths[3].scope); expect(declaratorPaths[0].scope).to.equal(declaratorPaths[3].scope);
// Scope bindings refer to Declarator nodes // Scope bindings refer to Declarator nodes
expect(declaratorPaths[0].scope.bindings.globalScope.identifier).to.equal( expect(declaratorPaths[0].scope.bindings.globalScope.path.node).to.equal(
declaratorPaths[0].node, declaratorPaths[0].node,
); );
expect(declaratorPaths[0].scope.bindings.alsoGlobalScope.identifier).to.equal( expect(declaratorPaths[0].scope.bindings.alsoGlobalScope.path.node).to.equal(
declaratorPaths[3].node, declaratorPaths[3].node,
); );
@ -194,7 +202,7 @@ describe('swcTraverse', () => {
} }
} }
`; `;
const swcAst = await AstService._getSwcAst(code); const oxcAst = await AstService._getOxcAst(code);
/** @type {SwcPath[]} */ /** @type {SwcPath[]} */
const declaratorPaths = []; const declaratorPaths = [];
@ -203,8 +211,8 @@ describe('swcTraverse', () => {
declaratorPaths.push(path); declaratorPaths.push(path);
}, },
}; };
swcTraverse(swcAst, visitor, { needsAdvancedPaths: true }); oxcTraverse(oxcAst, visitor, { needsAdvancedPaths: true });
const scopes = gatherAllScopes(swcAst); const scopes = gatherAllScopes(oxcAst);
expect(scopes[1].path?.node).to.equal(declaratorPaths[0].node); expect(scopes[1].path?.node).to.equal(declaratorPaths[0].node);
expect(scopes[2].path?.node).to.equal(declaratorPaths[1].node); expect(scopes[2].path?.node).to.equal(declaratorPaths[1].node);
@ -223,7 +231,7 @@ describe('swcTraverse', () => {
} }
} }
`; `;
const swcAst = await AstService._getSwcAst(code); const oxcAst = await AstService._getOxcAst(code);
/** @type {SwcPath[]} */ /** @type {SwcPath[]} */
const declaratorPaths = []; const declaratorPaths = [];
@ -232,7 +240,7 @@ describe('swcTraverse', () => {
declaratorPaths.push(path); declaratorPaths.push(path);
}, },
}; };
swcTraverse(swcAst, visitor, { needsAdvancedPaths: true }); oxcTraverse(oxcAst, visitor, { needsAdvancedPaths: true });
expect(declaratorPaths[0].scope.id).to.equal(2); expect(declaratorPaths[0].scope.id).to.equal(2);
}); });
@ -246,7 +254,7 @@ describe('swcTraverse', () => {
break; break;
default: default:
}`; }`;
const swcAst = await AstService._getSwcAst(code); const oxcAst = await AstService._getOxcAst(code);
/** @type {SwcPath[]} */ /** @type {SwcPath[]} */
const declaratorPaths = []; const declaratorPaths = [];
@ -255,10 +263,10 @@ describe('swcTraverse', () => {
declaratorPaths.push(path); declaratorPaths.push(path);
}, },
}; };
swcTraverse(swcAst, visitor, { needsAdvancedPaths: true }); oxcTraverse(oxcAst, visitor, { needsAdvancedPaths: true });
expect(declaratorPaths[0].node.id.value).to.equal('myCases'); expect(nameOf(declaratorPaths[0].node.id)).to.equal('myCases');
expect(declaratorPaths[1].node.id.value).to.equal('x'); expect(nameOf(declaratorPaths[1].node.id)).to.equal('x');
expect(declaratorPaths[0].scope.id).to.equal(0); expect(declaratorPaths[0].scope.id).to.equal(0);
expect(declaratorPaths[1].scope.id).to.equal(1); expect(declaratorPaths[1].scope.id).to.equal(1);
}); });
@ -269,18 +277,19 @@ describe('swcTraverse', () => {
toString(dateObj, opt = {}) {}, toString(dateObj, opt = {}) {},
}; };
`; `;
const swcAst = await AstService._getSwcAst(code); const oxcAst = await AstService._getOxcAst(code);
/** @type {SwcPath[]} */ /** @type {SwcPath[]} */
const results = []; const results = [];
const visitor = { const visitor = {
MethodProperty(/** @type {any} */ path) { // MethodProperty for swc...
ObjectProperty(/** @type {any} */ path) {
results.push(path); results.push(path);
}, },
}; };
swcTraverse(swcAst, visitor, { needsAdvancedPaths: true }); oxcTraverse(oxcAst, visitor, { needsAdvancedPaths: true });
expect(results[0].node.key.value).to.equal('toString'); expect(nameOf(results[0].node.key)).to.equal('toString');
expect(results[0].scope.id).to.equal(0); expect(results[0].scope.id).to.equal(0);
}); });
@ -292,7 +301,7 @@ describe('swcTraverse', () => {
}, },
}; };
`; `;
const swcAst = await AstService._getSwcAst(code); const oxcAst = await AstService._getOxcAst(code);
/** @type {SwcPath[]} */ /** @type {SwcPath[]} */
const declaratorPaths = []; const declaratorPaths = [];
@ -301,10 +310,10 @@ describe('swcTraverse', () => {
declaratorPaths.push(path); declaratorPaths.push(path);
}, },
}; };
swcTraverse(swcAst, visitor, { needsAdvancedPaths: true }); oxcTraverse(oxcAst, visitor, { needsAdvancedPaths: true });
expect(declaratorPaths[0].node.id.value).to.equal('x'); expect(nameOf(declaratorPaths[0].node.id)).to.equal('x');
expect(declaratorPaths[1].node.id.value).to.equal('z'); expect(nameOf(declaratorPaths[1].node.id)).to.equal('z');
expect(declaratorPaths[0].scope.id).to.equal(0); expect(declaratorPaths[0].scope.id).to.equal(0);
expect(declaratorPaths[1].scope.id).to.equal(1); expect(declaratorPaths[1].scope.id).to.equal(1);
}); });
@ -322,7 +331,7 @@ describe('swcTraverse', () => {
} }
let alsoGlobalScope = 3; let alsoGlobalScope = 3;
`; `;
const swcAst = await AstService._getSwcAst(code); const oxcAst = await AstService._getOxcAst(code);
/** @type {SwcPath[]} */ /** @type {SwcPath[]} */
const declaratorPaths = []; const declaratorPaths = [];
@ -334,17 +343,17 @@ describe('swcTraverse', () => {
declaratorPaths.push(path); declaratorPaths.push(path);
}, },
}; };
swcTraverse(swcAst, visitor, { needsAdvancedPaths: true }); oxcTraverse(oxcAst, visitor, { needsAdvancedPaths: true });
expect(Object.keys(declaratorPaths[0].scope.bindings)).to.deep.equal([ expect(Object.keys(declaratorPaths[0].scope.bindings)).to.deep.equal([
'globalScope', 'globalScope',
'alsoGlobalScope', 'alsoGlobalScope',
]); ]);
// Scope bindings refer to Declarator nodes // Scope bindings refer to Declarator nodes
expect(declaratorPaths[0].scope.bindings.globalScope.identifier).to.equal( expect(declaratorPaths[0].scope.bindings.globalScope.path.node).to.equal(
declaratorPaths[0].node, declaratorPaths[0].node,
); );
expect(declaratorPaths[0].scope.bindings.alsoGlobalScope.identifier).to.equal( expect(declaratorPaths[0].scope.bindings.alsoGlobalScope.path.node).to.equal(
declaratorPaths[3].node, declaratorPaths[3].node,
); );
}); });
@ -359,7 +368,7 @@ describe('swcTraverse', () => {
} }
} }
`; `;
const swcAst = await AstService._getSwcAst(code); const oxcAst = await AstService._getOxcAst(code);
/** @type {SwcPath[]} */ /** @type {SwcPath[]} */
const declaratorPaths = []; const declaratorPaths = [];
@ -368,7 +377,7 @@ describe('swcTraverse', () => {
declaratorPaths.push(path); declaratorPaths.push(path);
}, },
}; };
swcTraverse(swcAst, visitor, { needsAdvancedPaths: true }); oxcTraverse(oxcAst, visitor, { needsAdvancedPaths: true });
expect(Object.keys(declaratorPaths[0].scope.bindings)).to.deep.equal([ expect(Object.keys(declaratorPaths[0].scope.bindings)).to.deep.equal([
'globalScope', 'globalScope',
@ -389,7 +398,7 @@ describe('swcTraverse', () => {
* @param {string} code * @param {string} code
*/ */
async function compareScopeResultsWithBabel(code) { async function compareScopeResultsWithBabel(code) {
const swcAst = await AstService._getSwcAst(code); const oxcAst = await AstService._getOxcAst(code);
const babelAst = await AstService._getBabelAst(code); const babelAst = await AstService._getBabelAst(code);
/** /**
@ -407,7 +416,7 @@ describe('swcTraverse', () => {
/** @type {SwcScope[]} */ /** @type {SwcScope[]} */
const swcScopes = []; const swcScopes = [];
swcTraverse(swcAst, { oxcTraverse(oxcAst, {
enter({ scope }) { enter({ scope }) {
if (!swcScopes.includes(scope)) { if (!swcScopes.includes(scope)) {
swcScopes.push(scope); swcScopes.push(scope);
@ -420,9 +429,17 @@ describe('swcTraverse', () => {
expect(babelScopes.length).to.equal(swcScopes.length); expect(babelScopes.length).to.equal(swcScopes.length);
for (let i = 0; i < babelScopes.length; i += 1) { for (let i = 0; i < babelScopes.length; i += 1) {
expect(babelScopes[i].uid - babelRootScopeIdOffset).to.equal(swcScopes[i].id); expect(babelScopes[i].uid - babelRootScopeIdOffset).to.equal(swcScopes[i].id);
expect(Object.keys(babelScopes[i].bindings)).to.deep.equal(
Object.keys(swcScopes[i].bindings), const babelEntries = Object.entries(babelScopes[i].bindings);
); const swcEntries = Object.entries(swcScopes[i].bindings);
for (const [j, [bindingKey, binding]] of babelEntries.entries()) {
expect(bindingKey).to.equal(swcEntries[j][0]);
expect(binding.path.node.type).to.equal(swcEntries[j][1]?.path?.node.type);
}
// expect(Object.keys(babelScopes[i].bindings)).to.deep.equal(
// Object.keys(swcScopes[i].bindings),
// );
// expect(babelScopes[i].references).to.deep.equal(swcResults[i].references); // expect(babelScopes[i].references).to.deep.equal(swcResults[i].references);
} }
} }
@ -459,5 +476,15 @@ describe('swcTraverse', () => {
await compareScopeResultsWithBabel(code); await compareScopeResultsWithBabel(code);
}); });
it('handles all kinds of lexical scopes and bindings in a similar way 2', async () => {
const code = `
import { LionComp } from 'ref/LionComp.js';
export class WolfComp extends LionComp {}
`;
await compareScopeResultsWithBabel(code);
});
}); });
}); });

View file

@ -1,14 +1,14 @@
import { expect } from 'chai'; import { expect } from 'chai';
import { it } from 'mocha'; import { it } from 'mocha';
import { setupAnalyzerTest } from '../../../../test-helpers/setup-analyzer-test.js'; import { setupAnalyzerTest } from '../../../test-helpers/setup-analyzer-test.js';
import { mockProject } from '../../../../test-helpers/mock-project-helpers.js'; import { mockProject } from '../../../test-helpers/mock-project-helpers.js';
import { swcTraverse } from '../../../../src/program/utils/swc-traverse.js'; import { oxcTraverse } from '../../../src/program/utils/oxc-traverse.js';
import { AstService } from '../../../../src/program/core/AstService.js'; import { AstService } from '../../../src/program/core/AstService.js';
import { import {
trackDownIdentifier, trackDownIdentifier,
trackDownIdentifierFromScope, trackDownIdentifierFromScope,
} from '../../../../src/program/utils/track-down-identifier.js'; } from '../../../src/program/utils/track-down-identifier.js';
/** /**
* @typedef {import('@babel/traverse').NodePath} NodePath * @typedef {import('@babel/traverse').NodePath} NodePath
@ -295,7 +295,7 @@ describe('trackDownIdentifierFromScope', () => {
mockProject(projectFiles, { projectName: 'my-project', projectPath: '/my/project' }); mockProject(projectFiles, { projectName: 'my-project', projectPath: '/my/project' });
// const ast = AstService._getBabelAst(projectFiles['./src/declarationOfMyClass.js']); // const ast = AstService._getBabelAst(projectFiles['./src/declarationOfMyClass.js']);
const ast = AstService._getSwcAst(projectFiles['./src/declarationOfMyClass.js']); const ast = await AstService._getOxcAst(projectFiles['./src/declarationOfMyClass.js']);
// Let's say we want to track down 'MyClass' in the code above // Let's say we want to track down 'MyClass' in the code above
const identifierNameInScope = 'MyClass'; const identifierNameInScope = 'MyClass';
@ -309,7 +309,7 @@ describe('trackDownIdentifierFromScope', () => {
// astPath = path; // astPath = path;
// }, // },
// }); // });
swcTraverse(ast, { oxcTraverse(ast, {
ClassDeclaration(path) { ClassDeclaration(path) {
astPath = path; astPath = path;
}, },
@ -346,7 +346,7 @@ describe('trackDownIdentifierFromScope', () => {
mockProject(projectFiles, { projectName: 'my-project', projectPath: '/my/project' }); mockProject(projectFiles, { projectName: 'my-project', projectPath: '/my/project' });
// const ast = AstService._getBabelAst(projectFiles['./imported.js']); // const ast = AstService._getBabelAst(projectFiles['./imported.js']);
const ast = AstService._getSwcAst(projectFiles['./imported.js']); const ast = await AstService._getOxcAst(projectFiles['./imported.js']);
// Let's say we want to track down 'MyClass' in the code above // Let's say we want to track down 'MyClass' in the code above
const identifierNameInScope = 'MyClass'; const identifierNameInScope = 'MyClass';
@ -360,7 +360,7 @@ describe('trackDownIdentifierFromScope', () => {
// astPath = path; // astPath = path;
// }, // },
// }); // });
swcTraverse(ast, { oxcTraverse(ast, {
ImportDeclaration(path) { ImportDeclaration(path) {
astPath = path; astPath = path;
}, },
@ -394,7 +394,7 @@ describe('trackDownIdentifierFromScope', () => {
mockProject(projectFiles, { projectName: 'my-project', projectPath: '/my/project' }); mockProject(projectFiles, { projectName: 'my-project', projectPath: '/my/project' });
// const ast = AstService._getBabelAst(projectFiles['./imported.js']); // const ast = AstService._getBabelAst(projectFiles['./imported.js']);
const ast = AstService._getSwcAst(projectFiles['./imported.js']); const ast = await AstService._getOxcAst(projectFiles['./imported.js']);
// Let's say we want to track down 'MyClass' in the code above // Let's say we want to track down 'MyClass' in the code above
const identifierNameInScope = 'El1'; const identifierNameInScope = 'El1';
@ -408,7 +408,7 @@ describe('trackDownIdentifierFromScope', () => {
// astPath = path; // astPath = path;
// }, // },
// }); // });
swcTraverse(ast, { oxcTraverse(ast, {
ClassDeclaration(path) { ClassDeclaration(path) {
astPath = path; astPath = path;
}, },
@ -426,4 +426,54 @@ describe('trackDownIdentifierFromScope', () => {
specifier: 'El1', specifier: 'El1',
}); });
}); });
it(`handles edge cases`, async () => {
const projectName = 'my-project';
const projectPath = '/my/project';
const targetProject = {
path: projectPath,
name: projectName,
files: [
{
file: './WolfComp.js',
code: `
import { LionComp } from 'ref/LionComp.js';
export class WolfComp extends LionComp {}
`,
},
{
file: './node_modules/ref/LionComp.js',
code: `export class LionComp extends HTMLElement {};`,
},
],
};
mockProject(targetProject, { projectName, projectPath });
const ast = await AstService._getOxcAst(targetProject.files[0].code);
// Let's say we want to track down 'LionComp' in the code above
const identifierNameInScope = 'LionComp';
const fullCurrentFilePath = '/my/project/WolfComp.js';
/** @type {NodePath} */
let astPath;
oxcTraverse(ast, {
ClassDeclaration(path) {
astPath = path;
},
});
const rootFile = await trackDownIdentifierFromScope(
// @ts-ignore
astPath,
identifierNameInScope,
fullCurrentFilePath,
projectPath,
);
expect(rootFile).to.deep.equal({ file: 'ref/LionComp.js', specifier: 'LionComp' });
});
}); });

View file

@ -16,7 +16,7 @@ import {
*/ */
export type AnalyzerName = `${'find' | 'match'}-${string}` | ''; export type AnalyzerName = `${'find' | 'match'}-${string}` | '';
export type AnalyzerAst = 'babel' | 'swc-to-babel' | 'swc'; export type AnalyzerAst = 'babel' | 'swc' | 'oxc';
// TODO: make sure that data structures of JSON output (generated in ReportService) // TODO: make sure that data structures of JSON output (generated in ReportService)
// and data structure generated in Analyzer.prototype._finalize match exactly (move logic from ReportSerivce to _finalize) // and data structure generated in Analyzer.prototype._finalize match exactly (move logic from ReportSerivce to _finalize)

View file

@ -1,8 +1,11 @@
import { IdentifierName } from "../index.js";
export type SwcScope = { export type SwcScope = {
id: number; id: number;
parentScope?: Scope; parentScope?: Scope;
bindings: { [key: string]: SwcBinding }; bindings: { [key: string]: SwcBinding };
path: SwcPath | null; path: SwcPath | null;
getBinding: (IdentifierName:string) => SwcBinding;
_pendingRefsWithoutBinding: SwcNode[]; _pendingRefsWithoutBinding: SwcNode[];
_isIsolatedBlockStatement: boolean; _isIsolatedBlockStatement: boolean;
}; };
@ -19,7 +22,7 @@ export type SwcPath = {
node: SwcNode; node: SwcNode;
parent: SwcNode; parent: SwcNode;
stop: function; stop: function;
scope: SwcScope | undefined; scope: SwcScope;
parentPath: SwcPath | null | undefined; parentPath: SwcPath | null | undefined;
get: (id: string) => SwcPath | undefined; get: (id: string) => SwcPath | undefined;
type: string; type: string;

View file

@ -3,7 +3,7 @@
"target": "ESNext", "target": "ESNext",
"module": "ESNext", "module": "ESNext",
"moduleResolution": "NodeNext", "moduleResolution": "NodeNext",
"lib": ["es2017", "dom"], "lib": ["es2022", "dom"],
"allowJs": true, "allowJs": true,
"checkJs": true, "checkJs": true,
"strict": true, "strict": true,