Skip to content

Commit

Permalink
feat(extract): adds more granularity to dependency types (#884)
Browse files Browse the repository at this point in the history
## Description

- adds detection of more fine grained dependency types. See the updated
documentation in this PR for a complete list.

## Motivation and Context

... so it's possible to distinguish whether imports were done with
require, triple slash directive (and if yes: with which _type_ of triple
slash directive), import, type-import, import-equals, dynamic import,
...).

Some of these were already possible with dedicated attributes like
`dynamic` or `moduleSystems` but some weren't yet. For either case it'll
make writing rules simpler as there's one spot where you can check them
against.


## How Has This Been Tested?

- [x] green ci
- [x] updated automated non-regression tests

## Types of changes

- [ ] Bug fix (non-breaking change which fixes an issue)
- [ ] Documentation only change
- [ ] Refactor (non-breaking change which fixes an issue without
changing functionality)
- [x] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing
functionality to change)
  • Loading branch information
sverweij committed Dec 12, 2023
1 parent 83a5589 commit 11127b5
Show file tree
Hide file tree
Showing 90 changed files with 806 additions and 493 deletions.
2 changes: 1 addition & 1 deletion .c8rc.json
@@ -1,7 +1,7 @@
{
"checkCoverage": true,
"statements": 99.89,
"branches": 98.76,
"branches": 98.75,
"functions": 100,
"lines": 99.89,
"exclude": [
Expand Down
74 changes: 48 additions & 26 deletions doc/rules-reference.md

Large diffs are not rendered by default.

15 changes: 8 additions & 7 deletions src/extract/ast-extractors/extract-amd-deps.mjs
Expand Up @@ -14,9 +14,10 @@ function extractRegularAMDDependencies(pNode, pDependencies) {
moduleSystem: "amd",
dynamic: false,
exoticallyRequired: false,
dependencyTypes: ["amd-define"],
});
}
})
}),
);
}
}
Expand All @@ -30,24 +31,24 @@ function extractCommonJSWrappers(pNode, pDependencies, pExoticRequireStrings) {
pArgument.params.some(
(pParameter) =>
pParameter.name === "require" ||
pExoticRequireStrings.includes(pParameter.name)
)
pExoticRequireStrings.includes(pParameter.name),
),
)
.forEach((pFunction) =>
extractCommonJSDependencies(
pFunction.body,
pDependencies,
"amd",
pExoticRequireStrings
)
pExoticRequireStrings,
),
);
}
}

export default function extractAMDDependencies(
pAST,
pDependencies,
pExoticRequireStrings
pExoticRequireStrings,
) {
walk_simple(
pAST,
Expand All @@ -67,6 +68,6 @@ export default function extractAMDDependencies(
},
},
// see https://github.com/acornjs/acorn/issues/746
walk_base
walk_base,
);
}
26 changes: 20 additions & 6 deletions src/extract/ast-extractors/extract-cjs-deps.mjs
Expand Up @@ -13,10 +13,17 @@ function pryStringsFromArguments(pArguments) {
return lReturnValue;
}

function getRequireTypes(pModuleSystem) {
return pModuleSystem === "amd" ? ["amd-require"] : ["require"];
}
function getExoticRequireTypes(pModuleSystem) {
return pModuleSystem === "amd" ? ["amd-exotic-require"] : ["exotic-require"];
}

