Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(providence): allow to run with swc; make all analyzers compatible with swc #2472

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .changeset/smart-hairs-sneeze.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'providence-analytics': patch
---

- allow to provide `parser` to providence config (choose between oxc and swc)
- make all analyzers compatible with both oxc and swc
- make oxcTraverse fully compatible with both oxc and swc
35 changes: 18 additions & 17 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 3 additions & 3 deletions packages-node/providence-analytics/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,18 +41,18 @@
"commander": "^2.20.3",
"oxc-parser": "0.48.2",
"parse5": "^7.2.1",
"semver": "^7.7.0"
"semver": "^7.7.1"
},
"peerDependencies": {
"@babel/parser": "^7.25.8",
"@babel/plugin-syntax-import-assertions": "^7.25.7",
"@swc/core": "^1.7.36"
"@swc/core": "^1.10.14"
},
"devDependencies": {
"@babel/parser": "^7.26.7",
"@babel/plugin-syntax-import-assertions": "^7.26.0",
"@babel/traverse": "^7.26.7",
"@swc/core": "^1.10.12",
"@swc/core": "^1.10.14",
"@types/inquirer": "^9.0.7",
"@types/mocha": "^10.0.10",
"@web/dev-server": "^0.4.6",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,21 @@
/* eslint-disable no-shadow, no-param-reassign */
import path from 'path';

import { oxcTraverse, isProperty } from '../utils/oxc-traverse.js';

import { trackDownIdentifierFromScope } from '../utils/track-down-identifier.js';
import {
expressionOf,
isProperty,
isSetter,
isGetter,
isStatic,
nameOf,
idOf,
} from '../utils/ast-normalizations.js';
import { oxcTraverse } from '../utils/oxc-traverse.js';
import { Analyzer } from '../core/Analyzer.js';

import { isCustomElementsGet } from './find-customelements.js';

/**
* @typedef {import('@babel/types').File} File
* @typedef {import('@babel/types').ClassMethod} ClassMethod
Expand All @@ -16,22 +26,27 @@ import { Analyzer } from '../core/Analyzer.js';
* @typedef {import('../../../types/index.js').FindClassesAnalyzerEntry} FindClassesAnalyzerEntry
* @typedef {import('../../../types/index.js').FindClassesConfig} FindClassesConfig
* @typedef {import('../../../types/index.js').AnalyzerAst} AnalyzerAst
* @typedef {import("@swc/core").Node} SwcNode
*/

/**
* Finds import specifiers and sources
* @param {File} babelAst
* @param {File} oxcAst
* @param {string} fullCurrentFilePath the file being currently processed
*/
async function findMembersPerAstEntry(babelAst, fullCurrentFilePath, projectPath) {
async function findMembersPerAstEntry(oxcAst, fullCurrentFilePath, projectPath) {
// The transformed entry
const classesFound = [];
/**
* Detects private/publicness based on underscores. Checks '$' as well
* @param {string} name
* @returns {'public'|'protected'|'private'}
* @returns {'public'|'protected'|'private'|'[n/a]'}
*/
function computeAccessType(name) {
if (name === 'constructor') {
return '[n/a]';
}

if (name.startsWith('_') || name.startsWith('$')) {
// (at least) 2 prefixes
if (name.startsWith('__') || name.startsWith('$$')) {
Expand All @@ -47,7 +62,7 @@ async function findMembersPerAstEntry(babelAst, fullCurrentFilePath, projectPath
* @returns
*/
function isStaticProperties({ node }) {
return node.static && node.kind === 'get' && node.key.name === 'properties';
return isStatic(node) && isGetter(node) && nameOf(node.key) === 'properties';
}

// function isBlacklisted({ node }) {
Expand Down Expand Up @@ -86,26 +101,47 @@ async function findMembersPerAstEntry(babelAst, fullCurrentFilePath, projectPath
// }

/**
*
* @param {SwcNode|OxcNode} node
*/
function isSuperClassAMixin(superClassNode) {
if (!superClassNode) return false;

const isCallExpression = superClassNode?.type === 'CallExpression';
if (!isCallExpression) return false;
return !isCustomElementsGet(superClassNode.callee);
}

/**
* @param {NodePath} astPath
* @param {{isMixin?:boolean}} opts
*/
async function traverseClass(astPath, { isMixin = false } = {}) {
const classRes = {};
classRes.name = astPath.node.id && astPath.node.id.name;
classRes.name = (idOf(astPath.node) && nameOf(idOf(astPath.node))) || null;
classRes.isMixin = Boolean(isMixin);

if (astPath.node.superClass) {
const superClasses = [];

// Add all Identifier names
let parent = astPath.node.superClass;
while (parent.type === 'CallExpression') {
superClasses.push({ name: parent.callee.name, isMixin: true });
while (isSuperClassAMixin(parent)) {
superClasses.push({ name: nameOf(parent.callee), isMixin: true });
// As long as we are a CallExpression, we will have a parent
[parent] = parent.arguments;
[parent] = parent.arguments.map(expressionOf);
}

// At the end of the chain, we find type === Identifier or customElements.get directly.
if (isCustomElementsGet(parent.callee)) {
superClasses.push({
name: null,
customElementsGetRef: nameOf(parent.arguments?.map(expressionOf)[0]),
isMixin: false,
});
} else {
// an identifier like 'MyClass'
superClasses.push({ name: nameOf(expressionOf(parent)), isMixin: false });
}
// At the end of the chain, we find type === Identifier
superClasses.push({ name: parent.name, isMixin: false });

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

const methodRes = {};
const { name } = astPath.node.key;
const name = nameOf(astPath.node.key);
methodRes.name = name;
methodRes.accessType = computeAccessType(name);
if (
['constructor', 'connectedCallback', 'disconnectedCallback', 'adoptedCallback'].includes(
name,
)
) {
methodRes.isPartOfPlatformLifeCycle = true;
}

if (astPath.node.kind === 'set' || astPath.node.kind === 'get') {
if (astPath.node.static) {
if (isSetter(astPath.node) || isGetter(astPath.node)) {
const setOrGet = isSetter(astPath.node) ? 'set' : 'get';
if (isStatic(astPath.node)) {
methodRes.static = true;
}
methodRes.kind = [...(methodRes.kind || []), astPath.node.kind];
methodRes.kind = [...(methodRes.kind || []), setOrGet];
// Merge getter/setters into one
const found = classRes.members.props.find(p => p.name === name);
const found = classRes.members.props.find(p => nameOf(p) === name);
if (found) {
found.kind = [...(found.kind || []), astPath.node.kind];
found.kind = [...(found.kind || []), setOrGet];
} else {
classRes.members.props.push(methodRes);
}
Expand All @@ -184,14 +228,16 @@ async function findMembersPerAstEntry(babelAst, fullCurrentFilePath, projectPath
astPath.traverse({
ClassMethod: handleMethodDefinitionOrClassMethod,
MethodDefinition: handleMethodDefinitionOrClassMethod,
// for swc
Constructor: handleMethodDefinitionOrClassMethod,
});

classesFound.push(classRes);
}

const classesToTraverse = [];

oxcTraverse(babelAst, {
oxcTraverse(oxcAst, {
ClassDeclaration(astPath) {
classesToTraverse.push({ astPath, isMixin: false });
},
Expand Down
Loading