@groma/scanner-typescript 0.1.0 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +4 -7
- package/src/index.js +149 -67
package/package.json
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@groma/scanner-typescript",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"description": "TypeScript source and call evidence using the native TypeScript SDK.",
|
|
5
5
|
"private": false,
|
|
6
6
|
"type": "module",
|
|
7
7
|
"license": "MIT",
|
|
8
8
|
"os": [
|
|
9
|
-
"win32",
|
|
10
9
|
"linux",
|
|
11
|
-
"darwin"
|
|
10
|
+
"darwin",
|
|
11
|
+
"win32"
|
|
12
12
|
],
|
|
13
13
|
"cpu": [
|
|
14
14
|
"arm64",
|
|
@@ -43,10 +43,7 @@
|
|
|
43
43
|
}
|
|
44
44
|
],
|
|
45
45
|
"compatibility": {
|
|
46
|
-
"groma": "^0.3.0"
|
|
47
|
-
"technologyVersions": {
|
|
48
|
-
"typescript": "5.9.2 || 5.9.3 || 7.0.2"
|
|
49
|
-
}
|
|
46
|
+
"groma": "^0.3.0"
|
|
50
47
|
}
|
|
51
48
|
}
|
|
52
49
|
}
|
package/src/index.js
CHANGED
|
@@ -3803,9 +3803,9 @@ async function listTypeScriptFiles(repositoryRoot, config = defaultTypeScriptSca
|
|
|
3803
3803
|
}
|
|
3804
3804
|
|
|
3805
3805
|
// plugins/scanners/typescript/src/scan.ts
|
|
3806
|
-
import { existsSync as
|
|
3806
|
+
import { existsSync as existsSync3 } from "fs";
|
|
3807
3807
|
import { readFile as readFile2 } from "fs/promises";
|
|
3808
|
-
import
|
|
3808
|
+
import path9 from "path";
|
|
3809
3809
|
|
|
3810
3810
|
// node_modules/typescript/lib/version.cjs
|
|
3811
3811
|
var { version } = require_package();
|
|
@@ -3975,7 +3975,7 @@ function sourcePosition(position) {
|
|
|
3975
3975
|
|
|
3976
3976
|
// plugins/scanners/typescript/src/graph.ts
|
|
3977
3977
|
import { readFile } from "fs/promises";
|
|
3978
|
-
import
|
|
3978
|
+
import path8 from "path";
|
|
3979
3979
|
|
|
3980
3980
|
// plugins/scanners/typescript/src/naming.ts
|
|
3981
3981
|
function kebabCase(name) {
|
|
@@ -3990,7 +3990,7 @@ function displayName(kebab) {
|
|
|
3990
3990
|
}
|
|
3991
3991
|
|
|
3992
3992
|
// plugins/scanners/typescript/src/source-analysis.ts
|
|
3993
|
-
import
|
|
3993
|
+
import path7 from "path";
|
|
3994
3994
|
|
|
3995
3995
|
// node_modules/typescript/dist/enums/checkFlags.js
|
|
3996
3996
|
var CheckFlags;
|
|
@@ -17378,8 +17378,6 @@ function importBindings(source, dependencies) {
|
|
|
17378
17378
|
if (!isImportDeclaration(statement) || !isStringLiteral(statement.moduleSpecifier))
|
|
17379
17379
|
continue;
|
|
17380
17380
|
const specifier = statement.moduleSpecifier.text;
|
|
17381
|
-
if (!specifier.startsWith("."))
|
|
17382
|
-
continue;
|
|
17383
17381
|
const clause = statement.importClause;
|
|
17384
17382
|
const names = clause ? bindingNames(clause) : [];
|
|
17385
17383
|
if (names.length === 0 && clause?.phaseModifier !== SyntaxKind.TypeKeyword)
|
|
@@ -17433,8 +17431,94 @@ async function usedImportSpecifiers(source, checker) {
|
|
|
17433
17431
|
return [...dependencies];
|
|
17434
17432
|
}
|
|
17435
17433
|
|
|
17436
|
-
// plugins/scanners/typescript/src/
|
|
17434
|
+
// plugins/scanners/typescript/src/projects.ts
|
|
17435
|
+
import path4 from "path";
|
|
17436
|
+
import { stat } from "fs/promises";
|
|
17437
|
+
|
|
17438
|
+
// plugins/scanners/projects.ts
|
|
17439
|
+
import { execFile } from "child_process";
|
|
17440
|
+
import { existsSync as existsSync2 } from "fs";
|
|
17437
17441
|
import path3 from "path";
|
|
17442
|
+
import { promisify } from "util";
|
|
17443
|
+
var execute = promisify(execFile);
|
|
17444
|
+
var excluded = new Set([
|
|
17445
|
+
".git",
|
|
17446
|
+
"node_modules",
|
|
17447
|
+
"vendor",
|
|
17448
|
+
"target",
|
|
17449
|
+
"dist",
|
|
17450
|
+
"build",
|
|
17451
|
+
"bin",
|
|
17452
|
+
"obj",
|
|
17453
|
+
".gradle",
|
|
17454
|
+
".angular",
|
|
17455
|
+
"coverage",
|
|
17456
|
+
"generated",
|
|
17457
|
+
"groma",
|
|
17458
|
+
".groma"
|
|
17459
|
+
]);
|
|
17460
|
+
async function projectFiles(root, matches) {
|
|
17461
|
+
const { stdout } = await execute("git", ["-C", root, "ls-files", "-z", "--cached", "--others", "--exclude-standard"], { maxBuffer: 64 * 1024 * 1024 });
|
|
17462
|
+
return [...new Set(stdout.split("\x00").filter((file) => file && matches(file) && !file.split("/").some((part) => excluded.has(part)) && existsSync2(path3.join(root, file))))].sort();
|
|
17463
|
+
}
|
|
17464
|
+
|
|
17465
|
+
// plugins/scanners/typescript/src/projects.ts
|
|
17466
|
+
async function typescriptProjects(api, root, files) {
|
|
17467
|
+
const configurations = new Map;
|
|
17468
|
+
async function read(file) {
|
|
17469
|
+
if (configurations.has(file))
|
|
17470
|
+
return;
|
|
17471
|
+
const config = await api.parseConfigFile(file);
|
|
17472
|
+
if (config.errors.length)
|
|
17473
|
+
throw new Error(`${file}: ${config.errors.map((error) => error.text).join(`
|
|
17474
|
+
`)}`);
|
|
17475
|
+
configurations.set(file, config);
|
|
17476
|
+
for (const reference of config.projectReferences ?? []) {
|
|
17477
|
+
const target = (await stat(reference.path)).isDirectory() ? path4.join(reference.path, "tsconfig.json") : reference.path;
|
|
17478
|
+
await read(target);
|
|
17479
|
+
}
|
|
17480
|
+
}
|
|
17481
|
+
for (const file of await projectFiles(root, (file) => path4.posix.basename(file) === "tsconfig.json"))
|
|
17482
|
+
await read(path4.join(root, file));
|
|
17483
|
+
const selected = new Set(files.map((file) => path4.resolve(root, file)));
|
|
17484
|
+
const projects = [...configurations].map(([key, config]) => ({
|
|
17485
|
+
key,
|
|
17486
|
+
config,
|
|
17487
|
+
files: config.fileNames.filter((file) => selected.has(path4.resolve(file)))
|
|
17488
|
+
}));
|
|
17489
|
+
const owners = new Map;
|
|
17490
|
+
for (const project of projects) {
|
|
17491
|
+
for (const file of project.files) {
|
|
17492
|
+
const previous = owners.get(path4.resolve(file));
|
|
17493
|
+
if (!previous || path4.dirname(project.key).split(path4.sep).length >= path4.dirname(previous.key).split(path4.sep).length) {
|
|
17494
|
+
owners.set(path4.resolve(file), project);
|
|
17495
|
+
}
|
|
17496
|
+
}
|
|
17497
|
+
}
|
|
17498
|
+
for (const project of projects)
|
|
17499
|
+
project.files = project.files.filter((file) => owners.get(path4.resolve(file)) === project);
|
|
17500
|
+
const loose = [...selected].filter((file) => !owners.has(file));
|
|
17501
|
+
return [...projects.filter((project) => project.files.length), ...loose.length ? [{ key: "", files: loose }] : []];
|
|
17502
|
+
}
|
|
17503
|
+
|
|
17504
|
+
// plugins/scanners/typescript/src/source-imports.ts
|
|
17505
|
+
import path5 from "path";
|
|
17506
|
+
async function resolvedImports(root, source, specifiers, checker) {
|
|
17507
|
+
const used = new Set(specifiers);
|
|
17508
|
+
const nodes = [];
|
|
17509
|
+
function visit(node) {
|
|
17510
|
+
if (isStringLiteral(node) && used.has(node.text))
|
|
17511
|
+
nodes.push(node);
|
|
17512
|
+
node.forEachChild(visit);
|
|
17513
|
+
}
|
|
17514
|
+
visit(source);
|
|
17515
|
+
const symbols = await checker.getSymbolAtLocation(nodes);
|
|
17516
|
+
const declarations = await Promise.all(symbols.flatMap((symbol) => symbol?.declarations ?? []).map((handle) => handle.resolve()));
|
|
17517
|
+
return [...new Set(declarations.flatMap((declaration) => declaration ? [path5.relative(root, declaration.getSourceFile().fileName).split(path5.sep).join("/")] : []))].sort();
|
|
17518
|
+
}
|
|
17519
|
+
|
|
17520
|
+
// plugins/scanners/typescript/src/source-operations.ts
|
|
17521
|
+
import path6 from "path";
|
|
17438
17522
|
|
|
17439
17523
|
// plugins/scanners/typescript/src/source-tokens.ts
|
|
17440
17524
|
function lookup(scope, name) {
|
|
@@ -17656,7 +17740,7 @@ function executable(node) {
|
|
|
17656
17740
|
function location(root, node) {
|
|
17657
17741
|
const source = node.getSourceFile();
|
|
17658
17742
|
return {
|
|
17659
|
-
file:
|
|
17743
|
+
file: path6.relative(root, source.fileName).split(path6.sep).join("/"),
|
|
17660
17744
|
line: source.getLineAndCharacterOfPosition(node.getStart()).line + 1,
|
|
17661
17745
|
position: node.getStart()
|
|
17662
17746
|
};
|
|
@@ -17941,85 +18025,83 @@ async function analyzeSourceFiles(repositoryRoot, paths) {
|
|
|
17941
18025
|
return { files: [], operations: [], invocations: [] };
|
|
17942
18026
|
const api = new API({ cwd: repositoryRoot });
|
|
17943
18027
|
try {
|
|
17944
|
-
const
|
|
17945
|
-
|
|
17946
|
-
|
|
17947
|
-
|
|
17948
|
-
|
|
17949
|
-
|
|
17950
|
-
|
|
17951
|
-
module: ModuleKind.Preserve
|
|
18028
|
+
const result = { files: [], operations: [], invocations: [] };
|
|
18029
|
+
for (const project of await typescriptProjects(api, repositoryRoot, paths)) {
|
|
18030
|
+
if (!project.config) {
|
|
18031
|
+
const analyzed = new Set(result.files.map((file) => path7.resolve(repositoryRoot, file.file)));
|
|
18032
|
+
project.files = project.files.filter((file) => !analyzed.has(file));
|
|
18033
|
+
if (!project.files.length)
|
|
18034
|
+
continue;
|
|
17952
18035
|
}
|
|
17953
|
-
|
|
17954
|
-
|
|
17955
|
-
|
|
17956
|
-
|
|
17957
|
-
|
|
17958
|
-
|
|
17959
|
-
|
|
17960
|
-
const evidence = await sourceOperations(repositoryRoot, sources, program.getProject().checker);
|
|
17961
|
-
const files = await Promise.all(sources.map(async (source, index) => ({
|
|
17962
|
-
file: paths[index],
|
|
17963
|
-
specifiers: await usedImportSpecifiers(source, program.getProject().checker),
|
|
17964
|
-
symbols: exportSymbols(paths[index], source)
|
|
17965
|
-
})));
|
|
17966
|
-
return { files, ...evidence };
|
|
18036
|
+
const evidence = await analyzeProject(api, repositoryRoot, paths, project);
|
|
18037
|
+
result.files.push(...evidence.files);
|
|
18038
|
+
result.operations.push(...evidence.operations);
|
|
18039
|
+
result.invocations.push(...evidence.invocations);
|
|
18040
|
+
}
|
|
18041
|
+
result.operations = [...new Map(result.operations.map((operation) => [operation.id, operation])).values()];
|
|
18042
|
+
return result;
|
|
17967
18043
|
} finally {
|
|
17968
18044
|
await api.close();
|
|
17969
18045
|
}
|
|
17970
18046
|
}
|
|
18047
|
+
async function analyzeProject(api, repositoryRoot, paths, project) {
|
|
18048
|
+
const program = await api.createProgram(project.files, {
|
|
18049
|
+
compilerOptions: { ...project.config?.options ?? {
|
|
18050
|
+
noLib: true,
|
|
18051
|
+
types: [],
|
|
18052
|
+
allowJs: true,
|
|
18053
|
+
moduleResolution: ModuleResolutionKind.Bundler,
|
|
18054
|
+
module: ModuleKind.Preserve
|
|
18055
|
+
}, noEmit: true }
|
|
18056
|
+
});
|
|
18057
|
+
const sources = await Promise.all(paths.map((file) => program.getSourceFile(path7.join(repositoryRoot, file))));
|
|
18058
|
+
const selectedProgramSources = sources.filter((source) => source !== undefined);
|
|
18059
|
+
const checker = program.getProject().checker;
|
|
18060
|
+
const evidence = await sourceOperations(repositoryRoot, selectedProgramSources, checker);
|
|
18061
|
+
const files = await Promise.all(selectedProgramSources.map(async (source) => {
|
|
18062
|
+
const file = path7.relative(repositoryRoot, source.fileName).split(path7.sep).join("/");
|
|
18063
|
+
return {
|
|
18064
|
+
file,
|
|
18065
|
+
imports: await resolvedImports(repositoryRoot, source, await usedImportSpecifiers(source, checker), checker),
|
|
18066
|
+
symbols: exportSymbols(file, source)
|
|
18067
|
+
};
|
|
18068
|
+
}));
|
|
18069
|
+
return { files, ...evidence };
|
|
18070
|
+
}
|
|
17971
18071
|
|
|
17972
18072
|
// plugins/scanners/typescript/src/graph.ts
|
|
17973
|
-
function resolveSpecifier(fromFile, specifier, files) {
|
|
17974
|
-
if (!specifier.startsWith("."))
|
|
17975
|
-
return;
|
|
17976
|
-
const joined = path5.posix.normalize(path5.posix.join(path5.posix.dirname(fromFile), specifier));
|
|
17977
|
-
const candidates = specifier.endsWith(".ts") || specifier.endsWith(".tsx") ? [joined] : [
|
|
17978
|
-
joined,
|
|
17979
|
-
joined.replace(/\.jsx?$/, ".ts"),
|
|
17980
|
-
joined.replace(/\.jsx?$/, ".tsx"),
|
|
17981
|
-
`${joined}.ts`,
|
|
17982
|
-
`${joined}.tsx`,
|
|
17983
|
-
`${joined}.js`,
|
|
17984
|
-
`${joined}/index.ts`,
|
|
17985
|
-
`${joined}/index.tsx`
|
|
17986
|
-
];
|
|
17987
|
-
return candidates.find((candidate) => files.has(candidate));
|
|
17988
|
-
}
|
|
17989
18073
|
function fileStem(file) {
|
|
17990
|
-
return
|
|
18074
|
+
return path8.posix.basename(file).replace(/\.tsx?$/, "");
|
|
17991
18075
|
}
|
|
17992
18076
|
function fileLabel(file) {
|
|
17993
18077
|
const stem = fileStem(file);
|
|
17994
18078
|
if (stem === "index") {
|
|
17995
|
-
const parent =
|
|
18079
|
+
const parent = path8.posix.basename(path8.posix.dirname(file));
|
|
17996
18080
|
return displayName(kebabCase(parent === "." || parent === "" ? stem : parent));
|
|
17997
18081
|
}
|
|
17998
18082
|
return displayName(kebabCase(stem));
|
|
17999
18083
|
}
|
|
18000
18084
|
async function packageName(repositoryRoot) {
|
|
18001
18085
|
try {
|
|
18002
|
-
const source = await readFile(
|
|
18086
|
+
const source = await readFile(path8.join(repositoryRoot, "package.json"), "utf8");
|
|
18003
18087
|
const name = JSON.parse(source).name;
|
|
18004
18088
|
if (typeof name === "string" && kebabCase(name) !== "") {
|
|
18005
18089
|
return displayName(kebabCase(name));
|
|
18006
18090
|
}
|
|
18007
18091
|
} catch {}
|
|
18008
|
-
return displayName(kebabCase(
|
|
18092
|
+
return displayName(kebabCase(path8.basename(repositoryRoot)));
|
|
18009
18093
|
}
|
|
18010
18094
|
async function buildImportGraph(repositoryRoot, config = defaultTypeScriptScannerConfig) {
|
|
18011
18095
|
const paths = await listTypeScriptFiles(repositoryRoot, config);
|
|
18012
18096
|
const files = new Set(paths);
|
|
18013
18097
|
const { files: analyses, operations, invocations } = await analyzeSourceFiles(repositoryRoot, paths);
|
|
18014
|
-
const nodes = new Map
|
|
18015
|
-
|
|
18016
|
-
|
|
18017
|
-
|
|
18018
|
-
|
|
18019
|
-
|
|
18020
|
-
|
|
18021
|
-
symbols
|
|
18022
|
-
}]));
|
|
18098
|
+
const nodes = new Map;
|
|
18099
|
+
for (const analysis of analyses) {
|
|
18100
|
+
const node = nodes.get(analysis.file) ?? { file: analysis.file, imports: [], importedBy: [], symbols: [] };
|
|
18101
|
+
node.imports = [...new Set([...node.imports, ...analysis.imports.filter((file) => files.has(file))])].sort();
|
|
18102
|
+
node.symbols = [...new Map([...node.symbols, ...analysis.symbols].map((symbol) => [symbol.id, symbol])).values()];
|
|
18103
|
+
nodes.set(node.file, node);
|
|
18104
|
+
}
|
|
18023
18105
|
for (const node of nodes.values()) {
|
|
18024
18106
|
for (const imported of node.imports)
|
|
18025
18107
|
nodes.get(imported)?.importedBy.push(node.file);
|
|
@@ -18036,7 +18118,7 @@ function isPaintFile(file, node) {
|
|
|
18036
18118
|
}
|
|
18037
18119
|
async function packageBins(repositoryRoot) {
|
|
18038
18120
|
try {
|
|
18039
|
-
const source = await readFile2(
|
|
18121
|
+
const source = await readFile2(path9.join(repositoryRoot, "package.json"), "utf8");
|
|
18040
18122
|
const bin = JSON.parse(source).bin;
|
|
18041
18123
|
if (typeof bin === "string")
|
|
18042
18124
|
return [bin.replace(/^\.\//, "")];
|
|
@@ -18098,8 +18180,8 @@ function inferScopeFiles(graph, bins) {
|
|
|
18098
18180
|
return [...scopes].sort();
|
|
18099
18181
|
}
|
|
18100
18182
|
function commonDirectorySegments(left, right) {
|
|
18101
|
-
const leftParts =
|
|
18102
|
-
const rightParts =
|
|
18183
|
+
const leftParts = path9.posix.dirname(left).split("/");
|
|
18184
|
+
const rightParts = path9.posix.dirname(right).split("/");
|
|
18103
18185
|
let count = 0;
|
|
18104
18186
|
while (leftParts[count] !== undefined && leftParts[count] === rightParts[count])
|
|
18105
18187
|
count += 1;
|
|
@@ -18149,7 +18231,7 @@ function scopeNames(scopeFiles) {
|
|
|
18149
18231
|
for (const file of scopeFiles) {
|
|
18150
18232
|
let name = fileStem(file) === "cli" ? "Cli" : fileLabel(file);
|
|
18151
18233
|
if (used.has(name)) {
|
|
18152
|
-
name = displayName(kebabCase(`${
|
|
18234
|
+
name = displayName(kebabCase(`${path9.posix.basename(path9.posix.dirname(file))}-${fileStem(file)}`));
|
|
18153
18235
|
}
|
|
18154
18236
|
if (used.has(name))
|
|
18155
18237
|
name = `${name} ${file}`;
|
|
@@ -18179,7 +18261,7 @@ async function scanTypeScriptSource(repositoryRoot, config = defaultTypeScriptSc
|
|
|
18179
18261
|
id: "package",
|
|
18180
18262
|
kind: "package",
|
|
18181
18263
|
name,
|
|
18182
|
-
...
|
|
18264
|
+
...existsSync3(path9.join(repositoryRoot, "package.json")) ? { file: "package.json" } : {}
|
|
18183
18265
|
},
|
|
18184
18266
|
...scopeFiles.map((file) => ({
|
|
18185
18267
|
id: scopeId(file),
|
|
@@ -18197,7 +18279,7 @@ async function scanTypeScriptSource(repositoryRoot, config = defaultTypeScriptSc
|
|
|
18197
18279
|
}
|
|
18198
18280
|
|
|
18199
18281
|
// plugins/scanners/typescript/src/structure.ts
|
|
18200
|
-
import
|
|
18282
|
+
import path10 from "path";
|
|
18201
18283
|
function isExported(modifierFlags) {
|
|
18202
18284
|
return (modifierFlags & ModifierFlags.Export) !== 0;
|
|
18203
18285
|
}
|
|
@@ -18278,7 +18360,7 @@ function declarationsIn(source, symbols) {
|
|
|
18278
18360
|
async function readCodeStructure(repositoryRoot, references) {
|
|
18279
18361
|
if (references.length === 0)
|
|
18280
18362
|
return [];
|
|
18281
|
-
const filenames = references.map((reference) =>
|
|
18363
|
+
const filenames = references.map((reference) => path10.join(repositoryRoot, reference.file));
|
|
18282
18364
|
const api = new API({ cwd: repositoryRoot });
|
|
18283
18365
|
try {
|
|
18284
18366
|
const snapshot = await api.updateSnapshot({ openFiles: filenames });
|
|
@@ -18303,7 +18385,7 @@ async function readCodeStructure(repositoryRoot, references) {
|
|
|
18303
18385
|
var scanner2 = {
|
|
18304
18386
|
id: "typescript",
|
|
18305
18387
|
readCodeStructure,
|
|
18306
|
-
watch: { include: defaultTypeScriptScannerConfig.globs, exclude: defaultTypeScriptScannerConfig.ignore },
|
|
18388
|
+
watch: { include: [...defaultTypeScriptScannerConfig.globs, "**/tsconfig*.json", "**/package.json"], exclude: defaultTypeScriptScannerConfig.ignore },
|
|
18307
18389
|
scan: (root) => scanTypeScriptSource(root)
|
|
18308
18390
|
};
|
|
18309
18391
|
var src_default = scanner2;
|