function pushRequireCallsToDependencies(
pDependencies,
pModuleSystem,
pRequireStrings
pRequireStrings,
) {
return (pNode) => {
for (let lName of pRequireStrings) {
Expand All @@ -28,8 +35,15 @@ function pushRequireCallsToDependencies(
moduleSystem: pModuleSystem,
dynamic: false,
...(lName === "require"
? { exoticallyRequired: false }
: { exoticallyRequired: true, exoticRequire: lName }),
? {
exoticallyRequired: false,
dependencyTypes: getRequireTypes(pModuleSystem),
}
: {
exoticallyRequired: true,
exoticRequire: lName,
dependencyTypes: getExoticRequireTypes(pModuleSystem),
}),
});
}
}
Expand All @@ -41,7 +55,7 @@ export default function extractCommonJSDependencies(
pAST,
pDependencies,
pModuleSystem,
pExoticRequireStrings
pExoticRequireStrings,
) {
// var/const lalala = require('./lalala');
// require('./lalala');
Expand All @@ -58,10 +72,10 @@ export default function extractCommonJSDependencies(
CallExpression: pushRequireCallsToDependencies(
pDependencies,
pModuleSystem,
lRequireStrings
lRequireStrings,
),
},
// see https://github.com/acornjs/acorn/issues/746
walk_base
walk_base,
);
}
24 changes: 19 additions & 5 deletions src/extract/ast-extractors/extract-es6-deps.mjs
Expand Up @@ -12,39 +12,53 @@ function pushImportNodeValue(pDependencies) {
moduleSystem: "es6",
dynamic: true,
exoticallyRequired: false,
dependencyTypes: ["dynamic-import"],
});
} else if (estreeHelpers.isPlaceholderlessTemplateLiteral(pNode.source)) {
pDependencies.push({
module: pNode.source.quasis[0].value.cooked,
moduleSystem: "es6",
dynamic: true,
exoticallyRequired: false,
dependencyTypes: ["dynamic-import"],
});
}
};
}

export default function extractES6Dependencies(pAST, pDependencies) {
function pushSourceValue(pNode) {
function pushImportSourceValue(pNode) {
if (pNode.source && pNode.source.value) {
pDependencies.push({
module: pNode.source.value,
moduleSystem: "es6",
dynamic: false,
exoticallyRequired: false,
dependencyTypes: ["import"],
});
}
}
function pushExportSourceValue(pNode) {
if (pNode.source && pNode.source.value) {
pDependencies.push({
module: pNode.source.value,
moduleSystem: "es6",
dynamic: false,
exoticallyRequired: false,
dependencyTypes: ["export"],
});
}
}

walk_simple(
pAST,
{
ImportDeclaration: pushSourceValue,
ImportDeclaration: pushImportSourceValue,
ImportExpression: pushImportNodeValue(pDependencies),
ExportAllDeclaration: pushSourceValue,
ExportNamedDeclaration: pushSourceValue,
ExportAllDeclaration: pushExportSourceValue,
ExportNamedDeclaration: pushExportSourceValue,
},
// see https://github.com/acornjs/acorn/issues/746
walk_base
walk_base,
);
}
61 changes: 51 additions & 10 deletions src/extract/ast-extractors/extract-typescript-deps.mjs
@@ -1,3 +1,4 @@
/* eslint-disable max-lines */
/* eslint-disable no-inline-comments */
import tryImport from "semver-try-require";
import meta from "#meta.js";
Expand Down Expand Up @@ -43,28 +44,50 @@ function isTypeOnlyExport(pStatement) {
*/

/**
* Get all import and export statements from the top level AST node
* Get all import statements from the top level AST node
*
* @param {import("typescript").Node} pAST - the (top-level in this case) AST node
* @returns {{module: string; moduleSystem: string; exoticallyRequired: boolean; dependencyTypes?: string[];}[]} -
* all import and export statements in the
* (top level) AST node
* all import statements in the (top level) AST node
*/
function extractImports(pAST) {
return pAST.statements
.filter(
(pStatement) =>
typescript.SyntaxKind[pStatement.kind] === "ImportDeclaration" &&
Boolean(pStatement.moduleSpecifier),
)
.map((pStatement) => ({
module: pStatement.moduleSpecifier.text,
moduleSystem: "es6",
exoticallyRequired: false,
...(isTypeOnlyImport(pStatement)
? { dependencyTypes: ["type-only", "import"] }
: { dependencyTypes: ["import"] }),
}));
}

/**
* Get all export statements from the top level AST node
*
* @param {import("typescript").Node} pAST - the (top-level in this case) AST node
* @returns {{module: string; moduleSystem: string; exoticallyRequired: boolean; dependencyTypes?: string[];}[]} -
* all export statements in the (top level) AST node
*/
function extractImportsAndExports(pAST) {
function extractExports(pAST) {
return pAST.statements
.filter(
(pStatement) =>
(typescript.SyntaxKind[pStatement.kind] === "ImportDeclaration" ||
typescript.SyntaxKind[pStatement.kind] === "ExportDeclaration") &&
typescript.SyntaxKind[pStatement.kind] === "ExportDeclaration" &&
Boolean(pStatement.moduleSpecifier),
)
.map((pStatement) => ({
module: pStatement.moduleSpecifier.text,
moduleSystem: "es6",
exoticallyRequired: false,
...(isTypeOnlyImport(pStatement) || isTypeOnlyExport(pStatement)
? { dependencyTypes: ["type-only"] }
: {}),
...(isTypeOnlyExport(pStatement)
? { dependencyTypes: ["type-only", "export"] }
: { dependencyTypes: ["export"] }),
}));
}

Expand Down Expand Up @@ -92,6 +115,7 @@ function extractImportEquals(pAST) {
module: pStatement.moduleReference.expression.text,
moduleSystem: "cjs",
exoticallyRequired: false,
dependencyTypes: ["import-equals"],
}));
}

