From dc4da3991b8ff67cd953a0bd7b6ca6cea455e469 Mon Sep 17 00:00:00 2001 From: Francine Lucca <40550942+francinelucca@users.noreply.github.com> Date: Mon, 11 Mar 2024 16:11:49 -0400 Subject: [PATCH] feat: js-scope refactor (#195) * fix: js-scope refactor - various renames and structure changes * fix: make jsxScope static property read-only * fix: minor formatting changes --- .../get-tracked-source-files.ts | 21 +- .../complex-value.ts} | 4 +- .../scopes/js/find-relevant-source-files.ts | 88 ++++++++ .../import-parsers/all-import-parser.ts | 4 +- .../import-parsers/default-import-parser.ts | 6 +- .../import-parsers/import-parser.ts | 4 +- .../import-parsers/named-import-parser.ts | 4 +- .../import-parsers/renamed-import-parser.ts | 4 +- src/main/scopes/js/interfaces.ts | 37 +++ src/main/scopes/js/js-accumulator.ts | 19 ++ .../node-handlers}/import-node-handler.ts | 20 +- .../node-handlers/js-node-handler.ts} | 10 +- .../value-handlers}/default-handler.ts | 8 +- .../value-handlers}/false-keyword-handler.ts | 4 +- .../value-handlers}/identifier-handler.ts | 4 +- .../value-handlers}/jsx-attribute-handler.ts | 8 +- .../value-handlers}/jsx-expression-handler.ts | 8 +- .../jsx-spread-attribute-handler.ts | 8 +- .../value-handlers/node-value-handler.ts} | 6 +- .../value-handlers}/null-keyword-handler.ts | 4 +- .../numeric-literal-handler.ts | 4 +- .../value-handlers}/string-literal-handler.ts | 4 +- .../value-handlers}/true-keyword-handler.ts | 4 +- src/main/scopes/js/node-value-handler-map.ts | 46 ++++ src/main/scopes/js/process-file.ts | 36 +++ .../scopes/js/remove-irrelevant-imports.ts | 22 ++ .../source-file-handler.ts | 18 +- ...r.ts => jsx-element-all-import-matcher.ts} | 7 +- ...ts => jsx-element-named-import-matcher.ts} | 7 +- ... => jsx-element-renamed-import-matcher.ts} | 7 +- src/main/scopes/jsx/interfaces.ts | 52 +---- .../scopes/jsx/jsx-element-accumulator.ts | 11 +- .../jsx/{maps => }/jsx-node-handler-map.ts | 10 +- src/main/scopes/jsx/jsx-scope.ts | 211 +++--------------- .../jsx/maps/attribute-node-handler-map.ts | 46 ---- src/main/scopes/jsx/metrics/element-metric.ts | 7 +- .../elements/jsx-node-handler.ts | 8 +- .../scopes/npm/find-installers-from-tree.ts | 3 +- src/main/scopes/npm/find-nested-deps.ts | 3 +- src/main/scopes/npm/get-dependency-tree.ts | 2 +- .../scopes/npm/get-installed-version-paths.ts | 32 +++ src/main/scopes/npm/get-package-sub-tree.ts | 3 +- src/main/scopes/npm/get-package-trees.ts | 44 ++++ src/main/scopes/npm/get-tree-predecessor.ts | 34 +++ src/main/scopes/npm/interfaces.ts | 12 + .../get-tracked-source-files.test.ts | 38 +++- .../__snapshots__/process-file.test.ts.snap | 58 +++++ .../import-node-handler.test.ts.snap | 0 .../import-node-handler.test.ts | 17 +- .../jsx-expression-handler.test.ts.snap | 8 +- .../value-handlers}/default-handler.test.ts | 13 +- .../false-keyword-handler.test.ts | 9 +- .../identifier-handler.test.ts | 17 +- .../jsx-attribute-handler.test.ts | 9 +- .../jsx-expression-handler.test.ts | 13 +- .../jsx-spread-attribute-handler.test.ts | 13 +- .../null-keyword-handler.test.ts | 9 +- .../numeric-literal-handler.test.ts | 9 +- .../string-literal-handler.test.ts | 9 +- .../true-keyword-handler.test.ts | 9 +- src/test/scopes/js/process-file.test.ts | 33 +++ .../scopes/js/remove-irrelevant-imports.ts | 95 ++++++++ .../__snapshots__/jsx-scope.e2e.test.ts.snap | 4 +- ...=> jsx-element-all-import-matcher.test.ts} | 15 +- ... jsx-element-named-import-matcher.test.ts} | 15 +- ...sx-element-renamed-import-matcher.test.ts} | 15 +- src/test/scopes/jsx/jsx-scope.e2e.test.ts | 123 +--------- .../scopes/jsx/metrics/element-metric.test.ts | 9 +- .../jsx-element-node-handler.test.ts.snap | 6 +- ...-closing-element-node-handler.test.ts.snap | 6 +- .../elements/jsx-element-node-handler.test.ts | 7 +- ...-self-closing-element-node-handler.test.ts | 7 +- 72 files changed, 869 insertions(+), 601 deletions(-) rename src/main/{scopes/jsx/utils => core}/get-tracked-source-files.ts (66%) rename src/main/scopes/{jsx/complex-attribute.ts => js/complex-value.ts} (75%) create mode 100644 src/main/scopes/js/find-relevant-source-files.ts rename src/main/scopes/{jsx => js}/import-parsers/all-import-parser.ts (92%) rename src/main/scopes/{jsx => js}/import-parsers/default-import-parser.ts (90%) rename src/main/scopes/{jsx => js}/import-parsers/import-parser.ts (86%) rename src/main/scopes/{jsx => js}/import-parsers/named-import-parser.ts (93%) rename src/main/scopes/{jsx => js}/import-parsers/renamed-import-parser.ts (93%) create mode 100644 src/main/scopes/js/interfaces.ts create mode 100644 src/main/scopes/js/js-accumulator.ts rename src/main/scopes/{jsx/node-handlers/elements => js/node-handlers}/import-node-handler.ts (66%) rename src/main/scopes/{jsx/node-handlers/elements/element-node-handler.ts => js/node-handlers/js-node-handler.ts} (65%) rename src/main/scopes/{jsx/node-handlers/attributes => js/node-handlers/value-handlers}/default-handler.ts (62%) rename src/main/scopes/{jsx/node-handlers/attributes => js/node-handlers/value-handlers}/false-keyword-handler.ts (79%) rename src/main/scopes/{jsx/node-handlers/attributes => js/node-handlers/value-handlers}/identifier-handler.ts (85%) rename src/main/scopes/{jsx/node-handlers/attributes => js/node-handlers/value-handlers}/jsx-attribute-handler.ts (71%) rename src/main/scopes/{jsx/node-handlers/attributes => js/node-handlers/value-handlers}/jsx-expression-handler.ts (73%) rename src/main/scopes/{jsx/node-handlers/attributes => js/node-handlers/value-handlers}/jsx-spread-attribute-handler.ts (66%) rename src/main/scopes/{jsx/node-handlers/attributes/attribute-node-handler.ts => js/node-handlers/value-handlers/node-value-handler.ts} (78%) rename src/main/scopes/{jsx/node-handlers/attributes => js/node-handlers/value-handlers}/null-keyword-handler.ts (79%) rename src/main/scopes/{jsx/node-handlers/attributes => js/node-handlers/value-handlers}/numeric-literal-handler.ts (80%) rename src/main/scopes/{jsx/node-handlers/attributes => js/node-handlers/value-handlers}/string-literal-handler.ts (79%) rename src/main/scopes/{jsx/node-handlers/attributes => js/node-handlers/value-handlers}/true-keyword-handler.ts (79%) create mode 100644 src/main/scopes/js/node-value-handler-map.ts create mode 100644 src/main/scopes/js/process-file.ts create mode 100644 src/main/scopes/js/remove-irrelevant-imports.ts rename src/main/scopes/{jsx/node-handlers => js}/source-file-handler.ts (73%) rename src/main/scopes/jsx/import-matchers/{all-import-matcher.ts => jsx-element-all-import-matcher.ts} (77%) rename src/main/scopes/jsx/import-matchers/{named-import-matcher.ts => jsx-element-named-import-matcher.ts} (77%) rename src/main/scopes/jsx/import-matchers/{renamed-import-matcher.ts => jsx-element-renamed-import-matcher.ts} (77%) rename src/main/scopes/jsx/{maps => }/jsx-node-handler-map.ts (55%) delete mode 100644 src/main/scopes/jsx/maps/attribute-node-handler-map.ts create mode 100644 src/main/scopes/npm/get-installed-version-paths.ts create mode 100644 src/main/scopes/npm/get-package-trees.ts create mode 100644 src/main/scopes/npm/get-tree-predecessor.ts rename src/test/{scopes/jsx/utils => core}/get-tracked-source-files.test.ts (76%) create mode 100644 src/test/scopes/js/__snapshots__/process-file.test.ts.snap rename src/test/scopes/{jsx/node-handlers/elements => js/node-handlers}/__snapshots__/import-node-handler.test.ts.snap (100%) rename src/test/scopes/{jsx/node-handlers/elements => js/node-handlers}/import-node-handler.test.ts (56%) rename src/test/scopes/{jsx/node-handlers/attributes => js/node-handlers/value-handlers}/__snapshots__/jsx-expression-handler.test.ts.snap (82%) rename src/test/scopes/{jsx/node-handlers/attributes => js/node-handlers/value-handlers}/default-handler.test.ts (62%) rename src/test/scopes/{jsx/node-handlers/attributes => js/node-handlers/value-handlers}/false-keyword-handler.test.ts (70%) rename src/test/scopes/{jsx/node-handlers/attributes => js/node-handlers/value-handlers}/identifier-handler.test.ts (70%) rename src/test/scopes/{jsx/node-handlers/attributes => js/node-handlers/value-handlers}/jsx-attribute-handler.test.ts (73%) rename src/test/scopes/{jsx/node-handlers/attributes => js/node-handlers/value-handlers}/jsx-expression-handler.test.ts (76%) rename src/test/scopes/{jsx/node-handlers/attributes => js/node-handlers/value-handlers}/jsx-spread-attribute-handler.test.ts (65%) rename src/test/scopes/{jsx/node-handlers/attributes => js/node-handlers/value-handlers}/null-keyword-handler.test.ts (68%) rename src/test/scopes/{jsx/node-handlers/attributes => js/node-handlers/value-handlers}/numeric-literal-handler.test.ts (72%) rename src/test/scopes/{jsx/node-handlers/attributes => js/node-handlers/value-handlers}/string-literal-handler.test.ts (77%) rename src/test/scopes/{jsx/node-handlers/attributes => js/node-handlers/value-handlers}/true-keyword-handler.test.ts (71%) create mode 100644 src/test/scopes/js/process-file.test.ts create mode 100644 src/test/scopes/js/remove-irrelevant-imports.ts rename src/test/scopes/jsx/import-matchers/{all-import-matcher.test.ts => jsx-element-all-import-matcher.test.ts} (76%) rename src/test/scopes/jsx/import-matchers/{named-import-matcher.test.ts => jsx-element-named-import-matcher.test.ts} (78%) rename src/test/scopes/jsx/import-matchers/{renamed-import-matcher.test.ts => jsx-element-renamed-import-matcher.test.ts} (78%) diff --git a/src/main/scopes/jsx/utils/get-tracked-source-files.ts b/src/main/core/get-tracked-source-files.ts similarity index 66% rename from src/main/scopes/jsx/utils/get-tracked-source-files.ts rename to src/main/core/get-tracked-source-files.ts index 16e076fb..c5c0c3f6 100644 --- a/src/main/scopes/jsx/utils/get-tracked-source-files.ts +++ b/src/main/core/get-tracked-source-files.ts @@ -9,30 +9,35 @@ import path from 'node:path' import * as ts from 'typescript' -import { type Logger } from '../../../core/log/logger.js' -import { TrackedFileEnumerator } from '../../../core/tracked-file-enumerator.js' +import { type Logger } from './log/logger.js' +import { TrackedFileEnumerator } from './tracked-file-enumerator.js' /** - * Gets all tracked source files to consider for data collection. + * Gets all tracked source files to consider for data collection, + * filtered by supplied file extension array. * * @param root - Root directory in which to search for tracked source files. This is an absolute * path. * @param logger - Logger instance to use. + * @param fileExtensions - List of file extensions to filter files by. * @returns An array of source file objects. */ -export async function getTrackedSourceFiles(root: string, logger: Logger) { - logger.traceEnter('', 'getTrackedSourceFiles', [root]) +export async function getTrackedSourceFiles( + root: string, + logger: Logger, + fileExtensions: string[] +) { + logger.traceEnter('', 'getTrackedSourceFiles', [root, fileExtensions]) const fileEnumerator = new TrackedFileEnumerator(logger) - const allowedExtensions = ['.js', '.mjs', '.cjs', '.jsx', '.tsx'] const files = [] // If a file is passed instead of a directory, avoid the `git ls-tree` call - if (allowedExtensions.includes(path.extname(root))) { + if (fileExtensions.includes(path.extname(root))) { files.push(root) } else { files.push( - ...(await fileEnumerator.find(root, (file) => allowedExtensions.includes(path.extname(file)))) + ...(await fileEnumerator.find(root, (file) => fileExtensions.includes(path.extname(file)))) ) } diff --git a/src/main/scopes/jsx/complex-attribute.ts b/src/main/scopes/js/complex-value.ts similarity index 75% rename from src/main/scopes/jsx/complex-attribute.ts rename to src/main/scopes/js/complex-value.ts index bd6222ae..7956c288 100644 --- a/src/main/scopes/jsx/complex-attribute.ts +++ b/src/main/scopes/js/complex-value.ts @@ -6,8 +6,8 @@ */ /** - * Object representing a complex attribute. + * Object representing a complex value. */ -export class ComplexAttribute { +export class ComplexValue { constructor(public complexValue: unknown) {} } diff --git a/src/main/scopes/js/find-relevant-source-files.ts b/src/main/scopes/js/find-relevant-source-files.ts new file mode 100644 index 00000000..a93d7b36 --- /dev/null +++ b/src/main/scopes/js/find-relevant-source-files.ts @@ -0,0 +1,88 @@ +/* + * Copyright IBM Corp. 2024, 2024 + * + * This source code is licensed under the Apache-2.0 license found in the + * LICENSE file in the root directory of this source tree. + */ + +import getPropertyByPath from 'lodash/get.js' +import { ObjectPath } from 'object-scan' +import path from 'path' + +import { getTrackedSourceFiles } from '../../core/get-tracked-source-files.js' +import { Logger } from '../../core/log/logger.js' +import { NoInstallationFoundError } from '../../exceptions/no-installation-found-error.js' +import { getDependencyTree } from '../npm/get-dependency-tree.js' +import { getDirectoryPrefix } from '../npm/get-directory-prefix.js' +import { getInstalledVersionPaths } from '../npm/get-installed-version-paths.js' +import { getPackageData } from '../npm/get-package-data.js' +import { getPackageTrees } from '../npm/get-package-trees.js' +import { getTreePredecessor } from '../npm/get-tree-predecessor.js' +import { DependencyTree, PackageData } from '../npm/interfaces.js' + +/** + * Finds tracked source files and then filters them based on ones that appear in a project which + * depends on the in-context instrumented package/version. + * + * @param instrumentedPackage - Data about the instrumented package to use during filtering. + * @param cwd - Current working directory. This must be inside of the root directory. This is an + * absolute path. + * @param root - Root-most directory. This is an absolute path. + * @param fileExtensions - List of file extensions to capture metrics for. + * @param logger - Logger instance. + * @returns A (possibly empty) array of source files. + */ +export async function findRelevantSourceFiles( + instrumentedPackage: PackageData, + cwd: string, + root: string, + fileExtensions: string[], + logger: Logger +) { + logger.traceEnter('', 'findRelevantSourceFiles', [instrumentedPackage, cwd, root, fileExtensions]) + const sourceFiles = await getTrackedSourceFiles(root, logger, fileExtensions) + + const dependencyTree = await getDependencyTree(cwd, root, logger) + + const filterPromises = sourceFiles.map(async (f) => { + const prefix = await getDirectoryPrefix(path.dirname(f.fileName), logger) + const prefixPackageData = await getPackageData(prefix, root, logger) + + let packageTrees = getPackageTrees(dependencyTree, prefixPackageData) + + let instrumentedInstallVersions: string[] | undefined = undefined + let shortestPathLength: number | undefined = undefined + do { + for (const tree of packageTrees) { + const instrumentedInstallPaths = getInstalledVersionPaths(tree, instrumentedPackage.name) + if (instrumentedInstallPaths.length > 0) { + const pathsLength = instrumentedInstallPaths[0]?.length ?? 0 + if (shortestPathLength === undefined || pathsLength < shortestPathLength) { + instrumentedInstallVersions = instrumentedInstallPaths.map( + (path) => getPropertyByPath(tree, path)['version'] + ) + shortestPathLength = pathsLength + } + } + } + // did not find, go up one level for all packages + packageTrees = packageTrees + .map((tree) => getTreePredecessor(dependencyTree, tree['path'] as ObjectPath)) + .filter((tree) => tree !== undefined) as DependencyTree[] + } while (shortestPathLength === undefined && packageTrees.length > 0) + + if (instrumentedInstallVersions === undefined) { + throw new NoInstallationFoundError(instrumentedPackage.name) + } + + return instrumentedInstallVersions.some((version) => version === instrumentedPackage.version) + }) + const filterData = await Promise.all(filterPromises) + + const results = sourceFiles.filter((_, index) => { + return filterData[index] + }) + + logger.traceEnter('', 'findRelevantSourceFiles', results) + return results +} diff --git a/src/main/scopes/jsx/import-parsers/all-import-parser.ts b/src/main/scopes/js/import-parsers/all-import-parser.ts similarity index 92% rename from src/main/scopes/jsx/import-parsers/all-import-parser.ts rename to src/main/scopes/js/import-parsers/all-import-parser.ts index 90aa5b80..fa82ff23 100644 --- a/src/main/scopes/jsx/import-parsers/all-import-parser.ts +++ b/src/main/scopes/js/import-parsers/all-import-parser.ts @@ -7,7 +7,7 @@ import * as ts from 'typescript' -import { type JsxImport } from '../interfaces.js' +import { JsImport } from '../interfaces.js' import { ImportParser } from './import-parser.js' /** @@ -24,7 +24,7 @@ export class AllImportParser extends ImportParser { * @returns Array of JsxImport. */ parse(importNode: ts.ImportClause, importPath: string) { - const allImports: JsxImport[] = [] + const allImports: JsImport[] = [] if (importNode.namedBindings?.kind === ts.SyntaxKind.NamespaceImport) { allImports.push({ diff --git a/src/main/scopes/jsx/import-parsers/default-import-parser.ts b/src/main/scopes/js/import-parsers/default-import-parser.ts similarity index 90% rename from src/main/scopes/jsx/import-parsers/default-import-parser.ts rename to src/main/scopes/js/import-parsers/default-import-parser.ts index 85583702..cbe04ac5 100644 --- a/src/main/scopes/jsx/import-parsers/default-import-parser.ts +++ b/src/main/scopes/js/import-parsers/default-import-parser.ts @@ -7,8 +7,8 @@ import * as ts from 'typescript' -import { DEFAULT_ELEMENT_NAME, DEFAULT_IMPORT_KEY } from '../constants.js' -import { type JsxImport } from '../interfaces.js' +import { DEFAULT_ELEMENT_NAME, DEFAULT_IMPORT_KEY } from '../../jsx/constants.js' +import { JsImport } from '../interfaces.js' import { ImportParser } from './import-parser.js' /** @@ -25,7 +25,7 @@ export class DefaultImportParser extends ImportParser { * @returns Array of JsxImportElement. */ parse(importNode: ts.ImportClause, importPath: string) { - const defaultImports: JsxImport[] = [] + const defaultImports: JsImport[] = [] if (importNode.namedBindings?.kind === ts.SyntaxKind.NamedImports) { importNode.namedBindings.elements.forEach((element) => { diff --git a/src/main/scopes/jsx/import-parsers/import-parser.ts b/src/main/scopes/js/import-parsers/import-parser.ts similarity index 86% rename from src/main/scopes/jsx/import-parsers/import-parser.ts rename to src/main/scopes/js/import-parsers/import-parser.ts index e94291bc..8ae7057a 100644 --- a/src/main/scopes/jsx/import-parsers/import-parser.ts +++ b/src/main/scopes/js/import-parsers/import-parser.ts @@ -7,11 +7,11 @@ import type * as ts from 'typescript' -import { type JsxImport } from '../interfaces.js' +import { JsImport } from '../interfaces.js' /** * Defines API to construct JsxImportElements from ImportClause nodes. */ export abstract class ImportParser { - abstract parse(importNode: ts.ImportClause, importPath: string): JsxImport[] + abstract parse(importNode: ts.ImportClause, importPath: string): JsImport[] } diff --git a/src/main/scopes/jsx/import-parsers/named-import-parser.ts b/src/main/scopes/js/import-parsers/named-import-parser.ts similarity index 93% rename from src/main/scopes/jsx/import-parsers/named-import-parser.ts rename to src/main/scopes/js/import-parsers/named-import-parser.ts index 1e86d01a..ca3a54f7 100644 --- a/src/main/scopes/jsx/import-parsers/named-import-parser.ts +++ b/src/main/scopes/js/import-parsers/named-import-parser.ts @@ -7,7 +7,7 @@ import * as ts from 'typescript' -import { type JsxImport } from '../interfaces.js' +import { JsImport } from '../interfaces.js' import { ImportParser } from './import-parser.js' /** @@ -24,7 +24,7 @@ export class NamedImportParser extends ImportParser { * @returns Array of JsxImportElement. */ parse(importNode: ts.ImportClause, importPath: string) { - const namedImports: JsxImport[] = [] + const namedImports: JsImport[] = [] if (importNode.namedBindings?.kind === ts.SyntaxKind.NamedImports) { importNode.namedBindings.elements.forEach((element) => { diff --git a/src/main/scopes/jsx/import-parsers/renamed-import-parser.ts b/src/main/scopes/js/import-parsers/renamed-import-parser.ts similarity index 93% rename from src/main/scopes/jsx/import-parsers/renamed-import-parser.ts rename to src/main/scopes/js/import-parsers/renamed-import-parser.ts index a3be06c9..45dace5f 100644 --- a/src/main/scopes/jsx/import-parsers/renamed-import-parser.ts +++ b/src/main/scopes/js/import-parsers/renamed-import-parser.ts @@ -7,7 +7,7 @@ import * as ts from 'typescript' -import { type JsxImport } from '../interfaces.js' +import { JsImport } from '../interfaces.js' import { ImportParser } from './import-parser.js' /** @@ -24,7 +24,7 @@ export class RenamedImportParser extends ImportParser { * @returns Array of JsxImportElement. */ parse(importNode: ts.ImportClause, importPath: string) { - const renamedImports: JsxImport[] = [] + const renamedImports: JsImport[] = [] if (importNode.namedBindings?.kind === ts.SyntaxKind.NamedImports) { importNode.namedBindings.elements.forEach((element) => { diff --git a/src/main/scopes/js/interfaces.ts b/src/main/scopes/js/interfaces.ts new file mode 100644 index 00000000..349bd0a2 --- /dev/null +++ b/src/main/scopes/js/interfaces.ts @@ -0,0 +1,37 @@ +/* + * Copyright IBM Corp. 2024, 2024 + * + * This source code is licensed under the Apache-2.0 license found in the + * LICENSE file in the root directory of this source tree. + */ +import type * as ts from 'typescript' + +import { Logger } from '../../core/log/logger.js' +import { ComplexValue } from './complex-value.js' +import { JsNodeHandler } from './node-handlers/js-node-handler.js' +import { NodeValueHandler } from './node-handlers/value-handlers/node-value-handler.js' + +export interface JsImport { + name: string + path: string + isDefault: boolean + isAll: boolean + rename?: string +} + +type JsNodeHandlerClass = new ( + node: ts.SourceFile, + logger: Logger +) => JsNodeHandler + +export type JsNodeHandlerMap = Partial>> + +export type NodeValue = string | number | boolean | ComplexValue | null | undefined + +type NodeValueHandlerProducer = new (node: ts.SourceFile, logger: Logger) => NodeValueHandler + +export type NodeValueHandlerMap = Partial> + +export interface JsImportMatcher { + findMatch: (element: Element, imports: JsImport[]) => JsImport | undefined +} diff --git a/src/main/scopes/js/js-accumulator.ts b/src/main/scopes/js/js-accumulator.ts new file mode 100644 index 00000000..db3b4893 --- /dev/null +++ b/src/main/scopes/js/js-accumulator.ts @@ -0,0 +1,19 @@ +/* + * Copyright IBM Corp. 2023, 2024 + * + * This source code is licensed under the Apache-2.0 license found in the + * LICENSE file in the root directory of this source tree. + */ +import { JsImport } from '../js/interfaces.js' + +/** + * Base class for all JS Accumulators. + * Responsible for maintaining an aggregated state of imports and other elements. + */ +export abstract class JsAccumulator { + public readonly imports: JsImport[] + + constructor() { + this.imports = [] + } +} diff --git a/src/main/scopes/jsx/node-handlers/elements/import-node-handler.ts b/src/main/scopes/js/node-handlers/import-node-handler.ts similarity index 66% rename from src/main/scopes/jsx/node-handlers/elements/import-node-handler.ts rename to src/main/scopes/js/node-handlers/import-node-handler.ts index d97b8d6d..0d25aaa9 100644 --- a/src/main/scopes/jsx/node-handlers/elements/import-node-handler.ts +++ b/src/main/scopes/js/node-handlers/import-node-handler.ts @@ -6,19 +6,19 @@ */ import type * as ts from 'typescript' -import { AllImportParser } from '../../import-parsers/all-import-parser.js' -import { DefaultImportParser } from '../../import-parsers/default-import-parser.js' -import { NamedImportParser } from '../../import-parsers/named-import-parser.js' -import { RenamedImportParser } from '../../import-parsers/renamed-import-parser.js' -import { type JsxImport } from '../../interfaces.js' -import { type JsxElementAccumulator } from '../../jsx-element-accumulator.js' -import { ElementNodeHandler } from './element-node-handler.js' +import { type JsxElementAccumulator } from '../../jsx/jsx-element-accumulator.js' +import { AllImportParser } from '../import-parsers/all-import-parser.js' +import { DefaultImportParser } from '../import-parsers/default-import-parser.js' +import { NamedImportParser } from '../import-parsers/named-import-parser.js' +import { RenamedImportParser } from '../import-parsers/renamed-import-parser.js' +import { JsImport } from '../interfaces.js' +import { JsNodeHandler } from './js-node-handler.js' /** * Holds logic to construct a JsxImport object given an ImportDeclaration node. * */ -export class ImportNodeHandler extends ElementNodeHandler { +export class ImportNodeHandler extends JsNodeHandler { /** * Processes an ImportDeclaration node data and adds it to the given accumulator. * @@ -35,7 +35,7 @@ export class ImportNodeHandler extends ElementNodeHandler { * @param node - Node element to process. * @returns Constructed JsxImport object. */ - getData(node: ts.ImportDeclaration): JsxImport[] { + getData(node: ts.ImportDeclaration): JsImport[] { const importParsers = [ new AllImportParser(), new DefaultImportParser(), @@ -43,7 +43,7 @@ export class ImportNodeHandler extends ElementNodeHandler { new RenamedImportParser() ] - const results: JsxImport[] = [] + const results: JsImport[] = [] const importClause = node.importClause // This has quotes on it which need to be removed diff --git a/src/main/scopes/jsx/node-handlers/elements/element-node-handler.ts b/src/main/scopes/js/node-handlers/js-node-handler.ts similarity index 65% rename from src/main/scopes/jsx/node-handlers/elements/element-node-handler.ts rename to src/main/scopes/js/node-handlers/js-node-handler.ts index ea62bf10..78857563 100644 --- a/src/main/scopes/jsx/node-handlers/elements/element-node-handler.ts +++ b/src/main/scopes/js/node-handlers/js-node-handler.ts @@ -7,9 +7,9 @@ import type * as ts from 'typescript' -import { Loggable } from '../../../../core/log/loggable.js' -import { type Logger } from '../../../../core/log/logger.js' -import { type JsxElementAccumulator } from '../../jsx-element-accumulator.js' +import { Loggable } from '../../../core/log/loggable.js' +import { type Logger } from '../../../core/log/logger.js' +import { JsAccumulator } from '../js-accumulator.js' /** * Defines API to process typescript AST nodes and capture elements and imports. @@ -17,7 +17,7 @@ import { type JsxElementAccumulator } from '../../jsx-element-accumulator.js' * @param node - Node element to process. * @param accumulator - Keeps the state of the collected data (by the handlers). */ -export abstract class ElementNodeHandler extends Loggable { +export abstract class JsNodeHandler extends Loggable { protected readonly sourceFile: ts.SourceFile constructor(sourceFile: ts.SourceFile, logger: Logger) { @@ -25,7 +25,7 @@ export abstract class ElementNodeHandler extends Loggable { this.sourceFile = sourceFile } - abstract handle(node: ts.Node, accumulator: JsxElementAccumulator): void + abstract handle(node: ts.Node, accumulator: JsAccumulator): void abstract getData(node: ts.Node): DataType } diff --git a/src/main/scopes/jsx/node-handlers/attributes/default-handler.ts b/src/main/scopes/js/node-handlers/value-handlers/default-handler.ts similarity index 62% rename from src/main/scopes/jsx/node-handlers/attributes/default-handler.ts rename to src/main/scopes/js/node-handlers/value-handlers/default-handler.ts index f4096e71..67d0510b 100644 --- a/src/main/scopes/jsx/node-handlers/attributes/default-handler.ts +++ b/src/main/scopes/js/node-handlers/value-handlers/default-handler.ts @@ -6,14 +6,14 @@ */ import type * as ts from 'typescript' -import { ComplexAttribute } from '../../complex-attribute.js' -import { AttributeNodeHandler } from './attribute-node-handler.js' +import { ComplexValue } from '../../complex-value.js' +import { NodeValueHandler } from './node-value-handler.js' /** * Holds logic to extract raw data from an AST node. * */ -export class DefaultHandler extends AttributeNodeHandler { +export class DefaultHandler extends NodeValueHandler { /** * Extracts raw string representation of node. * @@ -21,6 +21,6 @@ export class DefaultHandler extends AttributeNodeHandler { * @returns Text value of node. */ public getData(node: ts.Node) { - return new ComplexAttribute(this.sourceFile.text.substring(node.pos, node.end).trim()) + return new ComplexValue(this.sourceFile.text.substring(node.pos, node.end).trim()) } } diff --git a/src/main/scopes/jsx/node-handlers/attributes/false-keyword-handler.ts b/src/main/scopes/js/node-handlers/value-handlers/false-keyword-handler.ts similarity index 79% rename from src/main/scopes/jsx/node-handlers/attributes/false-keyword-handler.ts rename to src/main/scopes/js/node-handlers/value-handlers/false-keyword-handler.ts index 6e446903..8b763a14 100644 --- a/src/main/scopes/jsx/node-handlers/attributes/false-keyword-handler.ts +++ b/src/main/scopes/js/node-handlers/value-handlers/false-keyword-handler.ts @@ -6,13 +6,13 @@ */ import type * as ts from 'typescript' -import { AttributeNodeHandler } from './attribute-node-handler.js' +import { NodeValueHandler } from './node-value-handler.js' /** * Holds logic to extract data from an AST node that is a FalseKeyword kind. * */ -export class FalseKeywordHandler extends AttributeNodeHandler { +export class FalseKeywordHandler extends NodeValueHandler { /** * Extracts string value of node. * diff --git a/src/main/scopes/jsx/node-handlers/attributes/identifier-handler.ts b/src/main/scopes/js/node-handlers/value-handlers/identifier-handler.ts similarity index 85% rename from src/main/scopes/jsx/node-handlers/attributes/identifier-handler.ts rename to src/main/scopes/js/node-handlers/value-handlers/identifier-handler.ts index 68bff9fb..b226401b 100644 --- a/src/main/scopes/jsx/node-handlers/attributes/identifier-handler.ts +++ b/src/main/scopes/js/node-handlers/value-handlers/identifier-handler.ts @@ -6,14 +6,14 @@ */ import type * as ts from 'typescript' -import { AttributeNodeHandler } from './attribute-node-handler.js' import { DefaultHandler } from './default-handler.js' +import { NodeValueHandler } from './node-value-handler.js' /** * Holds logic to extract data from an AST node that is a Identifier kind. * */ -export class IdentifierHandler extends AttributeNodeHandler { +export class IdentifierHandler extends NodeValueHandler { /** * Extracts string value of node. * diff --git a/src/main/scopes/jsx/node-handlers/attributes/jsx-attribute-handler.ts b/src/main/scopes/js/node-handlers/value-handlers/jsx-attribute-handler.ts similarity index 71% rename from src/main/scopes/jsx/node-handlers/attributes/jsx-attribute-handler.ts rename to src/main/scopes/js/node-handlers/value-handlers/jsx-attribute-handler.ts index 9afc080e..e625dd71 100644 --- a/src/main/scopes/jsx/node-handlers/attributes/jsx-attribute-handler.ts +++ b/src/main/scopes/js/node-handlers/value-handlers/jsx-attribute-handler.ts @@ -6,15 +6,15 @@ */ import type * as ts from 'typescript' -import { getAttributeNodeHandler } from '../../maps/attribute-node-handler-map.js' -import { AttributeNodeHandler } from './attribute-node-handler.js' +import { getNodeValueHandler } from '../../node-value-handler-map.js' +import { NodeValueHandler } from './node-value-handler.js' import { TrueKeywordHandler } from './true-keyword-handler.js' /** * Holds logic to extract data from an AST node that is a JsxAttribute kind. * */ -export class JsxAttributeHandler extends AttributeNodeHandler { +export class JsxAttributeHandler extends NodeValueHandler { /** * Extracts string value of node. * @@ -26,7 +26,7 @@ export class JsxAttributeHandler extends AttributeNodeHandler { if (node.initializer === undefined) { return new TrueKeywordHandler(this.sourceFile, this.logger).getData(node) } - return getAttributeNodeHandler(node.initializer.kind, this.sourceFile, this.logger).getData( + return getNodeValueHandler(node.initializer.kind, this.sourceFile, this.logger).getData( node.initializer ) } diff --git a/src/main/scopes/jsx/node-handlers/attributes/jsx-expression-handler.ts b/src/main/scopes/js/node-handlers/value-handlers/jsx-expression-handler.ts similarity index 73% rename from src/main/scopes/jsx/node-handlers/attributes/jsx-expression-handler.ts rename to src/main/scopes/js/node-handlers/value-handlers/jsx-expression-handler.ts index 2dd2ec33..249343c8 100644 --- a/src/main/scopes/jsx/node-handlers/attributes/jsx-expression-handler.ts +++ b/src/main/scopes/js/node-handlers/value-handlers/jsx-expression-handler.ts @@ -7,14 +7,14 @@ import type * as ts from 'typescript' import { NoAttributeExpressionFoundError } from '../../../../exceptions/no-attribute-expression-found-error.js' -import { getAttributeNodeHandler } from '../../maps/attribute-node-handler-map.js' -import { AttributeNodeHandler } from './attribute-node-handler.js' +import { getNodeValueHandler } from '../../node-value-handler-map.js' +import { NodeValueHandler } from './node-value-handler.js' /** * Holds logic to extract data from an AST node that is a JsxExpression kind. * */ -export class JsxExpressionHandler extends AttributeNodeHandler { +export class JsxExpressionHandler extends NodeValueHandler { /** * Extracts string value of node. * @@ -27,7 +27,7 @@ export class JsxExpressionHandler extends AttributeNodeHandler { throw new NoAttributeExpressionFoundError(node.getText(this.sourceFile)) } - return getAttributeNodeHandler(node.expression.kind, this.sourceFile, this.logger).getData( + return getNodeValueHandler(node.expression.kind, this.sourceFile, this.logger).getData( node.expression ) } diff --git a/src/main/scopes/jsx/node-handlers/attributes/jsx-spread-attribute-handler.ts b/src/main/scopes/js/node-handlers/value-handlers/jsx-spread-attribute-handler.ts similarity index 66% rename from src/main/scopes/jsx/node-handlers/attributes/jsx-spread-attribute-handler.ts rename to src/main/scopes/js/node-handlers/value-handlers/jsx-spread-attribute-handler.ts index 3ffb316b..d8dec2b9 100644 --- a/src/main/scopes/jsx/node-handlers/attributes/jsx-spread-attribute-handler.ts +++ b/src/main/scopes/js/node-handlers/value-handlers/jsx-spread-attribute-handler.ts @@ -6,14 +6,14 @@ */ import type * as ts from 'typescript' -import { ComplexAttribute } from '../../complex-attribute.js' -import { AttributeNodeHandler } from './attribute-node-handler.js' +import { ComplexValue } from '../../complex-value.js' +import { NodeValueHandler } from './node-value-handler.js' /** * Holds logic to extract data from an AST node that is a JsxSpreadAttribute kind. * */ -export class JsxSpreadAttributeHandler extends AttributeNodeHandler { +export class JsxSpreadAttributeHandler extends NodeValueHandler { /** * Extracts string value of node. * @@ -21,6 +21,6 @@ export class JsxSpreadAttributeHandler extends AttributeNodeHandler { * @returns Text value of node. */ public getData(node: ts.JsxSpreadAttribute) { - return new ComplexAttribute(node.getText(this.sourceFile)) + return new ComplexValue(node.getText(this.sourceFile)) } } diff --git a/src/main/scopes/jsx/node-handlers/attributes/attribute-node-handler.ts b/src/main/scopes/js/node-handlers/value-handlers/node-value-handler.ts similarity index 78% rename from src/main/scopes/jsx/node-handlers/attributes/attribute-node-handler.ts rename to src/main/scopes/js/node-handlers/value-handlers/node-value-handler.ts index ce735bb4..5a9ec90e 100644 --- a/src/main/scopes/jsx/node-handlers/attributes/attribute-node-handler.ts +++ b/src/main/scopes/js/node-handlers/value-handlers/node-value-handler.ts @@ -9,12 +9,12 @@ import type * as ts from 'typescript' import { Loggable } from '../../../../core/log/loggable.js' import { type Logger } from '../../../../core/log/logger.js' -import { type JsxElementAttribute } from '../../interfaces.js' +import { NodeValue } from '../../interfaces.js' /** * Defines API to process typescript AST nodes and capture elements and imports. */ -export abstract class AttributeNodeHandler extends Loggable { +export abstract class NodeValueHandler extends Loggable { protected readonly sourceFile: ts.SourceFile // Top-level root node containing raw text data (usually source file node). @@ -23,5 +23,5 @@ export abstract class AttributeNodeHandler extends Loggable { this.sourceFile = sourceFile } - abstract getData(node: ts.Node): JsxElementAttribute['value'] + abstract getData(node: ts.Node): NodeValue } diff --git a/src/main/scopes/jsx/node-handlers/attributes/null-keyword-handler.ts b/src/main/scopes/js/node-handlers/value-handlers/null-keyword-handler.ts similarity index 79% rename from src/main/scopes/jsx/node-handlers/attributes/null-keyword-handler.ts rename to src/main/scopes/js/node-handlers/value-handlers/null-keyword-handler.ts index 22eea057..14a0a62f 100644 --- a/src/main/scopes/jsx/node-handlers/attributes/null-keyword-handler.ts +++ b/src/main/scopes/js/node-handlers/value-handlers/null-keyword-handler.ts @@ -6,13 +6,13 @@ */ import type * as ts from 'typescript' -import { AttributeNodeHandler } from './attribute-node-handler.js' +import { NodeValueHandler } from './node-value-handler.js' /** * Holds logic to extract data from an AST node that is a NullKeyword kind. * */ -export class NullKeywordHandler extends AttributeNodeHandler { +export class NullKeywordHandler extends NodeValueHandler { /** * Extracts string value of node. * diff --git a/src/main/scopes/jsx/node-handlers/attributes/numeric-literal-handler.ts b/src/main/scopes/js/node-handlers/value-handlers/numeric-literal-handler.ts similarity index 80% rename from src/main/scopes/jsx/node-handlers/attributes/numeric-literal-handler.ts rename to src/main/scopes/js/node-handlers/value-handlers/numeric-literal-handler.ts index 3b9deafa..b250268e 100644 --- a/src/main/scopes/jsx/node-handlers/attributes/numeric-literal-handler.ts +++ b/src/main/scopes/js/node-handlers/value-handlers/numeric-literal-handler.ts @@ -6,13 +6,13 @@ */ import type * as ts from 'typescript' -import { AttributeNodeHandler } from './attribute-node-handler.js' +import { NodeValueHandler } from './node-value-handler.js' /** * Holds logic to extract data from an AST node that is a NumericLiteral kind. * */ -export class NumericLiteralHandler extends AttributeNodeHandler { +export class NumericLiteralHandler extends NodeValueHandler { /** * Extracts string value of node. * diff --git a/src/main/scopes/jsx/node-handlers/attributes/string-literal-handler.ts b/src/main/scopes/js/node-handlers/value-handlers/string-literal-handler.ts similarity index 79% rename from src/main/scopes/jsx/node-handlers/attributes/string-literal-handler.ts rename to src/main/scopes/js/node-handlers/value-handlers/string-literal-handler.ts index 553d30be..08dfc6c5 100644 --- a/src/main/scopes/jsx/node-handlers/attributes/string-literal-handler.ts +++ b/src/main/scopes/js/node-handlers/value-handlers/string-literal-handler.ts @@ -6,13 +6,13 @@ */ import type * as ts from 'typescript' -import { AttributeNodeHandler } from './attribute-node-handler.js' +import { NodeValueHandler } from './node-value-handler.js' /** * Holds logic to extract data from an AST node that is a StringLiteral kind. * */ -export class StringLiteralHandler extends AttributeNodeHandler { +export class StringLiteralHandler extends NodeValueHandler { /** * Extracts string value of node. * diff --git a/src/main/scopes/jsx/node-handlers/attributes/true-keyword-handler.ts b/src/main/scopes/js/node-handlers/value-handlers/true-keyword-handler.ts similarity index 79% rename from src/main/scopes/jsx/node-handlers/attributes/true-keyword-handler.ts rename to src/main/scopes/js/node-handlers/value-handlers/true-keyword-handler.ts index 4c86c35a..89f6a812 100644 --- a/src/main/scopes/jsx/node-handlers/attributes/true-keyword-handler.ts +++ b/src/main/scopes/js/node-handlers/value-handlers/true-keyword-handler.ts @@ -6,13 +6,13 @@ */ import type * as ts from 'typescript' -import { AttributeNodeHandler } from './attribute-node-handler.js' +import { NodeValueHandler } from './node-value-handler.js' /** * Holds logic to extract data from an AST node that is a TrueKeyword kind. * */ -export class TrueKeywordHandler extends AttributeNodeHandler { +export class TrueKeywordHandler extends NodeValueHandler { /** * Extracts string value of node. * diff --git a/src/main/scopes/js/node-value-handler-map.ts b/src/main/scopes/js/node-value-handler-map.ts new file mode 100644 index 00000000..0ebebf64 --- /dev/null +++ b/src/main/scopes/js/node-value-handler-map.ts @@ -0,0 +1,46 @@ +/* + * Copyright IBM Corp. 2023, 2024 + * + * This source code is licensed under the Apache-2.0 license found in the + * LICENSE file in the root directory of this source tree. + */ + +import * as ts from 'typescript' + +import { type Logger } from '../../core/log/logger.js' +import { NodeValueHandlerMap } from './interfaces.js' +import { DefaultHandler } from './node-handlers/value-handlers/default-handler.js' +import { FalseKeywordHandler } from './node-handlers/value-handlers/false-keyword-handler.js' +import { IdentifierHandler } from './node-handlers/value-handlers/identifier-handler.js' +import { JsxAttributeHandler } from './node-handlers/value-handlers/jsx-attribute-handler.js' +import { JsxExpressionHandler } from './node-handlers/value-handlers/jsx-expression-handler.js' +import { JsxSpreadAttributeHandler } from './node-handlers/value-handlers/jsx-spread-attribute-handler.js' +import { type NodeValueHandler } from './node-handlers/value-handlers/node-value-handler.js' +import { NullKeywordHandler } from './node-handlers/value-handlers/null-keyword-handler.js' +import { NumericLiteralHandler } from './node-handlers/value-handlers/numeric-literal-handler.js' +import { StringLiteralHandler } from './node-handlers/value-handlers/string-literal-handler.js' +import { TrueKeywordHandler } from './node-handlers/value-handlers/true-keyword-handler.js' + +/** + * Maps node kinds to handlers that know how to extract string representations of their value. + */ +export const nodeValueHandlersMap: NodeValueHandlerMap = { + [ts.SyntaxKind.StringLiteral]: StringLiteralHandler, + [ts.SyntaxKind.FalseKeyword]: FalseKeywordHandler, + [ts.SyntaxKind.NullKeyword]: NullKeywordHandler, + [ts.SyntaxKind.NumericLiteral]: NumericLiteralHandler, + [ts.SyntaxKind.TrueKeyword]: TrueKeywordHandler, + [ts.SyntaxKind.JsxExpression]: JsxExpressionHandler, + [ts.SyntaxKind.JsxAttribute]: JsxAttributeHandler, + [ts.SyntaxKind.JsxSpreadAttribute]: JsxSpreadAttributeHandler, + [ts.SyntaxKind.Identifier]: IdentifierHandler +} + +export const getNodeValueHandler = ( + nodeKind: ts.SyntaxKind, + sourceNode: ts.SourceFile, + logger: Logger +): NodeValueHandler => { + const Handler = nodeValueHandlersMap[nodeKind] ?? DefaultHandler + return new Handler(sourceNode, logger) +} diff --git a/src/main/scopes/js/process-file.ts b/src/main/scopes/js/process-file.ts new file mode 100644 index 00000000..e068cb46 --- /dev/null +++ b/src/main/scopes/js/process-file.ts @@ -0,0 +1,36 @@ +/* + * Copyright IBM Corp. 2024, 2024 + * + * This source code is licensed under the Apache-2.0 license found in the + * LICENSE file in the root directory of this source tree. + */ + +import type * as ts from 'typescript' + +import { Logger } from '../../core/log/logger.js' +import { JsNodeHandlerMap } from './interfaces.js' +import { JsAccumulator } from './js-accumulator.js' +import { SourceFileHandler } from './source-file-handler.js' + +/** + * Given a source file node, passes all file nodes through appropriate handlers + * and stores data in an accumulator. + * + * @param accumulator - The accumulator in which to store data. + * @param sourceFile - Root AST node to start explorations from. + * @param jsNodeHandlerMap - Object containing mappings between + * node types to process and their handlers. + * @param logger - Logger instance. + */ +export function processFile( + accumulator: JsAccumulator, + sourceFile: ts.SourceFile, + jsNodeHandlerMap: JsNodeHandlerMap, + logger: Logger +) { + logger.traceEnter('', 'processFile', [sourceFile.fileName]) + const handler = new SourceFileHandler(accumulator, jsNodeHandlerMap, logger) + + handler.handle(sourceFile, sourceFile) + logger.traceExit('', 'processFile', undefined) +} diff --git a/src/main/scopes/js/remove-irrelevant-imports.ts b/src/main/scopes/js/remove-irrelevant-imports.ts new file mode 100644 index 00000000..5c7428ce --- /dev/null +++ b/src/main/scopes/js/remove-irrelevant-imports.ts @@ -0,0 +1,22 @@ +/* + * Copyright IBM Corp. 2024, 2024 + * + * This source code is licensed under the Apache-2.0 license found in the + * LICENSE file in the root directory of this source tree. + */ +import { JsAccumulator } from './js-accumulator.js' + +/** + * Give a JsAccumulator with previously computed imports, discards all import objects + * that do not belong to the given package. + * + * @param accumulator - The accumulator in which to import data is stored. + * @param packageName - Name of the package to filter imports for. + */ +export function removeIrrelevantImports(accumulator: JsAccumulator, packageName: string) { + const imports = accumulator.imports.filter((jsxImport) => { + return jsxImport.path === packageName || jsxImport.path.startsWith(`${packageName}/`) + }) + + accumulator.imports.splice(0, accumulator.imports.length, ...imports) +} diff --git a/src/main/scopes/jsx/node-handlers/source-file-handler.ts b/src/main/scopes/js/source-file-handler.ts similarity index 73% rename from src/main/scopes/jsx/node-handlers/source-file-handler.ts rename to src/main/scopes/js/source-file-handler.ts index 27d55f2f..63be2b2e 100644 --- a/src/main/scopes/jsx/node-handlers/source-file-handler.ts +++ b/src/main/scopes/js/source-file-handler.ts @@ -6,18 +6,18 @@ */ import * as ts from 'typescript' -import { Loggable } from '../../../core/log/loggable.js' -import { type Logger } from '../../../core/log/logger.js' -import { type ElementNodeHandlerMap } from '../interfaces.js' -import { type JsxElementAccumulator } from '../jsx-element-accumulator.js' +import { Loggable } from '../../core/log/loggable.js' +import { type Logger } from '../../core/log/logger.js' +import { JsNodeHandlerMap } from './interfaces.js' +import { JsAccumulator } from './js-accumulator.js' /** * Class to handle traversing through a node's children and calling appropriate handlers. * */ export class SourceFileHandler extends Loggable { - private readonly accumulator: JsxElementAccumulator - private readonly nodeHandlerMap: ElementNodeHandlerMap + private readonly accumulator: JsAccumulator + private readonly nodeHandlerMap: JsNodeHandlerMap /** * Instantiates a new SourceFileHandler. @@ -27,11 +27,7 @@ export class SourceFileHandler extends Loggable { * the found node types. * @param logger - Logger instance to use. */ - constructor( - accumulator: JsxElementAccumulator, - nodeHandlerMap: ElementNodeHandlerMap, - logger: Logger - ) { + constructor(accumulator: JsAccumulator, nodeHandlerMap: JsNodeHandlerMap, logger: Logger) { super(logger) this.accumulator = accumulator diff --git a/src/main/scopes/jsx/import-matchers/all-import-matcher.ts b/src/main/scopes/jsx/import-matchers/jsx-element-all-import-matcher.ts similarity index 77% rename from src/main/scopes/jsx/import-matchers/all-import-matcher.ts rename to src/main/scopes/jsx/import-matchers/jsx-element-all-import-matcher.ts index da04f729..5eb74859 100644 --- a/src/main/scopes/jsx/import-matchers/all-import-matcher.ts +++ b/src/main/scopes/jsx/import-matchers/jsx-element-all-import-matcher.ts @@ -5,13 +5,14 @@ * LICENSE file in the root directory of this source tree. */ -import { type JsxElement, type JsxElementImportMatcher, type JsxImport } from '../interfaces.js' +import { JsImport, JsImportMatcher } from '../../js/interfaces.js' +import { type JsxElement } from '../interfaces.js' /** * Identifies JsxElements that have been imported as all imports, * and returns an import element match (if any) or undefined otherwise. */ -export class AllImportMatcher implements JsxElementImportMatcher { +export class JsxElementAllImportMatcher implements JsImportMatcher { /** * Determines if a given JsxElement is an all(*) import * (.e.g: import * as something from 'package') @@ -22,7 +23,7 @@ export class AllImportMatcher implements JsxElementImportMatcher { * @returns Corresponding JsxImportElement if element was imported as an all import, * undefined otherwise. */ - findMatch(element: JsxElement, imports: JsxImport[]) { + findMatch(element: JsxElement, imports: JsImport[]) { return element.prefix !== undefined ? imports.find((i) => i.isAll && i.name === element.prefix) : undefined diff --git a/src/main/scopes/jsx/import-matchers/named-import-matcher.ts b/src/main/scopes/jsx/import-matchers/jsx-element-named-import-matcher.ts similarity index 77% rename from src/main/scopes/jsx/import-matchers/named-import-matcher.ts rename to src/main/scopes/jsx/import-matchers/jsx-element-named-import-matcher.ts index fa2fa915..8a41e45d 100644 --- a/src/main/scopes/jsx/import-matchers/named-import-matcher.ts +++ b/src/main/scopes/jsx/import-matchers/jsx-element-named-import-matcher.ts @@ -5,13 +5,14 @@ * LICENSE file in the root directory of this source tree. */ -import { type JsxElement, type JsxElementImportMatcher, type JsxImport } from '../interfaces.js' +import { JsImport, JsImportMatcher } from '../../js/interfaces.js' +import { type JsxElement } from '../interfaces.js' /** * Identifies JsxElements that have been imported as named imports, * and returns an import element match (if any) or undefined otherwise. */ -export class NamedImportMatcher implements JsxElementImportMatcher { +export class JsxElementNamedImportMatcher implements JsImportMatcher { /** * Determines if a given JsxElement is a named import (e.g.: `import {something} from 'package'`). * @@ -20,7 +21,7 @@ export class NamedImportMatcher implements JsxElementImportMatcher { * @returns Corresponding JsxImport if element was imported as a name import, * undefined otherwise. */ - findMatch(element: JsxElement, imports: JsxImport[]) { + findMatch(element: JsxElement, imports: JsImport[]) { if (element.prefix !== undefined) { return imports.find((i) => !i.isDefault && !i.isAll && i.name === element.prefix) } else { diff --git a/src/main/scopes/jsx/import-matchers/renamed-import-matcher.ts b/src/main/scopes/jsx/import-matchers/jsx-element-renamed-import-matcher.ts similarity index 77% rename from src/main/scopes/jsx/import-matchers/renamed-import-matcher.ts rename to src/main/scopes/jsx/import-matchers/jsx-element-renamed-import-matcher.ts index 1b4b68c7..ba9c9fb9 100644 --- a/src/main/scopes/jsx/import-matchers/renamed-import-matcher.ts +++ b/src/main/scopes/jsx/import-matchers/jsx-element-renamed-import-matcher.ts @@ -5,12 +5,13 @@ * LICENSE file in the root directory of this source tree. */ -import { type JsxElement, type JsxElementImportMatcher, type JsxImport } from '../interfaces.js' +import { JsImport, JsImportMatcher } from '../../js/interfaces.js' +import { type JsxElement } from '../interfaces.js' /** * Identifies JsxElements that have been imported as renamed imports. */ -export class RenamedImportMatcher implements JsxElementImportMatcher { +export class JsxElementRenamedImportMatcher implements JsImportMatcher { /** * Determines if a given JsxElement is a renamed import * (e.g.: `import {something as somethingElse} from 'package'`) @@ -21,7 +22,7 @@ export class RenamedImportMatcher implements JsxElementImportMatcher { * @returns Corresponding JsxImport if element was imported as a renamed import, * undefined otherwise. */ - findMatch(element: JsxElement, imports: JsxImport[]) { + findMatch(element: JsxElement, imports: JsImport[]) { if (element.prefix !== undefined) { return imports.find((i) => i.rename === element.prefix) } else { diff --git a/src/main/scopes/jsx/interfaces.ts b/src/main/scopes/jsx/interfaces.ts index c5683c1e..3e8bd3da 100644 --- a/src/main/scopes/jsx/interfaces.ts +++ b/src/main/scopes/jsx/interfaces.ts @@ -4,25 +4,12 @@ * This source code is licensed under the Apache-2.0 license found in the * LICENSE file in the root directory of this source tree. */ -import type * as ts from 'typescript' -import { type Logger } from '../../core/log/logger.js' -import { PackageData } from '../npm/interfaces.js' -import { type ComplexAttribute } from './complex-attribute.js' -import { type AttributeNodeHandler } from './node-handlers/attributes/attribute-node-handler.js' -import { type ElementNodeHandler } from './node-handlers/elements/element-node-handler.js' +import { ComplexValue } from '../js/complex-value.js' export interface JsxElementAttribute { name: string - value: string | number | boolean | ComplexAttribute | null | undefined -} - -export interface JsxImport { - name: string - path: string - isDefault: boolean - isAll: boolean - rename?: string + value: string | number | boolean | ComplexValue | null | undefined } export interface JsxElement { @@ -30,38 +17,3 @@ export interface JsxElement { prefix: string | undefined attributes: JsxElementAttribute[] } - -export interface FileTree { - path: string - children: FileTree[] -} - -export interface JsxElementImportMatcher { - findMatch: (element: JsxElement, imports: JsxImport[]) => JsxImport | undefined -} - -type ElementNodeHandlerClass = new ( - node: ts.SourceFile, - logger: Logger -) => ElementNodeHandler - -export type ElementNodeHandlerMap = Partial>> - -type AttributeNodeHandlerProducer = new ( - node: ts.SourceFile, - logger: Logger -) => AttributeNodeHandler - -export type AttributeNodeHandlerMap = Partial> - -export interface DependencyTreeDependency { - version: string - dependencies: Record -} - -export interface DependencyTree extends PackageData { - name: string - version: string - dependencies: Record - [key: string]: unknown -} diff --git a/src/main/scopes/jsx/jsx-element-accumulator.ts b/src/main/scopes/jsx/jsx-element-accumulator.ts index c84e3f2d..79ab2ab8 100644 --- a/src/main/scopes/jsx/jsx-element-accumulator.ts +++ b/src/main/scopes/jsx/jsx-element-accumulator.ts @@ -4,18 +4,19 @@ * This source code is licensed under the Apache-2.0 license found in the * LICENSE file in the root directory of this source tree. */ -import { type JsxElement, type JsxImport } from './interfaces.js' +import { JsImport } from '../js/interfaces.js' +import { JsAccumulator } from '../js/js-accumulator.js' +import { type JsxElement } from './interfaces.js' /** * Responsible for maintaining an aggregated state of imports and elements. */ -export class JsxElementAccumulator { - public readonly imports: JsxImport[] +export class JsxElementAccumulator extends JsAccumulator { public readonly elements: JsxElement[] - public readonly elementImports: Map + public readonly elementImports: Map constructor() { - this.imports = [] + super() this.elements = [] this.elementImports = new Map() } diff --git a/src/main/scopes/jsx/maps/jsx-node-handler-map.ts b/src/main/scopes/jsx/jsx-node-handler-map.ts similarity index 55% rename from src/main/scopes/jsx/maps/jsx-node-handler-map.ts rename to src/main/scopes/jsx/jsx-node-handler-map.ts index cee089d0..161aa214 100644 --- a/src/main/scopes/jsx/maps/jsx-node-handler-map.ts +++ b/src/main/scopes/jsx/jsx-node-handler-map.ts @@ -6,17 +6,17 @@ */ import * as ts from 'typescript' -import { type ElementNodeHandlerMap } from '../interfaces.js' -import { ImportNodeHandler } from '../node-handlers/elements/import-node-handler.js' -import { JsxElementNodeHandler } from '../node-handlers/elements/jsx-element-node-handler.js' -import { JsxSelfClosingElementNodeHandler } from '../node-handlers/elements/jsx-self-closing-element-node-handler.js' +import { JsNodeHandlerMap } from '../js/interfaces.js' +import { ImportNodeHandler } from '../js/node-handlers/import-node-handler.js' +import { JsxElementNodeHandler } from './node-handlers/elements/jsx-element-node-handler.js' +import { JsxSelfClosingElementNodeHandler } from './node-handlers/elements/jsx-self-closing-element-node-handler.js' // /** * Maps node kinds to handlers that know how to process them to generate JsxElement metrics for the * JsxScope. */ -export const jsxNodeHandlerMap: ElementNodeHandlerMap = { +export const jsxNodeHandlerMap: JsNodeHandlerMap = { [ts.SyntaxKind.ImportDeclaration]: ImportNodeHandler, [ts.SyntaxKind.JsxElement]: JsxElementNodeHandler, [ts.SyntaxKind.JsxSelfClosingElement]: JsxSelfClosingElementNodeHandler diff --git a/src/main/scopes/jsx/jsx-scope.ts b/src/main/scopes/jsx/jsx-scope.ts index 6eb752f1..4619c535 100644 --- a/src/main/scopes/jsx/jsx-scope.ts +++ b/src/main/scopes/jsx/jsx-scope.ts @@ -4,34 +4,31 @@ * This source code is licensed under the Apache-2.0 license found in the * LICENSE file in the root directory of this source tree. */ -import getPropertyByPath from 'lodash/get.js' -import { ObjectPath } from 'object-scan' -import path from 'path' import type * as ts from 'typescript' import { Trace } from '../../core/log/trace.js' import { Scope } from '../../core/scope.js' import { EmptyScopeError } from '../../exceptions/empty-scope.error.js' -import { NoInstallationFoundError } from '../../exceptions/no-installation-found-error.js' -import { findNestedDeps } from '../npm/find-nested-deps.js' -import { getDependencyTree } from '../npm/get-dependency-tree.js' -import { getDirectoryPrefix } from '../npm/get-directory-prefix.js' +import { findRelevantSourceFiles } from '../js/find-relevant-source-files.js' +import { JsImportMatcher } from '../js/interfaces.js' +import { processFile } from '../js/process-file.js' +import { removeIrrelevantImports } from '../js/remove-irrelevant-imports.js' import { getPackageData } from '../npm/get-package-data.js' import { PackageData } from '../npm/interfaces.js' -import { AllImportMatcher } from './import-matchers/all-import-matcher.js' -import { NamedImportMatcher } from './import-matchers/named-import-matcher.js' -import { RenamedImportMatcher } from './import-matchers/renamed-import-matcher.js' -import { DependencyTree, type JsxElementImportMatcher } from './interfaces.js' +import { JsxElementAllImportMatcher } from './import-matchers/jsx-element-all-import-matcher.js' +import { JsxElementNamedImportMatcher } from './import-matchers/jsx-element-named-import-matcher.js' +import { JsxElementRenamedImportMatcher } from './import-matchers/jsx-element-renamed-import-matcher.js' +import { JsxElement } from './interfaces.js' import { JsxElementAccumulator } from './jsx-element-accumulator.js' -import { jsxNodeHandlerMap } from './maps/jsx-node-handler-map.js' +import { jsxNodeHandlerMap } from './jsx-node-handler-map.js' import { ElementMetric } from './metrics/element-metric.js' -import { SourceFileHandler } from './node-handlers/source-file-handler.js' -import { getTrackedSourceFiles } from './utils/get-tracked-source-files.js' /** * Scope class dedicated to data collection from a jsx environment. */ export class JsxScope extends Scope { + public static readonly fileExtensions = ['.js', '.mjs', '.cjs', '.jsx', '.tsx'] + public override name = 'jsx' as const private runSync = false @@ -65,12 +62,18 @@ export class JsxScope extends Scope { @Trace() async captureAllMetrics(): Promise { const importMatchers = [ - new AllImportMatcher(), - new NamedImportMatcher(), - new RenamedImportMatcher() + new JsxElementAllImportMatcher(), + new JsxElementNamedImportMatcher(), + new JsxElementRenamedImportMatcher() ] const instrumentedPackage = await getPackageData(this.cwd, this.cwd, this.logger) - const sourceFiles = await this.findRelevantSourceFiles(instrumentedPackage) + const sourceFiles = await findRelevantSourceFiles( + instrumentedPackage, + this.cwd, + this.root, + JsxScope.fileExtensions, + this.logger + ) this.logger.debug('Filtered source files: ' + sourceFiles.map((f) => f.fileName)) @@ -89,141 +92,6 @@ export class JsxScope extends Scope { await Promise.allSettled(promises) } - /** - * Retrieves a tree rooted at the parent of a package given it's dependencyTree path. - * - * @param dependencyTree - The tree to search. - * @param packagePath - Path to the package to get parent tree for in the dependencyTree. - * @returns A dependency tree rooted at the package's parent - * or undefined if the package is already the root. - */ - getTreePredecessor( - dependencyTree: DependencyTree, - packagePath: ObjectPath - ): DependencyTree | undefined { - // already at the root - if (packagePath.length === 0) return undefined - - if (packagePath.length === 2) return { path: [], ...dependencyTree } - - return { - path: packagePath.slice(0, -2), - ...getPropertyByPath(dependencyTree, packagePath.slice(0, -2)) - } - } - - /** - * Finds all dependency sub-trees rooted at the desired package/version - * given a bigger dependency tree. - * - * @param dependencyTree - The tree to search. - * @param pkg - Package to find rooted trees for. - * @returns A (possibly empty) array of dependency trees rooted at the pkg param. - */ - getPackageTrees(dependencyTree: DependencyTree, pkg: PackageData): DependencyTree[] { - if (dependencyTree.name === pkg.name && dependencyTree.version === pkg.version) { - dependencyTree['path'] = [] - return [dependencyTree] - } - - // TODOASKJOE: this could return more than one - // how do I know which one is the one that belongs specifically to the file? - const prefixPackagePaths = findNestedDeps( - dependencyTree, - pkg.name, - ({ value }) => value.version === pkg.version - ) - - if (prefixPackagePaths.length > 0) { - return prefixPackagePaths.map((path) => ({ - path, - ...getPropertyByPath(dependencyTree, path) - })) - } - - return [] - } - - /** - * Finds all installed versions of a given package within a dependency tree - * and returns the direct-most paths. - * - * @param dependencyTree - The tree to search. - * @param pkgName - The tree to search. - * @returns A (possibly empty) array of paths. - */ - getInstalledVersionPaths(dependencyTree: DependencyTree, pkgName: string) { - // find all versions, sort by shortest paths - const instrumentedInstallPaths = findNestedDeps(dependencyTree, pkgName, () => true).sort( - (a, b) => a.length - b.length - ) - - if (instrumentedInstallPaths.length > 0) { - // return all paths with shortest length - return instrumentedInstallPaths.filter( - (path) => path.length === instrumentedInstallPaths[0]?.length - ) - } - return [] - } - - /** - * Finds tracked source files and then filters them based on ones that appear in a project which - * depends on the in-context instrumented package/version. - * - * @param instrumentedPackage - Data about the instrumented package to use during filtering. - * @returns A (possibly empty) array of source files. - */ - async findRelevantSourceFiles(instrumentedPackage: PackageData) { - const sourceFiles = await getTrackedSourceFiles(this.root, this.logger) - - const dependencyTree = await getDependencyTree(this.cwd, this.root, this.logger) - - const filterPromises = sourceFiles.map(async (f) => { - const prefix = await getDirectoryPrefix(path.dirname(f.fileName), this.logger) - const prefixPackageData = await getPackageData(prefix, this.root, this.logger) - - // potentially more than one at this point - //(can't just pick whichever one because as I go upstream, this matters) - let packageTrees = this.getPackageTrees(dependencyTree, prefixPackageData) - - let instrumentedInstallVersions: string[] | undefined = undefined - let shortestPathLength: number | undefined = undefined - do { - for (const tree of packageTrees) { - const instrumentedInstallPaths = this.getInstalledVersionPaths( - tree, - instrumentedPackage.name - ) - if (instrumentedInstallPaths.length > 0) { - const pathsLength = instrumentedInstallPaths[0]?.length ?? 0 - if (shortestPathLength === undefined || pathsLength < shortestPathLength) { - instrumentedInstallVersions = instrumentedInstallPaths.map( - (path) => getPropertyByPath(tree, path)['version'] - ) - shortestPathLength = pathsLength - } - } - } - // did not find, go up one level for all packages - packageTrees = packageTrees - .map((tree) => this.getTreePredecessor(dependencyTree, tree['path'] as ObjectPath)) - .filter((tree) => tree !== undefined) as DependencyTree[] - } while (shortestPathLength === undefined && packageTrees.length > 0) - - if (instrumentedInstallVersions === undefined) { - throw new NoInstallationFoundError(instrumentedPackage.name) - } - - return instrumentedInstallVersions.some((version) => version === instrumentedPackage.version) - }) - const filterData = await Promise.all(filterPromises) - - return sourceFiles.filter((_, index) => { - return filterData[index] - }) - } - /** * Generates metrics for all discovered instrumented jsx elements found * in the supplied SourceFile node. @@ -236,12 +104,12 @@ export class JsxScope extends Scope { async captureFileMetrics( sourceFile: ts.SourceFile, instrumentedPackage: PackageData, - importMatchers: JsxElementImportMatcher[] + importMatchers: JsImportMatcher[] ) { const accumulator = new JsxElementAccumulator() - this.processFile(accumulator, sourceFile) - this.removeIrrelevantImports(accumulator, instrumentedPackage.name) + processFile(accumulator, sourceFile, jsxNodeHandlerMap, this.logger) + removeIrrelevantImports(accumulator, instrumentedPackage.name) this.resolveElementImports(accumulator, importMatchers) accumulator.elements.forEach((jsxElement) => { @@ -257,43 +125,16 @@ export class JsxScope extends Scope { }) } - /** - * Given a source file node, finds all JSX elements/imports and stores them in an accumulator. - * - * @param accumulator - The accumulator in which to store elements/imports. - * @param sourceFile - Root AST node to start Jsx explorations from. - */ - @Trace({ - argFormatter: (arg) => - arg instanceof JsxElementAccumulator ? '[JsxElementAccumulator]' : arg?.fileName - }) - processFile(accumulator: JsxElementAccumulator, sourceFile: ts.SourceFile) { - const handler = new SourceFileHandler(accumulator, jsxNodeHandlerMap, this.logger) - - handler.handle(sourceFile, sourceFile) - } - - removeIrrelevantImports(accumulator: JsxElementAccumulator, instrumentedPackageName: string) { - const imports = accumulator.imports.filter((jsxImport) => { - return ( - jsxImport.path === instrumentedPackageName || - jsxImport.path.startsWith(`${instrumentedPackageName}/`) - ) - }) - - accumulator.imports.splice(0, accumulator.imports.length, ...imports) - } - resolveElementImports( accumulator: JsxElementAccumulator, - elementMatchers: JsxElementImportMatcher[] + elementMatchers: JsImportMatcher[] ) { accumulator.elements.forEach((jsxElement) => { const jsxImport = elementMatchers .map((elementMatcher) => elementMatcher.findMatch(jsxElement, accumulator.imports)) .find((jsxImport) => jsxImport !== undefined) - if (!jsxImport) { + if (jsxImport === undefined) { return } diff --git a/src/main/scopes/jsx/maps/attribute-node-handler-map.ts b/src/main/scopes/jsx/maps/attribute-node-handler-map.ts deleted file mode 100644 index fb5d9ce3..00000000 --- a/src/main/scopes/jsx/maps/attribute-node-handler-map.ts +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright IBM Corp. 2023, 2024 - * - * This source code is licensed under the Apache-2.0 license found in the - * LICENSE file in the root directory of this source tree. - */ - -import * as ts from 'typescript' - -import { type Logger } from '../../../core/log/logger.js' -import { type AttributeNodeHandlerMap } from '../interfaces.js' -import { type AttributeNodeHandler } from '../node-handlers/attributes/attribute-node-handler.js' -import { DefaultHandler } from '../node-handlers/attributes/default-handler.js' -import { FalseKeywordHandler } from '../node-handlers/attributes/false-keyword-handler.js' -import { IdentifierHandler } from '../node-handlers/attributes/identifier-handler.js' -import { JsxAttributeHandler } from '../node-handlers/attributes/jsx-attribute-handler.js' -import { JsxExpressionHandler } from '../node-handlers/attributes/jsx-expression-handler.js' -import { JsxSpreadAttributeHandler } from '../node-handlers/attributes/jsx-spread-attribute-handler.js' -import { NullKeywordHandler } from '../node-handlers/attributes/null-keyword-handler.js' -import { NumericLiteralHandler } from '../node-handlers/attributes/numeric-literal-handler.js' -import { StringLiteralHandler } from '../node-handlers/attributes/string-literal-handler.js' -import { TrueKeywordHandler } from '../node-handlers/attributes/true-keyword-handler.js' - -/** - * Maps node kinds to handlers that know how to extract string representations of their value. - */ -export const attributeNodeHandlersMap: AttributeNodeHandlerMap = { - [ts.SyntaxKind.StringLiteral]: StringLiteralHandler, - [ts.SyntaxKind.FalseKeyword]: FalseKeywordHandler, - [ts.SyntaxKind.NullKeyword]: NullKeywordHandler, - [ts.SyntaxKind.NumericLiteral]: NumericLiteralHandler, - [ts.SyntaxKind.TrueKeyword]: TrueKeywordHandler, - [ts.SyntaxKind.JsxExpression]: JsxExpressionHandler, - [ts.SyntaxKind.JsxAttribute]: JsxAttributeHandler, - [ts.SyntaxKind.JsxSpreadAttribute]: JsxSpreadAttributeHandler, - [ts.SyntaxKind.Identifier]: IdentifierHandler -} - -export const getAttributeNodeHandler = ( - nodeKind: ts.SyntaxKind, - sourceNode: ts.SourceFile, - logger: Logger -): AttributeNodeHandler => { - const Handler = attributeNodeHandlersMap[nodeKind] ?? DefaultHandler - return new Handler(sourceNode, logger) -} diff --git a/src/main/scopes/jsx/metrics/element-metric.ts b/src/main/scopes/jsx/metrics/element-metric.ts index 42cba2be..ed8b81a3 100644 --- a/src/main/scopes/jsx/metrics/element-metric.ts +++ b/src/main/scopes/jsx/metrics/element-metric.ts @@ -14,8 +14,9 @@ import { deNull } from '../../../core/de-null.js' import { type Logger } from '../../../core/log/logger.js' import { PackageDetailsProvider } from '../../../core/package-details-provider.js' import { ScopeMetric } from '../../../core/scope-metric.js' +import { JsImport } from '../../js/interfaces.js' import { PackageData } from '../../npm/interfaces.js' -import { type JsxElement, type JsxElementAttribute, type JsxImport } from '../interfaces.js' +import { type JsxElement, type JsxElementAttribute } from '../interfaces.js' /** * JSX scope metric that generates a jsx.element individual metric for a given element. @@ -23,7 +24,7 @@ import { type JsxElement, type JsxElementAttribute, type JsxImport } from '../in export class ElementMetric extends ScopeMetric { public override name = 'jsx.element' as const private readonly jsxElement: JsxElement - private readonly matchingImport: JsxImport + private readonly matchingImport: JsImport private readonly allowedAttributeNames: string[] private readonly allowedAttributeStringValues: string[] private readonly instrumentedPackage: PackageData @@ -39,7 +40,7 @@ export class ElementMetric extends ScopeMetric { */ public constructor( jsxElement: JsxElement, - matchingImport: JsxImport, + matchingImport: JsImport, instrumentedPackage: PackageData, config: ConfigSchema, logger: Logger diff --git a/src/main/scopes/jsx/node-handlers/elements/jsx-node-handler.ts b/src/main/scopes/jsx/node-handlers/elements/jsx-node-handler.ts index df1aae33..77e49f7c 100644 --- a/src/main/scopes/jsx/node-handlers/elements/jsx-node-handler.ts +++ b/src/main/scopes/jsx/node-handlers/elements/jsx-node-handler.ts @@ -8,15 +8,15 @@ import * as ts from 'typescript' import { Trace } from '../../../../core/log/trace.js' import { NoAttributeExpressionFoundError } from '../../../../exceptions/no-attribute-expression-found-error.js' +import { JsNodeHandler } from '../../../js/node-handlers/js-node-handler.js' +import { getNodeValueHandler } from '../../../js/node-value-handler-map.js' import { type JsxElement, type JsxElementAttribute } from '../../interfaces.js' -import { getAttributeNodeHandler } from '../../maps/attribute-node-handler-map.js' -import { ElementNodeHandler } from './element-node-handler.js' /** * Holds node handling logic to be inherited by Jsx node handlers. * */ -export abstract class JsxNodeHandler extends ElementNodeHandler { +export abstract class JsxNodeHandler extends JsNodeHandler { /** * Given a TagName node representing a JsxElement, obtains the name and prefix values. * @@ -52,7 +52,7 @@ export abstract class JsxNodeHandler extends ElementNodeHandler { try { attrs.push({ name: attr.name.escapedText.toString(), - value: getAttributeNodeHandler(attr.kind, this.sourceFile, this.logger).getData(attr) + value: getNodeValueHandler(attr.kind, this.sourceFile, this.logger).getData(attr) }) } catch (e) { if (e instanceof NoAttributeExpressionFoundError) { diff --git a/src/main/scopes/npm/find-installers-from-tree.ts b/src/main/scopes/npm/find-installers-from-tree.ts index a0581ab8..06881b62 100644 --- a/src/main/scopes/npm/find-installers-from-tree.ts +++ b/src/main/scopes/npm/find-installers-from-tree.ts @@ -6,10 +6,9 @@ */ import { type Logger } from '../../core/log/logger.js' -import { DependencyTree } from '../jsx/interfaces.js' import { findNestedDeps } from './find-nested-deps.js' import { getPackageSubTree } from './get-package-sub-tree.js' -import { type InstallingPackage } from './interfaces.js' +import { DependencyTree, type InstallingPackage } from './interfaces.js' /* * Nested dependency considerations: diff --git a/src/main/scopes/npm/find-nested-deps.ts b/src/main/scopes/npm/find-nested-deps.ts index 51872e83..1b7a750d 100644 --- a/src/main/scopes/npm/find-nested-deps.ts +++ b/src/main/scopes/npm/find-nested-deps.ts @@ -6,8 +6,7 @@ */ import objectScan from 'object-scan' -import { DependencyTree } from '../jsx/interfaces.js' -import { type InstallingPackage } from './interfaces.js' +import { DependencyTree, type InstallingPackage } from './interfaces.js' /** * Given a dependency tree, package name, and package version, finds all object keys within the tree diff --git a/src/main/scopes/npm/get-dependency-tree.ts b/src/main/scopes/npm/get-dependency-tree.ts index f320e275..d3350637 100644 --- a/src/main/scopes/npm/get-dependency-tree.ts +++ b/src/main/scopes/npm/get-dependency-tree.ts @@ -9,8 +9,8 @@ import { DirectoryEnumerator } from '../../core/directory-enumerator.js' import { Logger } from '../../core/log/logger.js' import { runCommand } from '../../core/run-command.js' import { NoNodeModulesFoundError } from '../../exceptions/no-node-modules-found-error.js' -import { DependencyTree } from '../jsx/interfaces.js' import { hasNodeModulesFolder } from './has-node-modules-folder.js' +import { DependencyTree } from './interfaces.js' const cache = new Map>() diff --git a/src/main/scopes/npm/get-installed-version-paths.ts b/src/main/scopes/npm/get-installed-version-paths.ts new file mode 100644 index 00000000..0a8065ff --- /dev/null +++ b/src/main/scopes/npm/get-installed-version-paths.ts @@ -0,0 +1,32 @@ +/* + * Copyright IBM Corp. 2024, 2024 + * + * This source code is licensed under the Apache-2.0 license found in the + * LICENSE file in the root directory of this source tree. + */ + +import { findNestedDeps } from './find-nested-deps.js' +import { DependencyTree } from './interfaces.js' + +/** + * Finds all installed versions of a given package within a dependency tree + * and returns the direct-most paths. + * + * @param dependencyTree - The tree to search. + * @param pkgName - The tree to search. + * @returns A (possibly empty) array of paths. + */ +export function getInstalledVersionPaths(dependencyTree: DependencyTree, pkgName: string) { + // find all versions, sort by shortest paths + const instrumentedInstallPaths = findNestedDeps(dependencyTree, pkgName, () => true).sort( + (a, b) => a.length - b.length + ) + + if (instrumentedInstallPaths.length > 0) { + // return all paths with shortest length + return instrumentedInstallPaths.filter( + (path) => path.length === instrumentedInstallPaths[0]?.length + ) + } + return [] +} diff --git a/src/main/scopes/npm/get-package-sub-tree.ts b/src/main/scopes/npm/get-package-sub-tree.ts index 71df4165..64f2df2e 100644 --- a/src/main/scopes/npm/get-package-sub-tree.ts +++ b/src/main/scopes/npm/get-package-sub-tree.ts @@ -8,8 +8,7 @@ import getPropertyByPath from 'lodash/get.js' import { InvalidObjectPathError } from '../../exceptions/invalid-object-path-error.js' -import { DependencyTree } from '../jsx/interfaces.js' -import { type InstallingPackage, type PackageData } from './interfaces.js' +import { DependencyTree, type InstallingPackage, type PackageData } from './interfaces.js' /** * Given a dependency tree and an object path within that tree, return an object containing name, diff --git a/src/main/scopes/npm/get-package-trees.ts b/src/main/scopes/npm/get-package-trees.ts new file mode 100644 index 00000000..e20b38bd --- /dev/null +++ b/src/main/scopes/npm/get-package-trees.ts @@ -0,0 +1,44 @@ +/* + * Copyright IBM Corp. 2024, 2024 + * + * This source code is licensed under the Apache-2.0 license found in the + * LICENSE file in the root directory of this source tree. + */ + +import getPropertyByPath from 'lodash/get.js' + +import { findNestedDeps } from './find-nested-deps.js' +import { DependencyTree, PackageData } from './interfaces.js' + +/** + * Finds all dependency sub-trees rooted at the desired package/version + * given a bigger dependency tree. + * + * @param dependencyTree - The tree to search. + * @param pkg - Package to find rooted trees for. + * @returns A (possibly empty) array of dependency trees rooted at the pkg param. + */ +export function getPackageTrees( + dependencyTree: DependencyTree, + pkg: PackageData +): DependencyTree[] { + if (dependencyTree.name === pkg.name && dependencyTree.version === pkg.version) { + dependencyTree['path'] = [] + return [dependencyTree] + } + + const prefixPackagePaths = findNestedDeps( + dependencyTree, + pkg.name, + ({ value }) => value.version === pkg.version + ) + + if (prefixPackagePaths.length > 0) { + return prefixPackagePaths.map((path) => ({ + path, + ...getPropertyByPath(dependencyTree, path) + })) + } + + return [] +} diff --git a/src/main/scopes/npm/get-tree-predecessor.ts b/src/main/scopes/npm/get-tree-predecessor.ts new file mode 100644 index 00000000..90ddbf5e --- /dev/null +++ b/src/main/scopes/npm/get-tree-predecessor.ts @@ -0,0 +1,34 @@ +/* + * Copyright IBM Corp. 2024, 2024 + * + * This source code is licensed under the Apache-2.0 license found in the + * LICENSE file in the root directory of this source tree. + */ + +import getPropertyByPath from 'lodash/get.js' +import { ObjectPath } from 'object-scan' + +import { DependencyTree } from './interfaces.js' + +/** + * Retrieves a tree rooted at the parent of a package given it's dependencyTree path. + * + * @param dependencyTree - The tree to search. + * @param packagePath - Path to the package to get parent tree for in the dependencyTree. + * @returns A dependency tree rooted at the package's parent + * or undefined if the package is already the root. + */ +export function getTreePredecessor( + dependencyTree: DependencyTree, + packagePath: ObjectPath +): DependencyTree | undefined { + // already at the root + if (packagePath.length === 0) return undefined + + if (packagePath.length === 2) return { path: [], ...dependencyTree } + + return { + path: packagePath.slice(0, -2), + ...getPropertyByPath(dependencyTree, packagePath.slice(0, -2)) + } +} diff --git a/src/main/scopes/npm/interfaces.ts b/src/main/scopes/npm/interfaces.ts index 6fa32e52..11b05af5 100644 --- a/src/main/scopes/npm/interfaces.ts +++ b/src/main/scopes/npm/interfaces.ts @@ -13,3 +13,15 @@ export interface PackageData { export interface InstallingPackage extends PackageData { dependencies: PackageData[] } + +export interface DependencyTreeDependency { + version: string + dependencies: Record +} + +export interface DependencyTree extends PackageData { + name: string + version: string + dependencies: Record + [key: string]: unknown +} diff --git a/src/test/scopes/jsx/utils/get-tracked-source-files.test.ts b/src/test/core/get-tracked-source-files.test.ts similarity index 76% rename from src/test/scopes/jsx/utils/get-tracked-source-files.test.ts rename to src/test/core/get-tracked-source-files.test.ts index 2166cbac..2327436a 100644 --- a/src/test/scopes/jsx/utils/get-tracked-source-files.test.ts +++ b/src/test/core/get-tracked-source-files.test.ts @@ -7,9 +7,9 @@ import path from 'path' import { describe, expect, it } from 'vitest' -import { getTrackedSourceFiles } from '../../../../main/scopes/jsx/utils/get-tracked-source-files.js' -import { Fixture } from '../../../__utils/fixture.js' -import { initLogger } from '../../../__utils/init-logger.js' +import { getTrackedSourceFiles } from '../../main/core/get-tracked-source-files.js' +import { Fixture } from '../__utils/fixture.js' +import { initLogger } from '../__utils/init-logger.js' describe('getTrackedSourceFiles', () => { const logger = initLogger() @@ -17,7 +17,13 @@ describe('getTrackedSourceFiles', () => { it('correctly returns all tracked source files', async () => { const root = new Fixture('projects/all-extensions') - const sourceFiles = await getTrackedSourceFiles(root.path, logger) + const sourceFiles = await getTrackedSourceFiles(root.path, logger, [ + '.js', + '.mjs', + '.cjs', + '.jsx', + '.tsx' + ]) expect(sourceFiles.map((file) => file.fileName)).toStrictEqual([ path.join(root.path, 'test.cjs'), @@ -31,7 +37,13 @@ describe('getTrackedSourceFiles', () => { it('correctly includes root', async () => { const root = new Fixture('projects/all-extensions/test.js') - const sourceFiles = await getTrackedSourceFiles(root.path, logger) + const sourceFiles = await getTrackedSourceFiles(root.path, logger, [ + '.js', + '.mjs', + '.cjs', + '.jsx', + '.tsx' + ]) expect(sourceFiles.map((file) => file.fileName)).toStrictEqual([root.path]) }) @@ -39,7 +51,13 @@ describe('getTrackedSourceFiles', () => { it('correctly returns empty tracked files array when no files match the desired extensions', async () => { const root = new Fixture('projects/all-extensions/nested/deeply-nested/irrelevant-nested-files') - const sourceFiles = await getTrackedSourceFiles(root.path, logger) + const sourceFiles = await getTrackedSourceFiles(root.path, logger, [ + '.js', + '.mjs', + '.cjs', + '.jsx', + '.tsx' + ]) expect(sourceFiles.map((file) => file.fileName)).toStrictEqual([]) }) @@ -47,7 +65,13 @@ describe('getTrackedSourceFiles', () => { it('correctly returns empty tracked files array when directory does not exist', async () => { const root = new Fixture('scopes/jsx/not-a-real-directory') - const sourceFiles = await getTrackedSourceFiles(root.path, logger) + const sourceFiles = await getTrackedSourceFiles(root.path, logger, [ + '.js', + '.mjs', + '.cjs', + '.jsx', + '.tsx' + ]) expect(sourceFiles.map((file) => file.fileName)).toStrictEqual([]) }) diff --git a/src/test/scopes/js/__snapshots__/process-file.test.ts.snap b/src/test/scopes/js/__snapshots__/process-file.test.ts.snap new file mode 100644 index 00000000..b7b73183 --- /dev/null +++ b/src/test/scopes/js/__snapshots__/process-file.test.ts.snap @@ -0,0 +1,58 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`processFile > correctly detects imports and elements in a given file > elements 1`] = ` +[ + { + "attributes": [ + { + "name": "firstProp", + "value": ComplexValue { + "complexValue": "1 === 5 ? 'boo' : 'baa'", + }, + }, + { + "name": "simple", + "value": "4", + }, + { + "name": "woo", + "value": true, + }, + ], + "name": "Button", + "prefix": undefined, + }, + { + "attributes": [ + { + "name": "firstProp", + "value": ComplexValue { + "complexValue": "1 === 5 ? 'boo' : 'baa'", + }, + }, + { + "name": "simple", + "value": "4", + }, + { + "name": "woo", + "value": true, + }, + ], + "name": "Button", + "prefix": undefined, + }, +] +`; + +exports[`processFile > correctly detects imports and elements in a given file > imports 1`] = ` +[ + { + "isAll": false, + "isDefault": true, + "name": "[Default]", + "path": "instrumented", + "rename": "Button", + }, +] +`; diff --git a/src/test/scopes/jsx/node-handlers/elements/__snapshots__/import-node-handler.test.ts.snap b/src/test/scopes/js/node-handlers/__snapshots__/import-node-handler.test.ts.snap similarity index 100% rename from src/test/scopes/jsx/node-handlers/elements/__snapshots__/import-node-handler.test.ts.snap rename to src/test/scopes/js/node-handlers/__snapshots__/import-node-handler.test.ts.snap diff --git a/src/test/scopes/jsx/node-handlers/elements/import-node-handler.test.ts b/src/test/scopes/js/node-handlers/import-node-handler.test.ts similarity index 56% rename from src/test/scopes/jsx/node-handlers/elements/import-node-handler.test.ts rename to src/test/scopes/js/node-handlers/import-node-handler.test.ts index 1746e4cb..26f5f6e0 100644 --- a/src/test/scopes/jsx/node-handlers/elements/import-node-handler.test.ts +++ b/src/test/scopes/js/node-handlers/import-node-handler.test.ts @@ -7,17 +7,20 @@ import * as ts from 'typescript' import { describe, expect, it } from 'vitest' -import { JsxElementAccumulator } from '../../../../../main/scopes/jsx/jsx-element-accumulator.js' -import { ImportNodeHandler } from '../../../../../main/scopes/jsx/node-handlers/elements/import-node-handler.js' -import { getTrackedSourceFiles } from '../../../../../main/scopes/jsx/utils/get-tracked-source-files.js' -import { findNodesByType } from '../../../../__utils/find-nodes-by-type.js' -import { Fixture } from '../../../../__utils/fixture.js' -import { initLogger } from '../../../../__utils/init-logger.js' +import { getTrackedSourceFiles } from '../../../../main/core/get-tracked-source-files.js' +import { ImportNodeHandler } from '../../../../main/scopes/js/node-handlers/import-node-handler.js' +import { JsxElementAccumulator } from '../../../../main/scopes/jsx/jsx-element-accumulator.js' +import { JsxScope } from '../../../../main/scopes/jsx/jsx-scope.js' +import { findNodesByType } from '../../../__utils/find-nodes-by-type.js' +import { Fixture } from '../../../__utils/fixture.js' +import { initLogger } from '../../../__utils/init-logger.js' describe('class: ImportNodeHandler', async () => { const logger = initLogger() const fixture = new Fixture('jsx-samples/all-import-types.tsx') - const sourceFile = (await getTrackedSourceFiles(fixture.path, logger))[0] as ts.SourceFile + const sourceFile = ( + await getTrackedSourceFiles(fixture.path, logger, JsxScope.fileExtensions) + )[0] as ts.SourceFile const accumulator = new JsxElementAccumulator() const handler = new ImportNodeHandler(sourceFile, logger) it('correctly returns the JsxImports for a complex fixture', () => { diff --git a/src/test/scopes/jsx/node-handlers/attributes/__snapshots__/jsx-expression-handler.test.ts.snap b/src/test/scopes/js/node-handlers/value-handlers/__snapshots__/jsx-expression-handler.test.ts.snap similarity index 82% rename from src/test/scopes/jsx/node-handlers/attributes/__snapshots__/jsx-expression-handler.test.ts.snap rename to src/test/scopes/js/node-handlers/value-handlers/__snapshots__/jsx-expression-handler.test.ts.snap index d362d440..37fa4cd7 100644 --- a/src/test/scopes/jsx/node-handlers/attributes/__snapshots__/jsx-expression-handler.test.ts.snap +++ b/src/test/scopes/js/node-handlers/value-handlers/__snapshots__/jsx-expression-handler.test.ts.snap @@ -5,18 +5,18 @@ exports[`class: JsxExpressionHandler > correctly returns node text 1`] = ` 5, undefined, null, - ComplexAttribute { + ComplexValue { "complexValue": "{key: 'value'}", }, true, false, - ComplexAttribute { + ComplexValue { "complexValue": "obj['accessKey']", }, - ComplexAttribute { + ComplexValue { "complexValue": "someIdentifier", }, - ComplexAttribute { + ComplexValue { "complexValue": "1 === 5 ? 'expression lhs' : 'expression rhs'", }, ] diff --git a/src/test/scopes/jsx/node-handlers/attributes/default-handler.test.ts b/src/test/scopes/js/node-handlers/value-handlers/default-handler.test.ts similarity index 62% rename from src/test/scopes/jsx/node-handlers/attributes/default-handler.test.ts rename to src/test/scopes/js/node-handlers/value-handlers/default-handler.test.ts index a2482284..4d8764f5 100644 --- a/src/test/scopes/jsx/node-handlers/attributes/default-handler.test.ts +++ b/src/test/scopes/js/node-handlers/value-handlers/default-handler.test.ts @@ -7,9 +7,10 @@ import * as ts from 'typescript' import { describe, expect, it } from 'vitest' -import { ComplexAttribute } from '../../../../../main/scopes/jsx/complex-attribute.js' -import { DefaultHandler } from '../../../../../main/scopes/jsx/node-handlers/attributes/default-handler.js' -import { getTrackedSourceFiles } from '../../../../../main/scopes/jsx/utils/get-tracked-source-files.js' +import { getTrackedSourceFiles } from '../../../../../main/core/get-tracked-source-files.js' +import { ComplexValue } from '../../../../../main/scopes/js/complex-value.js' +import { DefaultHandler } from '../../../../../main/scopes/js/node-handlers/value-handlers/default-handler.js' +import { JsxScope } from '../../../../../main/scopes/jsx/jsx-scope.js' import { findNodesByType } from '../../../../__utils/find-nodes-by-type.js' import { Fixture } from '../../../../__utils/fixture.js' import { initLogger } from '../../../../__utils/init-logger.js' @@ -19,7 +20,9 @@ describe('defaultHandler', () => { it('correctly returns node text', async () => { const fixture = new Fixture('jsx-samples/all-attr-types.tsx') - const sourceFile = (await getTrackedSourceFiles(fixture.path, logger))[0] as ts.SourceFile + const sourceFile = ( + await getTrackedSourceFiles(fixture.path, logger, JsxScope.fileExtensions) + )[0] as ts.SourceFile const handler = new DefaultHandler(sourceFile, logger) @@ -31,6 +34,6 @@ describe('defaultHandler', () => { (node) => node.parent.getChildAt(0).getText(sourceFile) === 'expressionProp' )[0] as ts.JsxExpression ) - ).toStrictEqual(new ComplexAttribute("{1 === 5 ? 'expression lhs' : 'expression rhs'}")) + ).toStrictEqual(new ComplexValue("{1 === 5 ? 'expression lhs' : 'expression rhs'}")) }) }) diff --git a/src/test/scopes/jsx/node-handlers/attributes/false-keyword-handler.test.ts b/src/test/scopes/js/node-handlers/value-handlers/false-keyword-handler.test.ts similarity index 70% rename from src/test/scopes/jsx/node-handlers/attributes/false-keyword-handler.test.ts rename to src/test/scopes/js/node-handlers/value-handlers/false-keyword-handler.test.ts index 2bae9971..727a08db 100644 --- a/src/test/scopes/jsx/node-handlers/attributes/false-keyword-handler.test.ts +++ b/src/test/scopes/js/node-handlers/value-handlers/false-keyword-handler.test.ts @@ -7,8 +7,9 @@ import type * as ts from 'typescript' import { describe, expect, it } from 'vitest' -import { FalseKeywordHandler } from '../../../../../main/scopes/jsx/node-handlers/attributes/false-keyword-handler.js' -import { getTrackedSourceFiles } from '../../../../../main/scopes/jsx/utils/get-tracked-source-files.js' +import { getTrackedSourceFiles } from '../../../../../main/core/get-tracked-source-files.js' +import { FalseKeywordHandler } from '../../../../../main/scopes/js/node-handlers/value-handlers/false-keyword-handler.js' +import { JsxScope } from '../../../../../main/scopes/jsx/jsx-scope.js' import { Fixture } from '../../../../__utils/fixture.js' import { initLogger } from '../../../../__utils/init-logger.js' @@ -17,7 +18,9 @@ describe('falseKeywordHandler', () => { it('correctly returns node text', async () => { const fixture = new Fixture('jsx-samples/all-attr-types.tsx') - const sourceFile = (await getTrackedSourceFiles(fixture.path, logger))[0] as ts.SourceFile + const sourceFile = ( + await getTrackedSourceFiles(fixture.path, logger, JsxScope.fileExtensions) + )[0] as ts.SourceFile const handler = new FalseKeywordHandler(sourceFile, logger) diff --git a/src/test/scopes/jsx/node-handlers/attributes/identifier-handler.test.ts b/src/test/scopes/js/node-handlers/value-handlers/identifier-handler.test.ts similarity index 70% rename from src/test/scopes/jsx/node-handlers/attributes/identifier-handler.test.ts rename to src/test/scopes/js/node-handlers/value-handlers/identifier-handler.test.ts index 736da288..5a4bd9ca 100644 --- a/src/test/scopes/jsx/node-handlers/attributes/identifier-handler.test.ts +++ b/src/test/scopes/js/node-handlers/value-handlers/identifier-handler.test.ts @@ -7,9 +7,10 @@ import * as ts from 'typescript' import { describe, expect, it } from 'vitest' -import { ComplexAttribute } from '../../../../../main/scopes/jsx/complex-attribute.js' -import { IdentifierHandler } from '../../../../../main/scopes/jsx/node-handlers/attributes/identifier-handler.js' -import { getTrackedSourceFiles } from '../../../../../main/scopes/jsx/utils/get-tracked-source-files.js' +import { getTrackedSourceFiles } from '../../../../../main/core/get-tracked-source-files.js' +import { ComplexValue } from '../../../../../main/scopes/js/complex-value.js' +import { IdentifierHandler } from '../../../../../main/scopes/js/node-handlers/value-handlers/identifier-handler.js' +import { JsxScope } from '../../../../../main/scopes/jsx/jsx-scope.js' import { findNodesByType } from '../../../../__utils/find-nodes-by-type.js' import { Fixture } from '../../../../__utils/fixture.js' import { initLogger } from '../../../../__utils/init-logger.js' @@ -19,7 +20,9 @@ describe('identifierHandler', () => { it('correctly returns undefined for undefined attribute value', async () => { const fixture = new Fixture('jsx-samples/all-attr-types.tsx') - const sourceFile = (await getTrackedSourceFiles(fixture.path, logger))[0] as ts.SourceFile + const sourceFile = ( + await getTrackedSourceFiles(fixture.path, logger, JsxScope.fileExtensions) + )[0] as ts.SourceFile const handler = new IdentifierHandler(sourceFile, logger) @@ -34,7 +37,9 @@ describe('identifierHandler', () => { it('correctly returns complex attribute', async () => { const fixture = new Fixture('jsx-samples/all-attr-types.tsx') - const sourceFile = (await getTrackedSourceFiles(fixture.path, logger))[0] as ts.SourceFile + const sourceFile = ( + await getTrackedSourceFiles(fixture.path, logger, JsxScope.fileExtensions) + )[0] as ts.SourceFile const handler = new IdentifierHandler(sourceFile, logger) @@ -44,6 +49,6 @@ describe('identifierHandler', () => { return node.parent.getChildAt(0).getText(sourceFile) === 'identifierProp' })[0] as ts.Identifier ) - ).toStrictEqual(new ComplexAttribute('identifierProp')) + ).toStrictEqual(new ComplexValue('identifierProp')) }) }) diff --git a/src/test/scopes/jsx/node-handlers/attributes/jsx-attribute-handler.test.ts b/src/test/scopes/js/node-handlers/value-handlers/jsx-attribute-handler.test.ts similarity index 73% rename from src/test/scopes/jsx/node-handlers/attributes/jsx-attribute-handler.test.ts rename to src/test/scopes/js/node-handlers/value-handlers/jsx-attribute-handler.test.ts index afe03126..2a695196 100644 --- a/src/test/scopes/jsx/node-handlers/attributes/jsx-attribute-handler.test.ts +++ b/src/test/scopes/js/node-handlers/value-handlers/jsx-attribute-handler.test.ts @@ -7,8 +7,9 @@ import * as ts from 'typescript' import { describe, expect, it } from 'vitest' -import { JsxAttributeHandler } from '../../../../../main/scopes/jsx/node-handlers/attributes/jsx-attribute-handler.js' -import { getTrackedSourceFiles } from '../../../../../main/scopes/jsx/utils/get-tracked-source-files.js' +import { getTrackedSourceFiles } from '../../../../../main/core/get-tracked-source-files.js' +import { JsxAttributeHandler } from '../../../../../main/scopes/js/node-handlers/value-handlers/jsx-attribute-handler.js' +import { JsxScope } from '../../../../../main/scopes/jsx/jsx-scope.js' import { findNodesByType } from '../../../../__utils/find-nodes-by-type.js' import { Fixture } from '../../../../__utils/fixture.js' import { initLogger } from '../../../../__utils/init-logger.js' @@ -17,7 +18,9 @@ describe('class: JsxAttributeHandler', () => { const logger = initLogger() it('correctly returns node text', async () => { const fixture = new Fixture('jsx-samples/all-attr-types.tsx') - const sourceFile = (await getTrackedSourceFiles(fixture.path, logger))[0] as ts.SourceFile + const sourceFile = ( + await getTrackedSourceFiles(fixture.path, logger, JsxScope.fileExtensions) + )[0] as ts.SourceFile const handler = new JsxAttributeHandler(sourceFile, logger) const nodes = findNodesByType(sourceFile, ts.SyntaxKind.JsxAttribute) diff --git a/src/test/scopes/jsx/node-handlers/attributes/jsx-expression-handler.test.ts b/src/test/scopes/js/node-handlers/value-handlers/jsx-expression-handler.test.ts similarity index 76% rename from src/test/scopes/jsx/node-handlers/attributes/jsx-expression-handler.test.ts rename to src/test/scopes/js/node-handlers/value-handlers/jsx-expression-handler.test.ts index 27df32df..5b541b0a 100644 --- a/src/test/scopes/jsx/node-handlers/attributes/jsx-expression-handler.test.ts +++ b/src/test/scopes/js/node-handlers/value-handlers/jsx-expression-handler.test.ts @@ -7,9 +7,10 @@ import * as ts from 'typescript' import { describe, expect, it } from 'vitest' +import { getTrackedSourceFiles } from '../../../../../main/core/get-tracked-source-files.js' import { NoAttributeExpressionFoundError } from '../../../../../main/exceptions/no-attribute-expression-found-error.js' -import { JsxExpressionHandler } from '../../../../../main/scopes/jsx/node-handlers/attributes/jsx-expression-handler.js' -import { getTrackedSourceFiles } from '../../../../../main/scopes/jsx/utils/get-tracked-source-files.js' +import { JsxExpressionHandler } from '../../../../../main/scopes/js/node-handlers/value-handlers/jsx-expression-handler.js' +import { JsxScope } from '../../../../../main/scopes/jsx/jsx-scope.js' import { findNodesByType } from '../../../../__utils/find-nodes-by-type.js' import { Fixture } from '../../../../__utils/fixture.js' import { initLogger } from '../../../../__utils/init-logger.js' @@ -18,7 +19,9 @@ describe('class: JsxExpressionHandler', () => { const logger = initLogger() it('correctly returns node text', async () => { const fixture = new Fixture('jsx-samples/all-attr-types.tsx') - const sourceFile = (await getTrackedSourceFiles(fixture.path, logger))[0] as ts.SourceFile + const sourceFile = ( + await getTrackedSourceFiles(fixture.path, logger, JsxScope.fileExtensions) + )[0] as ts.SourceFile const handler = new JsxExpressionHandler(sourceFile, logger) const nodes = findNodesByType(sourceFile, ts.SyntaxKind.JsxExpression) @@ -29,7 +32,9 @@ describe('class: JsxExpressionHandler', () => { it('throws NoAttributeExpressionFoundError if attribute does not have an expression', async () => { const fixture = new Fixture('jsx-samples/all-jsx-element-types.tsx') - const sourceFile = (await getTrackedSourceFiles(fixture.path, logger))[0] as ts.SourceFile + const sourceFile = ( + await getTrackedSourceFiles(fixture.path, logger, JsxScope.fileExtensions) + )[0] as ts.SourceFile const handler = new JsxExpressionHandler(sourceFile, logger) const node = findNodesByType( diff --git a/src/test/scopes/jsx/node-handlers/attributes/jsx-spread-attribute-handler.test.ts b/src/test/scopes/js/node-handlers/value-handlers/jsx-spread-attribute-handler.test.ts similarity index 65% rename from src/test/scopes/jsx/node-handlers/attributes/jsx-spread-attribute-handler.test.ts rename to src/test/scopes/js/node-handlers/value-handlers/jsx-spread-attribute-handler.test.ts index ff1a06cb..f149e9c4 100644 --- a/src/test/scopes/jsx/node-handlers/attributes/jsx-spread-attribute-handler.test.ts +++ b/src/test/scopes/js/node-handlers/value-handlers/jsx-spread-attribute-handler.test.ts @@ -7,9 +7,10 @@ import * as ts from 'typescript' import { describe, expect, it } from 'vitest' -import { ComplexAttribute } from '../../../../../main/scopes/jsx/complex-attribute.js' -import { JsxSpreadAttributeHandler } from '../../../../../main/scopes/jsx/node-handlers/attributes/jsx-spread-attribute-handler.js' -import { getTrackedSourceFiles } from '../../../../../main/scopes/jsx/utils/get-tracked-source-files.js' +import { getTrackedSourceFiles } from '../../../../../main/core/get-tracked-source-files.js' +import { ComplexValue } from '../../../../../main/scopes/js/complex-value.js' +import { JsxSpreadAttributeHandler } from '../../../../../main/scopes/js/node-handlers/value-handlers/jsx-spread-attribute-handler.js' +import { JsxScope } from '../../../../../main/scopes/jsx/jsx-scope.js' import { findNodesByType } from '../../../../__utils/find-nodes-by-type.js' import { Fixture } from '../../../../__utils/fixture.js' import { initLogger } from '../../../../__utils/init-logger.js' @@ -18,13 +19,15 @@ describe('class: JsxSpreadAttributeHandler', () => { const logger = initLogger() it('correctly returns node text', async () => { const fixture = new Fixture('jsx-samples/all-jsx-element-types.tsx') - const sourceFile = (await getTrackedSourceFiles(fixture.path, logger))[0] as ts.SourceFile + const sourceFile = ( + await getTrackedSourceFiles(fixture.path, logger, JsxScope.fileExtensions) + )[0] as ts.SourceFile const handler = new JsxSpreadAttributeHandler(sourceFile, logger) expect( handler.getData( findNodesByType(sourceFile, ts.SyntaxKind.JsxSpreadAttribute)[0] as ts.JsxSpreadAttribute ) - ).toStrictEqual(new ComplexAttribute('{...spreadObj}')) + ).toStrictEqual(new ComplexValue('{...spreadObj}')) }) }) diff --git a/src/test/scopes/jsx/node-handlers/attributes/null-keyword-handler.test.ts b/src/test/scopes/js/node-handlers/value-handlers/null-keyword-handler.test.ts similarity index 68% rename from src/test/scopes/jsx/node-handlers/attributes/null-keyword-handler.test.ts rename to src/test/scopes/js/node-handlers/value-handlers/null-keyword-handler.test.ts index 9f0c4243..ac2b86e5 100644 --- a/src/test/scopes/jsx/node-handlers/attributes/null-keyword-handler.test.ts +++ b/src/test/scopes/js/node-handlers/value-handlers/null-keyword-handler.test.ts @@ -7,8 +7,9 @@ import type * as ts from 'typescript' import { describe, expect, it } from 'vitest' -import { NullKeywordHandler } from '../../../../../main/scopes/jsx/node-handlers/attributes/null-keyword-handler.js' -import { getTrackedSourceFiles } from '../../../../../main/scopes/jsx/utils/get-tracked-source-files.js' +import { getTrackedSourceFiles } from '../../../../../main/core/get-tracked-source-files.js' +import { NullKeywordHandler } from '../../../../../main/scopes/js/node-handlers/value-handlers/null-keyword-handler.js' +import { JsxScope } from '../../../../../main/scopes/jsx/jsx-scope.js' import { Fixture } from '../../../../__utils/fixture.js' import { initLogger } from '../../../../__utils/init-logger.js' @@ -17,7 +18,9 @@ describe('nullKeywordHandler', () => { it('correctly returns node text', async () => { const fixture = new Fixture('jsx-samples/all-attr-types.tsx') - const sourceFile = (await getTrackedSourceFiles(fixture.path, logger))[0] as ts.SourceFile + const sourceFile = ( + await getTrackedSourceFiles(fixture.path, logger, JsxScope.fileExtensions) + )[0] as ts.SourceFile const handler = new NullKeywordHandler(sourceFile, logger) diff --git a/src/test/scopes/jsx/node-handlers/attributes/numeric-literal-handler.test.ts b/src/test/scopes/js/node-handlers/value-handlers/numeric-literal-handler.test.ts similarity index 72% rename from src/test/scopes/jsx/node-handlers/attributes/numeric-literal-handler.test.ts rename to src/test/scopes/js/node-handlers/value-handlers/numeric-literal-handler.test.ts index 88dd8336..5e5635c0 100644 --- a/src/test/scopes/jsx/node-handlers/attributes/numeric-literal-handler.test.ts +++ b/src/test/scopes/js/node-handlers/value-handlers/numeric-literal-handler.test.ts @@ -7,8 +7,9 @@ import * as ts from 'typescript' import { describe, expect, it } from 'vitest' -import { NumericLiteralHandler } from '../../../../../main/scopes/jsx/node-handlers/attributes/numeric-literal-handler.js' -import { getTrackedSourceFiles } from '../../../../../main/scopes/jsx/utils/get-tracked-source-files.js' +import { getTrackedSourceFiles } from '../../../../../main/core/get-tracked-source-files.js' +import { NumericLiteralHandler } from '../../../../../main/scopes/js/node-handlers/value-handlers/numeric-literal-handler.js' +import { JsxScope } from '../../../../../main/scopes/jsx/jsx-scope.js' import { findNodesByType } from '../../../../__utils/find-nodes-by-type.js' import { Fixture } from '../../../../__utils/fixture.js' import { initLogger } from '../../../../__utils/init-logger.js' @@ -18,7 +19,9 @@ describe('numericLiteralHandler', () => { it('correctly returns node text', async () => { const fixture = new Fixture('jsx-samples/all-attr-types.tsx') - const sourceFile = (await getTrackedSourceFiles(fixture.path, logger))[0] as ts.SourceFile + const sourceFile = ( + await getTrackedSourceFiles(fixture.path, logger, JsxScope.fileExtensions) + )[0] as ts.SourceFile const handler = new NumericLiteralHandler(sourceFile, logger) diff --git a/src/test/scopes/jsx/node-handlers/attributes/string-literal-handler.test.ts b/src/test/scopes/js/node-handlers/value-handlers/string-literal-handler.test.ts similarity index 77% rename from src/test/scopes/jsx/node-handlers/attributes/string-literal-handler.test.ts rename to src/test/scopes/js/node-handlers/value-handlers/string-literal-handler.test.ts index c5c23a24..59aeccb1 100644 --- a/src/test/scopes/jsx/node-handlers/attributes/string-literal-handler.test.ts +++ b/src/test/scopes/js/node-handlers/value-handlers/string-literal-handler.test.ts @@ -7,8 +7,9 @@ import * as ts from 'typescript' import { describe, expect, it } from 'vitest' -import { StringLiteralHandler } from '../../../../../main/scopes/jsx/node-handlers/attributes/string-literal-handler.js' -import { getTrackedSourceFiles } from '../../../../../main/scopes/jsx/utils/get-tracked-source-files.js' +import { getTrackedSourceFiles } from '../../../../../main/core/get-tracked-source-files.js' +import { StringLiteralHandler } from '../../../../../main/scopes/js/node-handlers/value-handlers/string-literal-handler.js' +import { JsxScope } from '../../../../../main/scopes/jsx/jsx-scope.js' import { findNodesByType } from '../../../../__utils/find-nodes-by-type.js' import { Fixture } from '../../../../__utils/fixture.js' import { initLogger } from '../../../../__utils/init-logger.js' @@ -18,7 +19,9 @@ describe('stringLiteralHandler', () => { it('correctly returns node text', async () => { const fixture = new Fixture('jsx-samples/all-attr-types.tsx') - const sourceFile = (await getTrackedSourceFiles(fixture.path, logger))[0] as ts.SourceFile + const sourceFile = ( + await getTrackedSourceFiles(fixture.path, logger, JsxScope.fileExtensions) + )[0] as ts.SourceFile const handler = new StringLiteralHandler(sourceFile, logger) const nodes = findNodesByType(sourceFile, ts.SyntaxKind.StringLiteral, (node) => { diff --git a/src/test/scopes/jsx/node-handlers/attributes/true-keyword-handler.test.ts b/src/test/scopes/js/node-handlers/value-handlers/true-keyword-handler.test.ts similarity index 71% rename from src/test/scopes/jsx/node-handlers/attributes/true-keyword-handler.test.ts rename to src/test/scopes/js/node-handlers/value-handlers/true-keyword-handler.test.ts index 4c61f383..28ed32b5 100644 --- a/src/test/scopes/jsx/node-handlers/attributes/true-keyword-handler.test.ts +++ b/src/test/scopes/js/node-handlers/value-handlers/true-keyword-handler.test.ts @@ -7,8 +7,9 @@ import type * as ts from 'typescript' import { describe, expect, it } from 'vitest' -import { TrueKeywordHandler } from '../../../../../main/scopes/jsx/node-handlers/attributes/true-keyword-handler.js' -import { getTrackedSourceFiles } from '../../../../../main/scopes/jsx/utils/get-tracked-source-files.js' +import { getTrackedSourceFiles } from '../../../../../main/core/get-tracked-source-files.js' +import { TrueKeywordHandler } from '../../../../../main/scopes/js/node-handlers/value-handlers/true-keyword-handler.js' +import { JsxScope } from '../../../../../main/scopes/jsx/jsx-scope.js' import { Fixture } from '../../../../__utils/fixture.js' import { initLogger } from '../../../../__utils/init-logger.js' @@ -17,7 +18,9 @@ describe('trueKeywordHandler', () => { it('correctly returns node text', async () => { const fixture = new Fixture('jsx-samples/all-attr-types.tsx') - const sourceFile = (await getTrackedSourceFiles(fixture.path, logger))[0] as ts.SourceFile + const sourceFile = ( + await getTrackedSourceFiles(fixture.path, logger, JsxScope.fileExtensions) + )[0] as ts.SourceFile const handler = new TrueKeywordHandler(sourceFile, logger) diff --git a/src/test/scopes/js/process-file.test.ts b/src/test/scopes/js/process-file.test.ts new file mode 100644 index 00000000..a16601a7 --- /dev/null +++ b/src/test/scopes/js/process-file.test.ts @@ -0,0 +1,33 @@ +/* + * Copyright IBM Corp. 2024, 2024 + * + * This source code is licensed under the Apache-2.0 license found in the + * LICENSE file in the root directory of this source tree. + */ + +import * as ts from 'typescript' +import { describe, expect, it } from 'vitest' + +import { getTrackedSourceFiles } from '../../../main/core/get-tracked-source-files.js' +import { processFile } from '../../../main/scopes/js/process-file.js' +import { JsxElementAccumulator } from '../../../main/scopes/jsx/jsx-element-accumulator.js' +import { jsxNodeHandlerMap } from '../../../main/scopes/jsx/jsx-node-handler-map.js' +import { JsxScope } from '../../../main/scopes/jsx/jsx-scope.js' +import { Fixture } from '../../__utils/fixture.js' +import { initLogger } from '../../__utils/init-logger.js' + +describe('processFile', () => { + const fixture = new Fixture('projects/basic-project/test.jsx') + const logger = initLogger() + + it('correctly detects imports and elements in a given file', async () => { + const accumulator = new JsxElementAccumulator() + const sourceFile = ( + await getTrackedSourceFiles(fixture.path, logger, JsxScope.fileExtensions) + )[0] as ts.SourceFile + + processFile(accumulator, sourceFile, jsxNodeHandlerMap, logger) + expect(accumulator.elements).toMatchSnapshot('elements') + expect(accumulator.imports).toMatchSnapshot('imports') + }) +}) diff --git a/src/test/scopes/js/remove-irrelevant-imports.ts b/src/test/scopes/js/remove-irrelevant-imports.ts new file mode 100644 index 00000000..bc668ba3 --- /dev/null +++ b/src/test/scopes/js/remove-irrelevant-imports.ts @@ -0,0 +1,95 @@ +/* + * Copyright IBM Corp. 2024, 2024 + * + * This source code is licensed under the Apache-2.0 license found in the + * LICENSE file in the root directory of this source tree. + */ + +import { describe, expect, it } from 'vitest' + +import { removeIrrelevantImports } from '../../../main/scopes/js/remove-irrelevant-imports.js' +import { JsxElementAccumulator } from '../../../main/scopes/jsx/jsx-element-accumulator.js' + +describe('removeIrrelevantImports', () => { + it('correctly removes unwanted imports', () => { + const accumulator = new JsxElementAccumulator() + accumulator.imports.push({ + name: 'name', + path: 'path', + isDefault: false, + isAll: false + }) + accumulator.imports.push({ + name: 'name', + path: 'instrumented', + isDefault: true, + isAll: false + }) + accumulator.imports.push({ + name: 'name', + path: 'instrumented/bla', + isDefault: false, + isAll: true + }) + expect(accumulator.imports).toHaveLength(3) + removeIrrelevantImports(accumulator, 'instrumented') + expect(accumulator.imports).toHaveLength(2) + }) + + it('removes all imports if no imports match instrumented package', () => { + const accumulator = new JsxElementAccumulator() + accumulator.imports.push({ + name: 'name', + path: 'path', + isDefault: false, + isAll: false + }) + accumulator.imports.push({ + name: 'name', + path: 'not-instrumented', + isDefault: true, + isAll: false + }) + accumulator.imports.push({ + name: 'name', + path: 'not-instrumented/bla', + isDefault: false, + isAll: true + }) + expect(accumulator.imports).toHaveLength(3) + removeIrrelevantImports(accumulator, 'instrumented') + expect(accumulator.imports).toHaveLength(0) + }) + + it("does not remove imports if there aren't any to remove", () => { + const accumulator = new JsxElementAccumulator() + accumulator.imports.push({ + name: 'name', + path: 'instrumented/1', + isDefault: false, + isAll: false + }) + accumulator.imports.push({ + name: 'name', + path: 'instrumented', + isDefault: true, + isAll: false + }) + accumulator.imports.push({ + name: 'name', + path: 'instrumented/bla', + isDefault: false, + isAll: true + }) + expect(accumulator.imports).toHaveLength(3) + removeIrrelevantImports(accumulator, 'instrumented') + expect(accumulator.imports).toHaveLength(3) + }) + + it('can accept empty array', () => { + const accumulator = new JsxElementAccumulator() + expect(accumulator.imports).toHaveLength(0) + removeIrrelevantImports(accumulator, 'instrumented') + expect(accumulator.imports).toHaveLength(0) + }) +}) diff --git a/src/test/scopes/jsx/__snapshots__/jsx-scope.e2e.test.ts.snap b/src/test/scopes/jsx/__snapshots__/jsx-scope.e2e.test.ts.snap index 818508ef..994f65d0 100644 --- a/src/test/scopes/jsx/__snapshots__/jsx-scope.e2e.test.ts.snap +++ b/src/test/scopes/jsx/__snapshots__/jsx-scope.e2e.test.ts.snap @@ -6,7 +6,7 @@ exports[`class: JsxScope > parseFile > correctly detects imports and elements in "attributes": [ { "name": "firstProp", - "value": ComplexAttribute { + "value": ComplexValue { "complexValue": "1 === 5 ? 'boo' : 'baa'", }, }, @@ -26,7 +26,7 @@ exports[`class: JsxScope > parseFile > correctly detects imports and elements in "attributes": [ { "name": "firstProp", - "value": ComplexAttribute { + "value": ComplexValue { "complexValue": "1 === 5 ? 'boo' : 'baa'", }, }, diff --git a/src/test/scopes/jsx/import-matchers/all-import-matcher.test.ts b/src/test/scopes/jsx/import-matchers/jsx-element-all-import-matcher.test.ts similarity index 76% rename from src/test/scopes/jsx/import-matchers/all-import-matcher.test.ts rename to src/test/scopes/jsx/import-matchers/jsx-element-all-import-matcher.test.ts index ec4be0b1..a890ed08 100644 --- a/src/test/scopes/jsx/import-matchers/all-import-matcher.test.ts +++ b/src/test/scopes/jsx/import-matchers/jsx-element-all-import-matcher.test.ts @@ -6,37 +6,38 @@ */ import { describe, expect, it } from 'vitest' +import { JsImport } from '../../../../main/scopes/js/interfaces.js' import { DEFAULT_ELEMENT_NAME } from '../../../../main/scopes/jsx/constants.js' -import { AllImportMatcher } from '../../../../main/scopes/jsx/import-matchers/all-import-matcher.js' -import { type JsxElement, type JsxImport } from '../../../../main/scopes/jsx/interfaces.js' +import { JsxElementAllImportMatcher } from '../../../../main/scopes/jsx/import-matchers/jsx-element-all-import-matcher.js' +import { type JsxElement } from '../../../../main/scopes/jsx/interfaces.js' describe('class: AllImportMatcher', () => { - const allImportMatcher = new AllImportMatcher() + const allImportMatcher = new JsxElementAllImportMatcher() const element: JsxElement = { name: 'theElement', prefix: 'prefix', attributes: [] } - const allImport: JsxImport = { + const allImport: JsImport = { name: 'prefix', path: '@library/something', isAll: true, isDefault: false } - const defaultImport: JsxImport = { + const defaultImport: JsImport = { name: DEFAULT_ELEMENT_NAME, path: '@library/something', rename: 'theElement', isDefault: true, isAll: false } - const namedImport: JsxImport = { + const namedImport: JsImport = { name: 'theElement', path: '@library/something', isDefault: false, isAll: false } - const renamedImport: JsxImport = { + const renamedImport: JsImport = { name: 'Button', path: '@library/something', rename: 'theElement', diff --git a/src/test/scopes/jsx/import-matchers/named-import-matcher.test.ts b/src/test/scopes/jsx/import-matchers/jsx-element-named-import-matcher.test.ts similarity index 78% rename from src/test/scopes/jsx/import-matchers/named-import-matcher.test.ts rename to src/test/scopes/jsx/import-matchers/jsx-element-named-import-matcher.test.ts index bb6a7e07..392454d3 100644 --- a/src/test/scopes/jsx/import-matchers/named-import-matcher.test.ts +++ b/src/test/scopes/jsx/import-matchers/jsx-element-named-import-matcher.test.ts @@ -6,37 +6,38 @@ */ import { describe, expect, it } from 'vitest' +import { JsImport } from '../../../../main/scopes/js/interfaces.js' import { DEFAULT_ELEMENT_NAME } from '../../../../main/scopes/jsx/constants.js' -import { NamedImportMatcher } from '../../../../main/scopes/jsx/import-matchers/named-import-matcher.js' -import { type JsxElement, type JsxImport } from '../../../../main/scopes/jsx/interfaces.js' +import { JsxElementNamedImportMatcher } from '../../../../main/scopes/jsx/import-matchers/jsx-element-named-import-matcher.js' +import { type JsxElement } from '../../../../main/scopes/jsx/interfaces.js' describe('class: NamedImportMatcher', () => { - const namedImportMatcher = new NamedImportMatcher() + const namedImportMatcher = new JsxElementNamedImportMatcher() const element: JsxElement = { name: 'theElement', prefix: 'prefix', attributes: [] } - const allImport: JsxImport = { + const allImport: JsImport = { name: 'prefix', path: '@library/something', isAll: true, isDefault: false } - const defaultImport: JsxImport = { + const defaultImport: JsImport = { name: DEFAULT_ELEMENT_NAME, path: '@library/something', rename: 'theElement', isDefault: true, isAll: false } - const namedImport: JsxImport = { + const namedImport: JsImport = { name: 'theElement', path: '@library/something', isDefault: false, isAll: false } - const renamedImport: JsxImport = { + const renamedImport: JsImport = { name: 'Button', path: '@library/something', rename: 'theElement', diff --git a/src/test/scopes/jsx/import-matchers/renamed-import-matcher.test.ts b/src/test/scopes/jsx/import-matchers/jsx-element-renamed-import-matcher.test.ts similarity index 78% rename from src/test/scopes/jsx/import-matchers/renamed-import-matcher.test.ts rename to src/test/scopes/jsx/import-matchers/jsx-element-renamed-import-matcher.test.ts index 415b3245..429b83ac 100644 --- a/src/test/scopes/jsx/import-matchers/renamed-import-matcher.test.ts +++ b/src/test/scopes/jsx/import-matchers/jsx-element-renamed-import-matcher.test.ts @@ -6,37 +6,38 @@ */ import { describe, expect, it } from 'vitest' +import { JsImport } from '../../../../main/scopes/js/interfaces.js' import { DEFAULT_ELEMENT_NAME } from '../../../../main/scopes/jsx/constants.js' -import { RenamedImportMatcher } from '../../../../main/scopes/jsx/import-matchers/renamed-import-matcher.js' -import { type JsxElement, type JsxImport } from '../../../../main/scopes/jsx/interfaces.js' +import { JsxElementRenamedImportMatcher } from '../../../../main/scopes/jsx/import-matchers/jsx-element-renamed-import-matcher.js' +import { type JsxElement } from '../../../../main/scopes/jsx/interfaces.js' describe('class: RenamedImportMatcher', () => { - const renamedImportMatcher = new RenamedImportMatcher() + const renamedImportMatcher = new JsxElementRenamedImportMatcher() const element: JsxElement = { name: 'theElement', prefix: 'prefix', attributes: [] } - const allImport: JsxImport = { + const allImport: JsImport = { name: 'prefix', path: '@library/something', isAll: true, isDefault: false } - const defaultImport: JsxImport = { + const defaultImport: JsImport = { name: DEFAULT_ELEMENT_NAME, path: '@library/something', rename: 'theElement', isDefault: true, isAll: false } - const namedImport: JsxImport = { + const namedImport: JsImport = { name: 'theElement', path: '@library/something', isDefault: false, isAll: false } - const renamedImport: JsxImport = { + const renamedImport: JsImport = { name: 'Button', path: '@library/something', rename: 'theElement', diff --git a/src/test/scopes/jsx/jsx-scope.e2e.test.ts b/src/test/scopes/jsx/jsx-scope.e2e.test.ts index f50b6b97..e6413b35 100644 --- a/src/test/scopes/jsx/jsx-scope.e2e.test.ts +++ b/src/test/scopes/jsx/jsx-scope.e2e.test.ts @@ -5,16 +5,14 @@ * LICENSE file in the root directory of this source tree. */ import { type ConfigSchema } from '@ibm/telemetry-config-schema' -import type * as ts from 'typescript' import { describe, expect, it } from 'vitest' import { EmptyScopeError } from '../../../main/exceptions/empty-scope.error.js' -import { AllImportMatcher } from '../../../main/scopes/jsx/import-matchers/all-import-matcher.js' -import { NamedImportMatcher } from '../../../main/scopes/jsx/import-matchers/named-import-matcher.js' -import { RenamedImportMatcher } from '../../../main/scopes/jsx/import-matchers/renamed-import-matcher.js' +import { JsxElementAllImportMatcher } from '../../../main/scopes/jsx/import-matchers/jsx-element-all-import-matcher.js' +import { JsxElementNamedImportMatcher } from '../../../main/scopes/jsx/import-matchers/jsx-element-named-import-matcher.js' +import { JsxElementRenamedImportMatcher } from '../../../main/scopes/jsx/import-matchers/jsx-element-renamed-import-matcher.js' import { JsxElementAccumulator } from '../../../main/scopes/jsx/jsx-element-accumulator.js' import { JsxScope } from '../../../main/scopes/jsx/jsx-scope.js' -import { getTrackedSourceFiles } from '../../../main/scopes/jsx/utils/get-tracked-source-files.js' import { clearDataPointTimes } from '../../__utils/clear-data-point-times.js' import { clearTelemetrySdkVersion } from '../../__utils/clear-telemetry-sdk-version.js' import { Fixture } from '../../__utils/fixture.js' @@ -146,109 +144,6 @@ describe('class: JsxScope', () => { }) }) - describe('parseFile', () => { - const fixture = new Fixture('projects/basic-project/test.jsx') - const root = new Fixture('projects/basic-project') - const jsxScope = new JsxScope(fixture.path, root.path, config, logger) - - it('correctly detects imports and elements in a given file', async () => { - const accumulator = new JsxElementAccumulator() - const sourceFile = (await getTrackedSourceFiles(fixture.path, logger))[0] as ts.SourceFile - - jsxScope.processFile(accumulator, sourceFile) - expect(accumulator.elements).toMatchSnapshot('elements') - expect(accumulator.imports).toMatchSnapshot('imports') - }) - }) - - describe('removeIrrelevantImports', () => { - it('correctly removes unwanted imports', () => { - const accumulator = new JsxElementAccumulator() - accumulator.imports.push({ - name: 'name', - path: 'path', - isDefault: false, - isAll: false - }) - accumulator.imports.push({ - name: 'name', - path: 'instrumented', - isDefault: true, - isAll: false - }) - accumulator.imports.push({ - name: 'name', - path: 'instrumented/bla', - isDefault: false, - isAll: true - }) - expect(accumulator.imports).toHaveLength(3) - const jsxScope = new JsxScope('', '', config, logger) - jsxScope.removeIrrelevantImports(accumulator, 'instrumented') - expect(accumulator.imports).toHaveLength(2) - }) - - it('removes all imports if no imports match instrumented package', () => { - const accumulator = new JsxElementAccumulator() - accumulator.imports.push({ - name: 'name', - path: 'path', - isDefault: false, - isAll: false - }) - accumulator.imports.push({ - name: 'name', - path: 'not-instrumented', - isDefault: true, - isAll: false - }) - accumulator.imports.push({ - name: 'name', - path: 'not-instrumented/bla', - isDefault: false, - isAll: true - }) - expect(accumulator.imports).toHaveLength(3) - const jsxScope = new JsxScope('', '', config, logger) - jsxScope.removeIrrelevantImports(accumulator, 'instrumented') - expect(accumulator.imports).toHaveLength(0) - }) - - it("does not remove imports if there aren't any to remove", () => { - const accumulator = new JsxElementAccumulator() - accumulator.imports.push({ - name: 'name', - path: 'instrumented/1', - isDefault: false, - isAll: false - }) - accumulator.imports.push({ - name: 'name', - path: 'instrumented', - isDefault: true, - isAll: false - }) - accumulator.imports.push({ - name: 'name', - path: 'instrumented/bla', - isDefault: false, - isAll: true - }) - expect(accumulator.imports).toHaveLength(3) - const jsxScope = new JsxScope('', '', config, logger) - jsxScope.removeIrrelevantImports(accumulator, 'instrumented') - expect(accumulator.imports).toHaveLength(3) - }) - - it('can accept empty array', () => { - const accumulator = new JsxElementAccumulator() - expect(accumulator.imports).toHaveLength(0) - const jsxScope = new JsxScope('', '', config, logger) - jsxScope.removeIrrelevantImports(accumulator, 'instrumented') - expect(accumulator.imports).toHaveLength(0) - }) - }) - describe('resolveElementImports', () => { const jsxScope = new JsxScope('', '', config, logger) const defaultImport = { @@ -314,9 +209,9 @@ describe('class: JsxScope', () => { accumulator.elements.push(renamedElement) jsxScope.resolveElementImports(accumulator, [ - new AllImportMatcher(), - new NamedImportMatcher(), - new RenamedImportMatcher() + new JsxElementAllImportMatcher(), + new JsxElementNamedImportMatcher(), + new JsxElementRenamedImportMatcher() ]) expect(accumulator.elementImports.get(defaultElement)).toStrictEqual(defaultImport) @@ -351,9 +246,9 @@ describe('class: JsxScope', () => { const jsxScope = new JsxScope('', '', config, logger) jsxScope.resolveElementImports(accumulator, [ - new AllImportMatcher(), - new NamedImportMatcher(), - new RenamedImportMatcher() + new JsxElementAllImportMatcher(), + new JsxElementNamedImportMatcher(), + new JsxElementRenamedImportMatcher() ]) expect(accumulator.elementImports.get(defaultElement)).toStrictEqual(defaultImport) diff --git a/src/test/scopes/jsx/metrics/element-metric.test.ts b/src/test/scopes/jsx/metrics/element-metric.test.ts index d14d2211..1ef728e3 100644 --- a/src/test/scopes/jsx/metrics/element-metric.test.ts +++ b/src/test/scopes/jsx/metrics/element-metric.test.ts @@ -10,11 +10,8 @@ import { describe, expect, it } from 'vitest' import { hash } from '../../../../main/core/anonymize/hash.js' import { substitute } from '../../../../main/core/anonymize/substitute.js' -import { - type JsxElement, - JsxElementAttribute, - type JsxImport -} from '../../../../main/scopes/jsx/interfaces.js' +import { JsImport } from '../../../../main/scopes/js/interfaces.js' +import { type JsxElement, JsxElementAttribute } from '../../../../main/scopes/jsx/interfaces.js' import { ElementMetric } from '../../../../main/scopes/jsx/metrics/element-metric.js' import { initLogger } from '../../../__utils/init-logger.js' @@ -44,7 +41,7 @@ describe('class: ElementMetric', () => { } ] } - const jsxImport: JsxImport = { + const jsxImport: JsImport = { name: 'theName', path: 'path', isDefault: false, diff --git a/src/test/scopes/jsx/node-handlers/elements/__snapshots__/jsx-element-node-handler.test.ts.snap b/src/test/scopes/jsx/node-handlers/elements/__snapshots__/jsx-element-node-handler.test.ts.snap index 86e41670..e28a3fef 100644 --- a/src/test/scopes/jsx/node-handlers/elements/__snapshots__/jsx-element-node-handler.test.ts.snap +++ b/src/test/scopes/jsx/node-handlers/elements/__snapshots__/jsx-element-node-handler.test.ts.snap @@ -49,19 +49,19 @@ exports[`class: JsxElementNodeHandler > correctly returns the JsxElements for a }, { "name": "attr11", - "value": ComplexAttribute { + "value": ComplexValue { "complexValue": "variable", }, }, { "name": "attr12", - "value": ComplexAttribute { + "value": ComplexValue { "complexValue": "element[access]", }, }, { "name": "attr13", - "value": ComplexAttribute { + "value": ComplexValue { "complexValue": "{key: 'value', key2: 10}", }, }, diff --git a/src/test/scopes/jsx/node-handlers/elements/__snapshots__/jsx-self-closing-element-node-handler.test.ts.snap b/src/test/scopes/jsx/node-handlers/elements/__snapshots__/jsx-self-closing-element-node-handler.test.ts.snap index 6e242287..a8534fe6 100644 --- a/src/test/scopes/jsx/node-handlers/elements/__snapshots__/jsx-self-closing-element-node-handler.test.ts.snap +++ b/src/test/scopes/jsx/node-handlers/elements/__snapshots__/jsx-self-closing-element-node-handler.test.ts.snap @@ -49,19 +49,19 @@ exports[`class: JsxSelfClosingElementNodeHandler > correctly returns the JsxElem }, { "name": "attr11", - "value": ComplexAttribute { + "value": ComplexValue { "complexValue": "variable", }, }, { "name": "attr12", - "value": ComplexAttribute { + "value": ComplexValue { "complexValue": "element[access]", }, }, { "name": "attr13", - "value": ComplexAttribute { + "value": ComplexValue { "complexValue": "{key: 'value', key2: 10}", }, }, diff --git a/src/test/scopes/jsx/node-handlers/elements/jsx-element-node-handler.test.ts b/src/test/scopes/jsx/node-handlers/elements/jsx-element-node-handler.test.ts index aff804f7..15ac40d7 100644 --- a/src/test/scopes/jsx/node-handlers/elements/jsx-element-node-handler.test.ts +++ b/src/test/scopes/jsx/node-handlers/elements/jsx-element-node-handler.test.ts @@ -7,9 +7,10 @@ import * as ts from 'typescript' import { describe, expect, it } from 'vitest' +import { getTrackedSourceFiles } from '../../../../../main/core/get-tracked-source-files.js' import { JsxElementAccumulator } from '../../../../../main/scopes/jsx/jsx-element-accumulator.js' +import { JsxScope } from '../../../../../main/scopes/jsx/jsx-scope.js' import { JsxElementNodeHandler } from '../../../../../main/scopes/jsx/node-handlers/elements/jsx-element-node-handler.js' -import { getTrackedSourceFiles } from '../../../../../main/scopes/jsx/utils/get-tracked-source-files.js' import { findNodesByType } from '../../../../__utils/find-nodes-by-type.js' import { Fixture } from '../../../../__utils/fixture.js' import { initLogger } from '../../../../__utils/init-logger.js' @@ -17,7 +18,9 @@ import { initLogger } from '../../../../__utils/init-logger.js' describe('class: JsxElementNodeHandler', async () => { const logger = initLogger() const fixture = new Fixture('jsx-samples/all-jsx-element-types.tsx') - const sourceFile = (await getTrackedSourceFiles(fixture.path, logger))[0] as ts.SourceFile + const sourceFile = ( + await getTrackedSourceFiles(fixture.path, logger, JsxScope.fileExtensions) + )[0] as ts.SourceFile const accumulator = new JsxElementAccumulator() const handler = new JsxElementNodeHandler(sourceFile, logger) it('correctly returns the JsxElements for a complex fixture', async () => { diff --git a/src/test/scopes/jsx/node-handlers/elements/jsx-self-closing-element-node-handler.test.ts b/src/test/scopes/jsx/node-handlers/elements/jsx-self-closing-element-node-handler.test.ts index 64067b36..4cd2a416 100644 --- a/src/test/scopes/jsx/node-handlers/elements/jsx-self-closing-element-node-handler.test.ts +++ b/src/test/scopes/jsx/node-handlers/elements/jsx-self-closing-element-node-handler.test.ts @@ -7,9 +7,10 @@ import * as ts from 'typescript' import { describe, expect, it } from 'vitest' +import { getTrackedSourceFiles } from '../../../../../main/core/get-tracked-source-files.js' import { JsxElementAccumulator } from '../../../../../main/scopes/jsx/jsx-element-accumulator.js' +import { JsxScope } from '../../../../../main/scopes/jsx/jsx-scope.js' import { JsxSelfClosingElementNodeHandler } from '../../../../../main/scopes/jsx/node-handlers/elements/jsx-self-closing-element-node-handler.js' -import { getTrackedSourceFiles } from '../../../../../main/scopes/jsx/utils/get-tracked-source-files.js' import { findNodesByType } from '../../../../__utils/find-nodes-by-type.js' import { Fixture } from '../../../../__utils/fixture.js' import { initLogger } from '../../../../__utils/init-logger.js' @@ -17,7 +18,9 @@ import { initLogger } from '../../../../__utils/init-logger.js' describe('class: JsxSelfClosingElementNodeHandler', async () => { const logger = initLogger() const fixture = new Fixture('jsx-samples/all-jsx-element-types.tsx') - const sourceFile = (await getTrackedSourceFiles(fixture.path, logger))[0] as ts.SourceFile + const sourceFile = ( + await getTrackedSourceFiles(fixture.path, logger, JsxScope.fileExtensions) + )[0] as ts.SourceFile const accumulator = new JsxElementAccumulator() const handler = new JsxSelfClosingElementNodeHandler(sourceFile, logger)