@cplieger/deadset-ts 8.0.0-dev.6 → 8.0.0-dev.7
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 +3 -2
- package/dist/src/analysis.js +25 -43
- 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 +2 -1
- package/dist/src/configuration-files.js +27 -168
- package/dist/src/contract/contract.generated.js +13 -0
- package/dist/src/conventions.js +20 -60
- package/dist/src/dependencies.js +25 -43
- package/dist/src/discover.js +13 -39
- package/dist/src/edges.js +7 -15
- 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 +2 -4
- package/dist/src/exempt.js +6 -22
- package/dist/src/file-facts.js +2 -25
- package/dist/src/findings/reads-and-writes.js +2 -1
- package/dist/src/findings/self-check.js +3 -2
- package/dist/src/findings-pass.js +6 -11
- 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/intra-function-parts.js +10 -13
- package/dist/src/inventory.js +2 -21
- package/dist/src/json-document.js +4 -0
- package/dist/src/keyof-comparisons.js +4 -6
- 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 +3 -2
- package/dist/src/product.js +13 -0
- package/dist/src/references.js +14 -27
- package/dist/src/report-schema.js +15 -16
- 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 +11 -20
- package/dist/src/roots.js +7 -17
- package/dist/src/run.js +8 -7
- package/dist/src/schema.js +4 -3
- package/dist/src/scope.js +4 -5
- package/dist/src/serialization-contract.js +4 -8
- package/dist/src/session.js +2 -1
- package/dist/src/suppress-file.js +8 -10
- package/dist/src/symbol-keys.js +3 -5
- package/dist/src/template-field.js +5 -15
- 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/verbs/analyze.js +15 -13
- package/dist/src/verbs/describe.js +4 -4
- package/dist/src/verbs/explain.js +5 -3
- package/dist/src/verbs/print-projects.js +2 -1
- 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 +26 -51
- 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 +4 -2
- package/src/configuration-files.ts +27 -170
- package/src/contract/contract.generated.ts +25 -0
- package/src/conventions.ts +26 -74
- package/src/dependencies.ts +32 -50
- package/src/discover.ts +14 -39
- package/src/edges.ts +7 -21
- 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 +4 -4
- package/src/exempt.ts +5 -27
- package/src/file-facts.ts +6 -24
- package/src/findings/reads-and-writes.ts +2 -1
- package/src/findings/self-check.ts +3 -2
- package/src/findings-pass.ts +6 -12
- package/src/fs/manifest.ts +52 -0
- package/src/fs/tree.ts +110 -0
- package/src/git-ignore.ts +38 -25
- package/src/html-entries.ts +6 -33
- package/src/in-operands.ts +2 -1
- package/src/intra-function-parts.ts +10 -14
- package/src/inventory.ts +2 -20
- package/src/json-document.ts +5 -0
- package/src/keyof-comparisons.ts +3 -7
- 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 +3 -2
- package/src/product.ts +18 -0
- package/src/references.ts +14 -29
- package/src/report-schema.ts +15 -17
- package/src/report.ts +24 -34
- package/src/reporters/sarif.ts +4 -4
- package/src/reporters/template.ts +4 -20
- package/src/resolve.ts +20 -27
- package/src/roots.ts +7 -20
- package/src/run.ts +8 -13
- package/src/schema.ts +4 -3
- package/src/scope.ts +4 -10
- package/src/serialization-contract.ts +4 -9
- package/src/session.ts +2 -1
- package/src/suppress-file.ts +8 -11
- package/src/symbol-keys.ts +2 -5
- package/src/template-field.ts +5 -18
- 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/verbs/analyze.ts +22 -21
- package/src/verbs/describe.ts +4 -4
- package/src/verbs/explain.ts +5 -3
- package/src/verbs/print-projects.ts +2 -1
- package/src/verbs/print-retained.ts +2 -1
- package/src/verbs/print-roots.ts +2 -1
- 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
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
|
|
package/src/edges.ts
CHANGED
|
@@ -10,10 +10,12 @@ import type { Finding } from "./finding.ts";
|
|
|
10
10
|
import type { EdgeSide, EdgeSideName } from "./findings/boundary.ts";
|
|
11
11
|
import type { Host } from "./host.ts";
|
|
12
12
|
import type { InventorySymbol } from "./inventory.ts";
|
|
13
|
-
import { positionAt, walkDocument } from "./json-document.ts";
|
|
13
|
+
import { isRecord, positionAt, walkDocument } from "./json-document.ts";
|
|
14
14
|
import { joinPath } from "./paths.ts";
|
|
15
15
|
import { positionKey } from "./position.ts";
|
|
16
16
|
import { isPartSubject } from "./suppress.ts";
|
|
17
|
+
import { bytewise } from "./text/compare.ts";
|
|
18
|
+
import { message } from "./product.ts";
|
|
17
19
|
|
|
18
20
|
/** The name and the location the Contract fixes for the edges document. */
|
|
19
21
|
export const EDGES_FILE = "deadset-edges.json";
|
|
@@ -74,16 +76,12 @@ function carriesWildcard(ref: string): boolean {
|
|
|
74
76
|
return false;
|
|
75
77
|
}
|
|
76
78
|
|
|
77
|
-
function isObject(value: unknown): value is Record<string, unknown> {
|
|
78
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
79
|
-
}
|
|
80
|
-
|
|
81
79
|
/** One edge, checked: the closed key list, the value types and the value forms. */
|
|
82
80
|
function readEdge(value: unknown, at: string): { id: string; provides: string; usedBy: string } {
|
|
83
81
|
const refuse = (text: string, want: string): never => {
|
|
84
82
|
throw new EdgesError(at, text, want);
|
|
85
83
|
};
|
|
86
|
-
if (!
|
|
84
|
+
if (!isRecord(value)) {
|
|
87
85
|
return refuse(JSON.stringify(value), EDGE_WANT);
|
|
88
86
|
}
|
|
89
87
|
for (const [key, held] of Object.entries(value)) {
|
|
@@ -122,13 +120,9 @@ export function readEdgeSides(host: Host, targetRoot: string): readonly EdgeSide
|
|
|
122
120
|
try {
|
|
123
121
|
parsed = JSON.parse(text);
|
|
124
122
|
} catch (error: unknown) {
|
|
125
|
-
throw new EdgesError(
|
|
126
|
-
EDGES_FILE,
|
|
127
|
-
error instanceof Error ? error.message : String(error),
|
|
128
|
-
DOCUMENT_WANT,
|
|
129
|
-
);
|
|
123
|
+
throw new EdgesError(EDGES_FILE, message(error), DOCUMENT_WANT);
|
|
130
124
|
}
|
|
131
|
-
if (!
|
|
125
|
+
if (!isRecord(parsed)) {
|
|
132
126
|
throw new EdgesError(EDGES_FILE, JSON.stringify(parsed), DOCUMENT_WANT);
|
|
133
127
|
}
|
|
134
128
|
const walk = walkDocument(text, "edges");
|
|
@@ -190,14 +184,6 @@ interface Evaluated<F extends Finding> {
|
|
|
190
184
|
readonly evaluations: readonly EdgeEvaluation<F>[];
|
|
191
185
|
}
|
|
192
186
|
|
|
193
|
-
/** Two strings ordered bytewise. */
|
|
194
|
-
function compare(a: string, b: string): number {
|
|
195
|
-
if (a === b) {
|
|
196
|
-
return 0;
|
|
197
|
-
}
|
|
198
|
-
return a < b ? -1 : 1;
|
|
199
|
-
}
|
|
200
|
-
|
|
201
187
|
/**
|
|
202
188
|
* Evaluates every declared side of this language over the run's findings. A side naming a
|
|
203
189
|
* symbol the run enumerates nothing under is `absent`; otherwise it is evaluated once per
|
|
@@ -242,6 +228,6 @@ export function evaluateEdges<F extends Finding>(
|
|
|
242
228
|
return { ...side, state: "dead", finding };
|
|
243
229
|
});
|
|
244
230
|
});
|
|
245
|
-
evaluations.sort((a, b) =>
|
|
231
|
+
evaluations.sort((a, b) => bytewise(a.edge, b.edge) || bytewise(a.side, b.side));
|
|
246
232
|
return { findings: findings.filter((_finding, at) => !pending.has(at)), evaluations };
|
|
247
233
|
}
|
package/src/element-access.ts
CHANGED
|
@@ -47,6 +47,7 @@ import { renderPosition } from "./position.ts";
|
|
|
47
47
|
import { UNANSWERED } from "./query.ts";
|
|
48
48
|
import type { Reference } from "./references.ts";
|
|
49
49
|
import type { ProjectView } from "./session.ts";
|
|
50
|
+
import { parenthesesRemoved } from "./unwrap.ts";
|
|
50
51
|
|
|
51
52
|
type Use = "read" | "write" | "both";
|
|
52
53
|
|
|
@@ -93,13 +94,9 @@ function useOf(node: Node): Use {
|
|
|
93
94
|
return "read";
|
|
94
95
|
}
|
|
95
96
|
|
|
96
|
-
function unwrapped(node: Expression): Expression {
|
|
97
|
-
return isParenthesizedExpression(node) ? unwrapped(node.expression) : node;
|
|
98
|
-
}
|
|
99
|
-
|
|
100
97
|
/** Whether an index is a literal the checker resolves to a member by its own text. */
|
|
101
98
|
function isLiteralIndex(node: Expression): boolean {
|
|
102
|
-
const at =
|
|
99
|
+
const at = parenthesesRemoved(node);
|
|
103
100
|
return isStringLiteral(at) || isNumericLiteral(at) || isNoSubstitutionTemplateLiteral(at);
|
|
104
101
|
}
|
|
105
102
|
|
|
@@ -293,7 +290,7 @@ export function elementAccessReferences<Brand>(
|
|
|
293
290
|
const parameterOf = new Map<Candidate, { callable: Callable; at: number }>();
|
|
294
291
|
const byCallable = new Map<Identifier, Candidate[]>();
|
|
295
292
|
for (const candidate of candidates) {
|
|
296
|
-
const index =
|
|
293
|
+
const index = parenthesesRemoved(candidate.access.argumentExpression);
|
|
297
294
|
const fn = isIdentifier(index) ? enclosingFunction(candidate.access) : undefined;
|
|
298
295
|
const callable = fn === undefined ? undefined : callableOf(fn);
|
|
299
296
|
if (callable === undefined || !isIdentifier(index)) {
|
|
@@ -353,7 +350,7 @@ export function elementAccessReferences<Brand>(
|
|
|
353
350
|
continue;
|
|
354
351
|
}
|
|
355
352
|
// An object that is a parameter too is read as the argument each call passes there.
|
|
356
|
-
const object =
|
|
353
|
+
const object = parenthesesRemoved(candidate.access.expression);
|
|
357
354
|
const objectAt = isIdentifier(object)
|
|
358
355
|
? deferred.callable.parameters.findIndex(
|
|
359
356
|
(parameter) => isIdentifier(parameter.name) && parameter.name.text === object.text,
|
package/src/entry-point-gaps.ts
CHANGED
|
@@ -8,6 +8,8 @@
|
|
|
8
8
|
* one at a time: beside a finding, or in a report's declared gaps.
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
+
import { bytewise } from "./text/compare.ts";
|
|
12
|
+
|
|
11
13
|
/** One declined entry-point convention. */
|
|
12
14
|
export interface DeclinedConvention {
|
|
13
15
|
/** The tool the convention belongs to, by the name of the package or service. */
|
|
@@ -323,16 +325,8 @@ const PARTLY_ENTERED: readonly DeclinedConvention[] = [
|
|
|
323
325
|
},
|
|
324
326
|
];
|
|
325
327
|
|
|
326
|
-
/** Two strings ordered bytewise. */
|
|
327
|
-
function compare(a: string, b: string): number {
|
|
328
|
-
if (a === b) {
|
|
329
|
-
return 0;
|
|
330
|
-
}
|
|
331
|
-
return a < b ? -1 : 1;
|
|
332
|
-
}
|
|
333
|
-
|
|
334
328
|
/** Every declined entry-point convention, ordered by tool and then by convention. */
|
|
335
329
|
export const DECLINED_CONVENTIONS: readonly DeclinedConvention[] = [
|
|
336
330
|
...UNREAD_TOOLS.map((tool) => ({ tool, convention: EVERY_CONVENTION, reason: UNREAD })),
|
|
337
331
|
...PARTLY_ENTERED,
|
|
338
|
-
].sort((a, b) =>
|
|
332
|
+
].sort((a, b) => bytewise(a.tool, b.tool) || bytewise(a.convention, b.convention));
|
package/src/entry-points.ts
CHANGED
|
@@ -13,7 +13,6 @@
|
|
|
13
13
|
|
|
14
14
|
import {
|
|
15
15
|
isArrayLiteralExpression,
|
|
16
|
-
isAsExpression,
|
|
17
16
|
isCallExpression,
|
|
18
17
|
isExportAssignment,
|
|
19
18
|
isIdentifier,
|
|
@@ -21,10 +20,8 @@ import {
|
|
|
21
20
|
isNewExpression,
|
|
22
21
|
isNoSubstitutionTemplateLiteral,
|
|
23
22
|
isObjectLiteralExpression,
|
|
24
|
-
isParenthesizedExpression,
|
|
25
23
|
isPropertyAccessExpression,
|
|
26
24
|
isPropertyAssignment,
|
|
27
|
-
isSatisfiesExpression,
|
|
28
25
|
isStringLiteral,
|
|
29
26
|
SyntaxKind,
|
|
30
27
|
type Node,
|
|
@@ -39,7 +36,6 @@ import {
|
|
|
39
36
|
} from "./configuration-files.ts";
|
|
40
37
|
import type { Selected } from "./conventions.ts";
|
|
41
38
|
import { buildScriptEntries } from "./build-scripts.ts";
|
|
42
|
-
import { globExpression } from "./glob.ts";
|
|
43
39
|
import type { Host } from "./host.ts";
|
|
44
40
|
import { htmlEntries } from "./html-entries.ts";
|
|
45
41
|
import { documentEntries } from "./mdx-entries.ts";
|
|
@@ -49,6 +45,8 @@ import type { ProjectView } from "./session.ts";
|
|
|
49
45
|
import type { SourceFiles } from "./source-files.ts";
|
|
50
46
|
import { computedLoadDirectory, leadingLiteral } from "./unseen-loads.ts";
|
|
51
47
|
import { workflowEntries } from "./workflow-steps.ts";
|
|
48
|
+
import { unwrapped } from "./unwrap.ts";
|
|
49
|
+
import { globExpression } from "./text/glob.ts";
|
|
52
50
|
|
|
53
51
|
/** Which rule entered a file. */
|
|
54
52
|
export type EntryRule =
|
|
@@ -166,15 +164,6 @@ function literalOf(node: Node | undefined): string | undefined {
|
|
|
166
164
|
return undefined;
|
|
167
165
|
}
|
|
168
166
|
|
|
169
|
-
/** The expression a wrapper that changes no value holds: parentheses and type assertions. */
|
|
170
|
-
function unwrapped(node: Node): Node {
|
|
171
|
-
let at = node;
|
|
172
|
-
while (isParenthesizedExpression(at) || isAsExpression(at) || isSatisfiesExpression(at)) {
|
|
173
|
-
at = at.expression;
|
|
174
|
-
}
|
|
175
|
-
return at;
|
|
176
|
-
}
|
|
177
|
-
|
|
178
167
|
/**
|
|
179
168
|
* The object literals one configuration file's default export is written as: the
|
|
180
169
|
* literal itself, or each literal argument of the call it is, at any depth of calls.
|