depvitals 0.2.0 → 0.3.0
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/README.md +36 -40
- package/dist/cli/runCli.d.ts.map +1 -1
- package/dist/cli/runCli.js +9 -4
- package/dist/cli/runCli.js.map +1 -1
- package/dist/config/ignoreDependencies.d.ts +7 -0
- package/dist/config/ignoreDependencies.d.ts.map +1 -0
- package/dist/config/ignoreDependencies.js +18 -0
- package/dist/config/ignoreDependencies.js.map +1 -0
- package/dist/config/ignorePaths.d.ts +13 -0
- package/dist/config/ignorePaths.d.ts.map +1 -0
- package/dist/config/ignorePaths.js +47 -0
- package/dist/config/ignorePaths.js.map +1 -0
- package/dist/config/projectConfigError.d.ts +7 -0
- package/dist/config/projectConfigError.d.ts.map +1 -0
- package/dist/config/projectConfigError.js +10 -0
- package/dist/config/projectConfigError.js.map +1 -0
- package/dist/config/readProjectConfig.d.ts +23 -0
- package/dist/config/readProjectConfig.d.ts.map +1 -0
- package/dist/config/readProjectConfig.js +106 -0
- package/dist/config/readProjectConfig.js.map +1 -0
- package/dist/core/analyzeProject.d.ts +5 -1
- package/dist/core/analyzeProject.d.ts.map +1 -1
- package/dist/core/analyzeProject.js +37 -29
- package/dist/core/analyzeProject.js.map +1 -1
- package/dist/detectors/eslintManifestConfig.d.ts.map +1 -1
- package/dist/detectors/eslintManifestConfig.js +7 -4
- package/dist/detectors/eslintManifestConfig.js.map +1 -1
- package/dist/detectors/eslintRcJsonConfig.d.ts.map +1 -1
- package/dist/detectors/eslintRcJsonConfig.js +2 -1
- package/dist/detectors/eslintRcJsonConfig.js.map +1 -1
- package/dist/detectors/jestManifestConfig.d.ts.map +1 -1
- package/dist/detectors/jestManifestConfig.js +6 -4
- package/dist/detectors/jestManifestConfig.js.map +1 -1
- package/dist/scanner/moduleSpecifiers.d.ts +2 -2
- package/dist/scanner/moduleSpecifiers.d.ts.map +1 -1
- package/dist/scanner/moduleSpecifiers.js +1111 -178
- package/dist/scanner/moduleSpecifiers.js.map +1 -1
- package/dist/scanner/packageSpecifier.d.ts +26 -2
- package/dist/scanner/packageSpecifier.d.ts.map +1 -1
- package/dist/scanner/packageSpecifier.js +70 -2
- package/dist/scanner/packageSpecifier.js.map +1 -1
- package/dist/scanner/projectManifest.d.ts +19 -3
- package/dist/scanner/projectManifest.d.ts.map +1 -1
- package/dist/scanner/projectManifest.js +51 -12
- package/dist/scanner/projectManifest.js.map +1 -1
- package/dist/scanner/scriptUsage.d.ts +2 -1
- package/dist/scanner/scriptUsage.d.ts.map +1 -1
- package/dist/scanner/scriptUsage.js +182 -37
- package/dist/scanner/scriptUsage.js.map +1 -1
- package/dist/scanner/sourceFiles.d.ts +16 -2
- package/dist/scanner/sourceFiles.d.ts.map +1 -1
- package/dist/scanner/sourceFiles.js +12 -6
- package/dist/scanner/sourceFiles.js.map +1 -1
- package/package.json +1 -1
|
@@ -3,11 +3,13 @@
|
|
|
3
3
|
*
|
|
4
4
|
* This module classifies a single raw module specifier into a small set of categories without
|
|
5
5
|
* executing code, reading the filesystem or consulting `node_modules`. It is a pure transformation
|
|
6
|
-
* that sits between specifier extraction (Step 9) and dependency analysis (Step 12).
|
|
6
|
+
* that sits between specifier extraction (Step 9) and dependency analysis (Step 12). Separate internal
|
|
7
|
+
* helpers prepare bounded exact self-reference metadata without altering ordinary classification.
|
|
7
8
|
*
|
|
8
9
|
* It deliberately does not:
|
|
9
10
|
* - decide whether a specifier is used, unused or missing (that belongs to the analyzer);
|
|
10
|
-
* - resolve files on disk, `node_modules`,
|
|
11
|
+
* - resolve files on disk, `node_modules`, TS `paths` or bundler aliases;
|
|
12
|
+
* - implement general package `exports` resolution (conditions, patterns and fallbacks);
|
|
11
13
|
* - validate a name against npm registry publication rules (names are compared case-sensitively);
|
|
12
14
|
* - read `package.json#imports`, so `#` import aliases cannot be resolved here.
|
|
13
15
|
*
|
|
@@ -95,4 +97,70 @@ function isMalformed(specifier) {
|
|
|
95
97
|
specifier.includes('?') ||
|
|
96
98
|
specifier.includes('%'));
|
|
97
99
|
}
|
|
100
|
+
/**
|
|
101
|
+
* Prepare the bounded self-reference subset from static JSON metadata, without I/O or mutation.
|
|
102
|
+
* Only a direct root string or an object consisting entirely of exact keys and safe string targets
|
|
103
|
+
* is supported. Mixed/conditional/pattern/array exports are deliberately not partially interpreted.
|
|
104
|
+
* This helper does not load metadata or change analyzer findings.
|
|
105
|
+
*/
|
|
106
|
+
export function createPackageSelfReferenceScope(metadata) {
|
|
107
|
+
const rawName = metadata['name'];
|
|
108
|
+
const name = typeof rawName === 'string' && !rawName.includes('*') && !hasControlCharacter(rawName)
|
|
109
|
+
? rawName
|
|
110
|
+
: undefined;
|
|
111
|
+
const exports = metadata['exports'];
|
|
112
|
+
const exportedSubpaths = new Set();
|
|
113
|
+
if (typeof exports === 'string') {
|
|
114
|
+
if (isSafeExportPath(exports))
|
|
115
|
+
exportedSubpaths.add('.');
|
|
116
|
+
}
|
|
117
|
+
else if (typeof exports === 'object' && exports !== null && !Array.isArray(exports)) {
|
|
118
|
+
const entries = Object.entries(exports);
|
|
119
|
+
if (entries.every(([key, target]) => (key === '.' || isSafeExportPath(key)) &&
|
|
120
|
+
typeof target === 'string' &&
|
|
121
|
+
isSafeExportPath(target))) {
|
|
122
|
+
for (const [key] of entries)
|
|
123
|
+
exportedSubpaths.add(key);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
return { name, exportedSubpaths };
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Match the raw specifier before package-root normalization. A same-name declaration alone is not
|
|
130
|
+
* evidence of ownership, and builtin/unsupported references cannot become self references.
|
|
131
|
+
* Neither target existence nor runtime resolution is consulted.
|
|
132
|
+
*/
|
|
133
|
+
export function isPackageSelfReference(specifier, scope) {
|
|
134
|
+
if (scope?.name === undefined)
|
|
135
|
+
return false;
|
|
136
|
+
const key = specifier === scope.name
|
|
137
|
+
? '.'
|
|
138
|
+
: specifier.startsWith(`${scope.name}/`)
|
|
139
|
+
? `./${specifier.slice(scope.name.length + 1)}`
|
|
140
|
+
: undefined;
|
|
141
|
+
if (key === undefined || !scope.exportedSubpaths.has(key))
|
|
142
|
+
return false;
|
|
143
|
+
const classified = classifyPackageSpecifier(specifier);
|
|
144
|
+
return classified.kind === 'external' && classified.packageName === scope.name;
|
|
145
|
+
}
|
|
146
|
+
/** A deliberately literal subset: no normalization, URL decoding, patterns or path resolution. */
|
|
147
|
+
function isSafeExportPath(value) {
|
|
148
|
+
if (!value.startsWith('./') || /[\\%*?:#]/.test(value) || hasControlCharacter(value))
|
|
149
|
+
return false;
|
|
150
|
+
return value
|
|
151
|
+
.slice(2)
|
|
152
|
+
.split('/')
|
|
153
|
+
.every((segment) => segment !== '' &&
|
|
154
|
+
segment !== '.' &&
|
|
155
|
+
segment !== '..' &&
|
|
156
|
+
segment.toLowerCase() !== 'node_modules');
|
|
157
|
+
}
|
|
158
|
+
function hasControlCharacter(value) {
|
|
159
|
+
for (const character of value) {
|
|
160
|
+
const code = character.charCodeAt(0);
|
|
161
|
+
if (code < 32 || code === 127)
|
|
162
|
+
return true;
|
|
163
|
+
}
|
|
164
|
+
return false;
|
|
165
|
+
}
|
|
98
166
|
//# sourceMappingURL=packageSpecifier.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"packageSpecifier.js","sourceRoot":"","sources":["../../src/scanner/packageSpecifier.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"packageSpecifier.js","sourceRoot":"","sources":["../../src/scanner/packageSpecifier.ts"],"names":[],"mappings":"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"}
|
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
* text is read as UTF-8 without any BOM handling: a manifest that `JSON.parse` rejects is reported
|
|
17
17
|
* as invalid instead of being silently normalized.
|
|
18
18
|
*/
|
|
19
|
+
import { type PackageSelfReferenceScope } from './packageSpecifier.js';
|
|
19
20
|
/**
|
|
20
21
|
* A dependency section: package name mapped to the version specifier declared for it.
|
|
21
22
|
*
|
|
@@ -43,16 +44,22 @@ export type ProjectManifest = {
|
|
|
43
44
|
scripts: Readonly<Record<string, string>>;
|
|
44
45
|
/** Root package type, used only to classify `.js` top-level await without executing config. */
|
|
45
46
|
packageType: string | undefined;
|
|
47
|
+
/** Bounded exact exports ownership; never a dependency inventory. */
|
|
48
|
+
selfReference: PackageSelfReferenceScope;
|
|
46
49
|
};
|
|
47
50
|
/** The package-type hint declared by one package scope, or `undefined` when no manifest exists. */
|
|
48
51
|
export type PackageTypeHint = {
|
|
49
52
|
packageType: string | undefined;
|
|
50
53
|
};
|
|
54
|
+
/** One existing package scope, including nameless/no-exports scope boundaries. */
|
|
55
|
+
export type PackageScopeMetadata = PackageTypeHint & {
|
|
56
|
+
selfReference: PackageSelfReferenceScope;
|
|
57
|
+
};
|
|
51
58
|
/**
|
|
52
59
|
* Why a manifest could not be turned into a {@link ProjectManifest}.
|
|
53
60
|
*
|
|
54
61
|
* - `missing`: no `package.json` exists at the expected path.
|
|
55
|
-
* - `unreadable`: the
|
|
62
|
+
* - `unreadable`: the entry is linked/non-regular, or a present file could not be read.
|
|
56
63
|
* - `invalid-json`: the file content is not valid JSON.
|
|
57
64
|
* - `invalid-shape`: the JSON is valid but its structure is not what the model requires.
|
|
58
65
|
*/
|
|
@@ -73,14 +80,23 @@ export declare class ProjectManifestError extends Error {
|
|
|
73
80
|
*
|
|
74
81
|
* @throws {ProjectManifestError} when the manifest is missing, unreadable, invalid JSON, or has an
|
|
75
82
|
* unexpected structure.
|
|
83
|
+
* @throws {SourceFilesError} when the selected root is linked or is not a directory.
|
|
76
84
|
*/
|
|
77
85
|
export declare function loadProjectManifest(projectRoot: string): Promise<ProjectManifest>;
|
|
78
86
|
/**
|
|
79
87
|
* Read only the static `type` hint from the manifest in `directory`.
|
|
80
88
|
*
|
|
81
|
-
*
|
|
89
|
+
* The combined scope reader below supplies analysis with ownership too. A missing manifest means the
|
|
82
90
|
* parent scope remains applicable; an existing manifest with no string `type` starts a new scope
|
|
83
|
-
*
|
|
91
|
+
* whose `.js` files are ambiguous rather than inheriting the parent's explicit module mode.
|
|
84
92
|
*/
|
|
85
93
|
export declare function loadPackageTypeHint(directory: string): Promise<PackageTypeHint | undefined>;
|
|
94
|
+
/** Read module mode and bounded ownership together, with the same mandatory manifest guard. */
|
|
95
|
+
export declare function loadPackageScope(directory: string): Promise<PackageScopeMetadata | undefined>;
|
|
96
|
+
/**
|
|
97
|
+
* Read mandatory manifest text only from a non-linked regular file. Only absence at lstat means
|
|
98
|
+
* no scope; a present entry that cannot be read (including disappearance after lstat) is an error.
|
|
99
|
+
* This checks stable filesystem entries, not adversarial replacement between calls.
|
|
100
|
+
*/
|
|
101
|
+
export declare function readManifestText(packageJsonPath: string): Promise<string>;
|
|
86
102
|
//# sourceMappingURL=projectManifest.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"projectManifest.d.ts","sourceRoot":"","sources":["../../src/scanner/projectManifest.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;
|
|
1
|
+
{"version":3,"file":"projectManifest.d.ts","sourceRoot":"","sources":["../../src/scanner/projectManifest.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAMH,OAAO,EAEL,KAAK,yBAAyB,EAC/B,MAAM,uBAAuB,CAAC;AAE/B;;;;GAIG;AACH,MAAM,MAAM,aAAa,GAAG,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;AAE7D;;;;;;GAMG;AACH,MAAM,MAAM,eAAe,GAAG;IAC5B,oFAAoF;IACpF,IAAI,EAAE,MAAM,CAAC;IACb,8DAA8D;IAC9D,eAAe,EAAE,MAAM,CAAC;IACxB,6DAA6D;IAC7D,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,YAAY,EAAE,aAAa,CAAC;IAC5B,eAAe,EAAE,aAAa,CAAC;IAC/B,uBAAuB,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;IAC7C,2DAA2D;IAC3D,OAAO,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IAC1C,+FAA+F;IAC/F,WAAW,EAAE,MAAM,GAAG,SAAS,CAAC;IAChC,qEAAqE;IACrE,aAAa,EAAE,yBAAyB,CAAC;CAC1C,CAAC;AAEF,mGAAmG;AACnG,MAAM,MAAM,eAAe,GAAG;IAC5B,WAAW,EAAE,MAAM,GAAG,SAAS,CAAC;CACjC,CAAC;AAEF,kFAAkF;AAClF,MAAM,MAAM,oBAAoB,GAAG,eAAe,GAAG;IACnD,aAAa,EAAE,yBAAyB,CAAC;CAC1C,CAAC;AAEF;;;;;;;GAOG;AACH,MAAM,MAAM,wBAAwB,GAAG,SAAS,GAAG,YAAY,GAAG,cAAc,GAAG,eAAe,CAAC;AAInG;;;GAGG;AACH,qBAAa,oBAAqB,SAAQ,KAAK;IAC7C,QAAQ,CAAC,IAAI,EAAE,wBAAwB,CAAC;gBAE5B,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,wBAAwB,EAAE,KAAK,CAAC,EAAE,OAAO;CAK7E;AAED;;;;;;;;;GASG;AACH,wBAAsB,mBAAmB,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,CAAC,CAiBvF;AAED;;;;;;GAMG;AACH,wBAAsB,mBAAmB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,GAAG,SAAS,CAAC,CAGjG;AAED,+FAA+F;AAC/F,wBAAsB,gBAAgB,CACpC,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,oBAAoB,GAAG,SAAS,CAAC,CA6B3C;AAED;;;;GAIG;AACH,wBAAsB,gBAAgB,CAAC,eAAe,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAsB/E"}
|
|
@@ -16,8 +16,10 @@
|
|
|
16
16
|
* text is read as UTF-8 without any BOM handling: a manifest that `JSON.parse` rejects is reported
|
|
17
17
|
* as invalid instead of being silently normalized.
|
|
18
18
|
*/
|
|
19
|
-
import { readFile } from 'node:fs/promises';
|
|
19
|
+
import { lstat, readFile } from 'node:fs/promises';
|
|
20
20
|
import { join, resolve } from 'node:path';
|
|
21
|
+
import { assertReadableRoot, SourceFilesError } from './sourceFiles.js';
|
|
22
|
+
import { createPackageSelfReferenceScope, } from './packageSpecifier.js';
|
|
21
23
|
const MANIFEST_FILENAME = 'package.json';
|
|
22
24
|
/**
|
|
23
25
|
* Operational error for manifest loading. The `code` lets callers (the CLI in a later step) render
|
|
@@ -39,37 +41,47 @@ export class ProjectManifestError extends Error {
|
|
|
39
41
|
*
|
|
40
42
|
* @throws {ProjectManifestError} when the manifest is missing, unreadable, invalid JSON, or has an
|
|
41
43
|
* unexpected structure.
|
|
44
|
+
* @throws {SourceFilesError} when the selected root is linked or is not a directory.
|
|
42
45
|
*/
|
|
43
46
|
export async function loadProjectManifest(projectRoot) {
|
|
44
47
|
const root = resolve(projectRoot);
|
|
45
48
|
const packageJsonPath = join(root, MANIFEST_FILENAME);
|
|
46
|
-
let text;
|
|
47
49
|
try {
|
|
48
|
-
|
|
50
|
+
await assertReadableRoot(root);
|
|
49
51
|
}
|
|
50
52
|
catch (error) {
|
|
51
|
-
|
|
53
|
+
// Preserve manifest-loader diagnostics for missing/unreadable roots, but reject linked roots
|
|
54
|
+
// with the same typed root error as source discovery before any target input is consulted.
|
|
55
|
+
if (error instanceof SourceFilesError && error.code !== 'not-a-directory') {
|
|
56
|
+
throw toReadError(packageJsonPath, error.cause ?? error);
|
|
57
|
+
}
|
|
58
|
+
throw error;
|
|
52
59
|
}
|
|
60
|
+
const text = await readManifestText(packageJsonPath);
|
|
53
61
|
return parseManifest(root, packageJsonPath, text);
|
|
54
62
|
}
|
|
55
63
|
/**
|
|
56
64
|
* Read only the static `type` hint from the manifest in `directory`.
|
|
57
65
|
*
|
|
58
|
-
*
|
|
66
|
+
* The combined scope reader below supplies analysis with ownership too. A missing manifest means the
|
|
59
67
|
* parent scope remains applicable; an existing manifest with no string `type` starts a new scope
|
|
60
|
-
*
|
|
68
|
+
* whose `.js` files are ambiguous rather than inheriting the parent's explicit module mode.
|
|
61
69
|
*/
|
|
62
70
|
export async function loadPackageTypeHint(directory) {
|
|
71
|
+
const scope = await loadPackageScope(directory);
|
|
72
|
+
return scope === undefined ? undefined : { packageType: scope.packageType };
|
|
73
|
+
}
|
|
74
|
+
/** Read module mode and bounded ownership together, with the same mandatory manifest guard. */
|
|
75
|
+
export async function loadPackageScope(directory) {
|
|
63
76
|
const packageJsonPath = join(resolve(directory), MANIFEST_FILENAME);
|
|
64
77
|
let text;
|
|
65
78
|
try {
|
|
66
|
-
text = await
|
|
79
|
+
text = await readManifestText(packageJsonPath);
|
|
67
80
|
}
|
|
68
81
|
catch (error) {
|
|
69
|
-
if (
|
|
82
|
+
if (error instanceof ProjectManifestError && error.code === 'missing')
|
|
70
83
|
return undefined;
|
|
71
|
-
|
|
72
|
-
throw new ProjectManifestError(`Could not read ${MANIFEST_FILENAME} at ${packageJsonPath}`, 'unreadable', error);
|
|
84
|
+
throw error;
|
|
73
85
|
}
|
|
74
86
|
let parsed;
|
|
75
87
|
try {
|
|
@@ -81,11 +93,37 @@ export async function loadPackageTypeHint(directory) {
|
|
|
81
93
|
if (!isPlainRecord(parsed)) {
|
|
82
94
|
throw new ProjectManifestError(`${packageJsonPath} must contain a JSON object`, 'invalid-shape');
|
|
83
95
|
}
|
|
84
|
-
return {
|
|
96
|
+
return {
|
|
97
|
+
packageType: readOptionalString(parsed, 'type'),
|
|
98
|
+
selfReference: createPackageSelfReferenceScope(parsed),
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Read mandatory manifest text only from a non-linked regular file. Only absence at lstat means
|
|
103
|
+
* no scope; a present entry that cannot be read (including disappearance after lstat) is an error.
|
|
104
|
+
* This checks stable filesystem entries, not adversarial replacement between calls.
|
|
105
|
+
*/
|
|
106
|
+
export async function readManifestText(packageJsonPath) {
|
|
107
|
+
let info;
|
|
108
|
+
try {
|
|
109
|
+
info = await lstat(packageJsonPath);
|
|
110
|
+
}
|
|
111
|
+
catch (error) {
|
|
112
|
+
throw toReadError(packageJsonPath, error);
|
|
113
|
+
}
|
|
114
|
+
if (!info.isFile()) {
|
|
115
|
+
throw new ProjectManifestError(`${packageJsonPath} must be a regular file; symlinks and non-regular entries are not read`, 'unreadable');
|
|
116
|
+
}
|
|
117
|
+
try {
|
|
118
|
+
return await readFile(packageJsonPath, 'utf8');
|
|
119
|
+
}
|
|
120
|
+
catch (error) {
|
|
121
|
+
throw new ProjectManifestError(`Could not read ${MANIFEST_FILENAME} at ${packageJsonPath}`, 'unreadable', error);
|
|
122
|
+
}
|
|
85
123
|
}
|
|
86
124
|
/**
|
|
87
125
|
* Classify a filesystem failure: a missing manifest is expected and reported separately from any
|
|
88
|
-
* other I/O problem.
|
|
126
|
+
* other I/O problem. Missing nested scopes may inherit only when the entry was absent at lstat.
|
|
89
127
|
*/
|
|
90
128
|
function toReadError(packageJsonPath, error) {
|
|
91
129
|
if (isNotFoundError(error)) {
|
|
@@ -133,6 +171,7 @@ function parseManifest(root, packageJsonPath, text) {
|
|
|
133
171
|
]),
|
|
134
172
|
scripts,
|
|
135
173
|
packageType,
|
|
174
|
+
selfReference: createPackageSelfReferenceScope(parsed),
|
|
136
175
|
};
|
|
137
176
|
return name === undefined ? manifest : { ...manifest, name };
|
|
138
177
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"projectManifest.js","sourceRoot":"","sources":["../../src/scanner/projectManifest.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"projectManifest.js","sourceRoot":"","sources":["../../src/scanner/projectManifest.ts"],"names":[],"mappings":"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"}
|
|
@@ -38,7 +38,8 @@
|
|
|
38
38
|
* that is still open at the end of the script. Each such region is consumed as an opaque block, so a
|
|
39
39
|
* separator inside it never delimits the surrounding script. An invalidated segment yields no
|
|
40
40
|
* evidence at all, while the neighbouring segments delimited by real supported separators are still
|
|
41
|
-
* analyzed normally.
|
|
41
|
+
* analyzed normally. Here-document payloads stay opaque; scanning resumes only after a proven
|
|
42
|
+
* literal closing delimiter. Unknown headers discard the unproven remainder.
|
|
42
43
|
*/
|
|
43
44
|
/**
|
|
44
45
|
* Recognize the declared dependencies that `package.json` scripts clearly reference.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"scriptUsage.d.ts","sourceRoot":"","sources":["../../src/scanner/scriptUsage.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"scriptUsage.d.ts","sourceRoot":"","sources":["../../src/scanner/scriptUsage.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0CG;AA6TH;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,sBAAsB,CACpC,OAAO,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,EACzC,oBAAoB,EAAE,WAAW,CAAC,MAAM,CAAC,GACxC,WAAW,CAAC,MAAM,CAAC,CAYrB"}
|
|
@@ -38,7 +38,8 @@
|
|
|
38
38
|
* that is still open at the end of the script. Each such region is consumed as an opaque block, so a
|
|
39
39
|
* separator inside it never delimits the surrounding script. An invalidated segment yields no
|
|
40
40
|
* evidence at all, while the neighbouring segments delimited by real supported separators are still
|
|
41
|
-
* analyzed normally.
|
|
41
|
+
* analyzed normally. Here-document payloads stay opaque; scanning resumes only after a proven
|
|
42
|
+
* literal closing delimiter. Unknown headers discard the unproven remainder.
|
|
42
43
|
*/
|
|
43
44
|
import { classifyPackageSpecifier } from './packageSpecifier.js';
|
|
44
45
|
/**
|
|
@@ -466,7 +467,8 @@ function matchRunnerPackage(significant) {
|
|
|
466
467
|
* (Step 68.1). Unsupported constructs (single `&`, redirection, `$(...)`, `${...}`, quotes left open
|
|
467
468
|
* at the end of the script) mark the current segment as unsupported instead of guessing tokens from
|
|
468
469
|
* inside it. The marker is reset by the next real separator, so invalidation stays local to the
|
|
469
|
-
* affected segment.
|
|
470
|
+
* affected segment. Here-document payloads are the exception: a literal closing delimiter must be
|
|
471
|
+
* proven before scanning resumes; unknown headers discard the unproven remainder.
|
|
470
472
|
*/
|
|
471
473
|
function splitSegments(script) {
|
|
472
474
|
const segments = [];
|
|
@@ -502,6 +504,17 @@ function splitSegments(script) {
|
|
|
502
504
|
i = skipCommentToEndOfLine(script, i);
|
|
503
505
|
continue;
|
|
504
506
|
}
|
|
507
|
+
// A here-document owns its entire payload, including apparent commands and quotes.
|
|
508
|
+
// Only the bounded literal header can establish a trusted resumption point.
|
|
509
|
+
if (ch === '<' && script[i + 1] === '<') {
|
|
510
|
+
const next = unsupported ? undefined : skipHereDocument(script, i);
|
|
511
|
+
unsupported = true;
|
|
512
|
+
if (next === undefined)
|
|
513
|
+
break;
|
|
514
|
+
i = next;
|
|
515
|
+
flush();
|
|
516
|
+
continue;
|
|
517
|
+
}
|
|
505
518
|
// Supported separators. `&&`/`||` are two-character; `;`, `|` and newline are single-character.
|
|
506
519
|
if (ch === ';' || ch === '\n') {
|
|
507
520
|
i += 1;
|
|
@@ -541,6 +554,64 @@ function splitSegments(script) {
|
|
|
541
554
|
segments.push({ tokens, unsupported });
|
|
542
555
|
return segments;
|
|
543
556
|
}
|
|
557
|
+
/** Locate a single literal << / <<- payload without interpreting or expanding any of it. */
|
|
558
|
+
function skipHereDocument(script, start) {
|
|
559
|
+
let i = start + 2;
|
|
560
|
+
const stripTabs = script[i] === '-';
|
|
561
|
+
if (stripTabs)
|
|
562
|
+
i++;
|
|
563
|
+
while (script[i] === ' ' || script[i] === '\t')
|
|
564
|
+
i++;
|
|
565
|
+
const quote = script[i] === "'" || script[i] === '"' ? script[i++] : undefined;
|
|
566
|
+
const delimiterStart = i;
|
|
567
|
+
while (i < script.length && /[A-Za-z0-9_]/.test(script[i] ?? ''))
|
|
568
|
+
i++;
|
|
569
|
+
if (i === delimiterStart)
|
|
570
|
+
return undefined;
|
|
571
|
+
const delimiter = script.slice(delimiterStart, i);
|
|
572
|
+
if (quote !== undefined) {
|
|
573
|
+
if (script[i] !== quote)
|
|
574
|
+
return undefined;
|
|
575
|
+
i++;
|
|
576
|
+
}
|
|
577
|
+
const delimiterEnd = i;
|
|
578
|
+
while (script[i] === ' ' || script[i] === '\t')
|
|
579
|
+
i++;
|
|
580
|
+
// An adjacent # is part of the delimiter word, not a shell comment.
|
|
581
|
+
if (i > delimiterEnd && script[i] === '#')
|
|
582
|
+
i = skipCommentToEndOfLine(script, i);
|
|
583
|
+
if (script[i] === '\r' && script[i + 1] === '\n')
|
|
584
|
+
i++;
|
|
585
|
+
if (script[i] !== '\n')
|
|
586
|
+
return undefined;
|
|
587
|
+
i++;
|
|
588
|
+
while (i < script.length) {
|
|
589
|
+
const newline = script.indexOf('\n', i);
|
|
590
|
+
let end = newline < 0 ? script.length : newline;
|
|
591
|
+
if (newline >= 0 && script[end - 1] === '\r')
|
|
592
|
+
end--;
|
|
593
|
+
if (quote === undefined && newline >= 0) {
|
|
594
|
+
// Unquoted payloads remove an unescaped backslash-newline before delimiter matching.
|
|
595
|
+
// Logical-line reconstruction is outside the resume subset: do not trust the remainder.
|
|
596
|
+
let escapeStart = end;
|
|
597
|
+
while (escapeStart > i && script[escapeStart - 1] === '\\')
|
|
598
|
+
escapeStart--;
|
|
599
|
+
if ((end - escapeStart) % 2 === 1)
|
|
600
|
+
return undefined;
|
|
601
|
+
}
|
|
602
|
+
let content = i;
|
|
603
|
+
if (stripTabs)
|
|
604
|
+
while (script[content] === '\t')
|
|
605
|
+
content++;
|
|
606
|
+
if (script.slice(content, end) === delimiter) {
|
|
607
|
+
return newline < 0 ? script.length : newline + 1;
|
|
608
|
+
}
|
|
609
|
+
if (newline < 0)
|
|
610
|
+
return undefined;
|
|
611
|
+
i = newline + 1;
|
|
612
|
+
}
|
|
613
|
+
return undefined;
|
|
614
|
+
}
|
|
544
615
|
/**
|
|
545
616
|
* Skip a POSIX comment that starts at `start`, which must point at a `#`, and return the index just
|
|
546
617
|
* at the next LF, or the end of the script when none follows.
|
|
@@ -578,6 +649,9 @@ function scanToken(script, start) {
|
|
|
578
649
|
if (isWhitespace(ch) || ch === ';' || ch === '\n' || ch === '|' || ch === '&') {
|
|
579
650
|
break;
|
|
580
651
|
}
|
|
652
|
+
// Let the outer scanner consume a here-document even when it is attached to a word.
|
|
653
|
+
if (ch === '<' && script[i + 1] === '<')
|
|
654
|
+
break;
|
|
581
655
|
// Redirection is not parsed in v0.1.0; its presence alone invalidates the segment.
|
|
582
656
|
if (ch === '<' || ch === '>') {
|
|
583
657
|
unsupported = true;
|
|
@@ -689,6 +763,10 @@ function isUnsupportedRegionStart(script, index) {
|
|
|
689
763
|
* cannot close a region either. A single quote is literal text inside a double-quoted run, which is
|
|
690
764
|
* the one quote form that does not nest.
|
|
691
765
|
*
|
|
766
|
+
* Inside $(), command-position grouping parentheses own their closers and word-boundary comments
|
|
767
|
+
* stay inert through LF. Literal here-document boundaries are delegated to the same bounded helper.
|
|
768
|
+
* Arithmetic, case patterns, function declarations and unprovable group boundaries retain the remainder.
|
|
769
|
+
*
|
|
692
770
|
* If EOF is reached first the region is unterminated and `script.length` is returned, leaving the
|
|
693
771
|
* current outer segment unsupported. This never interprets shell semantics; it only locates opaque
|
|
694
772
|
* region boundaries safely.
|
|
@@ -696,83 +774,150 @@ function isUnsupportedRegionStart(script, index) {
|
|
|
696
774
|
function skipUnsupportedRegion(script, start) {
|
|
697
775
|
const length = script.length;
|
|
698
776
|
const stack = [];
|
|
777
|
+
const push = (close, group = false) => {
|
|
778
|
+
stack.push({ close, group, wordBoundary: true, commandStart: true });
|
|
779
|
+
};
|
|
780
|
+
const pop = () => {
|
|
781
|
+
const closed = stack.pop();
|
|
782
|
+
const parent = stack.at(-1);
|
|
783
|
+
if (parent !== undefined) {
|
|
784
|
+
parent.wordBoundary = closed?.group === true;
|
|
785
|
+
parent.commandStart = false;
|
|
786
|
+
}
|
|
787
|
+
};
|
|
699
788
|
const first = script[start];
|
|
700
789
|
const second = script[start + 1];
|
|
701
790
|
let i;
|
|
702
791
|
if (first === '`') {
|
|
703
|
-
|
|
792
|
+
push('`');
|
|
704
793
|
i = start + 1;
|
|
705
794
|
}
|
|
706
795
|
else if (first === '$' && (second === '(' || second === '{')) {
|
|
707
|
-
|
|
796
|
+
if (second === '(' && script[start + 2] === '(')
|
|
797
|
+
return length;
|
|
798
|
+
push(second === '(' ? ')' : '}');
|
|
708
799
|
i = start + 2;
|
|
709
800
|
}
|
|
710
801
|
else {
|
|
711
|
-
// Not a region opener; advance by one so a caller can never loop forever.
|
|
712
802
|
return start + 1;
|
|
713
803
|
}
|
|
714
804
|
while (i < length && stack.length > 0) {
|
|
715
805
|
const ch = script[i];
|
|
716
|
-
if (ch === undefined) {
|
|
717
|
-
break;
|
|
718
|
-
}
|
|
719
806
|
const top = stack.at(-1);
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
i
|
|
807
|
+
if (ch === undefined || top === undefined)
|
|
808
|
+
break;
|
|
809
|
+
if (top.close === "'") {
|
|
810
|
+
if (ch === "'")
|
|
811
|
+
pop();
|
|
812
|
+
i++;
|
|
726
813
|
continue;
|
|
727
814
|
}
|
|
728
|
-
// A backslash consumes the next character in `$()`, `${}`, backticks and double quotes.
|
|
729
815
|
if (ch === '\\') {
|
|
816
|
+
// A removed line continuation does not begin a new shell word.
|
|
817
|
+
if (script[i + 1] !== '\n') {
|
|
818
|
+
top.wordBoundary = false;
|
|
819
|
+
top.commandStart = false;
|
|
820
|
+
}
|
|
730
821
|
i += 2;
|
|
731
822
|
continue;
|
|
732
823
|
}
|
|
733
824
|
if (ch === "'" || ch === '"') {
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
// that follow it.
|
|
737
|
-
if (ch === "'" && top === '"') {
|
|
738
|
-
i += 1;
|
|
825
|
+
if (ch === "'" && top.close === '"') {
|
|
826
|
+
i++;
|
|
739
827
|
continue;
|
|
740
828
|
}
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
stack.pop();
|
|
744
|
-
}
|
|
829
|
+
if (ch === '"' && top.close === '"')
|
|
830
|
+
pop();
|
|
745
831
|
else {
|
|
746
|
-
|
|
832
|
+
top.wordBoundary = false;
|
|
833
|
+
top.commandStart = false;
|
|
834
|
+
push(ch);
|
|
747
835
|
}
|
|
748
|
-
i
|
|
836
|
+
i++;
|
|
749
837
|
continue;
|
|
750
838
|
}
|
|
751
839
|
const next = script[i + 1];
|
|
752
840
|
if (ch === '$' && opensExpansion(next)) {
|
|
753
|
-
|
|
841
|
+
if (next === '(' && script[i + 2] === '(')
|
|
842
|
+
return length;
|
|
843
|
+
top.wordBoundary = false;
|
|
844
|
+
top.commandStart = false;
|
|
845
|
+
push(next === '(' ? ')' : '}');
|
|
754
846
|
i += 2;
|
|
755
847
|
continue;
|
|
756
848
|
}
|
|
757
849
|
if (ch === '`') {
|
|
758
|
-
if (top === '`')
|
|
759
|
-
|
|
760
|
-
}
|
|
850
|
+
if (top.close === '`')
|
|
851
|
+
pop();
|
|
761
852
|
else {
|
|
762
|
-
|
|
853
|
+
top.wordBoundary = false;
|
|
854
|
+
top.commandStart = false;
|
|
855
|
+
push('`');
|
|
763
856
|
}
|
|
764
|
-
i
|
|
857
|
+
i++;
|
|
765
858
|
continue;
|
|
766
859
|
}
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
860
|
+
if (top.close === ')') {
|
|
861
|
+
if (ch === '#' && top.wordBoundary) {
|
|
862
|
+
i = skipCommentToEndOfLine(script, i);
|
|
863
|
+
continue;
|
|
864
|
+
}
|
|
865
|
+
// A standalone pipeline negation leaves the following command in command position.
|
|
866
|
+
if (ch === '!' && top.commandStart && isRegionWordBoundary(next)) {
|
|
867
|
+
top.wordBoundary = true;
|
|
868
|
+
i++;
|
|
869
|
+
continue;
|
|
870
|
+
}
|
|
871
|
+
// These reserved words leave the next word in command position.
|
|
872
|
+
if (top.commandStart && top.wordBoundary) {
|
|
873
|
+
const lead = ['if', 'then', 'do', 'else', 'elif', 'while', 'until', 'time', '{'].find((word) => script.startsWith(word, i) && isRegionWordBoundary(script[i + word.length]));
|
|
874
|
+
if (lead !== undefined) {
|
|
875
|
+
i += lead.length;
|
|
876
|
+
top.wordBoundary = true;
|
|
877
|
+
continue;
|
|
878
|
+
}
|
|
879
|
+
}
|
|
880
|
+
// Case patterns and arithmetic/function syntax cannot be balanced as simple groups.
|
|
881
|
+
if (top.commandStart &&
|
|
882
|
+
['case', 'function'].some((word) => script.startsWith(word, i) && isRegionWordBoundary(script[i + word.length])))
|
|
883
|
+
return length;
|
|
884
|
+
if (ch === '<' && next === '<') {
|
|
885
|
+
const after = skipHereDocument(script, i);
|
|
886
|
+
if (after === undefined)
|
|
887
|
+
return length;
|
|
888
|
+
i = after;
|
|
889
|
+
top.wordBoundary = true;
|
|
890
|
+
top.commandStart = true;
|
|
891
|
+
continue;
|
|
892
|
+
}
|
|
893
|
+
if (ch === '(') {
|
|
894
|
+
if (!top.commandStart || next === '(')
|
|
895
|
+
return length;
|
|
896
|
+
top.wordBoundary = false;
|
|
897
|
+
top.commandStart = false;
|
|
898
|
+
push(')', true);
|
|
899
|
+
i++;
|
|
900
|
+
continue;
|
|
901
|
+
}
|
|
902
|
+
if (isWhitespace(ch) || ch === '\n' || ch === ';' || ch === '|' || ch === '&') {
|
|
903
|
+
top.wordBoundary = true;
|
|
904
|
+
if (ch === '\n' || ch === ';' || ch === '|' || ch === '&')
|
|
905
|
+
top.commandStart = true;
|
|
906
|
+
}
|
|
907
|
+
else {
|
|
908
|
+
top.wordBoundary = ch === '<' || ch === '>';
|
|
909
|
+
top.commandStart = false;
|
|
910
|
+
}
|
|
770
911
|
}
|
|
771
|
-
|
|
912
|
+
if ((ch === ')' || ch === '}') && ch === top.close)
|
|
913
|
+
pop();
|
|
914
|
+
i++;
|
|
772
915
|
}
|
|
773
|
-
// An empty stack means the outermost region closed just before `i`; otherwise EOF arrived first.
|
|
774
916
|
return stack.length === 0 ? i : length;
|
|
775
917
|
}
|
|
918
|
+
function isRegionWordBoundary(ch) {
|
|
919
|
+
return (ch === undefined || isWhitespace(ch) || ['\n', ';', '|', '&', '(', ')', '<', '>'].includes(ch));
|
|
920
|
+
}
|
|
776
921
|
/**
|
|
777
922
|
* Whether a character is whitespace (spaces, tabs, CR, FF, VT).
|
|
778
923
|
*/
|