@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
package/src/verbs/analyze.ts
CHANGED
|
@@ -12,7 +12,7 @@ import {
|
|
|
12
12
|
type RunAnalysis,
|
|
13
13
|
type RunProject,
|
|
14
14
|
} from "../analysis.ts";
|
|
15
|
-
import {
|
|
15
|
+
import { kindOf } from "../config.ts";
|
|
16
16
|
import { CONFORMANCE, DECLARED_GAPS } from "../conformance.ts";
|
|
17
17
|
import { moduleIdentity } from "../consumers.ts";
|
|
18
18
|
import { analyzerProvenance, verdictOf } from "../findings-pass.ts";
|
|
@@ -23,7 +23,6 @@ import type { NotBuilt } from "../discover.ts";
|
|
|
23
23
|
import { partialNotes } from "../partial.ts";
|
|
24
24
|
import {
|
|
25
25
|
buildReport,
|
|
26
|
-
LANGUAGE,
|
|
27
26
|
capFindings,
|
|
28
27
|
sortFindings,
|
|
29
28
|
type ReportConfiguration,
|
|
@@ -39,6 +38,9 @@ import { resolve } from "../resolve.ts";
|
|
|
39
38
|
import { otherSchemeRows, rowReason, writeBaseline, type Recorded } from "../suppress-file.ts";
|
|
40
39
|
import type { UnseenLoad } from "../unseen-loads.ts";
|
|
41
40
|
import { type Verb, type VerbOptions } from "./verb.ts";
|
|
41
|
+
import { UsageError } from "../usage.ts";
|
|
42
|
+
import { bytewise } from "../text/compare.ts";
|
|
43
|
+
import { diagnose, message } from "../product.ts";
|
|
42
44
|
|
|
43
45
|
/** The options `analyze` takes beside the ones every verb takes. */
|
|
44
46
|
export const ANALYZE_OPTIONS: VerbOptions = {
|
|
@@ -60,7 +62,7 @@ class ReportPathError extends Error {
|
|
|
60
62
|
|
|
61
63
|
/** The formats this analyzer renders, for a message that names them. */
|
|
62
64
|
function formatNames(): string {
|
|
63
|
-
return [...RENDERINGS.keys()].sort().join(", ");
|
|
65
|
+
return [...RENDERINGS.keys()].sort(bytewise).join(", ");
|
|
64
66
|
}
|
|
65
67
|
|
|
66
68
|
/** The renderings one invocation writes: the ones it named, else the configuration's. */
|
|
@@ -69,14 +71,12 @@ function formatsOf(named: readonly string[], configured: readonly Format[]): rea
|
|
|
69
71
|
const seen = new Set<string>();
|
|
70
72
|
for (const format of formats) {
|
|
71
73
|
if (!RENDERINGS.has(format as Format)) {
|
|
72
|
-
throw new
|
|
73
|
-
"malformed",
|
|
74
|
-
"",
|
|
74
|
+
throw new UsageError(
|
|
75
75
|
`${JSON.stringify(format)} is not a format this analyzer renders: the formats are ${formatNames()}`,
|
|
76
76
|
);
|
|
77
77
|
}
|
|
78
78
|
if (seen.has(format)) {
|
|
79
|
-
throw new
|
|
79
|
+
throw new UsageError(`the format ${JSON.stringify(format)} is named twice`);
|
|
80
80
|
}
|
|
81
81
|
seen.add(format);
|
|
82
82
|
}
|
|
@@ -188,9 +188,7 @@ function templateOf(
|
|
|
188
188
|
): Template | undefined {
|
|
189
189
|
if (named === undefined || named === "") {
|
|
190
190
|
if (formats.includes("template")) {
|
|
191
|
-
throw new
|
|
192
|
-
"malformed",
|
|
193
|
-
"",
|
|
191
|
+
throw new UsageError(
|
|
194
192
|
"the template format renders the template --template names, and none was named",
|
|
195
193
|
);
|
|
196
194
|
}
|
|
@@ -200,11 +198,7 @@ function templateOf(
|
|
|
200
198
|
try {
|
|
201
199
|
source = host.readFile(resolvePath(invoked, named));
|
|
202
200
|
} catch (error: unknown) {
|
|
203
|
-
throw new
|
|
204
|
-
"malformed",
|
|
205
|
-
"",
|
|
206
|
-
`--template=${named}: ${error instanceof Error ? error.message : String(error)}`,
|
|
207
|
-
);
|
|
201
|
+
throw new UsageError(`--template=${named}: ${message(error)}`);
|
|
208
202
|
}
|
|
209
203
|
try {
|
|
210
204
|
return parseTemplate(source);
|
|
@@ -212,7 +206,7 @@ function templateOf(
|
|
|
212
206
|
if (!(error instanceof TemplateError)) {
|
|
213
207
|
throw error;
|
|
214
208
|
}
|
|
215
|
-
throw new
|
|
209
|
+
throw new UsageError(`--template=${named}: ${error.message}`);
|
|
216
210
|
}
|
|
217
211
|
}
|
|
218
212
|
|
|
@@ -234,25 +228,17 @@ interface Documents {
|
|
|
234
228
|
export const analyzeVerb: Verb = ({ err, host, inputs, scope, openClient, option, repeated }) => {
|
|
235
229
|
const reportPath = option("report");
|
|
236
230
|
if (reportPath === undefined || reportPath === "") {
|
|
237
|
-
throw new
|
|
238
|
-
"malformed",
|
|
239
|
-
"",
|
|
231
|
+
throw new UsageError(
|
|
240
232
|
"analyze writes its report to the path --report names, and no path was named",
|
|
241
233
|
);
|
|
242
234
|
}
|
|
243
235
|
const exitCode = option("exit-code") ?? EXIT_CODE_ON;
|
|
244
236
|
if (exitCode !== EXIT_CODE_ON && exitCode !== EXIT_CODE_OFF) {
|
|
245
|
-
throw new
|
|
246
|
-
"malformed",
|
|
247
|
-
"",
|
|
237
|
+
throw new UsageError(
|
|
248
238
|
`--exit-code=${JSON.stringify(exitCode)}: the values are ${EXIT_CODE_ON} and ${EXIT_CODE_OFF}`,
|
|
249
239
|
);
|
|
250
240
|
}
|
|
251
241
|
const { config, provenance } = resolve(inputs());
|
|
252
|
-
const kind = config.targetKind;
|
|
253
|
-
if (kind === "") {
|
|
254
|
-
throw new ConfigError("missing-target-kind", "target.kind", "target.kind is not set");
|
|
255
|
-
}
|
|
256
242
|
const invoked = host.workingDirectory();
|
|
257
243
|
const baseline = option("baseline-write");
|
|
258
244
|
const documents: Documents = {
|
|
@@ -268,7 +254,7 @@ export const analyzeVerb: Verb = ({ err, host, inputs, scope, openClient, option
|
|
|
268
254
|
try {
|
|
269
255
|
version = host.analyzerVersion();
|
|
270
256
|
} catch (error: unknown) {
|
|
271
|
-
err
|
|
257
|
+
diagnose(err, message(error));
|
|
272
258
|
return EXIT_FAILURE;
|
|
273
259
|
}
|
|
274
260
|
|
|
@@ -321,7 +307,7 @@ export const analyzeVerb: Verb = ({ err, host, inputs, scope, openClient, option
|
|
|
321
307
|
conformance: CONFORMANCE,
|
|
322
308
|
declaredGaps: DECLARED_GAPS,
|
|
323
309
|
target: {
|
|
324
|
-
kind: kindOf(kind
|
|
310
|
+
kind: kindOf(config.target.kind),
|
|
325
311
|
root,
|
|
326
312
|
identity: moduleIdentity(host, scoped.target),
|
|
327
313
|
},
|
|
@@ -343,13 +329,13 @@ export const analyzeVerb: Verb = ({ err, host, inputs, scope, openClient, option
|
|
|
343
329
|
...unseenLoadNotes(run.unseenLoads),
|
|
344
330
|
],
|
|
345
331
|
});
|
|
346
|
-
report = capFindings(sortFindings(built, config.reporters.sort), config.reporters.
|
|
332
|
+
report = capFindings(sortFindings(built, config.reporters.sort), config.reporters.max_findings);
|
|
347
333
|
verdict = verdictOf(result, config);
|
|
348
334
|
} catch (error: unknown) {
|
|
349
335
|
if (!(error instanceof ReportPathError)) {
|
|
350
336
|
throw error;
|
|
351
337
|
}
|
|
352
|
-
err
|
|
338
|
+
diagnose(err, error.message);
|
|
353
339
|
return EXIT_FAILURE;
|
|
354
340
|
}
|
|
355
341
|
|
|
@@ -382,38 +368,42 @@ export const analyzeVerb: Verb = ({ err, host, inputs, scope, openClient, option
|
|
|
382
368
|
);
|
|
383
369
|
}
|
|
384
370
|
} catch (error: unknown) {
|
|
385
|
-
err
|
|
371
|
+
diagnose(err, message(error));
|
|
386
372
|
return EXIT_FAILURE;
|
|
387
373
|
}
|
|
388
374
|
|
|
389
375
|
for (const note of partial) {
|
|
390
|
-
err
|
|
376
|
+
diagnose(err, note);
|
|
391
377
|
}
|
|
392
378
|
for (const line of [...unread, ...unfound]) {
|
|
393
|
-
err
|
|
379
|
+
diagnose(err, line);
|
|
394
380
|
}
|
|
395
381
|
for (const warning of warnings) {
|
|
396
|
-
|
|
397
|
-
|
|
382
|
+
diagnose(
|
|
383
|
+
err,
|
|
384
|
+
`${warning.path}:${String(warning.line)}: a <script> start tag that begins a line is not read as a block: ${warning.reason}`,
|
|
398
385
|
);
|
|
399
386
|
}
|
|
400
387
|
for (const skip of report.type_error_skips) {
|
|
401
|
-
|
|
402
|
-
|
|
388
|
+
diagnose(
|
|
389
|
+
err,
|
|
390
|
+
`${skip.path}:${String(skip.line)}: ${skip.message}: the function or statement holding this type error is not evaluated`,
|
|
403
391
|
);
|
|
404
392
|
}
|
|
405
393
|
if (report.totals.pending > 0) {
|
|
406
|
-
|
|
407
|
-
|
|
394
|
+
diagnose(
|
|
395
|
+
err,
|
|
396
|
+
`${counted(report.totals.pending, "pending finding")}: the report names a declared cross-language edge, and no merge has resolved it`,
|
|
408
397
|
);
|
|
409
398
|
}
|
|
410
399
|
if (report.totals.stale_suppressions > 0) {
|
|
411
|
-
err
|
|
400
|
+
diagnose(err, counted(report.totals.stale_suppressions, "stale suppression"));
|
|
412
401
|
}
|
|
413
402
|
if (exitCode === EXIT_CODE_OFF) {
|
|
414
403
|
if (verdict !== EXIT_CLEAN) {
|
|
415
|
-
|
|
416
|
-
|
|
404
|
+
diagnose(
|
|
405
|
+
err,
|
|
406
|
+
`the exit code is configured off: the verdict of this run is ${String(verdict)}`,
|
|
417
407
|
);
|
|
418
408
|
}
|
|
419
409
|
return EXIT_CLEAN;
|
package/src/verbs/describe.ts
CHANGED
|
@@ -6,8 +6,8 @@ import {
|
|
|
6
6
|
SCHEMA_VERSIONS,
|
|
7
7
|
} from "../contract/contract.generated.ts";
|
|
8
8
|
import { CONFORMANCE, type Conformance } from "../conformance.ts";
|
|
9
|
-
import { ANALYZER_NAME, LANGUAGE } from "../report.ts";
|
|
10
9
|
import { type Verb } from "./verb.ts";
|
|
10
|
+
import { diagnose, LANGUAGE, message, PRODUCT } from "../product.ts";
|
|
11
11
|
|
|
12
12
|
/**
|
|
13
13
|
* The document `describe` writes: the analyzer, the Contract version it implements, the
|
|
@@ -16,7 +16,7 @@ import { type Verb } from "./verb.ts";
|
|
|
16
16
|
*/
|
|
17
17
|
export function describeDocument(version: string, recorded: Conformance | undefined): string {
|
|
18
18
|
const document = {
|
|
19
|
-
name:
|
|
19
|
+
name: PRODUCT,
|
|
20
20
|
version,
|
|
21
21
|
contract_version: CONTRACT_VERSION,
|
|
22
22
|
schema_versions_accepted: SCHEMA_VERSIONS,
|
|
@@ -37,14 +37,14 @@ export function describeDocument(version: string, recorded: Conformance | undefi
|
|
|
37
37
|
/** Writes this analyzer's description to the output stream, and nothing to the error stream. */
|
|
38
38
|
export const describeVerb: Verb = ({ out, err, host, args }) => {
|
|
39
39
|
if (args.length > 0) {
|
|
40
|
-
err
|
|
40
|
+
diagnose(err, `describe takes no argument, got ${JSON.stringify(args[0])}`);
|
|
41
41
|
return EXIT_USAGE;
|
|
42
42
|
}
|
|
43
43
|
let version: string;
|
|
44
44
|
try {
|
|
45
45
|
version = host.analyzerVersion();
|
|
46
46
|
} catch (error: unknown) {
|
|
47
|
-
err
|
|
47
|
+
diagnose(err, `describe: ${message(error)}`);
|
|
48
48
|
return EXIT_FAILURE;
|
|
49
49
|
}
|
|
50
50
|
out.write(describeDocument(version, CONFORMANCE));
|
package/src/verbs/explain.ts
CHANGED
|
@@ -2,7 +2,6 @@ import { EXIT_CLEAN, EXIT_USAGE } from "../contract/contract.generated.ts";
|
|
|
2
2
|
import { runAnalysis, type RunAnalysis } from "../analysis.ts";
|
|
3
3
|
import { listing } from "../components.ts";
|
|
4
4
|
import type { Cascade } from "../config.ts";
|
|
5
|
-
import { ConfigError } from "../config.ts";
|
|
6
5
|
import type { CompletedFinding } from "../finding.ts";
|
|
7
6
|
import { OUTSIDE, swept as judged } from "../graph.ts";
|
|
8
7
|
import type { InventorySymbol } from "../inventory.ts";
|
|
@@ -14,6 +13,9 @@ import type { Reference } from "../references.ts";
|
|
|
14
13
|
import { resolve } from "../resolve.ts";
|
|
15
14
|
import type { Root } from "../roots.ts";
|
|
16
15
|
import { type Verb, type VerbOptions } from "./verb.ts";
|
|
16
|
+
import { UsageError } from "../usage.ts";
|
|
17
|
+
import { bytewise } from "../text/compare.ts";
|
|
18
|
+
import { diagnose } from "../product.ts";
|
|
17
19
|
|
|
18
20
|
/**
|
|
19
21
|
* The three spellings of an explanation request, each naming the symbol to explain. They are
|
|
@@ -45,16 +47,12 @@ function namedSymbol(option: (name: string) => string | undefined): string {
|
|
|
45
47
|
});
|
|
46
48
|
const [only, ...more] = named;
|
|
47
49
|
if (only === undefined) {
|
|
48
|
-
throw new
|
|
49
|
-
"malformed",
|
|
50
|
-
"",
|
|
50
|
+
throw new UsageError(
|
|
51
51
|
"explain explains the symbol --why, --why-live or --why-not names, and none was named",
|
|
52
52
|
);
|
|
53
53
|
}
|
|
54
54
|
if (more.length > 0) {
|
|
55
|
-
throw new
|
|
56
|
-
"malformed",
|
|
57
|
-
"",
|
|
55
|
+
throw new UsageError(
|
|
58
56
|
`explain explains one symbol, and ${named.map((one) => `--${one.name}`).join(", ")} each name one`,
|
|
59
57
|
);
|
|
60
58
|
}
|
|
@@ -102,7 +100,7 @@ function subjectOf(
|
|
|
102
100
|
symbol.ref.toLowerCase().includes(folded) || symbol.name.toLowerCase().includes(folded),
|
|
103
101
|
)
|
|
104
102
|
.map(described)
|
|
105
|
-
.sort(
|
|
103
|
+
.sort(bytewise);
|
|
106
104
|
return { candidates: partial.slice(0, MAX_PARTIAL_MATCHES) };
|
|
107
105
|
}
|
|
108
106
|
|
|
@@ -381,7 +379,7 @@ export const explainVerb: Verb = ({ out, err, host, inputs, scope, openClient, o
|
|
|
381
379
|
targetRoot: resolvePath(host.workingDirectory(), scoped.target.path),
|
|
382
380
|
});
|
|
383
381
|
if (subject === undefined) {
|
|
384
|
-
err
|
|
382
|
+
diagnose(err, `${JSON.stringify(named)} names no one symbol of the target`);
|
|
385
383
|
for (const candidate of candidates) {
|
|
386
384
|
err.write(` ${candidate}\n`);
|
|
387
385
|
}
|
|
@@ -392,7 +390,7 @@ export const explainVerb: Verb = ({ out, err, host, inputs, scope, openClient, o
|
|
|
392
390
|
out.write(explanation(analysis, subject, config.reporters.cascade));
|
|
393
391
|
}
|
|
394
392
|
for (const note of notes) {
|
|
395
|
-
err
|
|
393
|
+
diagnose(err, note);
|
|
396
394
|
}
|
|
397
395
|
return subject === undefined ? EXIT_USAGE : EXIT_CLEAN;
|
|
398
396
|
};
|
|
@@ -2,8 +2,9 @@ import { EXIT_CLEAN } from "../contract/contract.generated.ts";
|
|
|
2
2
|
import { runProjects } from "../analysis.ts";
|
|
3
3
|
import { partialNotes } from "../partial.ts";
|
|
4
4
|
import { resolvePath } from "../paths.ts";
|
|
5
|
-
import {
|
|
5
|
+
import { resolvePartial } from "../resolve.ts";
|
|
6
6
|
import { type Verb } from "./verb.ts";
|
|
7
|
+
import { diagnose } from "../product.ts";
|
|
7
8
|
|
|
8
9
|
/**
|
|
9
10
|
* Writes the name of every project one run analyzes, one per line: the identifier the
|
|
@@ -13,7 +14,7 @@ import { type Verb } from "./verb.ts";
|
|
|
13
14
|
* instead, and one that fails the analysis fails this command the same way.
|
|
14
15
|
*/
|
|
15
16
|
export const printProjectsVerb: Verb = ({ out, err, host, inputs, scope, openClient }) => {
|
|
16
|
-
const config =
|
|
17
|
+
const config = resolvePartial(inputs());
|
|
17
18
|
const scoped = scope();
|
|
18
19
|
const answer = runProjects(openClient(config), host, scoped, config);
|
|
19
20
|
for (const id of answer.configurations) {
|
|
@@ -24,7 +25,7 @@ export const printProjectsVerb: Verb = ({ out, err, host, inputs, scope, openCli
|
|
|
24
25
|
unanswered: answer.unanswered,
|
|
25
26
|
targetRoot: resolvePath(host.workingDirectory(), scoped.target.path),
|
|
26
27
|
})) {
|
|
27
|
-
err
|
|
28
|
+
diagnose(err, note);
|
|
28
29
|
}
|
|
29
30
|
return EXIT_CLEAN;
|
|
30
31
|
};
|
|
@@ -5,6 +5,7 @@ import { partialNotes } from "../partial.ts";
|
|
|
5
5
|
import { resolvePath } from "../paths.ts";
|
|
6
6
|
import { resolve } from "../resolve.ts";
|
|
7
7
|
import { type Verb } from "./verb.ts";
|
|
8
|
+
import { diagnose } from "../product.ts";
|
|
8
9
|
|
|
9
10
|
/**
|
|
10
11
|
* Writes every declaration an exemption held back in the production sweep a report is
|
|
@@ -28,7 +29,7 @@ export const printRetainedVerb: Verb = ({ out, err, host, inputs, scope, openCli
|
|
|
28
29
|
unanswered: swept.unanswered,
|
|
29
30
|
targetRoot: resolvePath(host.workingDirectory(), scoped.target.path),
|
|
30
31
|
})) {
|
|
31
|
-
err
|
|
32
|
+
diagnose(err, note);
|
|
32
33
|
}
|
|
33
34
|
return EXIT_CLEAN;
|
|
34
35
|
};
|
package/src/verbs/print-roots.ts
CHANGED
|
@@ -4,6 +4,7 @@ import { partialNotes } from "../partial.ts";
|
|
|
4
4
|
import { resolvePath } from "../paths.ts";
|
|
5
5
|
import { resolve } from "../resolve.ts";
|
|
6
6
|
import { type Verb } from "./verb.ts";
|
|
7
|
+
import { diagnose } from "../product.ts";
|
|
7
8
|
|
|
8
9
|
/**
|
|
9
10
|
* One root as a line: the declaration's reference, why it is a root, and the string
|
|
@@ -42,7 +43,7 @@ export const printRootsVerb: Verb = ({ out, err, host, inputs, scope, openClient
|
|
|
42
43
|
unanswered: answer.unanswered,
|
|
43
44
|
targetRoot: resolvePath(host.workingDirectory(), scoped.target.path),
|
|
44
45
|
})) {
|
|
45
|
-
err
|
|
46
|
+
diagnose(err, note);
|
|
46
47
|
}
|
|
47
48
|
return answer.findings.length > 0 ? EXIT_FINDINGS : EXIT_CLEAN;
|
|
48
49
|
};
|
package/src/verbs/verb.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { Inputs, PartialConfig } from "../config.ts";
|
|
2
2
|
import type { Host } from "../host.ts";
|
|
3
3
|
import type { Writer } from "../run.ts";
|
|
4
4
|
import type { Scope } from "../scope.ts";
|
|
@@ -21,7 +21,7 @@ interface Invocation {
|
|
|
21
21
|
* Opens the compiler client for a run under `config`. A client the verb opened is
|
|
22
22
|
* closed if the verb throws.
|
|
23
23
|
*/
|
|
24
|
-
readonly openClient: (config:
|
|
24
|
+
readonly openClient: (config: PartialConfig) => Engine;
|
|
25
25
|
/** The arguments after the verb's name, as written. */
|
|
26
26
|
readonly args: readonly string[];
|
|
27
27
|
/** The last value one of the verb's own options was given, undefined where none was. */
|
package/src/verbs/version.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { CONTRACT_VERSION, EXIT_CLEAN } from "../contract/contract.generated.ts";
|
|
2
2
|
import { type Verb } from "./verb.ts";
|
|
3
|
+
import { PRODUCT } from "../product.ts";
|
|
3
4
|
|
|
4
5
|
/** Writes the version the host reports and the Contract version this analyzer implements. */
|
|
5
6
|
export const versionVerb: Verb = ({ out, host }) => {
|
|
6
|
-
out.write(
|
|
7
|
+
out.write(`${PRODUCT} ${host.analyzerVersion()}\ncontract ${CONTRACT_VERSION}\n`);
|
|
7
8
|
return EXIT_CLEAN;
|
|
8
9
|
};
|
package/src/workflow-steps.ts
CHANGED
|
@@ -6,9 +6,10 @@
|
|
|
6
6
|
|
|
7
7
|
import type { SourceFile } from "@typescript/native/ast";
|
|
8
8
|
import type { Host } from "./host.ts";
|
|
9
|
-
import { scriptTokens } from "./manifest.ts";
|
|
10
9
|
import { joinPath, relativePath, resolvePath } from "./paths.ts";
|
|
11
10
|
import type { SourceFiles } from "./source-files.ts";
|
|
11
|
+
import { filesIn } from "./fs/tree.ts";
|
|
12
|
+
import { commandsOf, scriptTokens, unquoted, withoutComment } from "./text/shell.ts";
|
|
12
13
|
|
|
13
14
|
/** One file a workflow step names. */
|
|
14
15
|
interface WorkflowEntry {
|
|
@@ -36,30 +37,16 @@ const HOOK_MANAGERS: readonly (readonly [string, RegExp])[] = [
|
|
|
36
37
|
/** The names GNU make reads a makefile under, in the order it tries them. */
|
|
37
38
|
const MAKEFILES = ["GNUmakefile", "makefile", "Makefile"] as const;
|
|
38
39
|
|
|
39
|
-
/** The operators that end one shell command of a recipe line. */
|
|
40
|
-
const COMMAND_SEPARATORS = /&&|\|\||;|\|/u;
|
|
41
|
-
|
|
42
40
|
const BLOCK_SCALAR = /^[|>][+-]?$/u;
|
|
43
41
|
|
|
44
|
-
const QUOTED = /^(["'])(.*)\1$/su;
|
|
45
|
-
|
|
46
42
|
/** A token holding one of these is computed or a pattern, so it names no file. */
|
|
47
43
|
const COMPUTED = /[${*?[]/u;
|
|
48
44
|
|
|
49
45
|
const INDENT = /^\s*/u;
|
|
50
46
|
|
|
51
|
-
/** The files directly in one directory whose name `accepts`, in name order. */
|
|
52
|
-
function
|
|
53
|
-
|
|
54
|
-
try {
|
|
55
|
-
entries = host.readDirectory(dir);
|
|
56
|
-
} catch {
|
|
57
|
-
return [];
|
|
58
|
-
}
|
|
59
|
-
return entries
|
|
60
|
-
.filter((entry) => !entry.directory && accepts(entry.name))
|
|
61
|
-
.map((entry) => joinPath(dir, entry.name))
|
|
62
|
-
.sort();
|
|
47
|
+
/** The files directly in one directory whose name `accepts`, by path in name order. */
|
|
48
|
+
function pathsIn(host: Host, dir: string, accepts: (name: string) => boolean): readonly string[] {
|
|
49
|
+
return filesIn(host, dir, accepts).map((name) => joinPath(dir, name));
|
|
63
50
|
}
|
|
64
51
|
|
|
65
52
|
function readText(host: Host, path: string): string | undefined {
|
|
@@ -80,7 +67,7 @@ export function runCommands(text: string, key: RegExp = RUN_LINE): readonly stri
|
|
|
80
67
|
continue;
|
|
81
68
|
}
|
|
82
69
|
const column = (match[1] ?? "").length;
|
|
83
|
-
const value = (match[2] ?? "").trim()
|
|
70
|
+
const value = unquoted((match[2] ?? "").trim());
|
|
84
71
|
if (!BLOCK_SCALAR.test(value)) {
|
|
85
72
|
found.push(value);
|
|
86
73
|
continue;
|
|
@@ -113,7 +100,7 @@ function recipeLines(host: Host, root: string): readonly (readonly string[])[] {
|
|
|
113
100
|
.replace(/\\\r?\n/gu, " ")
|
|
114
101
|
.split(/\r?\n/u)
|
|
115
102
|
.filter((line) => line.startsWith("\t"))
|
|
116
|
-
.map((line) => withoutComment(line.slice(1).replace(/^[@+-]+/u, ""))
|
|
103
|
+
.map((line) => commandsOf(withoutComment(line.slice(1).replace(/^[@+-]+/u, ""))));
|
|
117
104
|
}
|
|
118
105
|
|
|
119
106
|
/**
|
|
@@ -140,28 +127,6 @@ function recipeTokens(host: Host, root: string): readonly { token: string; dir:
|
|
|
140
127
|
return found;
|
|
141
128
|
}
|
|
142
129
|
|
|
143
|
-
/** One shell line up to its comment: a `#` that starts a word outside quotes. */
|
|
144
|
-
function withoutComment(line: string): string {
|
|
145
|
-
let quote = "";
|
|
146
|
-
for (let at = 0; at < line.length; at += 1) {
|
|
147
|
-
const char = line[at];
|
|
148
|
-
if (quote !== "") {
|
|
149
|
-
if (char === quote) {
|
|
150
|
-
quote = "";
|
|
151
|
-
} else if (char === "\\" && quote === '"') {
|
|
152
|
-
at += 1;
|
|
153
|
-
}
|
|
154
|
-
} else if (char === "'" || char === '"') {
|
|
155
|
-
quote = char;
|
|
156
|
-
} else if (char === "\\") {
|
|
157
|
-
at += 1;
|
|
158
|
-
} else if (char === "#" && (at === 0 || /\s/u.test(line[at - 1] ?? ""))) {
|
|
159
|
-
return line.slice(0, at);
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
return line;
|
|
163
|
-
}
|
|
164
|
-
|
|
165
130
|
/**
|
|
166
131
|
* Every command the target root's workflow steps, git hooks and makefile run: the `run:`
|
|
167
132
|
* steps of the workflow files in `.github/workflows`, every line of each file in `.husky`
|
|
@@ -185,11 +150,11 @@ export function workflowTokens(host: Host, targetRoot: string): readonly string[
|
|
|
185
150
|
/** The commands of the workflow steps, git hooks and hook managers at one root. */
|
|
186
151
|
function stepCommands(host: Host, root: string): readonly string[] {
|
|
187
152
|
const commands: string[] = [];
|
|
188
|
-
for (const path of
|
|
153
|
+
for (const path of pathsIn(host, joinPath(root, WORKFLOWS), (name) => /\.ya?ml$/u.test(name))) {
|
|
189
154
|
commands.push(...runCommands(readText(host, path) ?? ""));
|
|
190
155
|
}
|
|
191
156
|
for (const dir of HOOK_DIRS) {
|
|
192
|
-
for (const path of
|
|
157
|
+
for (const path of pathsIn(host, joinPath(root, dir), () => true)) {
|
|
193
158
|
commands.push(...(readText(host, path) ?? "").split(/\r?\n/u).map(withoutComment));
|
|
194
159
|
}
|
|
195
160
|
}
|
|
@@ -12,6 +12,7 @@ import { readManifest, type Manifest, type ManifestEntry } from "./manifest.ts";
|
|
|
12
12
|
import { joinPath, relativePath } from "./paths.ts";
|
|
13
13
|
import type { WorkspaceMember } from "./workspace.ts";
|
|
14
14
|
import type { WorkspaceResolver } from "./workspace-resolution.ts";
|
|
15
|
+
import { MANIFEST } from "./fs/manifest.ts";
|
|
15
16
|
|
|
16
17
|
/** The one character a subpath pattern of a manifest's `exports` holds. */
|
|
17
18
|
const SUBPATH_WILDCARD = "*";
|
|
@@ -84,8 +85,7 @@ export function memberEntries(
|
|
|
84
85
|
}
|
|
85
86
|
const manifest = readManifest(host, member.dir);
|
|
86
87
|
const where =
|
|
87
|
-
relativePath(targetRoot, joinPath(member.dir,
|
|
88
|
-
joinPath(member.dir, "package.json");
|
|
88
|
+
relativePath(targetRoot, joinPath(member.dir, MANIFEST)) ?? joinPath(member.dir, MANIFEST);
|
|
89
89
|
const kept = manifest.entries.flatMap((entry): ManifestEntry[] => {
|
|
90
90
|
const exported = entry.member.startsWith("exports");
|
|
91
91
|
if (member.private && exported) {
|
|
@@ -21,6 +21,8 @@ import type { Host } from "./host.ts";
|
|
|
21
21
|
import { dirnamePath, resolvePath } from "./paths.ts";
|
|
22
22
|
import type { Engine } from "./session.ts";
|
|
23
23
|
import type { WorkspaceResolver } from "./workspace-resolution.ts";
|
|
24
|
+
import { bytewise } from "./text/compare.ts";
|
|
25
|
+
import { PRODUCT } from "./product.ts";
|
|
24
26
|
|
|
25
27
|
/** One member import no member source answers for. */
|
|
26
28
|
interface UnresolvedImport {
|
|
@@ -129,7 +131,7 @@ function unstatable(reading: Reading): string | undefined {
|
|
|
129
131
|
export function writtenPath(host: Host, configFile: string): string {
|
|
130
132
|
const stem = configFile.replace(/\.json$/u, "");
|
|
131
133
|
for (let counter = 0; ; counter += 1) {
|
|
132
|
-
const path = `${stem}
|
|
134
|
+
const path = `${stem}.${PRODUCT}${counter === 0 ? "" : `-${String(counter)}`}.json`;
|
|
133
135
|
if (host.kindOf(path) === "absent") {
|
|
134
136
|
return path;
|
|
135
137
|
}
|
|
@@ -181,10 +183,10 @@ function configurationOf(
|
|
|
181
183
|
...(exclude === undefined ? {} : { exclude }),
|
|
182
184
|
});
|
|
183
185
|
const file = writtenPath(host, reading.configFile);
|
|
184
|
-
const expected = [...reading.parsed.fileNames].sort().join("\u0000");
|
|
186
|
+
const expected = [...reading.parsed.fileNames].sort(bytewise).join("\u0000");
|
|
185
187
|
const listed = (value: Record<string, unknown>): string | undefined => {
|
|
186
188
|
try {
|
|
187
|
-
return [...engine.parseConfigJson(value, file).fileNames].sort().join("\u0000");
|
|
189
|
+
return [...engine.parseConfigJson(value, file).fileNames].sort(bytewise).join("\u0000");
|
|
188
190
|
} catch {
|
|
189
191
|
return undefined;
|
|
190
192
|
}
|
|
@@ -9,9 +9,18 @@
|
|
|
9
9
|
import { emittedFrom } from "./emit-map.ts";
|
|
10
10
|
import { gitIgnored } from "./git-ignore.ts";
|
|
11
11
|
import type { Host } from "./host.ts";
|
|
12
|
-
import {
|
|
12
|
+
import {
|
|
13
|
+
ancestors,
|
|
14
|
+
dirnamePath,
|
|
15
|
+
isAbsolutePath,
|
|
16
|
+
joinPath,
|
|
17
|
+
relativePath,
|
|
18
|
+
resolvePath,
|
|
19
|
+
} from "./paths.ts";
|
|
13
20
|
import { satisfies } from "./version-range.ts";
|
|
14
21
|
import type { Workspace, WorkspaceMember } from "./workspace.ts";
|
|
22
|
+
import { isRecord } from "./json-document.ts";
|
|
23
|
+
import { INSTALLED_PACKAGES, MANIFEST, manifestAt } from "./fs/manifest.ts";
|
|
15
24
|
|
|
16
25
|
/** What the compiler's own resolution answered for one import. */
|
|
17
26
|
export interface DefaultResolution {
|
|
@@ -47,10 +56,6 @@ const DEPENDENCY_MEMBERS = [
|
|
|
47
56
|
const SOURCE = /(?<!\.d)\.(?:[mc]?ts|tsx)$/u;
|
|
48
57
|
const OUTPUT = /(?:\.d\.[mc]?ts|\.[mc]?js|\.jsx)$/u;
|
|
49
58
|
|
|
50
|
-
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
51
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
52
|
-
}
|
|
53
|
-
|
|
54
59
|
/** The package name a bare specifier names, or `undefined` for any other specifier. */
|
|
55
60
|
export function packageNameOf(specifier: string): string | undefined {
|
|
56
61
|
if (
|
|
@@ -216,7 +221,7 @@ export function workspaceResolver(host: Host, workspace: Workspace): WorkspaceRe
|
|
|
216
221
|
if (links.has(key)) {
|
|
217
222
|
return links.get(key);
|
|
218
223
|
}
|
|
219
|
-
const candidate = joinPath(dir,
|
|
224
|
+
const candidate = joinPath(dir, INSTALLED_PACKAGES, name);
|
|
220
225
|
let found: string | undefined;
|
|
221
226
|
if (host.kindOf(candidate) === "absent") {
|
|
222
227
|
const up = dirnamePath(dir);
|
|
@@ -245,15 +250,14 @@ export function workspaceResolver(host: Host, workspace: Workspace): WorkspaceRe
|
|
|
245
250
|
if (manifests.has(dir)) {
|
|
246
251
|
return manifests.get(dir);
|
|
247
252
|
}
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
}
|
|
253
|
+
const manifest = manifestAt(host, dir);
|
|
254
|
+
const up = dirnamePath(dir);
|
|
255
|
+
const found =
|
|
256
|
+
manifest !== undefined
|
|
257
|
+
? { dir, manifest }
|
|
258
|
+
: up === dir || root === undefined || !inside(root, up)
|
|
259
|
+
? undefined
|
|
260
|
+
: nearestManifest(up);
|
|
257
261
|
manifests.set(dir, found);
|
|
258
262
|
return found;
|
|
259
263
|
};
|
|
@@ -265,7 +269,7 @@ export function workspaceResolver(host: Host, workspace: Workspace): WorkspaceRe
|
|
|
265
269
|
const member = candidates.find((one) => one.dir === linked);
|
|
266
270
|
return member === undefined ? { kind: "none" } : { kind: "member", member };
|
|
267
271
|
}
|
|
268
|
-
const scope = workspace.memberOf(joinPath(fromDir,
|
|
272
|
+
const scope = workspace.memberOf(joinPath(fromDir, MANIFEST));
|
|
269
273
|
if (scope?.name === name) {
|
|
270
274
|
return { kind: "member", member: scope };
|
|
271
275
|
}
|
|
@@ -300,7 +304,7 @@ export function workspaceResolver(host: Host, workspace: Workspace): WorkspaceRe
|
|
|
300
304
|
* directory however its configuration writes it.
|
|
301
305
|
*/
|
|
302
306
|
const mirrored = (member: WorkspaceMember, path: string): string | undefined => {
|
|
303
|
-
for (
|
|
307
|
+
for (const dir of ancestors(dirnamePath(path), member.dir).filter((at) => at !== member.dir)) {
|
|
304
308
|
const found = emittedFrom(path, { outDir: dir, sourceDirs: [member.dir] }).find(
|
|
305
309
|
(candidate) =>
|
|
306
310
|
SOURCE.test(candidate) && !ignored(candidate) && host.kindOf(candidate) === "file",
|