Merge pull request #1545 from ing-bank/feat/providenceTypes
chore: types for providence
This commit is contained in:
commit
30e0829a61
45 changed files with 5172 additions and 612 deletions
5
.changeset/dirty-pots-divide.md
Normal file
5
.changeset/dirty-pots-divide.md
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
'providence-analytics': patch
|
||||
---
|
||||
|
||||
add type support for (the majority of) providence-analytics
|
||||
4089
packages-node/providence-analytics/package-lock.json
generated
Normal file
4089
packages-node/providence-analytics/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -100,7 +100,7 @@ function targetDefault() {
|
|||
// eslint-disable-next-line import/no-dynamic-require, global-require
|
||||
const { name } = require(`${process.cwd()}/package.json`);
|
||||
if (name === 'providence') {
|
||||
return InputDataService.getTargetProjectPaths();
|
||||
return InputDataService.targetProjectPaths;
|
||||
}
|
||||
return [toPosixPath(process.cwd())];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,3 @@
|
|||
// @ts-ignore-next-line
|
||||
require('../program/types/index.js');
|
||||
|
||||
const child_process = require('child_process'); // eslint-disable-line camelcase
|
||||
const pathLib = require('path');
|
||||
const commander = require('commander');
|
||||
|
|
|
|||
|
|
@ -6,12 +6,12 @@ const { Analyzer } = require('./helpers/Analyzer.js');
|
|||
const { trackDownIdentifierFromScope } = require('./helpers/track-down-identifier.js');
|
||||
const { aForEach } = require('../utils/async-array-utils.js');
|
||||
|
||||
/** @typedef {import('./types').FindClassesAnalyzerOutput} FindClassesAnalyzerOutput */
|
||||
/** @typedef {import('./types').FindClassesAnalyzerOutputEntry} FindClassesAnalyzerOutputEntry */
|
||||
/** @typedef {import('./types').FindClassesConfig} FindClassesConfig */
|
||||
/** @typedef {import('../types/analyzers').FindClassesAnalyzerOutput} FindClassesAnalyzerOutput */
|
||||
/** @typedef {import('../types/analyzers').FindClassesAnalyzerOutputEntry} FindClassesAnalyzerOutputEntry */
|
||||
/** @typedef {import('../types/analyzers').FindClassesConfig} FindClassesConfig */
|
||||
|
||||
/**
|
||||
* @desc Finds import specifiers and sources
|
||||
* Finds import specifiers and sources
|
||||
* @param {BabelAst} ast
|
||||
* @param {string} relativePath the file being currently processed
|
||||
*/
|
||||
|
|
@ -19,8 +19,9 @@ async function findMembersPerAstEntry(ast, fullCurrentFilePath, projectPath) {
|
|||
// The transformed entry
|
||||
const classesFound = [];
|
||||
/**
|
||||
* @desc Detects private/publicness based on underscores. Checks '$' as well
|
||||
* @returns {'public|protected|private'}
|
||||
* Detects private/publicness based on underscores. Checks '$' as well
|
||||
* @param {string} name
|
||||
* @returns {'public'|'protected'|'private'}
|
||||
*/
|
||||
function computeAccessType(name) {
|
||||
if (name.startsWith('_') || name.startsWith('$')) {
|
||||
|
|
|
|||
|
|
@ -7,9 +7,8 @@ const { normalizeSourcePaths } = require('./helpers/normalize-source-paths.js');
|
|||
const { aForEach } = require('../utils/async-array-utils.js');
|
||||
const { LogService } = require('../services/LogService.js');
|
||||
|
||||
/** @typedef {import('./helpers/track-down-identifier.js').RootFile} RootFile */
|
||||
|
||||
/**
|
||||
* @typedef {import('./helpers/track-down-identifier.js').RootFile} RootFile
|
||||
* @typedef {object} RootFileMapEntry
|
||||
* @property {string} currentFileSpecifier this is the local name in the file we track from
|
||||
* @property {RootFile} rootFile contains file(filePath) and specifier
|
||||
|
|
|
|||
|
|
@ -5,16 +5,23 @@ const { normalizeSourcePaths } = require('./helpers/normalize-source-paths.js');
|
|||
const { Analyzer } = require('./helpers/Analyzer.js');
|
||||
const { LogService } = require('../services/LogService.js');
|
||||
|
||||
/**
|
||||
* @typedef {import('../types/core').AnalyzerName} AnalyzerName
|
||||
* @typedef {import('../types/analyzers').FindImportsAnalyzerResult} FindImportsAnalyzerResult
|
||||
* @typedef {import('../types/analyzers').FindImportsAnalyzerEntry} FindImportsAnalyzerEntry
|
||||
* @typedef {import('../types/core').PathRelativeFromProjectRoot} PathRelativeFromProjectRoot
|
||||
*/
|
||||
|
||||
/**
|
||||
* Options that allow to filter 'on a file basis'.
|
||||
* We can also filter on the total result
|
||||
*/
|
||||
const /** @type {AnalyzerOptions} */ options = {
|
||||
/**
|
||||
* @desc Only leaves entries with external sources:
|
||||
* Only leaves entries with external sources:
|
||||
* - keeps: '@open-wc/testing'
|
||||
* - drops: '../testing'
|
||||
* @param {FindImportsAnalysisResult} result
|
||||
* @param {FindImportsAnalyzerResult} result
|
||||
* @param {string} targetSpecifier for instance 'LitElement'
|
||||
*/
|
||||
onlyExternalSources(result) {
|
||||
|
|
@ -41,14 +48,14 @@ function getImportOrReexportsSpecifiers(node) {
|
|||
}
|
||||
|
||||
/**
|
||||
* @desc Finds import specifiers and sources
|
||||
* @param {BabelAst} ast
|
||||
* @param {string} context.relativePath the file being currently processed
|
||||
* Finds import specifiers and sources
|
||||
* @param {any} ast
|
||||
*/
|
||||
function findImportsPerAstEntry(ast) {
|
||||
LogService.debug(`Analyzer "find-imports": started findImportsPerAstEntry method`);
|
||||
|
||||
// Visit AST...
|
||||
/** @type {Partial<FindImportsAnalyzerEntry>[]} */
|
||||
const transformedEntry = [];
|
||||
traverse(ast, {
|
||||
ImportDeclaration(path) {
|
||||
|
|
@ -96,11 +103,12 @@ function findImportsPerAstEntry(ast) {
|
|||
class FindImportsAnalyzer extends Analyzer {
|
||||
constructor() {
|
||||
super();
|
||||
/** @type {AnalyzerName} */
|
||||
this.name = 'find-imports';
|
||||
}
|
||||
|
||||
/**
|
||||
* @desc Finds import specifiers and sources
|
||||
* Finds import specifiers and sources
|
||||
* @param {FindImportsConfig} customConfig
|
||||
*/
|
||||
async execute(customConfig = {}) {
|
||||
|
|
|
|||
|
|
@ -6,31 +6,40 @@ const { LogService } = require('../../services/LogService.js');
|
|||
const { QueryService } = require('../../services/QueryService.js');
|
||||
const { ReportService } = require('../../services/ReportService.js');
|
||||
const { InputDataService } = require('../../services/InputDataService.js');
|
||||
const { aForEach } = require('../../utils/async-array-utils.js');
|
||||
const { toPosixPath } = require('../../utils/to-posix-path.js');
|
||||
const { getFilePathRelativeFromRoot } = require('../../utils/get-file-path-relative-from-root.js');
|
||||
|
||||
/**
|
||||
* @desc analyzes one entry: the callback can traverse a given ast for each entry
|
||||
* @param {AstDataProject[]} astDataProjects
|
||||
* @typedef {import('../../types/core').AnalyzerName} AnalyzerName
|
||||
* @typedef {import('../../types/core').PathFromSystemRoot} PathFromSystemRoot
|
||||
* @typedef {import('../../types/core').QueryOutput} QueryOutput
|
||||
* @typedef {import('../../types/core').ProjectInputData} ProjectInputData
|
||||
* @typedef {import('../../types/core').ProjectInputDataWithMeta} ProjectInputDataWithMeta
|
||||
* @typedef {import('../../types/core').AnalyzerQueryResult} AnalyzerQueryResult
|
||||
* @typedef {import('../../types/core').MatchAnalyzerConfig} MatchAnalyzerConfig
|
||||
*/
|
||||
|
||||
/**
|
||||
* Analyzes one entry: the callback can traverse a given ast for each entry
|
||||
* @param {ProjectInputDataWithMeta} projectData
|
||||
* @param {function} astAnalysis
|
||||
*/
|
||||
async function analyzePerAstEntry(projectData, astAnalysis) {
|
||||
const entries = [];
|
||||
await aForEach(projectData.entries, async ({ file, ast, context: astContext }) => {
|
||||
for (const { file, ast, context: astContext } of projectData.entries) {
|
||||
const relativePath = getFilePathRelativeFromRoot(file, projectData.project.path);
|
||||
const context = { code: astContext.code, relativePath, projectData };
|
||||
LogService.debug(`${pathLib.resolve(projectData.project.path, file)}`);
|
||||
const { result, meta } = await astAnalysis(ast, context);
|
||||
entries.push({ file: relativePath, meta, result });
|
||||
});
|
||||
}
|
||||
const filteredEntries = entries.filter(({ result }) => Boolean(result.length));
|
||||
return filteredEntries;
|
||||
}
|
||||
|
||||
/**
|
||||
* Transforms QueryResult entries to posix path notations on Windows
|
||||
* @param {array|object} data
|
||||
* @param {object[]|object} data
|
||||
*/
|
||||
function posixify(data) {
|
||||
if (!data) {
|
||||
|
|
@ -55,9 +64,9 @@ function posixify(data) {
|
|||
* @desc This method ensures that the result returned by an analyzer always has a consistent format.
|
||||
* By returning the configuration for the queryOutput, it will be possible to run later queries
|
||||
* under the same circumstances
|
||||
* @param {array} queryOutput
|
||||
* @param {QueryOutput} queryOutput
|
||||
* @param {object} configuration
|
||||
* @param {object} analyzer
|
||||
* @param {Analyzer} analyzer
|
||||
*/
|
||||
function ensureAnalyzerResultFormat(queryOutput, configuration, analyzer) {
|
||||
const { targetProjectMeta, identifier, referenceProjectMeta } = analyzer;
|
||||
|
|
@ -71,7 +80,7 @@ function ensureAnalyzerResultFormat(queryOutput, configuration, analyzer) {
|
|||
delete optional.referenceProject.path; // get rid of machine specific info
|
||||
}
|
||||
|
||||
/** @type {AnalyzerResult} */
|
||||
/** @type {AnalyzerQueryResult} */
|
||||
const aResult = {
|
||||
queryOutput,
|
||||
analyzerMeta: {
|
||||
|
|
@ -114,11 +123,11 @@ function ensureAnalyzerResultFormat(queryOutput, configuration, analyzer) {
|
|||
}
|
||||
|
||||
/**
|
||||
* @desc Before running the analyzer, we need two conditions for a 'compatible match':
|
||||
* 1. referenceProject is imported by targetProject at all
|
||||
* 2. referenceProject and targetProject have compatible major versions
|
||||
* @param {string} referencePath
|
||||
* @param {string} targetPath
|
||||
* Before running the analyzer, we need two conditions for a 'compatible match':
|
||||
* - 1. referenceProject is imported by targetProject at all
|
||||
* - 2. referenceProject and targetProject have compatible major versions
|
||||
* @param {PathFromSystemRoot} referencePath
|
||||
* @param {PathFromSystemRoot} targetPath
|
||||
*/
|
||||
function checkForMatchCompatibility(referencePath, targetPath) {
|
||||
const refFile = pathLib.resolve(referencePath, 'package.json');
|
||||
|
|
@ -142,7 +151,7 @@ function checkForMatchCompatibility(referencePath, targetPath) {
|
|||
|
||||
/**
|
||||
* If in json format, 'unwind' to be compatible for analysis...
|
||||
* @param {AnalyzerResult} targetOrReferenceProjectResult
|
||||
* @param {AnalyzerQueryResult} targetOrReferenceProjectResult
|
||||
*/
|
||||
function unwindJsonResult(targetOrReferenceProjectResult) {
|
||||
const { queryOutput } = targetOrReferenceProjectResult;
|
||||
|
|
@ -153,6 +162,8 @@ function unwindJsonResult(targetOrReferenceProjectResult) {
|
|||
class Analyzer {
|
||||
constructor() {
|
||||
this.requiredAst = 'babel';
|
||||
/** @type {AnalyzerName|''} */
|
||||
this.name = '';
|
||||
}
|
||||
|
||||
static get requiresReference() {
|
||||
|
|
@ -262,7 +273,7 @@ class Analyzer {
|
|||
/**
|
||||
* @param {QueryOutput} queryOutput
|
||||
* @param {AnalyzerConfig} cfg
|
||||
* @returns {AnalyzerResult}
|
||||
* @returns {AnalyzerQueryResult}
|
||||
*/
|
||||
_finalize(queryOutput, cfg) {
|
||||
LogService.debug(`Analyzer "${this.name}": started _finalize method`);
|
||||
|
|
@ -319,7 +330,7 @@ class Analyzer {
|
|||
* @param {object} config
|
||||
* @param {string} config.analyzerName
|
||||
* @param {string} config.identifier
|
||||
* @returns {AnalyzerResult|undefined}
|
||||
* @returns {AnalyzerQueryResult|undefined}
|
||||
*/
|
||||
static _getCachedAnalyzerResult({ analyzerName, identifier }) {
|
||||
const cachedResult = ReportService.getCachedResult({ analyzerName, identifier });
|
||||
|
|
@ -328,7 +339,7 @@ class Analyzer {
|
|||
}
|
||||
LogService.success(`cached version found for ${identifier}`);
|
||||
|
||||
/** @type {AnalyzerResult} */
|
||||
/** @type {AnalyzerQueryResult} */
|
||||
const result = unwindJsonResult(cachedResult);
|
||||
result.analyzerMeta.__fromCache = true;
|
||||
return result;
|
||||
|
|
|
|||
|
|
@ -2,6 +2,10 @@ const { isRelativeSourcePath } = require('../../utils/relative-source-path.js');
|
|||
const { LogService } = require('../../services/LogService.js');
|
||||
const { resolveImportPath } = require('../../utils/resolve-import-path.js');
|
||||
|
||||
/**
|
||||
* @typedef {import('../../types/core').PathRelativeFromProjectRoot} PathRelativeFromProjectRoot
|
||||
*/
|
||||
|
||||
/**
|
||||
* @param {string} importee like '@lion/core/myFile.js'
|
||||
* @returns {string} project name ('@lion/core')
|
||||
|
|
@ -31,7 +35,7 @@ function getProjectFromImportee(importee) {
|
|||
* @param {object} config
|
||||
* @param {string} config.importee 'reference-project/foo.js'
|
||||
* @param {string} config.importer '/my/project/importing-file.js'
|
||||
* @returns {Promise<string|null>} './foo.js'
|
||||
* @returns {Promise<PathRelativeFromProjectRoot|null>} './foo.js'
|
||||
*/
|
||||
async function fromImportToExportPerspective({ importee, importer }) {
|
||||
if (isRelativeSourcePath(importee)) {
|
||||
|
|
@ -42,9 +46,14 @@ async function fromImportToExportPerspective({ importee, importer }) {
|
|||
const absolutePath = await resolveImportPath(importee, importer);
|
||||
const projectName = getProjectFromImportee(importee);
|
||||
|
||||
// from /my/reference/project/packages/foo/index.js to './packages/foo/index.js'
|
||||
/**
|
||||
* - from: '/my/reference/project/packages/foo/index.js'
|
||||
* - to: './packages/foo/index.js'
|
||||
*/
|
||||
return absolutePath
|
||||
? absolutePath.replace(new RegExp(`^.*/${projectName}/?(.*)$`), './$1')
|
||||
? /** @type {PathRelativeFromProjectRoot} */ (
|
||||
absolutePath.replace(new RegExp(`^.*/${projectName}/?(.*)$`), './$1')
|
||||
)
|
||||
: null;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,13 +1,22 @@
|
|||
/* eslint-disable no-param-reassign */
|
||||
const pathLib = require('path');
|
||||
const {
|
||||
isRelativeSourcePath,
|
||||
// toRelativeSourcePath,
|
||||
} = require('../../utils/relative-source-path.js');
|
||||
const { isRelativeSourcePath } = require('../../utils/relative-source-path.js');
|
||||
const { resolveImportPath } = require('../../utils/resolve-import-path.js');
|
||||
const { aMap } = require('../../utils/async-array-utils.js');
|
||||
const { toPosixPath } = require('../../utils/to-posix-path.js');
|
||||
const { aMap } = require('../../utils/async-array-utils.js');
|
||||
|
||||
/**
|
||||
* @typedef {import('../../types/core').PathRelative} PathRelative
|
||||
* @typedef {import('../../types/core').PathFromSystemRoot} PathFromSystemRoot
|
||||
* @typedef {import('../../types/core').QueryOutput} QueryOutput
|
||||
*/
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {PathFromSystemRoot} currentDirPath
|
||||
* @param {PathFromSystemRoot} resolvedPath
|
||||
* @returns {PathRelative}
|
||||
*/
|
||||
function toLocalPath(currentDirPath, resolvedPath) {
|
||||
let relativeSourcePath = pathLib.relative(currentDirPath, resolvedPath);
|
||||
if (!relativeSourcePath.startsWith('.')) {
|
||||
|
|
@ -16,29 +25,32 @@ function toLocalPath(currentDirPath, resolvedPath) {
|
|||
// so 'my-local-files.js' -> './my-local-files.js'
|
||||
relativeSourcePath = `./${relativeSourcePath}`;
|
||||
}
|
||||
return toPosixPath(relativeSourcePath);
|
||||
return /** @type {PathRelative} */ (toPosixPath(relativeSourcePath));
|
||||
}
|
||||
|
||||
/**
|
||||
* @desc Resolves and converts to normalized local/absolute path, based on file-system information.
|
||||
* Resolves and converts to normalized local/absolute path, based on file-system information.
|
||||
* - from: { source: '../../relative/file' }
|
||||
* - to: {
|
||||
* fullPath: './absolute/path/from/root/to/relative/file.js',
|
||||
* normalizedPath: '../../relative/file.js'
|
||||
* }
|
||||
* @param {FindImportsAnalysisResult} result
|
||||
* @param {string} result
|
||||
* @param {QueryOutput} queryOutput
|
||||
* @param {string} relativePath
|
||||
* @returns {string} a relative path from root (usually a project) or an external path like 'lion-based-ui/x.js'
|
||||
* @param {string} rootPath
|
||||
*/
|
||||
async function normalizeSourcePaths(queryOutput, relativePath, rootPath = process.cwd()) {
|
||||
const currentFilePath = pathLib.resolve(rootPath, relativePath);
|
||||
const currentDirPath = pathLib.dirname(currentFilePath);
|
||||
const currentFilePath = /** @type {PathFromSystemRoot} */ (
|
||||
pathLib.resolve(rootPath, relativePath)
|
||||
);
|
||||
const currentDirPath = /** @type {PathFromSystemRoot} */ (pathLib.dirname(currentFilePath));
|
||||
return aMap(queryOutput, async specifierResObj => {
|
||||
if (specifierResObj.source) {
|
||||
if (isRelativeSourcePath(specifierResObj.source) && relativePath) {
|
||||
// This will be a source like '../my/file.js' or './file.js'
|
||||
const resolvedPath = await resolveImportPath(specifierResObj.source, currentFilePath);
|
||||
const resolvedPath = /** @type {PathFromSystemRoot} */ (
|
||||
await resolveImportPath(specifierResObj.source, currentFilePath)
|
||||
);
|
||||
specifierResObj.normalizedSource =
|
||||
resolvedPath && toLocalPath(currentDirPath, resolvedPath);
|
||||
// specifierResObj.fullSource = resolvedPath && toRelativeSourcePath(resolvedPath, rootPath);
|
||||
|
|
|
|||
|
|
@ -9,14 +9,19 @@ const { AstService } = require('../../services/AstService.js');
|
|||
const { LogService } = require('../../services/LogService.js');
|
||||
const { memoizeAsync } = require('../../utils/memoize.js');
|
||||
|
||||
/** @typedef {import('./types').RootFile} RootFile */
|
||||
/**
|
||||
* @typedef {import('../../types/core').RootFile} RootFile
|
||||
* @typedef {import('../../types/core').SpecifierSource} SpecifierSource
|
||||
* @typedef {import('../../types/core').IdentifierName} IdentifierName
|
||||
* @typedef {import('../../types/core').PathFromSystemRoot} PathFromSystemRoot
|
||||
*/
|
||||
|
||||
/**
|
||||
* 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 {object} astPath Babel ast traversal path
|
||||
* @param {string} identifierName the name that should be tracked (and that exists inside scope of astPath)
|
||||
* @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';`
|
||||
|
|
@ -81,6 +86,7 @@ function getImportSourceFromAst(astPath, identifierName) {
|
|||
return { source, importedIdentifierName };
|
||||
}
|
||||
|
||||
/** @type {(source:SpecifierSource,identifierName:IdentifierName,currentFilePath:PathFromSystemRoot,rootPath:PathFromSystemRoot, depth?:number) => Promise<RootFile>} */
|
||||
let trackDownIdentifier;
|
||||
/**
|
||||
* @example
|
||||
|
|
@ -98,11 +104,11 @@ let trackDownIdentifier;
|
|||
* export class RefComp extends LitElement {...}
|
||||
*```
|
||||
*
|
||||
* @param {string} source an importSpecifier source, like 'ref-proj' or '../file'
|
||||
* @param {string} identifierName imported reference/Identifier name, like 'MyComp'
|
||||
* @param {string} currentFilePath file path, like '/path/to/target-proj/my-comp-import.js'
|
||||
* @param {string} rootPath dir path, like '/path/to/target-proj'
|
||||
* @returns {object} file: path of file containing the binding (exported declaration),
|
||||
* @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'
|
||||
* @returns {Promise<RootFile>} file: path of file containing the binding (exported declaration),
|
||||
* like '/path/to/ref-proj/src/RefComp.js'
|
||||
*/
|
||||
async function trackDownIdentifierFn(source, identifierName, currentFilePath, rootPath, depth = 0) {
|
||||
|
|
@ -122,9 +128,7 @@ async function trackDownIdentifierFn(source, identifierName, currentFilePath, ro
|
|||
}
|
||||
|
||||
/**
|
||||
* @prop resolvedSourcePath
|
||||
* @type {string}
|
||||
* @example resolveImportPath('../file') // => '/path/to/target-proj/file.js'
|
||||
* @type {PathFromSystemRoot}
|
||||
*/
|
||||
const resolvedSourcePath = await resolveImportPath(source, currentFilePath);
|
||||
LogService.debug(`[trackDownIdentifier] ${resolvedSourcePath}`);
|
||||
|
|
@ -132,7 +136,7 @@ async function trackDownIdentifierFn(source, identifierName, currentFilePath, ro
|
|||
const ast = AstService.getAst(code, 'babel', { filePath: resolvedSourcePath });
|
||||
const shouldLookForDefaultExport = identifierName === '[default]';
|
||||
|
||||
let reexportMatch = null; // named specifier declaration
|
||||
let reexportMatch = false; // named specifier declaration
|
||||
let pendingTrackDownPromise;
|
||||
|
||||
traverse(ast, {
|
||||
|
|
|
|||
|
|
@ -7,19 +7,22 @@ const { Analyzer } = require('./helpers/Analyzer.js');
|
|||
const { fromImportToExportPerspective } = require('./helpers/from-import-to-export-perspective.js');
|
||||
|
||||
/**
|
||||
* @typedef {import('../types/find-imports').FindImportsAnalyzerResult} FindImportsAnalyzerResult
|
||||
* @typedef {import('../types/find-exports').FindExportsAnalyzerResult} FindExportsAnalyzerResult
|
||||
* @typedef {import('../types/find-exports').IterableFindExportsAnalyzerEntry} IterableFindExportsAnalyzerEntry
|
||||
* @typedef {import('../types/find-imports').IterableFindImportsAnalyzerEntry} IterableFindImportsAnalyzerEntry
|
||||
* @typedef {import('../types/match-imports').ConciseMatchImportsAnalyzerResult} ConciseMatchImportsAnalyzerResult
|
||||
* @typedef {import('../types/core').PathRelativeFromRoot} PathRelativeFromRoot
|
||||
* @typedef {import('../types/analyzers').FindImportsAnalyzerResult} FindImportsAnalyzerResult
|
||||
* @typedef {import('../types/analyzers').FindExportsAnalyzerResult} FindExportsAnalyzerResult
|
||||
* @typedef {import('../types/analyzers').IterableFindExportsAnalyzerEntry} IterableFindExportsAnalyzerEntry
|
||||
* @typedef {import('../types/analyzers').IterableFindImportsAnalyzerEntry} IterableFindImportsAnalyzerEntry
|
||||
* @typedef {import('../types/analyzers').ConciseMatchImportsAnalyzerResult} ConciseMatchImportsAnalyzerResult
|
||||
* @typedef {import('../types/analyzers').MatchImportsConfig} MatchImportsConfig
|
||||
* @typedef {import('../types/analyzers').MatchImportsAnalyzerResult} MatchImportsAnalyzerResult
|
||||
* @typedef {import('../types/core').PathRelativeFromProjectRoot} PathRelativeFromProjectRoot
|
||||
* @typedef {import('../types/core').AnalyzerName} AnalyzerName
|
||||
*/
|
||||
|
||||
/**
|
||||
* Needed in case fromImportToExportPerspective does not have a
|
||||
* externalRootPath supplied.
|
||||
* @param {string} exportPath exportEntry.file
|
||||
* @param {string} translatedImportPath result of fromImportToExportPerspective
|
||||
* @param {PathRelativeFromProjectRoot} translatedImportPath result of fromImportToExportPerspective
|
||||
*/
|
||||
function compareImportAndExportPaths(exportPath, translatedImportPath) {
|
||||
return (
|
||||
|
|
@ -69,7 +72,7 @@ function transformIntoIterableFindExportsOutput(exportsAnalyzerResult) {
|
|||
for (const { file, result } of exportsAnalyzerResult.queryOutput) {
|
||||
for (const { exportSpecifiers, source, rootFileMap, localMap, meta } of result) {
|
||||
if (!exportSpecifiers) {
|
||||
break;
|
||||
continue;
|
||||
}
|
||||
for (const exportSpecifier of exportSpecifiers) {
|
||||
const i = exportSpecifiers.indexOf(exportSpecifier);
|
||||
|
|
@ -126,7 +129,7 @@ function transformIntoIterableFindImportsOutput(importsAnalyzerResult) {
|
|||
for (const { file, result } of importsAnalyzerResult.queryOutput) {
|
||||
for (const { importSpecifiers, source, normalizedSource } of result) {
|
||||
if (!importSpecifiers) {
|
||||
break;
|
||||
continue;
|
||||
}
|
||||
for (const importSpecifier of importSpecifiers) {
|
||||
/** @type {IterableFindImportsAnalyzerEntry} */
|
||||
|
|
@ -144,8 +147,9 @@ function transformIntoIterableFindImportsOutput(importsAnalyzerResult) {
|
|||
}
|
||||
|
||||
/**
|
||||
* Makes a concise results array a 'compatible resultsArray' (compatible with dashbaord + tests + ...?)
|
||||
* @param {object[]} conciseResultsArray
|
||||
* Makes a 'compatible resultsArray' (compatible with dashboard + tests + ...?) from
|
||||
* a conciseResultsArray.
|
||||
* @param {ConciseMatchImportsAnalyzerResult} conciseResultsArray
|
||||
* @param {string} importProject
|
||||
*/
|
||||
function createCompatibleMatchImportsResult(conciseResultsArray, importProject) {
|
||||
|
|
@ -169,8 +173,8 @@ function createCompatibleMatchImportsResult(conciseResultsArray, importProject)
|
|||
/**
|
||||
* @param {FindExportsAnalyzerResult} exportsAnalyzerResult
|
||||
* @param {FindImportsAnalyzerResult} importsAnalyzerResult
|
||||
* @param {matchImportsConfig} customConfig
|
||||
* @returns {Promise<AnalyzerResult>}
|
||||
* @param {MatchImportsConfig} customConfig
|
||||
* @returns {Promise<MatchImportsAnalyzerResult>}
|
||||
*/
|
||||
async function matchImportsPostprocess(exportsAnalyzerResult, importsAnalyzerResult, customConfig) {
|
||||
const cfg = {
|
||||
|
|
@ -217,7 +221,7 @@ async function matchImportsPostprocess(exportsAnalyzerResult, importsAnalyzerRes
|
|||
* => export const z = 'bar'
|
||||
* importFile 'importing-target-project/file.js'
|
||||
* => import { z } from '@reference/foo.js'
|
||||
* @type {PathRelativeFromRoot}
|
||||
* @type {PathRelativeFromProjectRoot|null}
|
||||
*/
|
||||
const fromImportToExport = await fromImportToExportPerspective({
|
||||
importee: importEntry.normalizedSource,
|
||||
|
|
@ -246,7 +250,7 @@ async function matchImportsPostprocess(exportsAnalyzerResult, importsAnalyzerRes
|
|||
}
|
||||
|
||||
const importProject = importsAnalyzerResult.analyzerMeta.targetProject.name;
|
||||
return /** @type {AnalyzerResult} */ createCompatibleMatchImportsResult(
|
||||
return /** @type {AnalyzerQueryResult} */ createCompatibleMatchImportsResult(
|
||||
conciseResultsArray,
|
||||
importProject,
|
||||
);
|
||||
|
|
@ -255,6 +259,7 @@ async function matchImportsPostprocess(exportsAnalyzerResult, importsAnalyzerRes
|
|||
class MatchImportsAnalyzer extends Analyzer {
|
||||
constructor() {
|
||||
super();
|
||||
/** @type {AnalyzerName} */
|
||||
this.name = 'match-imports';
|
||||
}
|
||||
|
||||
|
|
@ -263,7 +268,7 @@ class MatchImportsAnalyzer extends Analyzer {
|
|||
}
|
||||
|
||||
/**
|
||||
* @desc Based on ExportsAnalyzerResult of reference project(s) (for instance lion-based-ui)
|
||||
* Based on ExportsAnalyzerResult of reference project(s) (for instance lion-based-ui)
|
||||
* and ImportsAnalyzerResult of search-targets (for instance my-app-using-lion-based-ui),
|
||||
* an overview is returned of all matching imports and exports.
|
||||
* @param {MatchImportsConfig} customConfig
|
||||
|
|
|
|||
|
|
@ -258,7 +258,7 @@ function getTagPaths(
|
|||
* @param {FindCustomelementsAnalyzerResult} targetFindCustomelementsResult
|
||||
* @param {FindCustomelementsAnalyzerResult} refFindCustomelementsResult
|
||||
* @param {FindExportsAnalyzerResult} refFindExportsResult
|
||||
* @returns {AnalyzerResult}
|
||||
* @returns {AnalyzerQueryResult}
|
||||
*/
|
||||
function matchPathsPostprocess(
|
||||
targetMatchSubclassesResult,
|
||||
|
|
@ -268,7 +268,7 @@ function matchPathsPostprocess(
|
|||
refFindExportsResult,
|
||||
refProjectName,
|
||||
) {
|
||||
/** @type {AnalyzerResult} */
|
||||
/** @type {AnalyzerQueryResult} */
|
||||
const resultsArray = [];
|
||||
|
||||
targetMatchSubclassesResult.queryOutput.forEach(matchSubclassEntry => {
|
||||
|
|
|
|||
|
|
@ -6,8 +6,16 @@ const FindExportsAnalyzer = require('./find-exports.js');
|
|||
const { Analyzer } = require('./helpers/Analyzer.js');
|
||||
const { fromImportToExportPerspective } = require('./helpers/from-import-to-export-perspective.js');
|
||||
|
||||
/** @typedef {import('./types').FindClassesAnalyzerResult} FindClassesAnalyzerResult */
|
||||
/** @typedef {import('./types').FindExportsAnalyzerResult} FindExportsAnalyzerResult */
|
||||
/**
|
||||
* @typedef {import('../types/analyzers/find-classes').FindClassesAnalyzerResult} FindClassesAnalyzerResult
|
||||
* @typedef {import('../types/find-imports').FindImportsAnalyzerResult} FindImportsAnalyzerResult
|
||||
* @typedef {import('../types/find-exports').FindExportsAnalyzerResult} FindExportsAnalyzerResult
|
||||
* @typedef {import('../types/find-exports').IterableFindExportsAnalyzerEntry} IterableFindExportsAnalyzerEntry
|
||||
* @typedef {import('../types/find-imports').IterableFindImportsAnalyzerEntry} IterableFindImportsAnalyzerEntry
|
||||
* @typedef {import('../types/match-imports').ConciseMatchImportsAnalyzerResult} ConciseMatchImportsAnalyzerResult
|
||||
* @typedef {import('../types/match-imports').MatchImportsConfig} MatchImportsConfig
|
||||
* @typedef {import('../types/core/core').PathRelativeFromProjectRoot} PathRelativeFromProjectRoot
|
||||
*/
|
||||
|
||||
function getMemberOverrides(
|
||||
refClassesAResult,
|
||||
|
|
@ -63,7 +71,7 @@ function storeResult(resultsObj, exportId, filteredList, meta) {
|
|||
* @param {FindClassesAnalyzerResult} targetClassesAnalyzerResult
|
||||
* @param {FindClassesAnalyzerResult} refClassesAResult
|
||||
* @param {MatchSubclassesConfig} customConfig
|
||||
* @returns {AnalyzerResult}
|
||||
* @returns {AnalyzerQueryResult}
|
||||
*/
|
||||
async function matchSubclassesPostprocess(
|
||||
exportsAnalyzerResult,
|
||||
|
|
@ -243,7 +251,7 @@ async function matchSubclassesPostprocess(
|
|||
})
|
||||
.filter(r => Object.keys(r.matchesPerProject).length);
|
||||
|
||||
return /** @type {AnalyzerResult} */ resultsArray;
|
||||
return /** @type {AnalyzerQueryResult} */ resultsArray;
|
||||
}
|
||||
|
||||
// function postProcessAnalyzerResult(aResult) {
|
||||
|
|
|
|||
|
|
@ -9,9 +9,9 @@ const /** @type {AnalyzerOptions} */ options = {
|
|||
|
||||
/**
|
||||
*
|
||||
* @param {AnalyzerResult} analyzerResult
|
||||
* @param {AnalyzerQueryResult} analyzerResult
|
||||
* @param {FindImportsConfig} customConfig
|
||||
* @returns {AnalyzerResult}
|
||||
* @returns {AnalyzerQueryResult}
|
||||
*/
|
||||
function sortBySpecifier(analyzerResult, customConfig) {
|
||||
const cfg = {
|
||||
|
|
@ -74,7 +74,7 @@ function sortBySpecifier(analyzerResult, customConfig) {
|
|||
);
|
||||
}
|
||||
|
||||
return /** @type {AnalyzerResult} */ resultsBySpecifier;
|
||||
return /** @type {AnalyzerQueryResult} */ resultsBySpecifier;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
|
|
|
|||
|
|
@ -1,298 +0,0 @@
|
|||
import { ProjectReference } from 'typescript';
|
||||
|
||||
export interface RootFile {
|
||||
/** the file path containing declaration, for instance './target-src/direct-imports.js'. Can also contain keyword '[current]' */
|
||||
file: string;
|
||||
/** the specifier/identifier that was exported in root file, for instance 'MyClass' */
|
||||
specifier: string;
|
||||
}
|
||||
|
||||
export interface AnalyzerResult {
|
||||
/** meta info object */
|
||||
meta: Meta;
|
||||
/** array of AST traversal output, per project file */
|
||||
queryOutput: AnalyzerOutputFile[];
|
||||
}
|
||||
|
||||
export interface AnalyzerOutputFile {
|
||||
/** path relative from project root for which a result is generated based on AST traversal */
|
||||
file: string;
|
||||
/** result of AST traversal for file in project */
|
||||
result: array;
|
||||
}
|
||||
|
||||
// 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)
|
||||
// so that these type definitions can be used to generate a json schema: https://www.npmjs.com/package/typescript-json-schema
|
||||
interface Meta {
|
||||
/** type of the query. Currently onlu "ast-analyzer" supported */
|
||||
searchType: string;
|
||||
/** analyzer meta object */
|
||||
analyzerMeta: AnalyzerMeta;
|
||||
}
|
||||
|
||||
export interface AnalyzerMeta {
|
||||
/** analizer name like 'find-imports' or 'match-sublcasses' */
|
||||
name: string;
|
||||
/** the ast format. Currently only 'babel' */
|
||||
requiredAst: string;
|
||||
/** a unique hash based on target, reference and configuration */
|
||||
identifier: string;
|
||||
/** target project meta object */
|
||||
targetProject: Project;
|
||||
/** reference project meta object */
|
||||
referenceProject?: Project;
|
||||
/** the configuration used for this particular analyzer run */
|
||||
configuration: object;
|
||||
}
|
||||
|
||||
export interface Project {
|
||||
/** "name" found in package.json and under which the package is registered in npm */
|
||||
name: string;
|
||||
/** "version" found in package.json */
|
||||
version: string;
|
||||
/** "main" File found in package.json */
|
||||
mainFile: string;
|
||||
/** if a git repo is analyzed, stores commit hash, [not-a-git-repo] if not */
|
||||
commitHash: string;
|
||||
}
|
||||
|
||||
// match-customelements
|
||||
export interface MatchSubclassesAnalyzerResult extends AnalyzerResult {
|
||||
queryOutput: MatchSubclassesAnalyzerOutputEntry[];
|
||||
}
|
||||
|
||||
export interface MatchSubclassesAnalyzerOutputEntry {
|
||||
exportSpecifier: MatchedExportSpecifier;
|
||||
matchesPerProject: MatchSubclassesAnalyzerOutputEntryMatch[];
|
||||
}
|
||||
|
||||
export interface MatchSubclassesAnalyzerOutputEntryMatch {
|
||||
/** The target project that extends the class exported by reference project */
|
||||
project: string;
|
||||
/** Array of meta objects for matching files */
|
||||
files: MatchSubclassesAnalyzerOutputEntryMatchFile[];
|
||||
}
|
||||
|
||||
export interface MatchSubclassesAnalyzerOutputEntryMatchFile {
|
||||
/**
|
||||
* The local filepath that contains the matched class inside the target project
|
||||
* like `./src/ExtendedClass.js`
|
||||
*/
|
||||
file: string;
|
||||
/**
|
||||
* The local Identifier inside matched file that is exported
|
||||
* @example
|
||||
* - `ExtendedClass` for `export ExtendedClass extends RefClass {};`
|
||||
* - `[default]` for `export default ExtendedClass extends RefClass {};`
|
||||
*/
|
||||
identifier: string;
|
||||
}
|
||||
|
||||
export interface MatchedExportSpecifier extends AnalyzerResult {
|
||||
/** The exported Identifier name.
|
||||
*
|
||||
* For instance
|
||||
* - `export { X as Y } from 'q'` => `Y`
|
||||
* - `export default class Z {}` => `[default]`
|
||||
*/
|
||||
name: string;
|
||||
/** Project name as found in package.json */
|
||||
project: string;
|
||||
/** Path relative from project root, for instance `./index.js` */
|
||||
filePath: string;
|
||||
/** "[default]::./index.js::exporting-ref-project" */
|
||||
id: string;
|
||||
}
|
||||
|
||||
// "find-customelements"
|
||||
|
||||
export interface FindCustomelementsAnalyzerResult extends AnalyzerResult {
|
||||
queryOutput: FindCustomelementsAnalyzerOutputFile[];
|
||||
}
|
||||
|
||||
export interface FindCustomelementsAnalyzerOutputFile extends AnalyzerOutputFile {
|
||||
/** path relative from project root for which a result is generated based on AST traversal */
|
||||
file: string;
|
||||
/** result of AST traversal for file in project */
|
||||
result: FindCustomelementsAnalyzerEntry[];
|
||||
}
|
||||
|
||||
export interface FindCustomelementsAnalyzerEntry {
|
||||
/**
|
||||
* Tag name found in CE definition:
|
||||
* `customElements.define('my-name', MyConstructor)` => 'my-name'
|
||||
*/
|
||||
tagName: string;
|
||||
/**
|
||||
* Identifier found in CE definition:
|
||||
* `customElements.define('my-name', MyConstructor)` => MyConstructor
|
||||
*/
|
||||
constructorIdentifier: string;
|
||||
/** Rootfile traced for constuctorIdentifier found in CE definition */
|
||||
rootFile: RootFile;
|
||||
}
|
||||
|
||||
// "find-exports"
|
||||
|
||||
export interface FindExportsAnalyzerResult extends AnalyzerResult {
|
||||
queryOutput: FindExportsAnalyzerOutputFile[];
|
||||
}
|
||||
|
||||
export interface FindExportsAnalyzerOutputFile extends AnalyzerOutputFile {
|
||||
/** path relative from project root for which a result is generated based on AST traversal */
|
||||
file: string;
|
||||
/** result of AST traversal for file in project */
|
||||
result: FindExportsAnalyzerEntry[];
|
||||
}
|
||||
|
||||
export interface FindExportsAnalyzerEntry {
|
||||
/**
|
||||
* The specifiers found in an export statement.
|
||||
*
|
||||
* For example:
|
||||
* - file `export class X {}` gives `['X']`
|
||||
* - file `export default const y = 0` gives `['[default]']`
|
||||
* - file `export { y, z } from 'project'` gives `['y', 'z']`
|
||||
*/
|
||||
exportSpecifiers: string[];
|
||||
/**
|
||||
* The original "source" string belonging to specifier.
|
||||
* For example:
|
||||
* - file `export { x } from './my/file';` gives `"./my/file"`
|
||||
* - file `export { x } from 'project';` gives `"project"`
|
||||
*/
|
||||
source: string;
|
||||
/**
|
||||
* The normalized "source" string belonging to specifier
|
||||
* (based on file system information, resolves right names and extensions).
|
||||
* For example:
|
||||
* - file `export { x } from './my/file';` gives `"./my/file.js"`
|
||||
* - file `export { x } from 'project';` gives `"project"` (only files in current project are resolved)
|
||||
* - file `export { x } from '../';` gives `"../index.js"`
|
||||
*/
|
||||
normalizedSource: string;
|
||||
/** map of tracked down Identifiers */
|
||||
rootFileMap: RootFileMapEntry[];
|
||||
}
|
||||
|
||||
export interface RootFileMapEntry {
|
||||
/** This is the local name in the file we track from */
|
||||
currentFileSpecifier: string;
|
||||
/**
|
||||
* The file that contains the original declaration of a certain Identifier/Specifier.
|
||||
* Contains file(filePath) and specifier keys
|
||||
*/
|
||||
rootFile: RootFile;
|
||||
}
|
||||
|
||||
// "find-imports"
|
||||
|
||||
export interface FindImportsAnalyzerResult extends AnalyzerResult {
|
||||
queryOutput: FindImportsAnalyzerOutputFile[];
|
||||
}
|
||||
|
||||
export interface FindImportsAnalyzerOutputFile extends AnalyzerOutputFile {
|
||||
/** path relative from project root for which a result is generated based on AST traversal */
|
||||
file: string;
|
||||
/** result of AST traversal for file in project */
|
||||
result: FindImportsAnalyzerEntry[];
|
||||
}
|
||||
|
||||
export interface FindImportsAnalyzerEntry {
|
||||
/**
|
||||
* The specifiers found in an import statement.
|
||||
*
|
||||
* For example:
|
||||
* - file `import { X } from 'project'` gives `['X']`
|
||||
* - file `import X from 'project'` gives `['[default]']`
|
||||
* - file `import x, { y, z } from 'project'` gives `['[default]', 'y', 'z']`
|
||||
*/
|
||||
importSpecifiers: string[];
|
||||
/**
|
||||
* The original "source" string belonging to specifier.
|
||||
* For example:
|
||||
* - file `import { x } from './my/file';` gives `"./my/file"`
|
||||
* - file `import { x } from 'project';` gives `"project"`
|
||||
*/
|
||||
source: string;
|
||||
/**
|
||||
* The normalized "source" string belonging to specifier
|
||||
* (based on file system information, resolves right names and extensions).
|
||||
* For example:
|
||||
* - file `import { x } from './my/file';` gives `"./my/file.js"`
|
||||
* - file `import { x } from 'project';` gives `"project"` (only files in current project are resolved)
|
||||
* - file `import { x } from '../';` gives `"../index.js"`
|
||||
*/
|
||||
normalizedSource: string;
|
||||
}
|
||||
|
||||
// "find-classes"
|
||||
|
||||
export interface FindClassesAnalyzerResult extends AnalyzerResult {
|
||||
queryOutput: FindClassesAnalyzerOutputFile[];
|
||||
}
|
||||
|
||||
export interface FindClassesAnalyzerOutputFile extends AnalyzerOutputFile {
|
||||
/** path relative from project root for which a result is generated based on AST traversal */
|
||||
file: string;
|
||||
/** result of AST traversal for file in project */
|
||||
result: FindClassesAnalyzerEntry[];
|
||||
}
|
||||
|
||||
export interface FindClassesAnalyzerEntry {
|
||||
/** the name of the class */
|
||||
name: string;
|
||||
/** whether the class is a mixin function */
|
||||
isMixin: boolean;
|
||||
/** super classes and mixins */
|
||||
superClasses: SuperClass[];
|
||||
members: ClassMember;
|
||||
}
|
||||
|
||||
interface ClassMember {
|
||||
props: ClassProperty;
|
||||
methods: ClassMethod;
|
||||
}
|
||||
|
||||
interface ClassProperty {
|
||||
/** class property name */
|
||||
name: string;
|
||||
/** 'public', 'protected' or 'private' */
|
||||
accessType: string;
|
||||
/** can be 'get', 'set' or both */
|
||||
kind: Array;
|
||||
/** whether property is static */
|
||||
static: boolean;
|
||||
}
|
||||
|
||||
interface ClassMethod {
|
||||
/** class method name */
|
||||
name: string;
|
||||
/** 'public', 'protected' or 'private' */
|
||||
accessType: boolean;
|
||||
}
|
||||
|
||||
export interface SuperClass {
|
||||
/** the name of the super class */
|
||||
name: string;
|
||||
/** whether the superClass is a mixin function */
|
||||
isMixin: boolean;
|
||||
rootFile: RootFile;
|
||||
}
|
||||
|
||||
export interface FindClassesConfig {
|
||||
/** search target paths */
|
||||
targetProjectPath: string;
|
||||
}
|
||||
|
||||
export interface AnalyzerConfig {
|
||||
/** search target project path */
|
||||
targetProjectPath: string;
|
||||
gatherFilesConfig: GatherFilesConfig;
|
||||
}
|
||||
|
||||
export interface MatchAnalyzerConfig extends AnalyzerConfig {
|
||||
/** reference project path, used to match reference against target */
|
||||
referenceProjectPath: string;
|
||||
}
|
||||
|
|
@ -34,7 +34,7 @@ function report(queryResult, cfg) {
|
|||
}
|
||||
|
||||
/**
|
||||
* @desc creates unique QueryConfig for analyzer turn
|
||||
* Creates unique QueryConfig for analyzer turn
|
||||
* @param {QueryConfig} queryConfig
|
||||
* @param {string} targetProjectPath
|
||||
* @param {string} referenceProjectPath
|
||||
|
|
@ -194,7 +194,7 @@ async function providenceMain(queryConfig, customConfig) {
|
|||
}
|
||||
|
||||
let queryResults;
|
||||
if (queryConfig.type === 'analyzer') {
|
||||
if (queryConfig.type === 'ast-analyzer') {
|
||||
queryResults = await handleAnalyzer(queryConfig, cfg);
|
||||
} else {
|
||||
const inputData = InputDataService.createDataObject(
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
// @ts-nocheck
|
||||
const {
|
||||
createProgram,
|
||||
getPreEmitDiagnostics,
|
||||
|
|
@ -6,17 +7,22 @@ const {
|
|||
ScriptTarget,
|
||||
} = require('typescript');
|
||||
const babelParser = require('@babel/parser');
|
||||
// @ts-expect-error
|
||||
const esModuleLexer = require('es-module-lexer');
|
||||
const parse5 = require('parse5');
|
||||
const traverseHtml = require('../utils/traverse-html.js');
|
||||
const { LogService } = require('./LogService.js');
|
||||
|
||||
/**
|
||||
* @typedef {import('../types/core').PathFromSystemRoot} PathFromSystemRoot
|
||||
*/
|
||||
|
||||
class AstService {
|
||||
/**
|
||||
* @deprecated for simplicity/maintainability, only allow Babel for js
|
||||
* Compiles an array of file paths using Typescript.
|
||||
* @param {string[]} filePaths
|
||||
* @param options
|
||||
* @param {CompilerOptions} options
|
||||
*/
|
||||
static _getTypescriptAst(filePaths, options) {
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
|
|
@ -51,7 +57,6 @@ class AstService {
|
|||
/**
|
||||
* Compiles an array of file paths using Babel.
|
||||
* @param {string} code
|
||||
* @param {object} [options]
|
||||
*/
|
||||
static _getBabelAst(code) {
|
||||
const ast = babelParser.parse(code, {
|
||||
|
|
@ -62,7 +67,7 @@ class AstService {
|
|||
}
|
||||
|
||||
/**
|
||||
* @desc Combines all script tags as if it were one js file.
|
||||
* Combines all script tags as if it were one js file.
|
||||
* @param {string} htmlCode
|
||||
*/
|
||||
static getScriptsFromHtml(htmlCode) {
|
||||
|
|
@ -86,7 +91,7 @@ class AstService {
|
|||
}
|
||||
|
||||
/**
|
||||
* @desc Returns the desired AST
|
||||
* Returns the desired AST
|
||||
* Why would we support multiple ASTs/parsers?
|
||||
* - 'babel' is our default tool for analysis. It's the most versatile and popular tool, it's
|
||||
* close to the EStree standard (other than Typescript) and a lot of plugins and resources can
|
||||
|
|
@ -95,8 +100,7 @@ class AstService {
|
|||
* - 'es-module-lexer' (deprecated) is needed for the dedicated task of finding module imports; it is way
|
||||
* quicker than a full fledged AST parser
|
||||
* @param { 'babel' } astType
|
||||
* @param { object } [options]
|
||||
* @param { string } [options.filePath] the path of the file we're trying to parse
|
||||
* @param { {filePath: PathFromSystemRoot} } [options]
|
||||
*/
|
||||
// eslint-disable-next-line consistent-return
|
||||
static getAst(code, astType, { filePath } = {}) {
|
||||
|
|
|
|||
|
|
@ -1,20 +1,49 @@
|
|||
/* eslint-disable no-param-reassign */
|
||||
// @ts-ignore-next-line
|
||||
require('../types/index.js');
|
||||
|
||||
const fs = require('fs');
|
||||
const pathLib = require('path');
|
||||
|
||||
const child_process = require('child_process'); // eslint-disable-line camelcase
|
||||
const glob = require('glob');
|
||||
const anymatch = require('anymatch');
|
||||
// @ts-expect-error
|
||||
const isNegatedGlob = require('is-negated-glob');
|
||||
const { LogService } = require('./LogService.js');
|
||||
const { AstService } = require('./AstService.js');
|
||||
const { getFilePathRelativeFromRoot } = require('../utils/get-file-path-relative-from-root.js');
|
||||
const { toPosixPath } = require('../utils/to-posix-path.js');
|
||||
|
||||
/**
|
||||
* @typedef {import('../types/analyzers').FindImportsAnalyzerResult} FindImportsAnalyzerResult
|
||||
* @typedef {import('../types/analyzers').FindImportsAnalyzerEntry} FindImportsAnalyzerEntry
|
||||
* @typedef {import('../types/core').PathRelativeFromProjectRoot} PathRelativeFromProjectRoot
|
||||
* @typedef {import('../types/core').QueryConfig} QueryConfig
|
||||
* @typedef {import('../types/core').QueryResult} QueryResult
|
||||
* @typedef {import('../types/core').FeatureQueryConfig} FeatureQueryConfig
|
||||
* @typedef {import('../types/core').SearchQueryConfig} SearchQueryConfig
|
||||
* @typedef {import('../types/core').AnalyzerQueryConfig} AnalyzerQueryConfig
|
||||
* @typedef {import('../types/core').Feature} Feature
|
||||
* @typedef {import('../types/core').AnalyzerConfig} AnalyzerConfig
|
||||
* @typedef {import('../types/core').Analyzer} Analyzer
|
||||
* @typedef {import('../types/core').AnalyzerName} AnalyzerName
|
||||
* @typedef {import('../types/core').PathFromSystemRoot} PathFromSystemRoot
|
||||
* @typedef {import('../types/core').GatherFilesConfig} GatherFilesConfig
|
||||
* @typedef {import('../types/core').AnalyzerQueryResult} AnalyzerQueryResult
|
||||
* @typedef {import('../types/core').ProjectInputData} ProjectInputData
|
||||
* @typedef {import('../types/core').ProjectInputDataWithMeta} ProjectInputDataWithMeta
|
||||
* @typedef {import('../types/core').Project} Project
|
||||
* @typedef {import('../types/core').ProjectName} ProjectName
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {{path:PathFromSystemRoot; name:ProjectName}} ProjectNameAndPath
|
||||
* @typedef {{name:ProjectName;files:PathRelativeFromProjectRoot[], workspaces:string[]}} PkgJson
|
||||
*/
|
||||
|
||||
// TODO: memoize
|
||||
|
||||
/**
|
||||
* @param {PathFromSystemRoot} rootPath
|
||||
* @returns {PkgJson|undefined}
|
||||
*/
|
||||
function getPackageJson(rootPath) {
|
||||
try {
|
||||
const fileContent = fs.readFileSync(`${rootPath}/package.json`, 'utf8');
|
||||
|
|
@ -24,6 +53,9 @@ function getPackageJson(rootPath) {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {PathFromSystemRoot} rootPath
|
||||
*/
|
||||
function getLernaJson(rootPath) {
|
||||
try {
|
||||
const fileContent = fs.readFileSync(`${rootPath}/lerna.json`, 'utf8');
|
||||
|
|
@ -35,11 +67,12 @@ function getLernaJson(rootPath) {
|
|||
|
||||
/**
|
||||
*
|
||||
* @param {string[]} list
|
||||
* @param {string} rootPath
|
||||
* @returns {{path:string, name:string}[]}
|
||||
* @param {PathFromSystemRoot[]|string[]} list
|
||||
* @param {PathFromSystemRoot} rootPath
|
||||
* @returns {ProjectNameAndPath[]}
|
||||
*/
|
||||
function getPathsFromGlobList(list, rootPath) {
|
||||
/** @type {string[]} */
|
||||
const results = [];
|
||||
list.forEach(pathOrGlob => {
|
||||
if (!pathOrGlob.endsWith('/')) {
|
||||
|
|
@ -56,15 +89,19 @@ function getPathsFromGlobList(list, rootPath) {
|
|||
results.push(pathOrGlob);
|
||||
}
|
||||
});
|
||||
return results.map(path => {
|
||||
const packageRoot = pathLib.resolve(rootPath, path);
|
||||
const basename = pathLib.basename(path);
|
||||
const pkgJson = getPackageJson(packageRoot);
|
||||
const name = (pkgJson && pkgJson.name) || basename;
|
||||
return { name, path };
|
||||
return results.map(pkgPath => {
|
||||
const packageRoot = pathLib.resolve(rootPath, pkgPath);
|
||||
const basename = pathLib.basename(pkgPath);
|
||||
const pkgJson = getPackageJson(/** @type {PathFromSystemRoot} */ (packageRoot));
|
||||
const name = /** @type {ProjectName} */ ((pkgJson && pkgJson.name) || basename);
|
||||
return { name, path: /** @type {PathFromSystemRoot} */ (pkgPath) };
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {PathFromSystemRoot} rootPath
|
||||
* @returns {string|undefined}
|
||||
*/
|
||||
function getGitignoreFile(rootPath) {
|
||||
try {
|
||||
return fs.readFileSync(`${rootPath}/.gitignore`, 'utf8');
|
||||
|
|
@ -73,6 +110,10 @@ function getGitignoreFile(rootPath) {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {PathFromSystemRoot} rootPath
|
||||
* @returns {string[]}
|
||||
*/
|
||||
function getGitIgnorePaths(rootPath) {
|
||||
const fileContent = getGitignoreFile(rootPath);
|
||||
if (!fileContent) {
|
||||
|
|
@ -91,8 +132,8 @@ function getGitIgnorePaths(rootPath) {
|
|||
});
|
||||
|
||||
// normalize entries to be compatible with anymatch
|
||||
const normalizedEntries = entries.map(e => {
|
||||
let entry = toPosixPath(e);
|
||||
const normalizedEntries = entries.map(entry => {
|
||||
entry = toPosixPath(entry);
|
||||
|
||||
if (entry.startsWith('/')) {
|
||||
entry = entry.slice(1);
|
||||
|
|
@ -110,6 +151,8 @@ function getGitIgnorePaths(rootPath) {
|
|||
|
||||
/**
|
||||
* Gives back all files and folders that need to be added to npm artifact
|
||||
* @param {PathFromSystemRoot} rootPath
|
||||
* @returns {string[]}
|
||||
*/
|
||||
function getNpmPackagePaths(rootPath) {
|
||||
const pkgJson = getPackageJson(rootPath);
|
||||
|
|
@ -129,14 +172,17 @@ function getNpmPackagePaths(rootPath) {
|
|||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {string|array} v
|
||||
* @returns {array}
|
||||
* @param {any|any[]} v
|
||||
* @returns {any[]}
|
||||
*/
|
||||
function ensureArray(v) {
|
||||
return Array.isArray(v) ? v : [v];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string|string[]} patterns
|
||||
* @param {Partial<{keepDirs:boolean;root:string}>} [options]
|
||||
*/
|
||||
function multiGlobSync(patterns, { keepDirs = false, root } = {}) {
|
||||
patterns = ensureArray(patterns);
|
||||
const res = new Set();
|
||||
|
|
@ -161,43 +207,48 @@ function multiGlobSync(patterns, { keepDirs = false, root } = {}) {
|
|||
*/
|
||||
class InputDataService {
|
||||
/**
|
||||
* @desc create an array of ProjectData
|
||||
* @param {string[]} projectPaths
|
||||
* @param {GatherFilesConfig} gatherFilesConfig
|
||||
* @returns {ProjectData}
|
||||
* Create an array of ProjectData
|
||||
* @param {PathFromSystemRoot[]} projectPaths
|
||||
* @param {Partial<GatherFilesConfig>} gatherFilesConfig
|
||||
* @returns {ProjectInputData[]}
|
||||
*/
|
||||
static createDataObject(projectPaths, gatherFilesConfig = {}) {
|
||||
/** @type {ProjectInputData[]} */
|
||||
const inputData = projectPaths.map(projectPath => ({
|
||||
project: {
|
||||
project: /** @type {Project} */ ({
|
||||
name: pathLib.basename(projectPath),
|
||||
path: projectPath,
|
||||
},
|
||||
}),
|
||||
entries: this.gatherFilesFromDir(projectPath, {
|
||||
...this.defaultGatherFilesConfig,
|
||||
...gatherFilesConfig,
|
||||
}),
|
||||
}));
|
||||
// @ts-ignore
|
||||
return this._addMetaToProjectsData(inputData);
|
||||
}
|
||||
|
||||
/**
|
||||
* From 'main/file.js' or '/main/file.js' to './main/file.js'
|
||||
* @param {string} mainEntry
|
||||
* @returns {PathRelativeFromProjectRoot}
|
||||
*/
|
||||
static __normalizeMainEntry(mainEntry) {
|
||||
if (mainEntry.startsWith('/')) {
|
||||
return `.${mainEntry}`;
|
||||
return /** @type {PathRelativeFromProjectRoot} */ (`.${mainEntry}`);
|
||||
}
|
||||
if (!mainEntry.startsWith('.')) {
|
||||
return `./${mainEntry}`;
|
||||
}
|
||||
return mainEntry;
|
||||
return /** @type {PathRelativeFromProjectRoot} */ (mainEntry);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} projectPath
|
||||
* @returns { { path:string, name?:string, mainEntry?:string, version?: string, commitHash?:string }}
|
||||
* @param {PathFromSystemRoot} projectPath
|
||||
* @returns {Project}
|
||||
*/
|
||||
static getProjectMeta(projectPath) {
|
||||
/** @type {Partial<Project>} */
|
||||
const project = { path: projectPath };
|
||||
// Add project meta info
|
||||
try {
|
||||
|
|
@ -212,12 +263,16 @@ class InputDataService {
|
|||
// eslint-disable-next-line no-empty
|
||||
project.version = pkgJson.version;
|
||||
} catch (e) {
|
||||
LogService.warn(e);
|
||||
LogService.warn(/** @type {string} */ (e));
|
||||
}
|
||||
project.commitHash = this._getCommitHash(projectPath);
|
||||
return project;
|
||||
return /** @type {Project} */ (project);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {PathFromSystemRoot} projectPath
|
||||
* @returns {string|'[not-a-git-root]'|undefined}
|
||||
*/
|
||||
static _getCommitHash(projectPath) {
|
||||
let commitHash;
|
||||
let isGitRepo;
|
||||
|
|
@ -237,7 +292,7 @@ class InputDataService {
|
|||
// eslint-disable-next-line no-param-reassign
|
||||
commitHash = hash;
|
||||
} catch (e) {
|
||||
LogService.warn(e);
|
||||
LogService.warn(/** @type {string} */ (e));
|
||||
}
|
||||
} else {
|
||||
commitHash = '[not-a-git-root]';
|
||||
|
|
@ -246,12 +301,16 @@ class InputDataService {
|
|||
}
|
||||
|
||||
/**
|
||||
* @desc adds context with code (c.q. file contents), project name and project 'main' entry
|
||||
* @param {InputData} inputData
|
||||
* Adds context with code (c.q. file contents), project name and project 'main' entry
|
||||
* @param {ProjectInputData[]} inputData
|
||||
* @returns {ProjectInputDataWithMeta[]}
|
||||
*/
|
||||
static _addMetaToProjectsData(inputData) {
|
||||
return inputData.map(projectObj => {
|
||||
return /** @type {* & ProjectInputDataWithMeta[]} */ (
|
||||
inputData.map(projectObj => {
|
||||
// Add context obj with 'code' to files
|
||||
|
||||
/** @type {ProjectInputDataWithMeta['entries'][]} */
|
||||
const newEntries = [];
|
||||
projectObj.entries.forEach(entry => {
|
||||
const code = fs.readFileSync(entry, 'utf8');
|
||||
|
|
@ -263,7 +322,10 @@ class InputDataService {
|
|||
const extractedScripts = AstService.getScriptsFromHtml(code);
|
||||
// eslint-disable-next-line no-shadow
|
||||
extractedScripts.forEach((code, i) => {
|
||||
newEntries.push({ file: `${file}#${i}`, context: { code } });
|
||||
newEntries.push({
|
||||
file: /** @type {PathRelativeFromProjectRoot} */ (`${file}#${i}`),
|
||||
context: { code },
|
||||
});
|
||||
});
|
||||
} else {
|
||||
newEntries.push({ file, context: { code } });
|
||||
|
|
@ -273,15 +335,15 @@ class InputDataService {
|
|||
const project = this.getProjectMeta(toPosixPath(projectObj.project.path));
|
||||
|
||||
return { project, entries: newEntries };
|
||||
});
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
// TODO: rename to `get targetProjectPaths`
|
||||
/**
|
||||
* @desc gets all project directories/paths from './submodules'
|
||||
* @returns {string[]} a list of strings representing all entry paths for projects we want to query
|
||||
* Gets all project directories/paths from './submodules'
|
||||
* @type {PathFromSystemRoot[]} a list of strings representing all entry paths for projects we want to query
|
||||
*/
|
||||
static getTargetProjectPaths() {
|
||||
static get targetProjectPaths() {
|
||||
if (this.__targetProjectPaths) {
|
||||
return this.__targetProjectPaths;
|
||||
}
|
||||
|
|
@ -296,10 +358,13 @@ class InputDataService {
|
|||
return [];
|
||||
}
|
||||
return dirs
|
||||
.map(dir => pathLib.join(submoduleDir, dir))
|
||||
.map(dir => /** @type {PathFromSystemRoot} */ (pathLib.join(submoduleDir, dir)))
|
||||
.filter(dirPath => fs.lstatSync(dirPath).isDirectory());
|
||||
}
|
||||
|
||||
/**
|
||||
* @type {PathFromSystemRoot[]} a list of strings representing all entry paths for projects we want to query
|
||||
*/
|
||||
static get referenceProjectPaths() {
|
||||
if (this.__referenceProjectPaths) {
|
||||
return this.__referenceProjectPaths;
|
||||
|
|
@ -314,7 +379,7 @@ class InputDataService {
|
|||
.filter(dirPath => fs.lstatSync(dirPath).isDirectory());
|
||||
// eslint-disable-next-line no-empty
|
||||
} catch (_) {}
|
||||
return dirs;
|
||||
return /** @type {PathFromSystemRoot[]} */ (dirs);
|
||||
}
|
||||
|
||||
static set referenceProjectPaths(v) {
|
||||
|
|
@ -325,6 +390,9 @@ class InputDataService {
|
|||
this.__targetProjectPaths = ensureArray(v);
|
||||
}
|
||||
|
||||
/**
|
||||
* @type {GatherFilesConfig}
|
||||
*/
|
||||
static get defaultGatherFilesConfig() {
|
||||
return {
|
||||
extensions: ['.js'],
|
||||
|
|
@ -333,32 +401,32 @@ class InputDataService {
|
|||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {PathFromSystemRoot} startPath
|
||||
* @param {GatherFilesConfig} cfg
|
||||
* @param {boolean} withoutDepth
|
||||
*/
|
||||
static getGlobPattern(startPath, cfg, withoutDepth = false) {
|
||||
// if startPath ends with '/', remove
|
||||
let globPattern = startPath.replace(/\/$/, '');
|
||||
// let globPattern = '';
|
||||
if (process.platform === 'win32') {
|
||||
// root = root.replace(/^.\:/, '').replace(/\\/g, '/');
|
||||
globPattern = globPattern.replace(/^.:/, '').replace(/\\/g, '/');
|
||||
}
|
||||
if (!withoutDepth) {
|
||||
if (cfg.depth !== Infinity) {
|
||||
if (typeof cfg.depth === 'number' && cfg.depth !== Infinity) {
|
||||
globPattern += `/*`.repeat(cfg.depth + 1);
|
||||
} else {
|
||||
globPattern += `/**/*`;
|
||||
}
|
||||
}
|
||||
// globPattern = globPattern.slice(1)
|
||||
return { globPattern };
|
||||
}
|
||||
|
||||
/**
|
||||
* @desc Gets an array of files for given extension
|
||||
* @param {string} startPath - local filesystem path
|
||||
* @param {GatherFilesConfig} customConfig - configuration object
|
||||
* @param {number} [customConfig.depth=Infinity] how many recursive calls should be made
|
||||
* @param {string[]} [result] - list of file paths, for internal (recursive) calls
|
||||
* @returns {string[]} result list of file paths
|
||||
* Gets an array of files for given extension
|
||||
* @param {PathFromSystemRoot} startPath - local filesystem path
|
||||
* @param {Partial<GatherFilesConfig>} customConfig - configuration object
|
||||
* @returns {PathFromSystemRoot[]} result list of file paths
|
||||
*/
|
||||
static gatherFilesFromDir(startPath, customConfig = {}) {
|
||||
const cfg = {
|
||||
|
|
@ -380,7 +448,9 @@ class InputDataService {
|
|||
);
|
||||
}
|
||||
|
||||
/** @type {string[]} */
|
||||
let gitIgnorePaths = [];
|
||||
/** @type {string[]} */
|
||||
let npmPackagePaths = [];
|
||||
|
||||
const hasGitIgnore = getGitignoreFile(startPath);
|
||||
|
|
@ -411,15 +481,19 @@ class InputDataService {
|
|||
if (removeFilter.length || keepFilter.length) {
|
||||
filteredGlobRes = globRes.filter(filePath => {
|
||||
const localFilePath = toPosixPath(filePath).replace(`${toPosixPath(startPath)}/`, '');
|
||||
// @ts-expect-error
|
||||
let shouldRemove = removeFilter.length && anymatch(removeFilter, localFilePath);
|
||||
// @ts-expect-error
|
||||
let shouldKeep = keepFilter.length && anymatch(keepFilter, localFilePath);
|
||||
|
||||
if (shouldRemove && shouldKeep) {
|
||||
// Contradicting configs: the one defined by end user takes highest precedence
|
||||
// If the match came from allowListMode, it loses.
|
||||
// @ts-expect-error
|
||||
if (allowlistMode === 'git' && anymatch(gitIgnorePaths, localFilePath)) {
|
||||
// shouldRemove was caused by .gitignore, shouldKeep by custom allowlist
|
||||
shouldRemove = false;
|
||||
// @ts-expect-error
|
||||
} else if (allowlistMode === 'npm' && anymatch(npmPackagePaths, localFilePath)) {
|
||||
// shouldKeep was caused by npm "files", shouldRemove by custom allowlist
|
||||
shouldKeep = false;
|
||||
|
|
@ -438,15 +512,17 @@ class InputDataService {
|
|||
|
||||
if (!filteredGlobRes || !filteredGlobRes.length) {
|
||||
LogService.warn(`No files found for path '${startPath}'`);
|
||||
return [];
|
||||
}
|
||||
|
||||
// reappend startPath
|
||||
// const res = filteredGlobRes.map(f => pathLib.resolve(startPath, f));
|
||||
return filteredGlobRes.map(toPosixPath);
|
||||
return /** @type {PathFromSystemRoot[]} */ (filteredGlobRes.map(toPosixPath));
|
||||
}
|
||||
|
||||
// TODO: use modern web config helper
|
||||
/**
|
||||
* @desc Allows the user to provide a providence.conf.js file in its repository root
|
||||
* Allows the user to provide a providence.conf.js file in its repository root
|
||||
*/
|
||||
static getExternalConfig() {
|
||||
throw new Error(
|
||||
|
|
@ -456,6 +532,8 @@ class InputDataService {
|
|||
|
||||
/**
|
||||
* Gives back all monorepo package paths
|
||||
* @param {PathFromSystemRoot} rootPath
|
||||
* @returns {ProjectNameAndPath[]|undefined}
|
||||
*/
|
||||
static getMonoRepoPackages(rootPath) {
|
||||
// [1] Look for npm/yarn workspaces
|
||||
|
|
|
|||
|
|
@ -3,44 +3,85 @@ const chalk = require('chalk');
|
|||
const ora = require('ora');
|
||||
const fs = require('fs');
|
||||
|
||||
/**
|
||||
* @typedef {import('ora').Ora} Ora
|
||||
*/
|
||||
|
||||
const { log } = console;
|
||||
|
||||
/**
|
||||
* @param {string} [title]
|
||||
* @returns {string}
|
||||
*/
|
||||
function printTitle(title) {
|
||||
return `${title ? `${title}\n` : ''}`;
|
||||
}
|
||||
|
||||
/** @type {Ora} */
|
||||
let spinner;
|
||||
|
||||
class LogService {
|
||||
/**
|
||||
* @param {string} text
|
||||
* @param {string} [title]
|
||||
*/
|
||||
static debug(text, title) {
|
||||
if (!this.debugEnabled) return;
|
||||
log(chalk.bgCyanBright.black.bold(` debug${printTitle(title)}`), text);
|
||||
// @ts-ignore
|
||||
this._logHistory.push(`- debug -${printTitle(title)} ${text}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} text
|
||||
* @param {string} [title]
|
||||
*/
|
||||
static warn(text, title) {
|
||||
log(chalk.bgYellowBright.black.bold(`warning${printTitle(title)}`), text);
|
||||
// @ts-ignore
|
||||
this._logHistory.push(`- warning -${printTitle(title)} ${text}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} text
|
||||
* @param {string} [title]
|
||||
*/
|
||||
static error(text, title) {
|
||||
log(chalk.bgRedBright.black.bold(` error${printTitle(title)}`), text);
|
||||
// @ts-ignore
|
||||
this._logHistory.push(`- error -${printTitle(title)} ${text}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} text
|
||||
* @param {string} [title]
|
||||
*/
|
||||
static success(text, title) {
|
||||
log(chalk.bgGreen.black.bold(`success${printTitle(title)}`), text);
|
||||
// @ts-ignore
|
||||
this._logHistory.push(`- success -${printTitle(title)} ${text}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} text
|
||||
* @param {string} [title]
|
||||
*/
|
||||
static info(text, title) {
|
||||
log(chalk.bgBlue.black.bold(` info${printTitle(title)}`), text);
|
||||
// @ts-ignore
|
||||
this._logHistory.push(`- info -${printTitle(title)} ${text}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} text
|
||||
*/
|
||||
static spinnerStart(text) {
|
||||
spinner = ora(text).start();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} text
|
||||
*/
|
||||
static spinnerText(text) {
|
||||
if (!spinner) {
|
||||
this.spinnerStart(text);
|
||||
|
|
@ -56,9 +97,13 @@ class LogService {
|
|||
return spinner;
|
||||
}
|
||||
|
||||
static pad(str, minChars = 20) {
|
||||
let result = str;
|
||||
const padding = minChars - str.length;
|
||||
/**
|
||||
* @param {string} text
|
||||
* @param {number} minChars
|
||||
*/
|
||||
static pad(text, minChars = 20) {
|
||||
let result = text;
|
||||
const padding = minChars - text.length;
|
||||
if (padding > 0) {
|
||||
result += ' '.repeat(padding);
|
||||
}
|
||||
|
|
@ -68,15 +113,18 @@ class LogService {
|
|||
static writeLogFile() {
|
||||
const filePath = pathLib.join(process.cwd(), 'providence.log');
|
||||
let file = `[log ${new Date()}]\n`;
|
||||
// @ts-ignore
|
||||
this._logHistory.forEach(l => {
|
||||
file += `${l}\n`;
|
||||
});
|
||||
file += `[/log ${new Date()}]\n\n`;
|
||||
fs.writeFileSync(filePath, file, { flag: 'a' });
|
||||
// @ts-ignore
|
||||
this._logHistory = [];
|
||||
}
|
||||
}
|
||||
LogService.debugEnabled = false;
|
||||
/** @type {string[]} */
|
||||
LogService._logHistory = [];
|
||||
|
||||
module.exports = { LogService };
|
||||
|
|
|
|||
|
|
@ -1,35 +1,55 @@
|
|||
// @ts-ignore-next-line
|
||||
require('../types/index.js');
|
||||
|
||||
const deepmerge = require('deepmerge');
|
||||
const child_process = require('child_process'); // eslint-disable-line camelcase
|
||||
const { AstService } = require('./AstService.js');
|
||||
const { LogService } = require('./LogService.js');
|
||||
const { getFilePathRelativeFromRoot } = require('../utils/get-file-path-relative-from-root.js');
|
||||
|
||||
/**
|
||||
* @typedef {import('../types/analyzers').FindImportsAnalyzerResult} FindImportsAnalyzerResult
|
||||
* @typedef {import('../types/analyzers').FindImportsAnalyzerEntry} FindImportsAnalyzerEntry
|
||||
* @typedef {import('../types/core').PathRelativeFromProjectRoot} PathRelativeFromProjectRoot
|
||||
* @typedef {import('../types/core').QueryConfig} QueryConfig
|
||||
* @typedef {import('../types/core').QueryResult} QueryResult
|
||||
* @typedef {import('../types/core').FeatureQueryConfig} FeatureQueryConfig
|
||||
* @typedef {import('../types/core').SearchQueryConfig} SearchQueryConfig
|
||||
* @typedef {import('../types/core').AnalyzerQueryConfig} AnalyzerQueryConfig
|
||||
* @typedef {import('../types/core').Feature} Feature
|
||||
* @typedef {import('../types/core').AnalyzerConfig} AnalyzerConfig
|
||||
* @typedef {import('../types/core').Analyzer} Analyzer
|
||||
* @typedef {import('../types/core').AnalyzerName} AnalyzerName
|
||||
* @typedef {import('../types/core').PathFromSystemRoot} PathFromSystemRoot
|
||||
* @typedef {import('../types/core').GatherFilesConfig} GatherFilesConfig
|
||||
* @typedef {import('../types/core').AnalyzerQueryResult} AnalyzerQueryResult
|
||||
* @typedef {import('../types/core').ProjectInputData} ProjectInputData
|
||||
*/
|
||||
|
||||
const astProjectsDataCache = new Map();
|
||||
|
||||
class QueryService {
|
||||
/**
|
||||
* @param {string} regexString string for 'free' regex searches.
|
||||
* @returns {QueryConfig}
|
||||
* @returns {SearchQueryConfig}
|
||||
*/
|
||||
static getQueryConfigFromRegexSearchString(regexString) {
|
||||
return { type: 'search', regexString };
|
||||
}
|
||||
|
||||
/**
|
||||
* @desc Util function that can be used to parse cli input and feed the result object to a new
|
||||
* Util function that can be used to parse cli input and feed the result object to a new
|
||||
* instance of QueryResult
|
||||
* @example
|
||||
* const queryConfig = QueryService.getQueryConfigFromFeatureString(”tg-icon[size=xs]”)
|
||||
* const myQueryResult = QueryService.grepSearch(inputData, queryConfig)
|
||||
* @param {string} queryString - string like ”tg-icon[size=xs]”
|
||||
* @returns {QueryConfig}
|
||||
* @returns {FeatureQueryConfig}
|
||||
*/
|
||||
static getQueryConfigFromFeatureString(queryString) {
|
||||
/**
|
||||
* @param {string} candidate
|
||||
* @returns {[string, boolean]}
|
||||
*/
|
||||
function parseContains(candidate) {
|
||||
const hasAsterisk = candidate ? candidate.endsWith('*') : null;
|
||||
const hasAsterisk = candidate ? candidate.endsWith('*') : false;
|
||||
const filtered = hasAsterisk ? candidate.slice(0, -1) : candidate;
|
||||
return [filtered, hasAsterisk];
|
||||
}
|
||||
|
|
@ -67,16 +87,17 @@ class QueryService {
|
|||
};
|
||||
} else {
|
||||
// Just look for tag name
|
||||
featureObj = { tag, usesTagPartialMatch };
|
||||
featureObj = /** @type {Feature} */ ({ tag, usesTagPartialMatch });
|
||||
}
|
||||
|
||||
return { type: 'feature', feature: featureObj };
|
||||
}
|
||||
|
||||
/**
|
||||
* @desc retrieves the default export found in ./program/analyzers/findImport.js
|
||||
* @param {string|Analyzer} analyzer
|
||||
* @returns {QueryConfig}
|
||||
* RSetrieves the default export found in ./program/analyzers/findImport.js
|
||||
* @param {string|Analyzer} analyzerObjectOrString
|
||||
* @param {AnalyzerConfig} analyzerConfig
|
||||
* @returns {AnalyzerQueryConfig}
|
||||
*/
|
||||
static getQueryConfigFromAnalyzer(analyzerObjectOrString, analyzerConfig) {
|
||||
let analyzer;
|
||||
|
|
@ -85,7 +106,7 @@ class QueryService {
|
|||
// Mainly needed when this method is called via cli
|
||||
try {
|
||||
// eslint-disable-next-line import/no-dynamic-require, global-require
|
||||
analyzer = require(`../analyzers/${analyzerObjectOrString}`);
|
||||
analyzer = /** @type {Analyzer} */ (require(`../analyzers/${analyzerObjectOrString}`));
|
||||
} catch (e) {
|
||||
LogService.error(e);
|
||||
process.exit(1);
|
||||
|
|
@ -95,8 +116,8 @@ class QueryService {
|
|||
analyzer = analyzerObjectOrString;
|
||||
}
|
||||
return {
|
||||
type: 'analyzer',
|
||||
analyzerName: analyzer.name,
|
||||
type: 'ast-analyzer',
|
||||
analyzerName: /** @type {AnalyzerName} */ (analyzer.name),
|
||||
analyzerConfig,
|
||||
analyzer,
|
||||
};
|
||||
|
|
@ -169,27 +190,27 @@ class QueryService {
|
|||
}
|
||||
|
||||
/**
|
||||
* @desc Search via ast (typescript compilation)
|
||||
* @param {QueryConfig} queryConfig
|
||||
* Search via ast (typescript compilation)
|
||||
* @param {AnalyzerQueryConfig} analyzerQueryConfig
|
||||
* @param {AnalyzerConfig} [customConfig]
|
||||
* @param {GatherFilesConfig} [customConfig.gatherFilesConfig]
|
||||
* @returns {QueryResult}
|
||||
* @returns {Promise<AnalyzerQueryResult>}
|
||||
*/
|
||||
static async astSearch(queryConfig, customConfig) {
|
||||
static async astSearch(analyzerQueryConfig, customConfig) {
|
||||
LogService.debug('started astSearch method');
|
||||
if (queryConfig.type !== 'analyzer') {
|
||||
if (analyzerQueryConfig.type !== 'ast-analyzer') {
|
||||
LogService.error('Only analyzers supported for ast searches at the moment');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// @ts-ignore
|
||||
// eslint-disable-next-line new-cap
|
||||
const analyzer = new queryConfig.analyzer();
|
||||
const analyzer = new analyzerQueryConfig.analyzer();
|
||||
const analyzerResult = await analyzer.execute(customConfig);
|
||||
if (!analyzerResult) {
|
||||
return analyzerResult;
|
||||
}
|
||||
const { queryOutput, analyzerMeta } = analyzerResult;
|
||||
const /** @type {QueryResult} */ queryResult = {
|
||||
const /** @type {AnalyzerQueryResult} */ queryResult = {
|
||||
meta: {
|
||||
searchType: 'ast-analyzer',
|
||||
analyzerMeta,
|
||||
|
|
@ -200,7 +221,7 @@ class QueryService {
|
|||
}
|
||||
|
||||
/**
|
||||
* @param {ProjectData[]} projectsData
|
||||
* @param {ProjectInputData[]} projectsData
|
||||
* @param {'babel'|'typescript'|'es-module-lexer'} requiredAst
|
||||
*/
|
||||
static async addAstToProjectsData(projectsData, requiredAst) {
|
||||
|
|
@ -242,13 +263,8 @@ class QueryService {
|
|||
}
|
||||
|
||||
/**
|
||||
* @desc Performs a grep on given path for a certain tag name and feature
|
||||
* @param {string} searchPath - the project path to search in
|
||||
* Performs a grep on given path for a certain tag name and feature
|
||||
* @param {Feature} feature
|
||||
* @param {object} [customConfig]
|
||||
* @param {boolean} [customConfig.count] - enable wordcount in grep
|
||||
* @param {GatherFilesConfig} [customConfig.gatherFilesConfig] - extensions, excludes
|
||||
* @param {boolean} [customConfig.hasDebugEnabled]
|
||||
*/
|
||||
static _getFeatureRegex(feature) {
|
||||
const { name, value, tag } = feature;
|
||||
|
|
@ -287,6 +303,13 @@ class QueryService {
|
|||
return regex;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {PathFromSystemRoot} searchPath
|
||||
* @param {string} regex
|
||||
* @param {{ count:number; gatherFilesConfig:GatherFilesConfig; hasDebugEnabled:boolean }} customConfig
|
||||
* @returns
|
||||
*/
|
||||
static _performGrep(searchPath, regex, customConfig) {
|
||||
const cfg = deepmerge(
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,21 +1,28 @@
|
|||
// @ts-ignore-next-line
|
||||
require('../types/index.js');
|
||||
|
||||
const fs = require('fs');
|
||||
const pathLib = require('path');
|
||||
const getHash = require('../utils/get-hash.js');
|
||||
|
||||
/**
|
||||
* @desc Should be used to write results to and read results from the file system.
|
||||
* @typedef {import('../types/core').Project} Project
|
||||
* @typedef {import('../types/core').ProjectName} ProjectName
|
||||
* @typedef {import('../types/core').AnalyzerQueryResult} AnalyzerQueryResult
|
||||
* @typedef {import('../types/core').AnalyzerConfig} AnalyzerConfig
|
||||
* @typedef {import('../types/core').AnalyzerName} AnalyzerName
|
||||
* @typedef {import('../types/core').QueryResult} QueryResult
|
||||
* @typedef {import('../types/core').PathFromSystemRoot} PathFromSystemRoot
|
||||
*/
|
||||
|
||||
/**
|
||||
* Should be used to write results to and read results from the file system.
|
||||
* Creates a unique identifier based on searchP, refP (optional) and an already created
|
||||
* @param {object} searchP search target project meta
|
||||
* @param {object} cfg configuration used for analyzer
|
||||
* @param {object} [refP] reference project meta
|
||||
* @param {Project} searchP search target project meta
|
||||
* @param {AnalyzerConfig} cfg configuration used for analyzer
|
||||
* @param {Project} [refP] reference project meta
|
||||
* @returns {string} identifier
|
||||
*/
|
||||
function createResultIdentifier(searchP, cfg, refP) {
|
||||
// why encodeURIComponent: filters out slashes for path names for stuff like @lion/button
|
||||
const format = p =>
|
||||
const format = (/** @type {Project} */ p) =>
|
||||
`${encodeURIComponent(p.name)}_${p.version || (p.commitHash && p.commitHash.slice(0, 5))}`;
|
||||
const cfgHash = getHash(cfg);
|
||||
return `${format(searchP)}${refP ? `_+_${format(refP)}` : ''}__${cfgHash}`;
|
||||
|
|
@ -23,7 +30,6 @@ function createResultIdentifier(searchP, cfg, refP) {
|
|||
|
||||
class ReportService {
|
||||
/**
|
||||
* @desc
|
||||
* Prints queryResult report to console
|
||||
* @param {QueryResult} queryResult
|
||||
*/
|
||||
|
|
@ -38,15 +44,14 @@ class ReportService {
|
|||
}
|
||||
|
||||
/**
|
||||
* @desc
|
||||
* Prints queryResult report as JSON to outputPath
|
||||
* @param {QueryResult} queryResult
|
||||
* @param {AnalyzerQueryResult} queryResult
|
||||
* @param {string} [identifier]
|
||||
* @param {string} [outputPath]
|
||||
*/
|
||||
static writeToJson(
|
||||
queryResult,
|
||||
identifier = new Date().getTime() / 1000,
|
||||
identifier = (new Date().getTime() / 1000).toString(),
|
||||
outputPath = this.outputPath,
|
||||
) {
|
||||
const output = JSON.stringify(queryResult, null, 2);
|
||||
|
|
@ -58,18 +63,29 @@ class ReportService {
|
|||
fs.writeFileSync(filePath, output, { flag: 'w' });
|
||||
}
|
||||
|
||||
static set outputPath(p) {
|
||||
this.__outputPath = p;
|
||||
}
|
||||
|
||||
/**
|
||||
* @type {string}
|
||||
*/
|
||||
static get outputPath() {
|
||||
return this.__outputPath || pathLib.join(process.cwd(), '/providence-output');
|
||||
}
|
||||
|
||||
static set outputPath(p) {
|
||||
this.__outputPath = p;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {{ targetProject: Project; referenceProject: Project; analyzerConfig: AnalyzerConfig }} options
|
||||
* @returns {string}
|
||||
*/
|
||||
static createIdentifier({ targetProject, referenceProject, analyzerConfig }) {
|
||||
return createResultIdentifier(targetProject, analyzerConfig, referenceProject);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {{analyzerName: AnalyzerName; identifier: string}} options
|
||||
* @returns {QueryResult}
|
||||
*/
|
||||
static getCachedResult({ analyzerName, identifier }) {
|
||||
let cachedResult;
|
||||
try {
|
||||
|
|
@ -81,10 +97,21 @@ class ReportService {
|
|||
return cachedResult;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} name
|
||||
* @param {string} identifier
|
||||
* @returns {PathFromSystemRoot}
|
||||
*/
|
||||
static _getResultFileNameAndPath(name, identifier) {
|
||||
return pathLib.join(this.outputPath, `${name || 'query'}_-_${identifier}.json`);
|
||||
return /** @type {PathFromSystemRoot} */ (
|
||||
pathLib.join(this.outputPath, `${name || 'query'}_-_${identifier}.json`)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {ProjectName} depProj
|
||||
* @param {Project} rootProjectMeta
|
||||
*/
|
||||
static writeEntryToSearchTargetDepsFile(depProj, rootProjectMeta) {
|
||||
const rootProj = `${rootProjectMeta.name}#${rootProjectMeta.version}`;
|
||||
const filePath = pathLib.join(this.outputPath, 'search-target-deps-file.json');
|
||||
|
|
|
|||
63
packages-node/providence-analytics/src/program/types/analyzers/find-classes.d.ts
vendored
Normal file
63
packages-node/providence-analytics/src/program/types/analyzers/find-classes.d.ts
vendored
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
import {
|
||||
PathFromSystemRoot,
|
||||
IdentifierName,
|
||||
RootFile,
|
||||
AnalyzerQueryResult,
|
||||
FindAnalyzerOutputFile,
|
||||
} from '../core';
|
||||
|
||||
export interface FindClassesAnalyzerResult extends AnalyzerQueryResult {
|
||||
queryOutput: FindClassesAnalyzerOutputFile[];
|
||||
}
|
||||
|
||||
export interface FindClassesAnalyzerOutputFile extends FindAnalyzerOutputFile {
|
||||
/** result of AST traversal for file in project */
|
||||
result: FindClassesAnalyzerEntry[];
|
||||
}
|
||||
|
||||
export interface FindClassesAnalyzerEntry {
|
||||
/** the name of the class */
|
||||
name: IdentifierName;
|
||||
/** whether the class is a mixin function */
|
||||
isMixin: boolean;
|
||||
/** super classes and mixins */
|
||||
superClasses: SuperClass[];
|
||||
members: ClassMember;
|
||||
}
|
||||
|
||||
interface ClassMember {
|
||||
props: ClassProperty;
|
||||
methods: ClassMethod;
|
||||
}
|
||||
|
||||
type AccessType = 'public' | 'protected' | 'private';
|
||||
|
||||
interface ClassProperty {
|
||||
/** class property name */
|
||||
name: IdentifierName;
|
||||
/** 'public', 'protected' or 'private' */
|
||||
accessType: AccessType;
|
||||
/** can be 'get', 'set' or both */
|
||||
kind: ('get' | 'set')[];
|
||||
/** whether property is static */
|
||||
static: boolean;
|
||||
}
|
||||
|
||||
interface ClassMethod {
|
||||
/** class method name */
|
||||
name: IdentifierName;
|
||||
accessType: AccessType;
|
||||
}
|
||||
|
||||
export interface SuperClass {
|
||||
/** the name of the super class */
|
||||
name: IdentifierName;
|
||||
/** whether the superClass is a mixin function */
|
||||
isMixin: boolean;
|
||||
rootFile: RootFile;
|
||||
}
|
||||
|
||||
export interface FindClassesConfig {
|
||||
/** search target paths */
|
||||
targetProjectPath: PathFromSystemRoot;
|
||||
}
|
||||
33
packages-node/providence-analytics/src/program/types/analyzers/find-customelements.d.ts
vendored
Normal file
33
packages-node/providence-analytics/src/program/types/analyzers/find-customelements.d.ts
vendored
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
import {
|
||||
PathRelativeFromProjectRoot,
|
||||
IdentifierName,
|
||||
RootFile,
|
||||
AnalyzerQueryResult,
|
||||
FindAnalyzerOutputFile,
|
||||
} from '../core';
|
||||
|
||||
export interface FindCustomelementsAnalyzerResult extends AnalyzerQueryResult {
|
||||
queryOutput: FindCustomelementsAnalyzerOutputFile[];
|
||||
}
|
||||
|
||||
export interface FindCustomelementsAnalyzerOutputFile extends FindAnalyzerOutputFile {
|
||||
/** path relative from project root for which a result is generated based on AST traversal */
|
||||
file: PathRelativeFromProjectRoot;
|
||||
/** result of AST traversal for file in project */
|
||||
result: FindCustomelementsAnalyzerEntry[];
|
||||
}
|
||||
|
||||
export interface FindCustomelementsAnalyzerEntry {
|
||||
/**
|
||||
* Tag name found in CE definition:
|
||||
* `customElements.define('my-name', MyConstructor)` => 'my-name'
|
||||
*/
|
||||
tagName: string;
|
||||
/**
|
||||
* Identifier found in CE definition:
|
||||
* `customElements.define('my-name', MyConstructor)` => MyConstructor
|
||||
*/
|
||||
constructorIdentifier: IdentifierName;
|
||||
/** Rootfile traced for constuctorIdentifier found in CE definition */
|
||||
rootFile: RootFile;
|
||||
}
|
||||
67
packages-node/providence-analytics/src/program/types/analyzers/find-exports.d.ts
vendored
Normal file
67
packages-node/providence-analytics/src/program/types/analyzers/find-exports.d.ts
vendored
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
import {
|
||||
SpecifierName,
|
||||
SpecifierSource,
|
||||
PathRelativeFromProjectRoot,
|
||||
RootFileMapEntry,
|
||||
RootFile,
|
||||
AnalyzerQueryResult,
|
||||
FindAnalyzerOutputFile,
|
||||
} from '../core';
|
||||
|
||||
export interface FindExportsAnalyzerResult extends AnalyzerQueryResult {
|
||||
queryOutput: FindExportsAnalyzerOutputFile[];
|
||||
}
|
||||
|
||||
export interface FindExportsAnalyzerOutputFile extends FindAnalyzerOutputFile {
|
||||
/** path relative from project root for which a result is generated based on AST traversal */
|
||||
file: PathRelativeFromProjectRoot;
|
||||
/** result of AST traversal for file in project */
|
||||
result: FindExportsAnalyzerEntry[];
|
||||
}
|
||||
|
||||
export interface FindExportsAnalyzerEntry {
|
||||
/**
|
||||
* The specifiers found in an export statement.
|
||||
*
|
||||
* For example:
|
||||
* - file `export class X {}` gives `['X']`
|
||||
* - file `export default const y = 0` gives `['[default]']`
|
||||
* - file `export { y, z } from 'project'` gives `['y', 'z']`
|
||||
*/
|
||||
exportSpecifiers: SpecifierName[];
|
||||
/**
|
||||
* The original "source" string belonging to specifier.
|
||||
* For example:
|
||||
* - file `export { x } from './my/file';` gives `"./my/file"`
|
||||
* - file `export { x } from 'project';` gives `"project"`
|
||||
*/
|
||||
source: SpecifierSource;
|
||||
/**
|
||||
* The normalized "source" string belonging to specifier
|
||||
* (based on file system information, resolves right names and extensions).
|
||||
* For example:
|
||||
* - file `export { x } from './my/file';` gives `"./my/file.js"`
|
||||
* - file `export { x } from 'project';` gives `"project"` (only files in current project are resolved)
|
||||
* - file `export { x } from '../';` gives `"../index.js"`
|
||||
*/
|
||||
normalizedSource: SpecifierSource;
|
||||
/** map of tracked down Identifiers */
|
||||
rootFileMap: RootFileMapEntry[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Iterable version of `FindExportsAnalyzerEntry`.
|
||||
* Makes it easier to do comparisons inside MatchAnalyzers
|
||||
*/
|
||||
export interface IterableFindExportsAnalyzerEntry {
|
||||
file: PathRelativeFromProjectRoot;
|
||||
specifier: SpecifierName;
|
||||
/**
|
||||
* The local name of an export. Example:
|
||||
* 'a' in case of `export {a as b} from 'c';`
|
||||
*/
|
||||
localSpecifier: SpecifierName;
|
||||
source: SpecifierSource | null;
|
||||
rootFile: RootFile;
|
||||
meta?: object;
|
||||
}
|
||||
58
packages-node/providence-analytics/src/program/types/analyzers/find-imports.d.ts
vendored
Normal file
58
packages-node/providence-analytics/src/program/types/analyzers/find-imports.d.ts
vendored
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
import {
|
||||
SpecifierName,
|
||||
SpecifierSource,
|
||||
PathRelativeFromProjectRoot,
|
||||
AnalyzerQueryResult,
|
||||
FindAnalyzerOutputFile,
|
||||
} from '../core';
|
||||
|
||||
export interface FindImportsAnalyzerResult extends AnalyzerQueryResult {
|
||||
queryOutput: FindImportsAnalyzerOutputFile[];
|
||||
}
|
||||
|
||||
export interface FindImportsAnalyzerOutputFile extends FindAnalyzerOutputFile {
|
||||
/** result of AST traversal for file in project */
|
||||
result: FindImportsAnalyzerEntry[];
|
||||
}
|
||||
|
||||
export interface FindImportsAnalyzerEntry {
|
||||
/**
|
||||
* The specifiers found in an import statement.
|
||||
*
|
||||
* For example:
|
||||
* - file `import { X } from 'project'` gives `['X']`
|
||||
* - file `import X from 'project'` gives `['[default]']`
|
||||
* - file `import x, { y, z } from 'project'` gives `['[default]', 'y', 'z']`
|
||||
*/
|
||||
importSpecifiers: SpecifierName[];
|
||||
/**
|
||||
* The original "source" string belonging to specifier.
|
||||
* For example:
|
||||
* - file `import { x } from './my/file';` gives `"./my/file"`
|
||||
* - file `import { x } from 'project';` gives `"project"`
|
||||
*/
|
||||
source: SpecifierSource;
|
||||
/**
|
||||
* The normalized "source" string belonging to specifier
|
||||
* (based on file system information, resolves right names and extensions).
|
||||
* For example:
|
||||
* - file `import { x } from './my/file';` gives `"./my/file.js"`
|
||||
* - file `import { x } from 'project';` gives `"project"` (only files in current project are resolved)
|
||||
* - file `import { x } from '../';` gives `"../index.js"`
|
||||
*/
|
||||
normalizedSource: SpecifierSource;
|
||||
}
|
||||
|
||||
/**
|
||||
* Iterable version of `FindExportsAnalyzerEntry`.
|
||||
* Makes it easier to do comparisons inside MatchAnalyzers
|
||||
*/
|
||||
export interface IterableFindImportsAnalyzerEntry {
|
||||
file: PathRelativeFromProjectRoot;
|
||||
specifier: SpecifierName;
|
||||
source: SpecifierSource;
|
||||
/**
|
||||
* Resolved `SpecifierSource` o relative file path
|
||||
*/
|
||||
normalizedSource: SpecifierSource;
|
||||
}
|
||||
6
packages-node/providence-analytics/src/program/types/analyzers/index.d.ts
vendored
Normal file
6
packages-node/providence-analytics/src/program/types/analyzers/index.d.ts
vendored
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
export * from './find-classes';
|
||||
export * from './find-customelements';
|
||||
export * from './find-exports';
|
||||
export * from './find-imports';
|
||||
export * from './match-imports';
|
||||
export * from './match-subclasses';
|
||||
27
packages-node/providence-analytics/src/program/types/analyzers/match-imports.d.ts
vendored
Normal file
27
packages-node/providence-analytics/src/program/types/analyzers/match-imports.d.ts
vendored
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
import { ImportOrExportId, PathRelativeFromProjectRoot, ProjectName } from '../core/core';
|
||||
import { AnalyzerQueryResult, MatchedExportSpecifier, MatchAnalyzerConfig } from '../core/Analyzer';
|
||||
|
||||
export interface MatchImportsAnalyzerResult extends AnalyzerQueryResult {
|
||||
queryOutput: MatchImportsAnalyzerOutputEntry[];
|
||||
}
|
||||
|
||||
export interface MatchImportsAnalyzerOutputEntry {
|
||||
exportSpecifier: MatchedExportSpecifier;
|
||||
matchesPerProject: MatchImportsAnalyzerOutputEntryMatch[];
|
||||
}
|
||||
|
||||
export interface MatchImportsAnalyzerOutputEntryMatch {
|
||||
/** The target project that extends the class exported by reference project */
|
||||
project: ProjectName;
|
||||
/** Array of meta objects for matching files */
|
||||
files: PathRelativeFromProjectRoot[];
|
||||
}
|
||||
|
||||
export type ConciseMatchImportsAnalyzerResult = ConciseMatchImportsAnalyzerResultEntry[];
|
||||
|
||||
export interface ConciseMatchImportsAnalyzerResultEntry {
|
||||
exportSpecifier: { id: ImportOrExportId; meta?: object };
|
||||
importProjectFiles: PathRelativeFromProjectRoot[];
|
||||
}
|
||||
|
||||
export interface MatchImportsConfig extends MatchAnalyzerConfig {}
|
||||
38
packages-node/providence-analytics/src/program/types/analyzers/match-subclasses.d.ts
vendored
Normal file
38
packages-node/providence-analytics/src/program/types/analyzers/match-subclasses.d.ts
vendored
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
import {
|
||||
SpecifierName,
|
||||
ProjectName,
|
||||
PathRelativeFromProjectRoot,
|
||||
AnalyzerQueryResult,
|
||||
MatchedExportSpecifier,
|
||||
} from '../core';
|
||||
|
||||
export interface MatchSubclassesAnalyzerResult extends AnalyzerQueryResult {
|
||||
queryOutput: MatchSubclassesAnalyzerOutputEntry[];
|
||||
}
|
||||
|
||||
export interface MatchSubclassesAnalyzerOutputEntry {
|
||||
exportSpecifier: MatchedExportSpecifier;
|
||||
matchesPerProject: MatchSubclassesAnalyzerOutputEntryMatch[];
|
||||
}
|
||||
|
||||
export interface MatchSubclassesAnalyzerOutputEntryMatch {
|
||||
/** The target project that extends the class exported by reference project */
|
||||
project: ProjectName;
|
||||
/** Array of meta objects for matching files */
|
||||
files: MatchSubclassesAnalyzerOutputEntryMatchFile[];
|
||||
}
|
||||
|
||||
export interface MatchSubclassesAnalyzerOutputEntryMatchFile {
|
||||
/**
|
||||
* The local filepath that contains the matched class inside the target project
|
||||
* like `./src/ExtendedClass.js`
|
||||
*/
|
||||
file: PathRelativeFromProjectRoot;
|
||||
/**
|
||||
* The local Identifier inside matched file that is exported
|
||||
* @example
|
||||
* - `ExtendedClass` for `export ExtendedClass extends RefClass {};`
|
||||
* - `[default]` for `export default ExtendedClass extends RefClass {};`
|
||||
*/
|
||||
identifier: SpecifierName;
|
||||
}
|
||||
78
packages-node/providence-analytics/src/program/types/core/Analyzer.d.ts
vendored
Normal file
78
packages-node/providence-analytics/src/program/types/core/Analyzer.d.ts
vendored
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
import {
|
||||
PathRelativeFromProjectRoot,
|
||||
PathFromSystemRoot,
|
||||
QueryType,
|
||||
QueryResult,
|
||||
RequiredAst,
|
||||
ImportOrExportId,
|
||||
Project,
|
||||
GatherFilesConfig,
|
||||
SpecifierName,
|
||||
} from './index';
|
||||
|
||||
/**
|
||||
* Name of the analyzer, like 'find-imports' or 'match-sublcasses'
|
||||
*/
|
||||
export type AnalyzerName = `${'find' | 'match'}-${string}`;
|
||||
|
||||
// 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)
|
||||
// so that these type definitions can be used to generate a json schema: https://www.npmjs.com/package/typescript-json-schema
|
||||
export interface Meta {
|
||||
searchType: QueryType;
|
||||
/** analyzer meta object */
|
||||
analyzerMeta: AnalyzerMeta;
|
||||
}
|
||||
|
||||
export interface AnalyzerMeta {
|
||||
name: AnalyzerName;
|
||||
requiredAst: RequiredAst;
|
||||
/** a unique hash based on target, reference and configuration */
|
||||
identifier: ImportOrExportId;
|
||||
/** target project meta object */
|
||||
targetProject: Project;
|
||||
/** reference project meta object */
|
||||
referenceProject?: Project;
|
||||
/** the configuration used for this particular analyzer run */
|
||||
configuration: object;
|
||||
}
|
||||
|
||||
export interface AnalyzerQueryResult extends QueryResult {
|
||||
/** meta info object */
|
||||
meta: Meta;
|
||||
/** array of AST traversal output, per project file */
|
||||
queryOutput: any[];
|
||||
}
|
||||
|
||||
export interface FindAnalyzerQueryResult extends AnalyzerQueryResult {
|
||||
queryOutput: FindAnalyzerOutputFile[];
|
||||
}
|
||||
|
||||
export interface FindAnalyzerOutputFile {
|
||||
/** path relative from project root for which a result is generated based on AST traversal */
|
||||
file: PathRelativeFromProjectRoot;
|
||||
/** result of AST traversal for file in project */
|
||||
result: any[];
|
||||
}
|
||||
|
||||
export interface AnalyzerConfig {
|
||||
/** search target project path */
|
||||
targetProjectPath: PathFromSystemRoot;
|
||||
gatherFilesConfig: GatherFilesConfig;
|
||||
}
|
||||
|
||||
export interface MatchAnalyzerConfig extends AnalyzerConfig {
|
||||
/** reference project path, used to match reference against target */
|
||||
referenceProjectPath: PathFromSystemRoot;
|
||||
targetProjectResult: AnalyzerQueryResult;
|
||||
referenceProjectResult: AnalyzerQueryResult;
|
||||
}
|
||||
|
||||
export interface MatchedExportSpecifier extends AnalyzerQueryResult {
|
||||
name: SpecifierName;
|
||||
/** Project name as found in package.json */
|
||||
project: string;
|
||||
/** Path relative from project root, for instance `./index.js` */
|
||||
filePath: PathRelativeFromProjectRoot;
|
||||
id: ImportOrExportId;
|
||||
}
|
||||
48
packages-node/providence-analytics/src/program/types/core/QueryService.d.ts
vendored
Normal file
48
packages-node/providence-analytics/src/program/types/core/QueryService.d.ts
vendored
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
import { AnalyzerName, Feature, AnalyzerConfig, PathRelativeFromProjectRoot } from './index';
|
||||
import { Analyzer } from '../../analyzers/helpers/Analyzer';
|
||||
export { Analyzer } from '../../analyzers/helpers/Analyzer';
|
||||
|
||||
/**
|
||||
* Type of the query. Currently only "ast-analyzer" supported
|
||||
*/
|
||||
export type QueryType = 'ast-analyzer' | 'search' | 'feature';
|
||||
|
||||
/** an object containing keys name, value, term, tag */
|
||||
export interface QueryConfig {
|
||||
/**
|
||||
* The type of the tag we are searching for.
|
||||
* A certain type has an additional property with more detailed information about the type
|
||||
*/
|
||||
type: QueryType;
|
||||
}
|
||||
|
||||
export interface AnalyzerQueryConfig extends QueryConfig {
|
||||
/** query details for a feature search */
|
||||
analyzer: Analyzer;
|
||||
analyzerName: AnalyzerName;
|
||||
analyzerConfig: AnalyzerConfig;
|
||||
}
|
||||
|
||||
export interface FeatureQueryConfig extends QueryConfig {
|
||||
/** query details for a feature search */
|
||||
feature: Feature;
|
||||
}
|
||||
|
||||
export interface SearchQueryConfig extends QueryConfig {
|
||||
/** if type is 'search', a regexString should be provided */
|
||||
regexString: string;
|
||||
}
|
||||
|
||||
export interface QueryOutputEntry {
|
||||
result: any;
|
||||
file: PathRelativeFromProjectRoot;
|
||||
}
|
||||
|
||||
export type QueryOutput = QueryOutputEntry[];
|
||||
|
||||
export interface QueryResult {
|
||||
queryOutput: QueryOutput;
|
||||
meta: {
|
||||
searchType: QueryType;
|
||||
};
|
||||
}
|
||||
151
packages-node/providence-analytics/src/program/types/core/core.d.ts
vendored
Normal file
151
packages-node/providence-analytics/src/program/types/core/core.d.ts
vendored
Normal file
|
|
@ -0,0 +1,151 @@
|
|||
/**
|
||||
* The name of a variable in a local context. Examples:
|
||||
* - 'b': (`import {a as b } from 'c';`)
|
||||
* - 'MyClass': (`class MyClass {}`)
|
||||
*/
|
||||
export type IdentifierName = string;
|
||||
|
||||
/**
|
||||
* The string representation of an export.
|
||||
* Examples:
|
||||
* - 'x': (`export { x } from 'y';` or `import { x } from 'y';`)
|
||||
* - '[default]': (`export x from 'y';` or `import x from 'y';`)
|
||||
* - '[*]': (`export * from 'y';` or `import * as allFromY from 'y';`)
|
||||
*/
|
||||
export type SpecifierName = '[default]' | '[*]' | IdentifierName;
|
||||
|
||||
/**
|
||||
* Source of import or export declaration. Examples:
|
||||
* - 'my/external/project'
|
||||
* - './my/local/file.js'
|
||||
*/
|
||||
export type SpecifierSource = string;
|
||||
|
||||
/**
|
||||
* The resolved filePath relative from project root.
|
||||
* Examples:
|
||||
* - "./index.js"
|
||||
* - "./src/helpers/foo.js"
|
||||
*/
|
||||
export type PathRelative = `${'./' | '../'}${string}`;
|
||||
|
||||
/**
|
||||
* The resolved filePath relative from project root.
|
||||
* Examples:
|
||||
* - "./index.js"
|
||||
* - "./src/helpers/foo.js"
|
||||
*/
|
||||
export type PathRelativeFromProjectRoot = `./${string}`;
|
||||
|
||||
/**
|
||||
* Posix compatible path from system root.
|
||||
* Example:
|
||||
* - "/my/projects/project-x"
|
||||
*/
|
||||
export type PathFromSystemRoot = `/${string}`;
|
||||
|
||||
export type RootFile = {
|
||||
/** the file path containing declaration, for instance './target-src/direct-imports.js'. Can also contain keyword '[current]' */
|
||||
file: string;
|
||||
/** the specifier/identifier that was exported in root file, for instance 'MyClass' */
|
||||
specifier: SpecifierName;
|
||||
};
|
||||
|
||||
/**
|
||||
* Required ast for the analysis. Currently, only Babel is supported
|
||||
*/
|
||||
export type RequiredAst = 'babel';
|
||||
|
||||
/**
|
||||
* Name entry found in package.json
|
||||
*/
|
||||
export type ProjectName = string;
|
||||
|
||||
export type ImportOrExportId = `${SpecifierName}::${PathRelativeFromProjectRoot}::${ProjectName}`;
|
||||
|
||||
export interface RootFileMapEntry {
|
||||
/** This is the local name in the file we track from */
|
||||
currentFileSpecifier: SpecifierName;
|
||||
/**
|
||||
* The file that contains the original declaration of a certain Identifier/Specifier.
|
||||
* Contains file(filePath) and specifier keys
|
||||
*/
|
||||
rootFile: RootFile;
|
||||
}
|
||||
|
||||
export interface Project {
|
||||
/** "name" found in package.json and under which the package is registered in npm */
|
||||
name: ProjectName;
|
||||
/** "version" found in package.json */
|
||||
version: string;
|
||||
/** "main" File found in package.json */
|
||||
mainEntry: PathRelativeFromProjectRoot;
|
||||
/** if a git repo is analyzed, stores commit hash, [not-a-git-repo] if not */
|
||||
commitHash: '[not-a-git-repo]' | string;
|
||||
|
||||
path: PathFromSystemRoot;
|
||||
}
|
||||
|
||||
export interface Feature {
|
||||
/** the name of the feature. For instance 'size' */
|
||||
name?: string;
|
||||
|
||||
/** the value of the feature. For instance 'xl' */
|
||||
value?: string;
|
||||
/** the name of the object this feature belongs to. */
|
||||
memberOf?: string;
|
||||
/**
|
||||
* the HTML element it belongs to. Will be used in html
|
||||
* queries. This option will take precedence over 'memberOf' when configured
|
||||
*/
|
||||
tag?: string;
|
||||
/**
|
||||
* useful for HTML queries explicitly looking for attribute
|
||||
* name instead of property name. When false(default), query searches for properties
|
||||
*/
|
||||
isAttribute?: boolean;
|
||||
/** when the attribute value is not an exact match */
|
||||
usesValueContains?: boolean;
|
||||
/**
|
||||
* when looking for a partial match:
|
||||
* div[class*=foo*] -> <div class="baz foo-bar">*/
|
||||
usesValuePartialMatch?: boolean;
|
||||
/**
|
||||
* when looking for an exact match inside a space
|
||||
* separated list within an attr: div[class*=foo] -> <div class="baz foo bar">
|
||||
*/
|
||||
usesTagPartialMatch?: boolean;
|
||||
}
|
||||
|
||||
export interface GatherFilesConfig {
|
||||
/** file extension like ['.js', '.html'] */
|
||||
extensions: `.${string}`[];
|
||||
filter?: AnyMatchString[];
|
||||
omitDefaultAllowlist?: boolean;
|
||||
depth?: number;
|
||||
allowlist: string[];
|
||||
allowlistMode?: 'npm' | 'git' | 'all';
|
||||
}
|
||||
|
||||
export interface ProjectInputData {
|
||||
project: Project;
|
||||
/**
|
||||
* Array of paths that are found within 'project' that
|
||||
* comply to the rules as configured in 'gatherFilesConfig'
|
||||
*/
|
||||
entries: PathFromSystemRoot[];
|
||||
}
|
||||
|
||||
export interface ProjectInputDataWithMeta {
|
||||
project: Project;
|
||||
entries: { file: PathRelativeFromProjectRoot; context: { code: string } }[];
|
||||
}
|
||||
/**
|
||||
* See: https://www.npmjs.com/package/anymatch
|
||||
* Allows negations as well. See: https://www.npmjs.com/package/is-negated-glob
|
||||
* Examples:
|
||||
* - `'scripts/**\/*.js'
|
||||
* - '!scripts / vendor/**'
|
||||
* - 'scripts/vendor/react.js'
|
||||
*/
|
||||
export type AnyMatchString = string;
|
||||
3
packages-node/providence-analytics/src/program/types/core/index.d.ts
vendored
Normal file
3
packages-node/providence-analytics/src/program/types/core/index.d.ts
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
export * from './core';
|
||||
export * from './Analyzer';
|
||||
export * from './QueryService';
|
||||
|
|
@ -1,65 +0,0 @@
|
|||
/**
|
||||
* @typedef {Object} Feature
|
||||
* @property {string} [name] the name of the feature. For instance 'size'
|
||||
* @property {string} [value] the value of the feature. For instance 'xl'
|
||||
* @property {string} [memberOf] the name of the object this feature belongs to.
|
||||
*
|
||||
* @property {string} [tag] the HTML element it belongs to. Will be used in html
|
||||
* queries. This option will take precedence over 'memberOf' when configured
|
||||
* @property {boolean} [isAttribute] useful for HTML queries explicitly looking for attribute
|
||||
* name instead of property name. When false(default), query searches for properties
|
||||
* @property {boolean} [usesValueContains] when the attribute value is not an exact match
|
||||
* @property {boolean} [usesValuePartialMatch] when looking for a partial match:
|
||||
* div[class*=foo*] -> <div class="baz foo-bar">
|
||||
* @property {boolean} [usesTagPartialMatch] when looking for an exact match inside a space
|
||||
* separated list within an attr: div[class*=foo] -> <div class="baz foo bar">
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {Object} QueryResult result of a query. For all projects and files, gives the
|
||||
* result of the query.
|
||||
* @property {Object} QueryResult.meta
|
||||
* @property {'ast'|'grep'} QueryResult.meta.searchType
|
||||
* @property {QueryConfig} QueryResult.meta.query
|
||||
* @property {Object[]} QueryResult.results
|
||||
* @property {string} QueryResult.queryOutput[].project project name as determined by InputDataService (based on folder name)
|
||||
* @property {number} QueryResult.queryOutput[].count
|
||||
* @property {Object[]} [QueryResult.queryOutput[].files]
|
||||
* @property {string} QueryResult.queryOutput[].files[].file
|
||||
* @property {number} QueryResult.queryOutput[].files[].line
|
||||
* @property {string} QueryResult.queryOutput[].files[].match
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {object} QueryConfig an object containing keys name, value, term, tag
|
||||
* @property {string} QueryConfig.type the type of the tag we are searching for.
|
||||
* A certain type has an additional property with more detailed information about the type
|
||||
* @property {Feature} feature query details for a feature search
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {Object} InputDataProject - all files found that are queryable
|
||||
* @property {string} InputDataProject.project - the project name
|
||||
* @property {string} InputDataProject.path - the path to the project
|
||||
* @property {string[]} InputDataProject.entries - array of paths that are found within 'project' that
|
||||
* comply to the rules as configured in 'gatherFilesConfig'
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {InputDataProject[]} InputData - all files found that are queryable
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {Array|String|RegExp|Function} AnyMatchString see: https://www.npmjs.com/package/anymatch
|
||||
* Allows negations as well. See: https://www.npmjs.com/package/is-negated-glob
|
||||
* @example
|
||||
* `'scripts/**\/*.js'
|
||||
* '!scripts / vendor/**'
|
||||
* 'scripts/vendor/react.js'
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {Object} GatherFilesConfig
|
||||
* @property {string[]} [extensions] file extension like ['.js', '.html']
|
||||
* @property {AnyMatchString[]} [filter] file patterns filtered out. See: https://www.npmjs.com/package/anymatch
|
||||
*/
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
/**
|
||||
* @desc Readable way to do an async forEach
|
||||
* Readable way to do an async forEach
|
||||
* Since predictability matters, all array items will be handled in a queue,
|
||||
* one after another
|
||||
* @param {any[]} array
|
||||
|
|
@ -12,7 +12,7 @@ async function aForEach(array, callback) {
|
|||
}
|
||||
}
|
||||
/**
|
||||
* @desc Readable way to do an async forEach
|
||||
* Readable way to do an async forEach
|
||||
* If predictability does not matter, this method will traverse array items concurrently,
|
||||
* leading to a better performance
|
||||
* @param {any[]} array
|
||||
|
|
@ -22,11 +22,11 @@ async function aForEachNonSequential(array, callback) {
|
|||
return Promise.all(array.map(callback));
|
||||
}
|
||||
/**
|
||||
* @desc Readable way to do an async map
|
||||
* Readable way to do an async map
|
||||
* Since predictability is crucial for a map, all array items will be handled in a queue,
|
||||
* one after anotoher
|
||||
* @param {any[]} array
|
||||
* @param {function} callback
|
||||
* @param {Array<any>} array
|
||||
* @param {(param:any, i:number) => any} callback
|
||||
*/
|
||||
async function aMap(array, callback) {
|
||||
const mappedResults = [];
|
||||
|
|
|
|||
|
|
@ -1,12 +1,18 @@
|
|||
/**
|
||||
* @desc relative path of analyzed file, realtive to project root of analyzed project
|
||||
* @typedef {import('../types/core/core').PathRelativeFromProjectRoot} PathRelativeFromProjectRoot
|
||||
* @typedef {import('../types/core/core').PathFromSystemRoot} PathFromSystemRoot
|
||||
*/
|
||||
|
||||
/**
|
||||
* Relative path of analyzed file, realtive to project root of analyzed project
|
||||
* - from: '/my/machine/details/analyzed-project/relevant/file.js'
|
||||
* - to: './relevant/file.js'
|
||||
* @param {string} absolutePath
|
||||
* @param {string} projectRoot
|
||||
* @param {PathFromSystemRoot} absolutePath
|
||||
* @param {PathFromSystemRoot} projectRoot
|
||||
* @returns {PathRelativeFromProjectRoot}
|
||||
*/
|
||||
function getFilePathRelativeFromRoot(absolutePath, projectRoot) {
|
||||
return absolutePath.replace(projectRoot, '.');
|
||||
return /** @type {PathRelativeFromProjectRoot} */ (absolutePath.replace(projectRoot, '.'));
|
||||
}
|
||||
|
||||
module.exports = { getFilePathRelativeFromRoot };
|
||||
|
|
|
|||
|
|
@ -2,10 +2,9 @@ const { InputDataService } = require('../services/InputDataService.js');
|
|||
|
||||
/**
|
||||
* @param {function} func
|
||||
* @param {{}} externalStorage
|
||||
* @param {object} [storage]
|
||||
*/
|
||||
function memoize(func, externalStorage) {
|
||||
const storage = externalStorage || {};
|
||||
function memoize(func, storage = {}) {
|
||||
// eslint-disable-next-line func-names
|
||||
return function () {
|
||||
// eslint-disable-next-line prefer-rest-params
|
||||
|
|
@ -19,6 +18,7 @@ function memoize(func, externalStorage) {
|
|||
// @ts-ignore
|
||||
const outcome = func.apply(this, args);
|
||||
// @ts-ignore
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
storage[args] = outcome;
|
||||
return outcome;
|
||||
};
|
||||
|
|
@ -26,10 +26,9 @@ function memoize(func, externalStorage) {
|
|||
|
||||
/**
|
||||
* @param {function} func
|
||||
* @param {{}} externalStorage
|
||||
* @param {object} [storage]
|
||||
*/
|
||||
function memoizeAsync(func, externalStorage) {
|
||||
const storage = externalStorage || {};
|
||||
function memoizeAsync(func, storage = {}) {
|
||||
// eslint-disable-next-line func-names
|
||||
return async function () {
|
||||
// eslint-disable-next-line prefer-rest-params
|
||||
|
|
@ -43,6 +42,7 @@ function memoizeAsync(func, externalStorage) {
|
|||
// @ts-ignore
|
||||
const outcome = await func.apply(this, args);
|
||||
// @ts-ignore
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
storage[args] = outcome;
|
||||
return outcome;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -3,6 +3,12 @@
|
|||
* https://github.com/open-wc/open-wc/blob/master/packages/es-dev-server/src/utils/resolve-module-imports.js
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {import('../types/core/core').PathRelativeFromProjectRoot} PathRelativeFromProjectRoot
|
||||
* @typedef {import('../types/core/core').PathFromSystemRoot} PathFromSystemRoot
|
||||
* @typedef {import('../types/core/core').SpecifierSource} SpecifierSource
|
||||
*/
|
||||
|
||||
const pathLib = require('path');
|
||||
const { nodeResolve } = require('@rollup/plugin-node-resolve');
|
||||
const { LogService } = require('../services/LogService.js');
|
||||
|
|
@ -13,6 +19,9 @@ const fakePluginContext = {
|
|||
rollupVersion: '^2.42.0',
|
||||
},
|
||||
resolve: () => {},
|
||||
/**
|
||||
* @param {string[]} msg
|
||||
*/
|
||||
warn(...msg) {
|
||||
LogService.warn('[resolve-import-path]: ', ...msg);
|
||||
},
|
||||
|
|
@ -22,9 +31,10 @@ const fakePluginContext = {
|
|||
* Based on importee (in a statement "import {x} from '@lion/core'", "@lion/core" is an
|
||||
* importee), which can be a bare module specifier, a filename without extension, or a folder
|
||||
* name without an extension.
|
||||
* @param {string} importee source like '@lion/core'
|
||||
* @param {string} importer importing file, like '/my/project/importing-file.js'
|
||||
* @returns {string} the resolved file system path, like '/my/project/node_modules/@lion/core/index.js'
|
||||
* @param {SpecifierSource} importee source like '@lion/core' or '../helpers/index.js'
|
||||
* @param {PathFromSystemRoot} importer importing file, like '/my/project/importing-file.js'
|
||||
* @param {{customResolveOptions?: {preserveSymlinks:boolean}}} [opts] nodeResolve options
|
||||
* @returns {Promise<PathFromSystemRoot|null>} the resolved file system path, like '/my/project/node_modules/@lion/core/index.js'
|
||||
*/
|
||||
async function resolveImportPath(importee, importer, opts = {}) {
|
||||
const rollupResolve = nodeResolve({
|
||||
|
|
@ -37,14 +47,18 @@ async function resolveImportPath(importee, importer, opts = {}) {
|
|||
|
||||
const preserveSymlinks =
|
||||
(opts && opts.customResolveOptions && opts.customResolveOptions.preserveSymlinks) || false;
|
||||
// @ts-ignore
|
||||
rollupResolve.buildStart.call(fakePluginContext, { preserveSymlinks });
|
||||
|
||||
// @ts-ignore
|
||||
const result = await rollupResolve.resolveId.call(fakePluginContext, importee, importer, {});
|
||||
// @ts-ignore
|
||||
if (!result || !result.id) {
|
||||
// throw new Error(`importee ${importee} not found in filesystem.`);
|
||||
LogService.warn(`importee ${importee} not found in filesystem for importer '${importer}'.`);
|
||||
return null;
|
||||
}
|
||||
// @ts-ignore
|
||||
return result.id;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,12 +1,16 @@
|
|||
/**
|
||||
* @param {string} pathStr C:\Example\path/like/this
|
||||
* returns {string} /Example/path/like/this
|
||||
* @typedef {import('../types/core/core').PathFromSystemRoot} PathFromSystemRoot
|
||||
*/
|
||||
|
||||
/**
|
||||
* @param {PathFromSystemRoot|string} pathStr C:\Example\path/like/this
|
||||
* @returns {PathFromSystemRoot} /Example/path/like/this
|
||||
*/
|
||||
function toPosixPath(pathStr) {
|
||||
if (process.platform === 'win32') {
|
||||
return pathStr.replace(/^.:/, '').replace(/\\/g, '/');
|
||||
return /** @type {PathFromSystemRoot} */ (pathStr.replace(/^.:/, '').replace(/\\/g, '/'));
|
||||
}
|
||||
return pathStr;
|
||||
return /** @type {PathFromSystemRoot} */ (pathStr);
|
||||
}
|
||||
|
||||
module.exports = { toPosixPath };
|
||||
|
|
|
|||
|
|
@ -6,9 +6,9 @@ const /** @type {PostProcessorOptions} */ options = {
|
|||
|
||||
/**
|
||||
*
|
||||
* @param {AnalyzerResult} analyzerResult
|
||||
* @param {AnalyzerQueryResult} analyzerResult
|
||||
* @param {FindImportsConfig} customConfig
|
||||
* @returns {AnalyzerResult}
|
||||
* @returns {AnalyzerQueryResult}
|
||||
*/
|
||||
function myPostProcessor(analyzerResult, customConfig) {
|
||||
const cfg = {
|
||||
|
|
@ -31,7 +31,7 @@ function myPostProcessor(analyzerResult, customConfig) {
|
|||
transformedResult = options.optionA(transformedResult);
|
||||
}
|
||||
|
||||
return /** @type {AnalyzerResult} */ transformedResult;
|
||||
return /** @type {AnalyzerQueryResult} */ transformedResult;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
|
|
|
|||
|
|
@ -80,7 +80,7 @@ describe('Analyzer', () => {
|
|||
describe('Traverse phase', () => {});
|
||||
|
||||
describe('Finalize phase', () => {
|
||||
it('returns an AnalyzerResult', async () => {
|
||||
it('returns an AnalyzerQueryResult', async () => {
|
||||
const queryResult = queryResults[0];
|
||||
const { queryOutput, meta } = queryResult;
|
||||
|
||||
|
|
|
|||
|
|
@ -22,7 +22,6 @@ const _providenceCfg = {
|
|||
|
||||
describe('Analyzer "find-imports"', () => {
|
||||
const queryResults = [];
|
||||
|
||||
const cacheDisabledInitialValue = QueryService.cacheDisabled;
|
||||
|
||||
before(() => {
|
||||
|
|
|
|||
|
|
@ -185,7 +185,7 @@ const expectedExportIds = [
|
|||
...expectedExportIdsNamespaced,
|
||||
];
|
||||
|
||||
// 3. The AnalyzerResult generated by "match-imports"
|
||||
// 3. The AnalyzerQueryResult generated by "match-imports"
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
const expectedMatchesOutput = [
|
||||
{
|
||||
|
|
|
|||
|
|
@ -105,7 +105,7 @@ const expectedExportIdsDirect = [
|
|||
// eslint-disable-next-line no-unused-vars
|
||||
const expectedExportIds = [...expectedExportIdsIndirect, ...expectedExportIdsDirect];
|
||||
|
||||
// 3. The AnalyzerResult generated by "match-subclasses"
|
||||
// 3. The AnalyzerQueryResult generated by "match-subclasses"
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
const expectedMatchesOutput = [
|
||||
{
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ describe('InputDataService', () => {
|
|||
it('allows to set targetProjectPaths', async () => {
|
||||
const newPaths = ['/my/path', '/my/other/path'];
|
||||
InputDataService.targetProjectPaths = newPaths;
|
||||
expect(InputDataService.getTargetProjectPaths()).to.equal(newPaths);
|
||||
expect(InputDataService.targetProjectPaths).to.equal(newPaths);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -61,7 +61,7 @@ describe('InputDataService', () => {
|
|||
);
|
||||
});
|
||||
|
||||
it('"getTargetProjectPaths"', async () => {});
|
||||
it('"targetProjectPaths"', async () => {});
|
||||
|
||||
it('"getReferenceProjectPaths"', async () => {});
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue