Skip to content

Commit fd2e799

Browse files
committed
feat(providence): allow to run with swc; make all analyzers compatible with swc
1 parent 22b8f24 commit fd2e799

33 files changed

+2577
-3320
lines changed

.changeset/smart-hairs-sneeze.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'providence-analytics': patch
3+
---
4+
5+
allow to run with swc; make all analyzers compatible with swc

package-lock.json

Lines changed: 18 additions & 17 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages-node/providence-analytics/package.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -41,18 +41,18 @@
4141
"commander": "^2.20.3",
4242
"oxc-parser": "0.48.2",
4343
"parse5": "^7.2.1",
44-
"semver": "^7.7.0"
44+
"semver": "^7.7.1"
4545
},
4646
"peerDependencies": {
4747
"@babel/parser": "^7.25.8",
4848
"@babel/plugin-syntax-import-assertions": "^7.25.7",
49-
"@swc/core": "^1.7.36"
49+
"@swc/core": "^1.10.14"
5050
},
5151
"devDependencies": {
5252
"@babel/parser": "^7.26.7",
5353
"@babel/plugin-syntax-import-assertions": "^7.26.0",
5454
"@babel/traverse": "^7.26.7",
55-
"@swc/core": "^1.10.12",
55+
"@swc/core": "^1.10.14",
5656
"@types/inquirer": "^9.0.7",
5757
"@types/mocha": "^10.0.10",
5858
"@web/dev-server": "^0.4.6",

packages-node/providence-analytics/src/program/analyzers/find-classes.js

Lines changed: 66 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,21 @@
11
/* eslint-disable no-shadow, no-param-reassign */
22
import path from 'path';
33

4-
import { oxcTraverse, isProperty } from '../utils/oxc-traverse.js';
5-
64
import { trackDownIdentifierFromScope } from '../utils/track-down-identifier.js';
5+
import {
6+
expressionOf,
7+
isProperty,
8+
isSetter,
9+
isGetter,
10+
isStatic,
11+
nameOf,
12+
idOf,
13+
} from '../utils/ast-normalizations.js';
14+
import { oxcTraverse } from '../utils/oxc-traverse.js';
715
import { Analyzer } from '../core/Analyzer.js';
816

17+
import { isCustomElementsGet } from './find-customelements.js';
18+
919
/**
1020
* @typedef {import('@babel/types').File} File
1121
* @typedef {import('@babel/types').ClassMethod} ClassMethod
@@ -16,22 +26,27 @@ import { Analyzer } from '../core/Analyzer.js';
1626
* @typedef {import('../../../types/index.js').FindClassesAnalyzerEntry} FindClassesAnalyzerEntry
1727
* @typedef {import('../../../types/index.js').FindClassesConfig} FindClassesConfig
1828
* @typedef {import('../../../types/index.js').AnalyzerAst} AnalyzerAst
29+
* @typedef {import("@swc/core").Node} SwcNode
1930
*/
2031

2132
/**
2233
* Finds import specifiers and sources
23-
* @param {File} babelAst
34+
* @param {File} oxcAst
2435
* @param {string} fullCurrentFilePath the file being currently processed
2536
*/
26-
async function findMembersPerAstEntry(babelAst, fullCurrentFilePath, projectPath) {
37+
async function findMembersPerAstEntry(oxcAst, fullCurrentFilePath, projectPath) {
2738
// The transformed entry
2839
const classesFound = [];
2940
/**
3041
* Detects private/publicness based on underscores. Checks '$' as well
3142
* @param {string} name
32-
* @returns {'public'|'protected'|'private'}
43+
* @returns {'public'|'protected'|'private'|'[n/a]'}
3344
*/
3445
function computeAccessType(name) {
46+
if (name === 'constructor') {
47+
return '[n/a]';
48+
}
49+
3550
if (name.startsWith('_') || name.startsWith('$')) {
3651
// (at least) 2 prefixes
3752
if (name.startsWith('__') || name.startsWith('$$')) {
@@ -47,7 +62,7 @@ async function findMembersPerAstEntry(babelAst, fullCurrentFilePath, projectPath
4762
* @returns
4863
*/
4964
function isStaticProperties({ node }) {
50-
return node.static && node.kind === 'get' && node.key.name === 'properties';
65+
return isStatic(node) && isGetter(node) && nameOf(node.key) === 'properties';
5166
}
5267

5368
// function isBlacklisted({ node }) {
@@ -86,26 +101,47 @@ async function findMembersPerAstEntry(babelAst, fullCurrentFilePath, projectPath
86101
// }
87102

88103
/**
89-
*
104+
* @param {SwcNode|OxcNode} node
105+
*/
106+
function isSuperClassAMixin(superClassNode) {
107+
if (!superClassNode) return false;
108+
109+
const isCallExpression = superClassNode?.type === 'CallExpression';
110+
if (!isCallExpression) return false;
111+
return !isCustomElementsGet(superClassNode.callee);
112+
}
113+
114+
/**
90115
* @param {NodePath} astPath
91116
* @param {{isMixin?:boolean}} opts
92117
*/
93118
async function traverseClass(astPath, { isMixin = false } = {}) {
94119
const classRes = {};
95-
classRes.name = astPath.node.id && astPath.node.id.name;
120+
classRes.name = (idOf(astPath.node) && nameOf(idOf(astPath.node))) || null;
96121
classRes.isMixin = Boolean(isMixin);
122+
97123
if (astPath.node.superClass) {
98124
const superClasses = [];
99125

100126
// Add all Identifier names
101127
let parent = astPath.node.superClass;
102-
while (parent.type === 'CallExpression') {
103-
superClasses.push({ name: parent.callee.name, isMixin: true });
128+
while (isSuperClassAMixin(parent)) {
129+
superClasses.push({ name: nameOf(parent.callee), isMixin: true });
104130
// As long as we are a CallExpression, we will have a parent
105-
[parent] = parent.arguments;
131+
[parent] = parent.arguments.map(expressionOf);
132+
}
133+
134+
// At the end of the chain, we find type === Identifier or customElements.get directly.
135+
if (isCustomElementsGet(parent.callee)) {
136+
superClasses.push({
137+
name: null,
138+
customElementsGetRef: nameOf(parent.arguments?.map(expressionOf)[0]),
139+
isMixin: false,
140+
});
141+
} else {
142+
// an identifier like 'MyClass'
143+
superClasses.push({ name: nameOf(expressionOf(parent)), isMixin: false });
106144
}
107-
// At the end of the chain, we find type === Identifier
108-
superClasses.push({ name: parent.name, isMixin: false });
109145

110146
// For all found superclasses, track down their root location.
111147
// This will either result in a local, relative astPath in the project,
@@ -160,19 +196,27 @@ async function findMembersPerAstEntry(babelAst, fullCurrentFilePath, projectPath
160196
}
161197

162198
const methodRes = {};
163-
const { name } = astPath.node.key;
199+
const name = nameOf(astPath.node.key);
164200
methodRes.name = name;
165201
methodRes.accessType = computeAccessType(name);
202+
if (
203+
['constructor', 'connectedCallback', 'disconnectedCallback', 'adoptedCallback'].includes(
204+
name,
205+
)
206+
) {
207+
methodRes.isPartOfPlatformLifeCycle = true;
208+
}
166209

167-
if (astPath.node.kind === 'set' || astPath.node.kind === 'get') {
168-
if (astPath.node.static) {
210+
if (isSetter(astPath.node) || isGetter(astPath.node)) {
211+
const setOrGet = isSetter(astPath.node) ? 'set' : 'get';
212+
if (isStatic(astPath.node)) {
169213
methodRes.static = true;
170214
}
171-
methodRes.kind = [...(methodRes.kind || []), astPath.node.kind];
215+
methodRes.kind = [...(methodRes.kind || []), setOrGet];
172216
// Merge getter/setters into one
173-
const found = classRes.members.props.find(p => p.name === name);
217+
const found = classRes.members.props.find(p => nameOf(p) === name);
174218
if (found) {
175-
found.kind = [...(found.kind || []), astPath.node.kind];
219+
found.kind = [...(found.kind || []), setOrGet];
176220
} else {
177221
classRes.members.props.push(methodRes);
178222
}
@@ -184,14 +228,16 @@ async function findMembersPerAstEntry(babelAst, fullCurrentFilePath, projectPath
184228
astPath.traverse({
185229
ClassMethod: handleMethodDefinitionOrClassMethod,
186230
MethodDefinition: handleMethodDefinitionOrClassMethod,
231+
// for swc
232+
Constructor: handleMethodDefinitionOrClassMethod,
187233
});
188234

189235
classesFound.push(classRes);
190236
}
191237

192238
const classesToTraverse = [];
193239

194-
oxcTraverse(babelAst, {
240+
oxcTraverse(oxcAst, {
195241
ClassDeclaration(astPath) {
196242
classesToTraverse.push({ astPath, isMixin: false });
197243
},

0 commit comments

Comments
 (0)