@cplieger/deadset-ts 8.0.0-dev.6 → 8.0.0-dev.8
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/dist/bin/mapper-connection.js +2 -1
- package/dist/bin/node-host.js +24 -5
- package/dist/src/analysis.js +37 -56
- package/dist/src/automatic-types.js +10 -29
- package/dist/src/build-scripts.js +17 -29
- package/dist/src/component-layer.js +4 -2
- package/dist/src/components.js +2 -3
- package/dist/src/config.js +6 -82
- package/dist/src/configuration-files.js +27 -168
- package/dist/src/configured-declarations.js +15 -20
- package/dist/src/consumers.js +2 -2
- package/dist/src/contract/config.generated.js +544 -0
- package/dist/src/contract/contract.generated.js +13 -0
- package/dist/src/contract/report.generated.js +3 -2
- package/dist/src/conventions.js +20 -60
- package/dist/src/dependencies.js +25 -43
- package/dist/src/discover.js +14 -40
- package/dist/src/edges.js +38 -122
- package/dist/src/element-access.js +4 -6
- package/dist/src/entry-point-gaps.js +2 -8
- package/dist/src/entry-points.js +3 -10
- package/dist/src/enum-group.js +3 -10
- package/dist/src/erased-stores.js +4 -6
- package/dist/src/exempt-classes.js +0 -4
- package/dist/src/exempt.js +6 -22
- package/dist/src/file-facts.js +2 -25
- package/dist/src/findings/completion.js +5 -4
- package/dist/src/findings/emitters.js +1 -1
- package/dist/src/findings/intra-function.js +1 -1
- package/dist/src/findings/non-code-artifacts.js +2 -2
- package/dist/src/findings/reads-and-writes.js +2 -1
- package/dist/src/findings/self-check.js +3 -2
- package/dist/src/findings/unused-declarations.js +1 -1
- package/dist/src/findings/visibility-narrowing.js +2 -2
- package/dist/src/findings-pass.js +7 -12
- package/dist/src/framework-lifecycle.js +7 -7
- package/dist/src/fs/document.js +59 -0
- package/dist/src/fs/manifest.js +43 -0
- package/dist/src/fs/tree.js +88 -0
- package/dist/src/git-ignore.js +34 -25
- package/dist/src/html-entries.js +6 -33
- package/dist/src/in-operands.js +2 -1
- package/dist/src/injection-container.js +1 -1
- package/dist/src/intra-function-parts.js +10 -13
- package/dist/src/inventory.js +2 -21
- package/dist/src/json-document.js +198 -67
- package/dist/src/keyof-comparisons.js +4 -6
- package/dist/src/kinds.js +0 -7
- package/dist/src/manifest.js +5 -43
- package/dist/src/mdx-entries.js +3 -26
- package/dist/src/partial.js +2 -1
- package/dist/src/paths.js +26 -15
- package/dist/src/position.js +2 -1
- package/dist/src/print.js +11 -79
- package/dist/src/product.js +13 -0
- package/dist/src/references.js +14 -27
- package/dist/src/report-schema.js +3 -225
- package/dist/src/report.js +22 -31
- package/dist/src/reporters/sarif.js +4 -2
- package/dist/src/reporters/template.js +4 -19
- package/dist/src/resolve.js +245 -724
- package/dist/src/roots.js +7 -17
- package/dist/src/run.js +55 -61
- package/dist/src/schema.js +320 -343
- package/dist/src/scope.js +34 -103
- package/dist/src/serialization-contract.js +5 -9
- package/dist/src/session.js +2 -1
- package/dist/src/suppress-file.js +21 -25
- package/dist/src/suppress.js +3 -3
- package/dist/src/symbol-keys.js +3 -5
- package/dist/src/template-field.js +9 -19
- package/dist/src/test-globals.js +6 -28
- package/dist/src/test-joins.js +6 -6
- package/dist/src/text/compare.js +26 -0
- package/dist/src/text/glob.js +194 -0
- package/dist/src/text/shell.js +159 -0
- package/dist/src/{yaml-document.js → text/yaml.js} +38 -0
- package/dist/src/type-errors.js +14 -38
- package/dist/src/unseen-loads.js +2 -9
- package/dist/src/unwrap.js +23 -0
- package/dist/src/usage.js +10 -0
- package/dist/src/verbs/analyze.js +25 -26
- package/dist/src/verbs/describe.js +4 -4
- package/dist/src/verbs/explain.js +8 -6
- package/dist/src/verbs/print-projects.js +4 -3
- package/dist/src/verbs/print-retained.js +2 -1
- package/dist/src/verbs/print-roots.js +2 -1
- package/dist/src/verbs/version.js +2 -1
- package/dist/src/workflow-steps.js +9 -46
- package/dist/src/workspace-manifests.js +2 -2
- package/dist/src/workspace-programs.js +5 -3
- package/dist/src/workspace-resolution.js +13 -17
- package/dist/src/workspace.js +28 -116
- package/jsr.json +1 -1
- package/package.json +1 -1
- package/src/analysis.ts +55 -76
- package/src/automatic-types.ts +11 -29
- package/src/build-scripts.ts +17 -38
- package/src/component-layer.ts +4 -2
- package/src/components.ts +2 -4
- package/src/config.ts +31 -296
- package/src/configuration-files.ts +27 -170
- package/src/configured-declarations.ts +18 -23
- package/src/consumers.ts +4 -4
- package/src/contract/config.generated.ts +798 -0
- package/src/contract/contract.generated.ts +25 -0
- package/src/contract/report.generated.ts +3 -2
- package/src/conventions.ts +26 -74
- package/src/dependencies.ts +32 -50
- package/src/discover.ts +15 -40
- package/src/edges.ts +41 -140
- package/src/element-access.ts +4 -7
- package/src/entry-point-gaps.ts +3 -9
- package/src/entry-points.ts +2 -13
- package/src/enum-group.ts +2 -11
- package/src/erased-stores.ts +3 -7
- package/src/exempt-classes.ts +0 -5
- package/src/exempt.ts +8 -30
- package/src/file-facts.ts +6 -24
- package/src/findings/completion.ts +5 -6
- package/src/findings/emitter.ts +2 -2
- package/src/findings/emitters.ts +1 -1
- package/src/findings/intra-function.ts +1 -1
- package/src/findings/non-code-artifacts.ts +4 -4
- package/src/findings/reads-and-writes.ts +2 -1
- package/src/findings/self-check.ts +3 -2
- package/src/findings/unused-declarations.ts +1 -1
- package/src/findings/visibility-narrowing.ts +2 -2
- package/src/findings-pass.ts +9 -15
- package/src/framework-lifecycle.ts +7 -7
- package/src/fs/document.ts +74 -0
- package/src/fs/manifest.ts +52 -0
- package/src/fs/tree.ts +110 -0
- package/src/git-ignore.ts +38 -25
- package/src/host.ts +12 -5
- package/src/html-entries.ts +6 -33
- package/src/in-operands.ts +2 -1
- package/src/injection-container.ts +1 -1
- package/src/intra-function-parts.ts +10 -14
- package/src/inventory.ts +2 -20
- package/src/json-document.ts +241 -76
- package/src/keyof-comparisons.ts +3 -7
- package/src/kinds.ts +0 -8
- package/src/manifest.ts +5 -46
- package/src/mdx-entries.ts +3 -26
- package/src/partial.ts +2 -1
- package/src/paths.ts +27 -15
- package/src/position.ts +2 -1
- package/src/print.ts +12 -92
- package/src/product.ts +18 -0
- package/src/references.ts +14 -29
- package/src/report-schema.ts +3 -301
- package/src/report.ts +24 -34
- package/src/reporters/sarif.ts +4 -4
- package/src/reporters/template.ts +4 -20
- package/src/resolve.ts +283 -1114
- package/src/roots.ts +7 -20
- package/src/run.ts +57 -75
- package/src/schema.ts +391 -388
- package/src/scope.ts +40 -126
- package/src/serialization-contract.ts +5 -10
- package/src/session.ts +2 -1
- package/src/suppress-file.ts +21 -29
- package/src/suppress.ts +3 -3
- package/src/symbol-keys.ts +2 -5
- package/src/template-field.ts +15 -25
- package/src/test-globals.ts +9 -27
- package/src/test-joins.ts +6 -8
- package/src/text/compare.ts +27 -0
- package/src/text/glob.ts +210 -0
- package/src/text/shell.ts +161 -0
- package/src/{yaml-document.ts → text/yaml.ts} +46 -0
- package/src/type-errors.ts +25 -36
- package/src/unseen-loads.ts +1 -12
- package/src/unwrap.ts +34 -0
- package/src/usage.ts +10 -0
- package/src/verbs/analyze.ts +32 -42
- package/src/verbs/describe.ts +4 -4
- package/src/verbs/explain.ts +8 -10
- package/src/verbs/print-projects.ts +4 -3
- package/src/verbs/print-retained.ts +2 -1
- package/src/verbs/print-roots.ts +2 -1
- package/src/verbs/verb.ts +2 -2
- package/src/verbs/version.ts +2 -1
- package/src/workflow-steps.ts +9 -44
- package/src/workspace-manifests.ts +2 -2
- package/src/workspace-programs.ts +5 -3
- package/src/workspace-resolution.ts +21 -17
- package/src/workspace.ts +35 -121
- package/dist/src/glob.js +0 -65
- package/dist/src/pnpm-workspace.js +0 -120
- package/src/glob.ts +0 -67
- package/src/pnpm-workspace.ts +0 -135
|
@@ -28,6 +28,31 @@ export const FORMATS = [
|
|
|
28
28
|
"template",
|
|
29
29
|
] as const satisfies readonly Format[];
|
|
30
30
|
|
|
31
|
+
/** Each analyzed language's row: its analyzer, the names that mark it and the directories it skips. */
|
|
32
|
+
export const LANGUAGE_ROWS = {
|
|
33
|
+
go: {
|
|
34
|
+
analyzer: "deadset-go",
|
|
35
|
+
markers: ["go.mod"],
|
|
36
|
+
skippedDirectories: ["node_modules", "testdata", "vendor", ".*", "_*"],
|
|
37
|
+
},
|
|
38
|
+
ts: {
|
|
39
|
+
analyzer: "deadset-ts",
|
|
40
|
+
markers: ["tsconfig*.json"],
|
|
41
|
+
skippedDirectories: ["node_modules", ".git"],
|
|
42
|
+
},
|
|
43
|
+
} as const satisfies Readonly<
|
|
44
|
+
Partial<
|
|
45
|
+
Record<
|
|
46
|
+
Language,
|
|
47
|
+
{
|
|
48
|
+
readonly analyzer: string;
|
|
49
|
+
readonly markers: readonly string[];
|
|
50
|
+
readonly skippedDirectories: readonly string[];
|
|
51
|
+
}
|
|
52
|
+
>
|
|
53
|
+
>
|
|
54
|
+
>;
|
|
55
|
+
|
|
31
56
|
export const EXIT_CLEAN = 0;
|
|
32
57
|
export const EXIT_FINDINGS = 1;
|
|
33
58
|
export const EXIT_USAGE = 2;
|
|
@@ -589,7 +589,7 @@ export const REPORT_SCHEMAS = {
|
|
|
589
589
|
additionalProperties: false,
|
|
590
590
|
required: ["edge", "side", "symbol", "state"],
|
|
591
591
|
properties: {
|
|
592
|
-
edge: {
|
|
592
|
+
edge: { $ref: "finding.schema.json#/$defs/edge_identifier" },
|
|
593
593
|
side: { type: "string", enum: ["provides", "used_by"] },
|
|
594
594
|
symbol: { type: "string" },
|
|
595
595
|
state: { type: "string", enum: ["live", "dead", "absent"] },
|
|
@@ -969,7 +969,7 @@ export const REPORT_SCHEMAS = {
|
|
|
969
969
|
uniqueItems: true,
|
|
970
970
|
items: { type: "string", minLength: 1 },
|
|
971
971
|
},
|
|
972
|
-
edge: {
|
|
972
|
+
edge: { $ref: "#/$defs/edge_identifier" },
|
|
973
973
|
sides: {
|
|
974
974
|
type: "array",
|
|
975
975
|
minItems: 1,
|
|
@@ -1125,6 +1125,7 @@ export const REPORT_SCHEMAS = {
|
|
|
1125
1125
|
},
|
|
1126
1126
|
],
|
|
1127
1127
|
$defs: {
|
|
1128
|
+
edge_identifier: { type: "string", pattern: "^[A-Za-z0-9_.-]+(/[A-Za-z0-9_.-]+)*$" },
|
|
1128
1129
|
position: {
|
|
1129
1130
|
type: "object",
|
|
1130
1131
|
additionalProperties: false,
|
package/src/conventions.ts
CHANGED
|
@@ -8,7 +8,6 @@
|
|
|
8
8
|
|
|
9
9
|
import {
|
|
10
10
|
isArrayLiteralExpression,
|
|
11
|
-
isAsExpression,
|
|
12
11
|
isBinaryExpression,
|
|
13
12
|
isCallExpression,
|
|
14
13
|
isClassDeclaration,
|
|
@@ -22,27 +21,20 @@ import {
|
|
|
22
21
|
isNamedExports,
|
|
23
22
|
isNamespaceImport,
|
|
24
23
|
isNoSubstitutionTemplateLiteral,
|
|
25
|
-
isNonNullExpression,
|
|
26
24
|
isNumericLiteral,
|
|
27
25
|
isObjectBindingPattern,
|
|
28
26
|
isObjectLiteralExpression,
|
|
29
|
-
isParenthesizedExpression,
|
|
30
27
|
isPropertyAccessExpression,
|
|
31
28
|
isPropertyAssignment,
|
|
32
|
-
isSatisfiesExpression,
|
|
33
29
|
isShorthandPropertyAssignment,
|
|
34
30
|
isSpreadAssignment,
|
|
35
31
|
isStringLiteral,
|
|
36
32
|
isVariableStatement,
|
|
37
33
|
SyntaxKind,
|
|
38
|
-
type AsExpression,
|
|
39
34
|
type Identifier,
|
|
40
35
|
type Node,
|
|
41
36
|
type ObjectBindingPattern,
|
|
42
|
-
type NonNullExpression,
|
|
43
37
|
type ObjectLiteralExpression,
|
|
44
|
-
type ParenthesizedExpression,
|
|
45
|
-
type SatisfiesExpression,
|
|
46
38
|
type SourceFile,
|
|
47
39
|
} from "@typescript/native/ast";
|
|
48
40
|
import { lineOf } from "./component-files.ts";
|
|
@@ -60,13 +52,25 @@ import {
|
|
|
60
52
|
type TestFileReading,
|
|
61
53
|
} from "./convention-rows.ts";
|
|
62
54
|
import { documentStrings } from "./configuration-files.ts";
|
|
63
|
-
import { globExpression } from "./glob.ts";
|
|
64
55
|
import type { Host } from "./host.ts";
|
|
65
|
-
import {
|
|
56
|
+
import {
|
|
57
|
+
ancestors,
|
|
58
|
+
dirnamePath,
|
|
59
|
+
joinPath,
|
|
60
|
+
normalizePath,
|
|
61
|
+
relativePath,
|
|
62
|
+
resolvePath,
|
|
63
|
+
} from "./paths.ts";
|
|
66
64
|
import type { SetupFailure } from "./setup-failure.ts";
|
|
67
65
|
import type { SourceFiles } from "./source-files.ts";
|
|
68
66
|
import { satisfies } from "./version-range.ts";
|
|
69
|
-
import {
|
|
67
|
+
import { bytewise } from "./text/compare.ts";
|
|
68
|
+
import { isRecord } from "./json-document.ts";
|
|
69
|
+
import { unwrapped } from "./unwrap.ts";
|
|
70
|
+
import { filesIn } from "./fs/tree.ts";
|
|
71
|
+
import { INSTALLED_PACKAGES, MANIFEST, readJSON } from "./fs/manifest.ts";
|
|
72
|
+
import { globExpression } from "./text/glob.ts";
|
|
73
|
+
import { yamlDocument } from "./text/yaml.ts";
|
|
70
74
|
|
|
71
75
|
/** One manifest the analysis reads. */
|
|
72
76
|
export interface ConventionManifest {
|
|
@@ -171,18 +175,6 @@ const DEPENDENCY_SECTIONS = [
|
|
|
171
175
|
"optionalDependencies",
|
|
172
176
|
] as const;
|
|
173
177
|
|
|
174
|
-
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
175
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
function readJSON(host: Host, path: string): unknown {
|
|
179
|
-
try {
|
|
180
|
-
return JSON.parse(host.readFile(path));
|
|
181
|
-
} catch {
|
|
182
|
-
return undefined;
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
|
|
186
178
|
function declares(
|
|
187
179
|
manifest: unknown,
|
|
188
180
|
name: string,
|
|
@@ -203,38 +195,15 @@ function declares(
|
|
|
203
195
|
* none, or `undefined` where no `node_modules` holds it.
|
|
204
196
|
*/
|
|
205
197
|
function installedVersion(host: Host, dir: string, name: string): string | undefined {
|
|
206
|
-
for (
|
|
207
|
-
const path = joinPath(at,
|
|
198
|
+
for (const at of ancestors(dir)) {
|
|
199
|
+
const path = joinPath(at, INSTALLED_PACKAGES, name, MANIFEST);
|
|
208
200
|
if (host.kindOf(path) === "file") {
|
|
209
201
|
const manifest = readJSON(host, path);
|
|
210
202
|
const version = isRecord(manifest) ? manifest["version"] : undefined;
|
|
211
203
|
return typeof version === "string" ? version : "";
|
|
212
204
|
}
|
|
213
|
-
if (dirnamePath(at) === at) {
|
|
214
|
-
return undefined;
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
/** Whether one node wraps an expression without changing its value. */
|
|
220
|
-
function isWrapper(
|
|
221
|
-
node: Node,
|
|
222
|
-
): node is ParenthesizedExpression | AsExpression | SatisfiesExpression | NonNullExpression {
|
|
223
|
-
return (
|
|
224
|
-
isParenthesizedExpression(node) ||
|
|
225
|
-
isAsExpression(node) ||
|
|
226
|
-
isSatisfiesExpression(node) ||
|
|
227
|
-
isNonNullExpression(node)
|
|
228
|
-
);
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
/** The expression a wrapper that changes no value holds. */
|
|
232
|
-
function unwrapped(node: Node): Node {
|
|
233
|
-
let at = node;
|
|
234
|
-
while (isWrapper(at)) {
|
|
235
|
-
at = at.expression;
|
|
236
205
|
}
|
|
237
|
-
return
|
|
206
|
+
return undefined;
|
|
238
207
|
}
|
|
239
208
|
|
|
240
209
|
/** The key one property is written with, where it is written as a name or as literal text. */
|
|
@@ -823,16 +792,7 @@ function expand(
|
|
|
823
792
|
/** The configuration files in one directory that one glob names, by name. */
|
|
824
793
|
function configurationFiles(host: Host, dir: string, glob: string): readonly string[] {
|
|
825
794
|
const expression = globExpression(glob);
|
|
826
|
-
|
|
827
|
-
try {
|
|
828
|
-
entries = host.readDirectory(dir);
|
|
829
|
-
} catch {
|
|
830
|
-
return [];
|
|
831
|
-
}
|
|
832
|
-
return entries
|
|
833
|
-
.filter((entry) => !entry.directory && expression.test(entry.name))
|
|
834
|
-
.map((entry) => entry.name)
|
|
835
|
-
.sort();
|
|
795
|
+
return filesIn(host, dir, (name) => expression.test(name));
|
|
836
796
|
}
|
|
837
797
|
|
|
838
798
|
/** What one move resolved to: its directories, or the failure its property met. */
|
|
@@ -948,7 +908,7 @@ function shortNameUses(
|
|
|
948
908
|
}
|
|
949
909
|
}
|
|
950
910
|
}
|
|
951
|
-
return [...used].filter((name) => declared.has(name)).sort(
|
|
911
|
+
return [...used].filter((name) => declared.has(name)).sort(bytewise);
|
|
952
912
|
}
|
|
953
913
|
|
|
954
914
|
/**
|
|
@@ -1008,7 +968,7 @@ function prefixedNameUses(
|
|
|
1008
968
|
}
|
|
1009
969
|
}
|
|
1010
970
|
}
|
|
1011
|
-
return [...used].sort(
|
|
971
|
+
return [...used].sort(bytewise);
|
|
1012
972
|
}
|
|
1013
973
|
|
|
1014
974
|
/** The declared dependencies an applied row's helper settings use. */
|
|
@@ -1092,14 +1052,6 @@ function packageKeyUses(
|
|
|
1092
1052
|
);
|
|
1093
1053
|
}
|
|
1094
1054
|
|
|
1095
|
-
/** Two strings ordered bytewise. */
|
|
1096
|
-
function compare(a: string, b: string): number {
|
|
1097
|
-
if (a === b) {
|
|
1098
|
-
return 0;
|
|
1099
|
-
}
|
|
1100
|
-
return a < b ? -1 : 1;
|
|
1101
|
-
}
|
|
1102
|
-
|
|
1103
1055
|
/**
|
|
1104
1056
|
* What the rows decide over the manifests the analysis reads. A row `disabled` names
|
|
1105
1057
|
* is skipped before anything is read for it.
|
|
@@ -1119,7 +1071,7 @@ export function readConventions(
|
|
|
1119
1071
|
const selected = new Map<string, { starts: Set<number>; texts: string[] }>();
|
|
1120
1072
|
const testFiles = new Set<string>();
|
|
1121
1073
|
for (const manifest of manifests) {
|
|
1122
|
-
const declared = readJSON(host, joinPath(manifest.dir,
|
|
1074
|
+
const declared = readJSON(host, joinPath(manifest.dir, MANIFEST));
|
|
1123
1075
|
const versions = new Map<string, string | undefined>();
|
|
1124
1076
|
const missing = new Set<string>();
|
|
1125
1077
|
for (const row of rows) {
|
|
@@ -1222,16 +1174,16 @@ export function readConventions(
|
|
|
1222
1174
|
const unique = [...new Map(applied.map((one) => [JSON.stringify(one), one])).values()];
|
|
1223
1175
|
unique.sort(
|
|
1224
1176
|
(a, b) =>
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1177
|
+
bytewise(a.name, b.name) ||
|
|
1178
|
+
bytewise(a.manifest, b.manifest) ||
|
|
1179
|
+
bytewise(JSON.stringify(a), JSON.stringify(b)),
|
|
1228
1180
|
);
|
|
1229
1181
|
return {
|
|
1230
1182
|
applied: unique,
|
|
1231
1183
|
globs,
|
|
1232
1184
|
generated,
|
|
1233
1185
|
failures,
|
|
1234
|
-
uses: [...uses].sort(
|
|
1186
|
+
uses: [...uses].sort(bytewise),
|
|
1235
1187
|
selected,
|
|
1236
1188
|
testFiles,
|
|
1237
1189
|
};
|
package/src/dependencies.ts
CHANGED
|
@@ -42,17 +42,25 @@ import {
|
|
|
42
42
|
import type { FindingPosition } from "./finding.ts";
|
|
43
43
|
import type { Host } from "./host.ts";
|
|
44
44
|
import { nodeKey, packageScope, type Inventory } from "./inventory.ts";
|
|
45
|
-
import {
|
|
46
|
-
|
|
45
|
+
import {
|
|
46
|
+
ancestors,
|
|
47
|
+
dirnamePath,
|
|
48
|
+
isAbsolutePath,
|
|
49
|
+
joinPath,
|
|
50
|
+
relativePath,
|
|
51
|
+
resolvePath,
|
|
52
|
+
} from "./paths.ts";
|
|
47
53
|
import type { DependencySection, Module } from "./ref.ts";
|
|
48
54
|
import { UNANSWERED } from "./query.ts";
|
|
49
55
|
import type { Handle, ProjectView } from "./session.ts";
|
|
50
56
|
import { sourceFilesOf, type SourceFiles } from "./source-files.ts";
|
|
51
57
|
import { workflowCommands, workflowTokens } from "./workflow-steps.ts";
|
|
58
|
+
import { bytewise } from "./text/compare.ts";
|
|
59
|
+
import { isRecord } from "./json-document.ts";
|
|
60
|
+
import { inInstalledPackages, INSTALLED_PACKAGES, MANIFEST, readDocument } from "./fs/manifest.ts";
|
|
61
|
+
import { commandsOf, scriptTokens } from "./text/shell.ts";
|
|
52
62
|
|
|
53
|
-
const
|
|
54
|
-
const MODULES_DIR = "node_modules";
|
|
55
|
-
const MODULES_SEGMENT = `/${MODULES_DIR}/`;
|
|
63
|
+
const MODULES_SEGMENT = `/${INSTALLED_PACKAGES}/`;
|
|
56
64
|
const TYPES_SCOPE = "@types/";
|
|
57
65
|
|
|
58
66
|
/** A script token naming a JavaScript file by a path no expansion changes. */
|
|
@@ -160,25 +168,6 @@ export interface Dependencies {
|
|
|
160
168
|
readonly heldBy: ReadonlyMap<string, readonly string[]>;
|
|
161
169
|
}
|
|
162
170
|
|
|
163
|
-
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
164
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
/** One JSON document's decoded value, or undefined where it cannot be read or parsed. */
|
|
168
|
-
function readJSON(host: Host, path: string): { text: string; value: unknown } | undefined {
|
|
169
|
-
let text: string;
|
|
170
|
-
try {
|
|
171
|
-
text = host.readFile(path);
|
|
172
|
-
} catch {
|
|
173
|
-
return undefined;
|
|
174
|
-
}
|
|
175
|
-
try {
|
|
176
|
-
return { text, value: JSON.parse(text) };
|
|
177
|
-
} catch {
|
|
178
|
-
return undefined;
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
|
|
182
171
|
/**
|
|
183
172
|
* The package a bare module specifier names: its first segment, or its first two
|
|
184
173
|
* where it starts with a scope. A relative, absolute or scheme-prefixed specifier and
|
|
@@ -429,7 +418,7 @@ export function projectNeeds<Brand>(
|
|
|
429
418
|
}
|
|
430
419
|
for (const { strings, manifestDirs } of configuration.documents) {
|
|
431
420
|
for (const manifestDir of manifestDirs) {
|
|
432
|
-
const names = declaredAt(joinPath(manifestDir,
|
|
421
|
+
const names = declaredAt(joinPath(manifestDir, MANIFEST));
|
|
433
422
|
for (const text of strings) {
|
|
434
423
|
use([PROJECT_OWNER], dependenciesNamed(text, names), joinPath(manifestDir, MANIFEST));
|
|
435
424
|
}
|
|
@@ -444,7 +433,7 @@ export function projectNeeds<Brand>(
|
|
|
444
433
|
);
|
|
445
434
|
for (const { file, manifestDirs } of [...configuration.outside, ...ranFiles]) {
|
|
446
435
|
for (const manifestDir of manifestDirs) {
|
|
447
|
-
const names = declaredAt(joinPath(manifestDir,
|
|
436
|
+
const names = declaredAt(joinPath(manifestDir, MANIFEST));
|
|
448
437
|
for (const { text } of moduleStrings(file)) {
|
|
449
438
|
use([PROJECT_OWNER], dependenciesNamed(text, names), joinPath(manifestDir, MANIFEST));
|
|
450
439
|
}
|
|
@@ -467,8 +456,8 @@ function scriptRunFiles(
|
|
|
467
456
|
const root = resolvePath(host.workingDirectory(), targetRoot);
|
|
468
457
|
const dirs = new Set<string>([root]);
|
|
469
458
|
for (const name of files.byName.keys()) {
|
|
470
|
-
for (
|
|
471
|
-
if (
|
|
459
|
+
for (const dir of ancestors(dirnamePath(name), root)) {
|
|
460
|
+
if (dirs.has(dir)) {
|
|
472
461
|
break;
|
|
473
462
|
}
|
|
474
463
|
dirs.add(dir);
|
|
@@ -478,7 +467,7 @@ function scriptRunFiles(
|
|
|
478
467
|
[root, new Set(workflowTokens(host, root))],
|
|
479
468
|
]);
|
|
480
469
|
for (const dir of dirs) {
|
|
481
|
-
const read =
|
|
470
|
+
const read = readDocument(host, joinPath(dir, MANIFEST));
|
|
482
471
|
if (read !== undefined && isRecord(read.value)) {
|
|
483
472
|
const own = scriptTokensOf(read.value);
|
|
484
473
|
tokens.set(dir, dir === root ? new Set([...own, ...(tokens.get(root) ?? [])]) : own);
|
|
@@ -491,7 +480,8 @@ function scriptRunFiles(
|
|
|
491
480
|
const below = path === undefined ? undefined : relativePath(root, path);
|
|
492
481
|
if (
|
|
493
482
|
path !== undefined &&
|
|
494
|
-
below
|
|
483
|
+
below !== undefined &&
|
|
484
|
+
!inInstalledPackages(below) &&
|
|
495
485
|
!files.byName.has(path) &&
|
|
496
486
|
host.kindOf(path) === "file"
|
|
497
487
|
) {
|
|
@@ -797,8 +787,8 @@ function lineAndColumn(text: string, offset: number): { line: number; column: nu
|
|
|
797
787
|
|
|
798
788
|
/** The installed copy of one dependency, read where the package manager puts it for the target. */
|
|
799
789
|
function installedOf(host: Host, root: string, name: string): InstalledDependency | undefined {
|
|
800
|
-
for (
|
|
801
|
-
const read =
|
|
790
|
+
for (const dir of ancestors(root)) {
|
|
791
|
+
const read = readDocument(host, joinPath(dir, INSTALLED_PACKAGES, name, MANIFEST));
|
|
802
792
|
if (read !== undefined && isRecord(read.value)) {
|
|
803
793
|
const bin = read.value["bin"];
|
|
804
794
|
const peers = read.value["peerDependencies"];
|
|
@@ -815,10 +805,8 @@ function installedOf(host: Host, root: string, name: string): InstalledDependenc
|
|
|
815
805
|
: [],
|
|
816
806
|
};
|
|
817
807
|
}
|
|
818
|
-
if (dirnamePath(dir) === dir) {
|
|
819
|
-
return undefined;
|
|
820
|
-
}
|
|
821
808
|
}
|
|
809
|
+
return undefined;
|
|
822
810
|
}
|
|
823
811
|
|
|
824
812
|
/** Every dependency the manifest text at `path` declares, in the order the text writes them. */
|
|
@@ -904,9 +892,6 @@ const PRELOADING_PROGRAMS: ReadonlySet<string> = new Set([
|
|
|
904
892
|
const LAUNCHERS: ReadonlySet<string> = new Set(["cross-env", "env", "npx"]);
|
|
905
893
|
const ASSIGNMENT = /^[A-Za-z_][A-Za-z0-9_]*=/u;
|
|
906
894
|
|
|
907
|
-
/** The commands one script string runs, split at the shell's list and pipeline operators. */
|
|
908
|
-
const COMMAND_SEPARATORS = /&&|\|\||;|\||&/u;
|
|
909
|
-
|
|
910
895
|
/**
|
|
911
896
|
* The command strings one manifest runs: each of its `scripts`, and the `exec` command of
|
|
912
897
|
* nodemon's configuration, in the manifest's `nodemonConfig` or the `nodemon.json` beside
|
|
@@ -919,7 +904,10 @@ function manifestCommands(
|
|
|
919
904
|
manifest: Record<string, unknown>,
|
|
920
905
|
): readonly string[] {
|
|
921
906
|
const scripts = manifest["scripts"];
|
|
922
|
-
const nodemon = [
|
|
907
|
+
const nodemon = [
|
|
908
|
+
manifest["nodemonConfig"],
|
|
909
|
+
readDocument(host, joinPath(dir, "nodemon.json"))?.value,
|
|
910
|
+
];
|
|
923
911
|
return [
|
|
924
912
|
...(isRecord(scripts) ? Object.values(scripts) : []),
|
|
925
913
|
...nodemon.map((config) => (isRecord(config) ? config["exec"] : undefined)),
|
|
@@ -934,7 +922,7 @@ function manifestCommands(
|
|
|
934
922
|
function preloadsOf(scripts: readonly string[]): ReadonlySet<string> {
|
|
935
923
|
const found = new Set<string>();
|
|
936
924
|
for (const script of scripts) {
|
|
937
|
-
for (const command of script
|
|
925
|
+
for (const command of commandsOf(script)) {
|
|
938
926
|
const tokens = scriptTokens(command);
|
|
939
927
|
let at = 0;
|
|
940
928
|
while (
|
|
@@ -1076,7 +1064,7 @@ export function dependenciesOf(
|
|
|
1076
1064
|
const peers = new Map<string, readonly string[]>();
|
|
1077
1065
|
const uninstalledHere = new Set(nested);
|
|
1078
1066
|
for (const [dir, path] of manifests) {
|
|
1079
|
-
const read =
|
|
1067
|
+
const read = readDocument(host, joinPath(dir, MANIFEST));
|
|
1080
1068
|
if (read === undefined || !isRecord(read.value)) {
|
|
1081
1069
|
continue;
|
|
1082
1070
|
}
|
|
@@ -1154,10 +1142,7 @@ export function dependenciesOf(
|
|
|
1154
1142
|
const heldBy = new Map<string, readonly string[]>();
|
|
1155
1143
|
for (const [key, held] of holders) {
|
|
1156
1144
|
if (!live.has(key) && !ran.has(key)) {
|
|
1157
|
-
heldBy.set(
|
|
1158
|
-
key,
|
|
1159
|
-
[...held].sort((a, b) => (a < b ? -1 : 1)),
|
|
1160
|
-
);
|
|
1145
|
+
heldBy.set(key, [...held].sort(bytewise));
|
|
1161
1146
|
}
|
|
1162
1147
|
}
|
|
1163
1148
|
const users = new Map<string, number>();
|
|
@@ -1169,10 +1154,7 @@ export function dependenciesOf(
|
|
|
1169
1154
|
for (const id of candidates) {
|
|
1170
1155
|
const sole = [...(uses.get(id) ?? [])].filter((one) => users.get(one) === 1 && names.has(one));
|
|
1171
1156
|
if (sole.length > 0) {
|
|
1172
|
-
lastUses.set(
|
|
1173
|
-
id,
|
|
1174
|
-
sole.sort((a, b) => (a < b ? -1 : 1)),
|
|
1175
|
-
);
|
|
1157
|
+
lastUses.set(id, sole.sort(bytewise));
|
|
1176
1158
|
}
|
|
1177
1159
|
}
|
|
1178
1160
|
const owners = new Map<string, Set<string>>();
|
|
@@ -1184,7 +1166,7 @@ export function dependenciesOf(
|
|
|
1184
1166
|
}
|
|
1185
1167
|
}
|
|
1186
1168
|
const fileLastUses = new Map<string, string[]>();
|
|
1187
|
-
for (const [one, held] of [...owners].sort(([a], [b]) => (a
|
|
1169
|
+
for (const [one, held] of [...owners].sort(([a], [b]) => bytewise(a, b))) {
|
|
1188
1170
|
const paths = new Set([...held].map((owner) => owner.replace(/:\d+:\d+$/u, "")));
|
|
1189
1171
|
const [path] = paths;
|
|
1190
1172
|
if (paths.size === 1 && path !== undefined && path !== PROJECT_OWNER) {
|
package/src/discover.ts
CHANGED
|
@@ -8,12 +8,17 @@ import type { Scope } from "./scope.ts";
|
|
|
8
8
|
import { SetupError } from "./setup-failure.ts";
|
|
9
9
|
import type { Engine, ProjectDiagnostics } from "./session.ts";
|
|
10
10
|
import { writtenPath, type WorkspaceProgram } from "./workspace-programs.ts";
|
|
11
|
+
import { filesBelow } from "./fs/tree.ts";
|
|
12
|
+
import { LANGUAGE, message } from "./product.ts";
|
|
13
|
+
import { LANGUAGE_ROWS } from "./contract/contract.generated.ts";
|
|
14
|
+
import { globExpression } from "./text/glob.ts";
|
|
11
15
|
|
|
12
|
-
|
|
13
|
-
export const IGNORED_DIRECTORIES: ReadonlySet<string> = new Set(["node_modules", ".git"]);
|
|
16
|
+
const MARKERS = LANGUAGE_ROWS[LANGUAGE].markers.map(globExpression);
|
|
14
17
|
|
|
15
|
-
/**
|
|
16
|
-
|
|
18
|
+
/** Whether one file name is a compiler configuration's: one of the language row's markers. */
|
|
19
|
+
export function isConfigFile(name: string): boolean {
|
|
20
|
+
return MARKERS.some((marker) => marker.test(name));
|
|
21
|
+
}
|
|
17
22
|
|
|
18
23
|
/**
|
|
19
24
|
* A run that cannot read what it was asked to analyze. It ends the run with the
|
|
@@ -218,39 +223,9 @@ interface Discovery {
|
|
|
218
223
|
readonly fromScope: readonly string[];
|
|
219
224
|
}
|
|
220
225
|
|
|
221
|
-
/**
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
* analysis may not enter names no project.
|
|
225
|
-
*/
|
|
226
|
-
function configFilesUnder(host: Host, root: string): string[] {
|
|
227
|
-
const found: string[] = [];
|
|
228
|
-
const pending = [root];
|
|
229
|
-
while (pending.length > 0) {
|
|
230
|
-
const dir = pending.pop();
|
|
231
|
-
if (dir === undefined) {
|
|
232
|
-
break;
|
|
233
|
-
}
|
|
234
|
-
let entries: readonly { readonly name: string; readonly directory: boolean }[];
|
|
235
|
-
try {
|
|
236
|
-
entries = host.readDirectory(dir);
|
|
237
|
-
} catch {
|
|
238
|
-
continue;
|
|
239
|
-
}
|
|
240
|
-
for (const entry of [...entries].sort((a, b) => (a.name < b.name ? -1 : 1))) {
|
|
241
|
-
const path = joinPath(dir, entry.name);
|
|
242
|
-
if (entry.directory) {
|
|
243
|
-
if (!IGNORED_DIRECTORIES.has(entry.name)) {
|
|
244
|
-
pending.push(path);
|
|
245
|
-
}
|
|
246
|
-
continue;
|
|
247
|
-
}
|
|
248
|
-
if (CONFIG_FILE.test(entry.name)) {
|
|
249
|
-
found.push(path);
|
|
250
|
-
}
|
|
251
|
-
}
|
|
252
|
-
}
|
|
253
|
-
return found.sort();
|
|
226
|
+
/** The configuration files below `root`, by absolute path in ascending order. */
|
|
227
|
+
function configFilesUnder(host: Host, root: string): readonly string[] {
|
|
228
|
+
return filesBelow(host, root, isConfigFile).map((path) => joinPath(root, path));
|
|
254
229
|
}
|
|
255
230
|
|
|
256
231
|
/**
|
|
@@ -329,7 +304,7 @@ export function consumerProjects(engine: Engine, host: Host, root: string): read
|
|
|
329
304
|
if (kind !== "directory") {
|
|
330
305
|
throw new DiscoveryError(`the consumer ${root} is not a directory`, []);
|
|
331
306
|
}
|
|
332
|
-
const found = derivedFrom(engine, host, configFilesUnder(host, root), () => true);
|
|
307
|
+
const found = derivedFrom(engine, host, [...configFilesUnder(host, root)], () => true);
|
|
333
308
|
if (found.length === 0) {
|
|
334
309
|
throw new DiscoveryError(
|
|
335
310
|
`the consumer ${root} holds no compiler configuration, so none of its references can be read`,
|
|
@@ -384,7 +359,7 @@ function readConfiguration(
|
|
|
384
359
|
? { references: referencesOf(parsed) }
|
|
385
360
|
: { reason: `TS${String(circular.code)}: ${circular.text.replace("{0}", () => configFile)}` };
|
|
386
361
|
} catch (error: unknown) {
|
|
387
|
-
return { reason:
|
|
362
|
+
return { reason: message(error) };
|
|
388
363
|
}
|
|
389
364
|
}
|
|
390
365
|
|
|
@@ -529,7 +504,7 @@ export function discoverProjects(
|
|
|
529
504
|
configurations: readonly BuildConfiguration[] = [],
|
|
530
505
|
): Discovery {
|
|
531
506
|
const declared = configurations.filter(
|
|
532
|
-
(entry): entry is ProjectConfiguration =>
|
|
507
|
+
(entry): entry is ProjectConfiguration => "project" in entry,
|
|
533
508
|
);
|
|
534
509
|
if (declared.length > 0) {
|
|
535
510
|
return declaredProjects(engine, host, scope.target.path, declared);
|