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.
Files changed (54) hide show
  1. package/README.md +36 -40
  2. package/dist/cli/runCli.d.ts.map +1 -1
  3. package/dist/cli/runCli.js +9 -4
  4. package/dist/cli/runCli.js.map +1 -1
  5. package/dist/config/ignoreDependencies.d.ts +7 -0
  6. package/dist/config/ignoreDependencies.d.ts.map +1 -0
  7. package/dist/config/ignoreDependencies.js +18 -0
  8. package/dist/config/ignoreDependencies.js.map +1 -0
  9. package/dist/config/ignorePaths.d.ts +13 -0
  10. package/dist/config/ignorePaths.d.ts.map +1 -0
  11. package/dist/config/ignorePaths.js +47 -0
  12. package/dist/config/ignorePaths.js.map +1 -0
  13. package/dist/config/projectConfigError.d.ts +7 -0
  14. package/dist/config/projectConfigError.d.ts.map +1 -0
  15. package/dist/config/projectConfigError.js +10 -0
  16. package/dist/config/projectConfigError.js.map +1 -0
  17. package/dist/config/readProjectConfig.d.ts +23 -0
  18. package/dist/config/readProjectConfig.d.ts.map +1 -0
  19. package/dist/config/readProjectConfig.js +106 -0
  20. package/dist/config/readProjectConfig.js.map +1 -0
  21. package/dist/core/analyzeProject.d.ts +5 -1
  22. package/dist/core/analyzeProject.d.ts.map +1 -1
  23. package/dist/core/analyzeProject.js +37 -29
  24. package/dist/core/analyzeProject.js.map +1 -1
  25. package/dist/detectors/eslintManifestConfig.d.ts.map +1 -1
  26. package/dist/detectors/eslintManifestConfig.js +7 -4
  27. package/dist/detectors/eslintManifestConfig.js.map +1 -1
  28. package/dist/detectors/eslintRcJsonConfig.d.ts.map +1 -1
  29. package/dist/detectors/eslintRcJsonConfig.js +2 -1
  30. package/dist/detectors/eslintRcJsonConfig.js.map +1 -1
  31. package/dist/detectors/jestManifestConfig.d.ts.map +1 -1
  32. package/dist/detectors/jestManifestConfig.js +6 -4
  33. package/dist/detectors/jestManifestConfig.js.map +1 -1
  34. package/dist/scanner/moduleSpecifiers.d.ts +2 -2
  35. package/dist/scanner/moduleSpecifiers.d.ts.map +1 -1
  36. package/dist/scanner/moduleSpecifiers.js +1111 -178
  37. package/dist/scanner/moduleSpecifiers.js.map +1 -1
  38. package/dist/scanner/packageSpecifier.d.ts +26 -2
  39. package/dist/scanner/packageSpecifier.d.ts.map +1 -1
  40. package/dist/scanner/packageSpecifier.js +70 -2
  41. package/dist/scanner/packageSpecifier.js.map +1 -1
  42. package/dist/scanner/projectManifest.d.ts +19 -3
  43. package/dist/scanner/projectManifest.d.ts.map +1 -1
  44. package/dist/scanner/projectManifest.js +51 -12
  45. package/dist/scanner/projectManifest.js.map +1 -1
  46. package/dist/scanner/scriptUsage.d.ts +2 -1
  47. package/dist/scanner/scriptUsage.d.ts.map +1 -1
  48. package/dist/scanner/scriptUsage.js +182 -37
  49. package/dist/scanner/scriptUsage.js.map +1 -1
  50. package/dist/scanner/sourceFiles.d.ts +16 -2
  51. package/dist/scanner/sourceFiles.d.ts.map +1 -1
  52. package/dist/scanner/sourceFiles.js +12 -6
  53. package/dist/scanner/sourceFiles.js.map +1 -1
  54. 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`, package `exports`, TS `paths` or bundler aliases;
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":"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"}
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 file exists but could not be read (permissions, not a directory, ...).
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
- * Source-mode resolution calls this for nested package scopes. A missing manifest means that the
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
- * with script/CommonJS behavior.
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;AAKH;;;;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;CACjC,CAAC;AAEF,mGAAmG;AACnG,MAAM,MAAM,eAAe,GAAG;IAC5B,WAAW,EAAE,MAAM,GAAG,SAAS,CAAC;CACjC,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;;;;;;;;GAQG;AACH,wBAAsB,mBAAmB,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,CAAC,CAYvF;AAED;;;;;;GAMG;AACH,wBAAsB,mBAAmB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,GAAG,SAAS,CAAC,CAiCjG"}
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
- text = await readFile(packageJsonPath, 'utf8');
50
+ await assertReadableRoot(root);
49
51
  }
50
52
  catch (error) {
51
- throw toReadError(packageJsonPath, error);
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
- * Source-mode resolution calls this for nested package scopes. A missing manifest means that the
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
- * with script/CommonJS behavior.
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 readFile(packageJsonPath, 'utf8');
79
+ text = await readManifestText(packageJsonPath);
67
80
  }
68
81
  catch (error) {
69
- if (isNotFoundError(error)) {
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 { packageType: readOptionalString(parsed, 'type') };
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. A root that turns out to be a file surfaces as `ENOTDIR` and stays unreadable.
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":"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"}
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;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyCG;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"}
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
- stack.push('`');
792
+ push('`');
704
793
  i = start + 1;
705
794
  }
706
795
  else if (first === '$' && (second === '(' || second === '{')) {
707
- stack.push(second === '(' ? ')' : '}');
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
- // Inside single quotes everything is literal, so only the closing single quote is special.
721
- if (top === "'") {
722
- if (ch === "'") {
723
- stack.pop();
724
- }
725
- i += 1;
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
- // A single quote is literal text inside a double-quoted run, so it must never open a region of
735
- // its own. Doing so would keep the outer double quote open until EOF and hide the separators
736
- // that follow it.
737
- if (ch === "'" && top === '"') {
738
- i += 1;
825
+ if (ch === "'" && top.close === '"') {
826
+ i++;
739
827
  continue;
740
828
  }
741
- // A double quote closes only the quoted run it opened; any other quote opens a literal run.
742
- if (ch === '"' && top === '"') {
743
- stack.pop();
744
- }
829
+ if (ch === '"' && top.close === '"')
830
+ pop();
745
831
  else {
746
- stack.push(ch);
832
+ top.wordBoundary = false;
833
+ top.commandStart = false;
834
+ push(ch);
747
835
  }
748
- i += 1;
836
+ i++;
749
837
  continue;
750
838
  }
751
839
  const next = script[i + 1];
752
840
  if (ch === '$' && opensExpansion(next)) {
753
- stack.push(next === '(' ? ')' : '}');
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
- stack.pop();
760
- }
850
+ if (top.close === '`')
851
+ pop();
761
852
  else {
762
- stack.push('`');
853
+ top.wordBoundary = false;
854
+ top.commandStart = false;
855
+ push('`');
763
856
  }
764
- i += 1;
857
+ i++;
765
858
  continue;
766
859
  }
767
- // `)` and `}` close only a matching region; otherwise they are inert region content.
768
- if ((ch === ')' || ch === '}') && ch === top) {
769
- stack.pop();
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
- i += 1;
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
  */