Expand All @@ -108,19 +132,31 @@ function extractTripleSlashDirectives(pAST) {
module: pReference.fileName,
moduleSystem: "tsd",
exoticallyRequired: false,
dependencyTypes: [
"triple-slash-directive",
"triple-slash-file-reference",
],
}))
.concat(
pAST.typeReferenceDirectives.map((pReference) => ({
module: pReference.fileName,
moduleSystem: "tsd",
exoticallyRequired: false,
dependencyTypes: [
"triple-slash-directive",
"triple-slash-type-reference",
],
})),
)
.concat(
pAST.amdDependencies.map((pReference) => ({
module: pReference.path,
moduleSystem: "tsd",
exoticallyRequired: false,
dependencyTypes: [
"triple-slash-directive",
"triple-slash-amd-dependency",
],
})),
);
}
Expand Down Expand Up @@ -224,6 +260,7 @@ function walk(pResult, pExoticRequireStrings) {
module: pASTNode.arguments[0].text,
moduleSystem: "cjs",
exoticallyRequired: false,
dependencyTypes: ["require"],
});
}

Expand All @@ -235,6 +272,7 @@ function walk(pResult, pExoticRequireStrings) {
moduleSystem: "cjs",
exoticallyRequired: true,
exoticRequire: pExoticRequireString,
dependencyTypes: ["exotic-require"],
});
}
});
Expand All @@ -246,6 +284,7 @@ function walk(pResult, pExoticRequireStrings) {
moduleSystem: "es6",
dynamic: true,
exoticallyRequired: false,
dependencyTypes: ["dynamic-import"],
});
}

Expand All @@ -256,6 +295,7 @@ function walk(pResult, pExoticRequireStrings) {
module: pASTNode.argument.literal.text,
moduleSystem: "es6",
exoticallyRequired: false,
dependencyTypes: ["type-import"],
});
}
typescript.forEachChild(pASTNode, walk(pResult, pExoticRequireStrings));
Expand Down Expand Up @@ -287,7 +327,8 @@ export default function extractTypeScriptDependencies(
pExoticRequireStrings,
) {
return Boolean(typescript)
? extractImportsAndExports(pTypeScriptAST)
? extractImports(pTypeScriptAST)
.concat(extractExports(pTypeScriptAST))
.concat(extractImportEquals(pTypeScriptAST))
.concat(extractTripleSlashDirectives(pTypeScriptAST))
.concat(
Expand Down

0 comments on commit 11127b5

Please sign in to comment.