@cplieger/deadset-ts 7.2.0-dev.3 → 8.0.0-dev.1
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 +7 -4
- package/dist/src/analysis.js +116 -16
- package/dist/src/augmentations.js +21 -8
- package/dist/src/automatic-types.js +189 -0
- package/dist/src/base-url.js +18 -0
- package/dist/src/component-files.js +96 -31
- package/dist/src/components.js +7 -6
- package/dist/src/config.js +1 -0
- package/dist/src/configuration-files.js +54 -15
- package/dist/src/configured-declarations.js +24 -2
- package/dist/src/conformance-record.js +2 -2
- package/dist/src/contextual-uses.js +26 -26
- package/dist/src/convention-rows.js +75 -0
- package/dist/src/conventions.js +101 -5
- package/dist/src/decorator.js +21 -18
- package/dist/src/dependencies.js +185 -30
- package/dist/src/destructuring.js +77 -2
- package/dist/src/discover.js +188 -85
- package/dist/src/edges.js +1 -1
- package/dist/src/entry-point-gaps.js +7 -10
- package/dist/src/entry-points.js +112 -11
- package/dist/src/findings/completion.js +116 -21
- package/dist/src/findings/dependencies-and-module-machinery.js +71 -25
- package/dist/src/findings/emitters.js +1 -1
- package/dist/src/findings/intra-function.js +44 -13
- package/dist/src/findings/non-code-artifacts.js +66 -33
- package/dist/src/findings/unused-declarations.js +21 -1
- package/dist/src/findings/visibility-narrowing.js +22 -3
- package/dist/src/generated-file.js +11 -0
- package/dist/src/git-ignore.js +86 -0
- package/dist/src/graph.js +4 -1
- package/dist/src/import-bindings.js +97 -0
- package/dist/src/indexed-access.js +130 -28
- package/dist/src/interface-satisfaction.js +30 -12
- package/dist/src/intra-function-parts.js +313 -50
- package/dist/src/inventory.js +63 -8
- package/dist/src/json-document.js +5 -5
- package/dist/src/kinds.js +13 -13
- package/dist/src/matrix.js +18 -9
- package/dist/src/mdx-entries.js +197 -0
- package/dist/src/module-declarations.js +9 -13
- package/dist/src/namespace-objects.js +28 -18
- package/dist/src/overrides.js +45 -9
- package/dist/src/paths.js +9 -0
- package/dist/src/print.js +1 -0
- package/dist/src/ref.js +10 -0
- package/dist/src/references.js +29 -13
- package/dist/src/reflective-lookup.js +8 -6
- package/dist/src/report.js +4 -2
- package/dist/src/reporters/template.js +22 -13
- package/dist/src/resolve.js +2 -0
- package/dist/src/roots.js +110 -38
- package/dist/src/schema.js +1 -0
- package/dist/src/serialization-contract.js +97 -20
- package/dist/src/session.js +26 -1
- package/dist/src/source-files.js +39 -14
- package/dist/src/spelled-names.js +58 -0
- package/dist/src/suppress-file.js +12 -4
- package/dist/src/suppress-inline.js +21 -3
- package/dist/src/suppress.js +7 -5
- package/dist/src/sweep.js +27 -10
- package/dist/src/symbol-keys.js +103 -2
- package/dist/src/template-field.js +9 -6
- package/dist/src/test-support.js +121 -17
- package/dist/src/type-errors.js +116 -35
- package/dist/src/type-parameter-reads.js +172 -0
- package/dist/src/value-reach.js +17 -5
- package/dist/src/verbs/analyze.js +19 -1
- package/dist/src/verbs/explain.js +4 -2
- package/dist/src/version-range.js +10 -5
- package/dist/src/version.js +1 -1
- package/dist/src/workflow-steps.js +119 -22
- package/dist/src/workspace-programs.js +7 -3
- package/dist/src/workspace-resolution.js +72 -13
- package/dist/src/workspace.js +1 -1
- package/jsr.json +1 -1
- package/package.json +2 -2
- package/src/analysis.ts +175 -16
- package/src/augmentations.ts +21 -10
- package/src/automatic-types.ts +246 -0
- package/src/base-url.ts +24 -0
- package/src/component-files.ts +116 -31
- package/src/components.ts +7 -6
- package/src/config.ts +3 -0
- package/src/configuration-files.ts +80 -18
- package/src/configured-declarations.ts +33 -4
- package/src/conformance-record.ts +2 -2
- package/src/contextual-uses.ts +27 -27
- package/src/convention-rows.ts +103 -0
- package/src/conventions.ts +121 -7
- package/src/decorator.ts +28 -18
- package/src/dependencies.ts +249 -43
- package/src/destructuring.ts +93 -2
- package/src/discover.ts +234 -86
- package/src/edges.ts +1 -1
- package/src/entry-point-gaps.ts +8 -11
- package/src/entry-points.ts +133 -11
- package/src/finding.ts +10 -0
- package/src/findings/completion.ts +147 -24
- package/src/findings/dependencies-and-module-machinery.ts +90 -28
- package/src/findings/emitters.ts +1 -1
- package/src/findings/intra-function.ts +56 -14
- package/src/findings/non-code-artifacts.ts +81 -41
- package/src/findings/unused-declarations.ts +25 -1
- package/src/findings/visibility-narrowing.ts +32 -3
- package/src/generated-file.ts +16 -0
- package/src/git-ignore.ts +98 -0
- package/src/graph.ts +6 -0
- package/src/host.ts +5 -9
- package/src/import-bindings.ts +128 -0
- package/src/indexed-access.ts +152 -30
- package/src/interface-satisfaction.ts +33 -13
- package/src/intra-function-parts.ts +415 -64
- package/src/inventory.ts +83 -7
- package/src/json-document.ts +7 -7
- package/src/kinds.ts +13 -13
- package/src/matrix.ts +32 -14
- package/src/mdx-entries.ts +241 -0
- package/src/module-declarations.ts +11 -13
- package/src/namespace-objects.ts +34 -21
- package/src/overrides.ts +56 -9
- package/src/paths.ts +10 -0
- package/src/print.ts +1 -0
- package/src/ref.ts +12 -0
- package/src/references.ts +38 -17
- package/src/reflective-lookup.ts +9 -7
- package/src/report.ts +16 -3
- package/src/reporters/template.ts +21 -13
- package/src/resolve.ts +3 -0
- package/src/roots.ts +163 -45
- package/src/schema.ts +1 -0
- package/src/serialization-contract.ts +108 -17
- package/src/session.ts +29 -1
- package/src/source-files.ts +66 -16
- package/src/spelled-names.ts +102 -0
- package/src/suppress-file.ts +14 -3
- package/src/suppress-inline.ts +21 -2
- package/src/suppress.ts +9 -5
- package/src/sweep.ts +32 -11
- package/src/symbol-keys.ts +129 -2
- package/src/template-field.ts +9 -6
- package/src/test-support.ts +149 -16
- package/src/type-errors.ts +132 -41
- package/src/type-parameter-reads.ts +227 -0
- package/src/value-reach.ts +19 -6
- package/src/verbs/analyze.ts +28 -1
- package/src/verbs/explain.ts +5 -2
- package/src/version-range.ts +10 -5
- package/src/version.ts +1 -1
- package/src/workflow-steps.ts +127 -22
- package/src/workspace-programs.ts +7 -3
- package/src/workspace-resolution.ts +86 -14
- package/src/workspace.ts +1 -1
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* The serialization-contract class: the data members and `toJSON` of every type a value's
|
|
3
3
|
* static type reaches, where the value reaches `JSON.stringify` or a call `ts.serializers`
|
|
4
|
-
* names, and `toString` too where it reaches an
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
4
|
+
* names, and `toString` too where it reaches an erased parameter of a function outside the
|
|
5
|
+
* analysis, or an erased property of a type the program does not declare. A function of
|
|
6
|
+
* the project handing such a parameter to a destination is one, to a fixpoint, and a
|
|
7
|
+
* function invoked through `call`, `apply` or `bind` is the callee.
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
import {
|
|
@@ -45,6 +45,8 @@ import {
|
|
|
45
45
|
SignatureKind,
|
|
46
46
|
SymbolFlags,
|
|
47
47
|
TypeFlags,
|
|
48
|
+
type NodeHandle,
|
|
49
|
+
type Signature,
|
|
48
50
|
type Symbol as TSSymbol,
|
|
49
51
|
type Type,
|
|
50
52
|
} from "@typescript/native/sync";
|
|
@@ -64,7 +66,7 @@ import { renderPosition } from "./position.ts";
|
|
|
64
66
|
import { must } from "./query.ts";
|
|
65
67
|
import type { ProjectView } from "./session.ts";
|
|
66
68
|
import { erasedStores, type ErasedStores } from "./erased-stores.ts";
|
|
67
|
-
import { checkedTypes, valueReach, type Reached } from "./value-reach.ts";
|
|
69
|
+
import { checkedTypes, CONVERSION_MEMBERS, valueReach, type Reached } from "./value-reach.ts";
|
|
68
70
|
|
|
69
71
|
/** The serializer every run reads, whatever the configuration names. */
|
|
70
72
|
const JSON_STRINGIFY: DeclarationEntry = { shape: "global", global: "JSON.stringify" };
|
|
@@ -78,6 +80,9 @@ const SERIALIZED_CONVERSION = "toJSON";
|
|
|
78
80
|
/** The parameter types that keep nothing of the value they are given. */
|
|
79
81
|
const ERASED = TypeFlags.Any | TypeFlags.Unknown;
|
|
80
82
|
|
|
83
|
+
/** How deep a callee's result is searched for a type parameter: `Promise<Box<T>>` is two levels. */
|
|
84
|
+
const MENTION_DEPTH = 3;
|
|
85
|
+
|
|
81
86
|
/**
|
|
82
87
|
* What a destination holds back of a class: its properties, or its properties and its
|
|
83
88
|
* two conversion methods. The second holds back everything the first does.
|
|
@@ -484,14 +489,19 @@ function destinations<Brand>(
|
|
|
484
489
|
own.has(path) || input.consumers.some((root) => path === root || path.startsWith(`${root}/`));
|
|
485
490
|
const implemented = implementedOwners(project, inAnalysis);
|
|
486
491
|
/**
|
|
487
|
-
* Whether every
|
|
488
|
-
* declaration has no body, so the code that runs is
|
|
489
|
-
* member of an interface or an object type a class
|
|
490
|
-
* analysis implements; `settled` false skips that
|
|
492
|
+
* Whether every one of `declarations`, all of the callee's by default, is outside the
|
|
493
|
+
* analysis or ambient. An ambient declaration has no body, so the code that runs is
|
|
494
|
+
* outside the analysis, unless it is a member of an interface or an object type a class
|
|
495
|
+
* or a typed object literal of the analysis implements; `settled` false skips that
|
|
496
|
+
* question, which asks the checker.
|
|
491
497
|
*/
|
|
492
|
-
const outside = (
|
|
493
|
-
target
|
|
494
|
-
|
|
498
|
+
const outside = (
|
|
499
|
+
target: TSSymbol,
|
|
500
|
+
settled: boolean,
|
|
501
|
+
declarations: readonly NodeHandle[] = target.declarations,
|
|
502
|
+
): boolean =>
|
|
503
|
+
declarations.length > 0 &&
|
|
504
|
+
declarations.every((handle) => {
|
|
495
505
|
if (!inAnalysis(handle.path)) {
|
|
496
506
|
return true;
|
|
497
507
|
}
|
|
@@ -505,6 +515,13 @@ function destinations<Brand>(
|
|
|
505
515
|
const owner = must(queries.parentOf(target));
|
|
506
516
|
return owner === undefined || !implemented().has(owner.id);
|
|
507
517
|
});
|
|
518
|
+
/**
|
|
519
|
+
* Whether some one declaration of a callee is outside the analysis or ambient, without
|
|
520
|
+
* asking the checker. A project's declaration merged into a library's symbol leaves the
|
|
521
|
+
* library's beside it, so a call can still resolve to the library's.
|
|
522
|
+
*/
|
|
523
|
+
const mayBeOutside = (target: TSSymbol): boolean =>
|
|
524
|
+
target.declarations.some((handle) => outside(target, false, [handle]));
|
|
508
525
|
const invocation = (call: CallExpression): Invocation => {
|
|
509
526
|
const form = forms.get(call);
|
|
510
527
|
const method = form === undefined ? undefined : callees.get(form.method);
|
|
@@ -513,7 +530,7 @@ function destinations<Brand>(
|
|
|
513
530
|
if (
|
|
514
531
|
form !== undefined &&
|
|
515
532
|
method !== undefined &&
|
|
516
|
-
|
|
533
|
+
mayBeOutside(method) &&
|
|
517
534
|
FUNCTION_INTERFACES.has(must(project.queries.parentOf(method))?.name ?? "")
|
|
518
535
|
) {
|
|
519
536
|
return form.invocation;
|
|
@@ -524,6 +541,56 @@ function destinations<Brand>(
|
|
|
524
541
|
const name = calleeName(invocation(call).callee);
|
|
525
542
|
return name === undefined ? undefined : callees.get(name);
|
|
526
543
|
};
|
|
544
|
+
const signatures = new Map<CallExpression, Signature | undefined>();
|
|
545
|
+
const resolvedOf = (call: CallExpression): Signature | undefined => {
|
|
546
|
+
if (!signatures.has(call)) {
|
|
547
|
+
signatures.set(call, must(queries.resolvedSignature(call)));
|
|
548
|
+
}
|
|
549
|
+
return signatures.get(call);
|
|
550
|
+
};
|
|
551
|
+
/**
|
|
552
|
+
* The declarations of a callee that the declaration of a direct call's resolved
|
|
553
|
+
* signature lies in, or all of them where it lies in none: a declaration merged into
|
|
554
|
+
* the callee's symbol that the call does not resolve to does not decide where it runs.
|
|
555
|
+
*/
|
|
556
|
+
const calledDeclarations = (call: CallExpression, target: TSSymbol): readonly NodeHandle[] => {
|
|
557
|
+
const declaration = invocation(call).indirect ? undefined : resolvedOf(call)?.declaration;
|
|
558
|
+
const signature =
|
|
559
|
+
declaration === undefined ? undefined : project.declarationAt(declaration)?.node;
|
|
560
|
+
if (declaration === undefined || signature === undefined) {
|
|
561
|
+
return target.declarations;
|
|
562
|
+
}
|
|
563
|
+
const containing = target.declarations.filter((handle) => {
|
|
564
|
+
const node =
|
|
565
|
+
handle.path === declaration.path ? project.declarationAt(handle)?.node : undefined;
|
|
566
|
+
return node !== undefined && node.pos <= signature.pos && signature.end <= node.end;
|
|
567
|
+
});
|
|
568
|
+
return containing.length === 0 ? target.declarations : containing;
|
|
569
|
+
};
|
|
570
|
+
/** Whether a type names a type parameter, through constituents, type arguments and call signatures. */
|
|
571
|
+
const mentions = (type: Type, parameter: Type, depth: number): boolean => {
|
|
572
|
+
// A conditional type an alias names, `Awaited<T>` among them, carries its arguments
|
|
573
|
+
// only on the alias the client's type object reports.
|
|
574
|
+
const aliased = (type as { readonly aliasTypeArguments?: readonly number[] })
|
|
575
|
+
.aliasTypeArguments;
|
|
576
|
+
if (type.id === parameter.id || aliased?.includes(parameter.id) === true) {
|
|
577
|
+
return true;
|
|
578
|
+
}
|
|
579
|
+
if (depth >= MENTION_DEPTH) {
|
|
580
|
+
return false;
|
|
581
|
+
}
|
|
582
|
+
const inner = [
|
|
583
|
+
...(type.isUnionType() || type.isIntersectionType() ? must(queries.constituents(type)) : []),
|
|
584
|
+
...must(queries.typeArguments(type)),
|
|
585
|
+
...must(queries.signaturesOf(type, SignatureKind.Call)).flatMap((signature) => [
|
|
586
|
+
...signature.parameters.map((_parameter, at) =>
|
|
587
|
+
must(queries.typeAtPosition(signature, at)),
|
|
588
|
+
),
|
|
589
|
+
...[must(queries.returnTypeOf(signature))].filter((one) => one !== undefined),
|
|
590
|
+
]),
|
|
591
|
+
];
|
|
592
|
+
return inner.some((one) => mentions(one, parameter, depth + 1));
|
|
593
|
+
};
|
|
527
594
|
const erasedArguments = new Map<CallExpression, ReadonlySet<number>>();
|
|
528
595
|
const erasedAt = (call: CallExpression, argument: number): boolean => {
|
|
529
596
|
let indexes = erasedArguments.get(call);
|
|
@@ -536,9 +603,33 @@ function destinations<Brand>(
|
|
|
536
603
|
? type === undefined
|
|
537
604
|
? []
|
|
538
605
|
: must(queries.signaturesOf(type, SignatureKind.Call))
|
|
539
|
-
: [
|
|
606
|
+
: [resolvedOf(call)].filter((one) => one !== undefined);
|
|
607
|
+
// A type parameter of the callee that nothing constrains and its result does not name
|
|
608
|
+
// keeps nothing of the value either: the instantiated signature names the value's own
|
|
609
|
+
// type there instead, and a result that names it hands the value back.
|
|
610
|
+
const generic = invoked.indirect ? undefined : resolvedOf(call);
|
|
611
|
+
const declared = generic === undefined ? undefined : must(queries.genericOf(generic));
|
|
612
|
+
const unconstrained = (index: number): boolean => {
|
|
613
|
+
const parameter =
|
|
614
|
+
declared === undefined ? undefined : must(queries.parameterType(declared, index));
|
|
615
|
+
const parts =
|
|
616
|
+
parameter === undefined
|
|
617
|
+
? []
|
|
618
|
+
: parameter.isUnionType()
|
|
619
|
+
? must(queries.constituents(parameter))
|
|
620
|
+
: [parameter];
|
|
621
|
+
const result = declared === undefined ? undefined : must(queries.returnTypeOf(declared));
|
|
622
|
+
return parts.some((part) => {
|
|
623
|
+
if (!part.isTypeParameter() || (result !== undefined && mentions(result, part, 0))) {
|
|
624
|
+
return false;
|
|
625
|
+
}
|
|
626
|
+
const constraint = must(queries.constraintOf(part));
|
|
627
|
+
return constraint === undefined || (constraint.flags & ERASED) !== 0;
|
|
628
|
+
});
|
|
629
|
+
};
|
|
540
630
|
indexes = new Set(
|
|
541
631
|
invoked.arguments.flatMap((_unused, index) =>
|
|
632
|
+
unconstrained(index) ||
|
|
542
633
|
signatures.some((signature) => {
|
|
543
634
|
const parameter = must(queries.parameterType(signature, index));
|
|
544
635
|
return parameter !== undefined && (parameter.flags & ERASED) !== 0;
|
|
@@ -563,7 +654,7 @@ function destinations<Brand>(
|
|
|
563
654
|
if (entry !== undefined) {
|
|
564
655
|
return { reach: "properties", callee: spelledEntry(entry.entry, held) };
|
|
565
656
|
}
|
|
566
|
-
if (outside(target, true)) {
|
|
657
|
+
if (outside(target, true, calledDeclarations(call, target))) {
|
|
567
658
|
return erasedAt(call, argument)
|
|
568
659
|
? {
|
|
569
660
|
reach: "conversions",
|
|
@@ -602,7 +693,7 @@ function destinations<Brand>(
|
|
|
602
693
|
if (target === undefined) {
|
|
603
694
|
return false;
|
|
604
695
|
}
|
|
605
|
-
if (named.some((one) => names(one, target, chains)) ||
|
|
696
|
+
if (named.some((one) => names(one, target, chains)) || mayBeOutside(target)) {
|
|
606
697
|
return true;
|
|
607
698
|
}
|
|
608
699
|
return chains
|
|
@@ -733,7 +824,7 @@ export function serializationContract<Brand>(input: DetectorInput<Brand>): reado
|
|
|
733
824
|
// the value left the analysis.
|
|
734
825
|
const holdsBack = (flags: SymbolFlags, name: string, to: Destination): boolean =>
|
|
735
826
|
(flags & SymbolFlags.Property) !== 0 ||
|
|
736
|
-
((flags &
|
|
827
|
+
((flags & CONVERSION_MEMBERS) !== 0 &&
|
|
737
828
|
(name === SERIALIZED_CONVERSION || (to.reach === "conversions" && CONVERSIONS.has(name))));
|
|
738
829
|
const found: Evidence[] = [];
|
|
739
830
|
for (const flow of flows) {
|
package/src/session.ts
CHANGED
|
@@ -129,6 +129,7 @@ export function openEngine(options: EngineOptions): Engine {
|
|
|
129
129
|
...(mapper === undefined ? {} : { runExternalCode: true }),
|
|
130
130
|
});
|
|
131
131
|
let closed = false;
|
|
132
|
+
const parsed = new Map<string, { readonly text: string; readonly file: SourceFile }>();
|
|
132
133
|
return {
|
|
133
134
|
parseConfigFile: (file) => api.parseConfigFile(file),
|
|
134
135
|
parseConfigJson: (json, configFile) =>
|
|
@@ -160,8 +161,14 @@ export function openEngine(options: EngineOptions): Engine {
|
|
|
160
161
|
moduleResolutions: { fallback: "resolve", entries: [...entries] },
|
|
161
162
|
}),
|
|
162
163
|
parseSourceFile: (fileName, text) => {
|
|
164
|
+
// Every project of a run reads the same files outside its program, so one parse serves all.
|
|
165
|
+
const known = parsed.get(fileName);
|
|
166
|
+
if (known?.text === text) {
|
|
167
|
+
return known.file;
|
|
168
|
+
}
|
|
163
169
|
const retained = api.createSourceFile(fileName, text);
|
|
164
170
|
try {
|
|
171
|
+
parsed.set(fileName, { text, file: retained.sourceFile });
|
|
165
172
|
return retained.sourceFile;
|
|
166
173
|
} finally {
|
|
167
174
|
retained.dispose();
|
|
@@ -419,6 +426,8 @@ export interface ProjectDiagnostics {
|
|
|
419
426
|
readonly syntactic: readonly Diagnostic[];
|
|
420
427
|
readonly semantic: readonly Diagnostic[];
|
|
421
428
|
readonly configParsing: readonly Diagnostic[];
|
|
429
|
+
/** The program's diagnostics placed in no file, each once, its `fileName` undefined. */
|
|
430
|
+
readonly unplaced: readonly Diagnostic[];
|
|
422
431
|
/** The line and column of one offset into one of the program's files, counted from one. */
|
|
423
432
|
readonly locate: (
|
|
424
433
|
fileName: string,
|
|
@@ -426,12 +435,31 @@ export interface ProjectDiagnostics {
|
|
|
426
435
|
) => { line: number; column: number } | undefined;
|
|
427
436
|
}
|
|
428
437
|
|
|
429
|
-
/**
|
|
438
|
+
/**
|
|
439
|
+
* The program's diagnostics placed in no file. The per-file sets never carry one; the
|
|
440
|
+
* client answers its `fileName` as `null`, and the global set repeats the program set:
|
|
441
|
+
* https://github.com/microsoft/typescript-go/blob/89d5d5b2849a0db0957065889ca58536fa6d2e4a/internal/api/session.go#L3726-L3752
|
|
442
|
+
*/
|
|
443
|
+
function unplacedOf(program: Program): readonly Diagnostic[] {
|
|
444
|
+
const seen = new Set<string>();
|
|
445
|
+
return [...program.getGlobalDiagnostics(), ...program.getProgramDiagnostics()]
|
|
446
|
+
.filter((diagnostic) => diagnostic.fileName == null)
|
|
447
|
+
.filter((diagnostic) => {
|
|
448
|
+
const key = `${String(diagnostic.code)}\u0000${diagnostic.text}`;
|
|
449
|
+
const fresh = !seen.has(key);
|
|
450
|
+
seen.add(key);
|
|
451
|
+
return fresh;
|
|
452
|
+
})
|
|
453
|
+
.map((diagnostic) => ({ ...diagnostic, fileName: undefined }));
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
/** The diagnostic sets one project carries, read before any analysis. */
|
|
430
457
|
export function diagnosticsOf<Brand>(project: ProjectView<Brand>): ProjectDiagnostics {
|
|
431
458
|
return {
|
|
432
459
|
syntactic: project.program.getSyntacticDiagnostics(),
|
|
433
460
|
semantic: project.program.getSemanticDiagnostics(),
|
|
434
461
|
configParsing: project.program.getConfigFileParsingDiagnostics(),
|
|
462
|
+
unplaced: unplacedOf(project.program),
|
|
435
463
|
locate: (fileName, offset) => {
|
|
436
464
|
const file = project.program.getSourceFile(fileName);
|
|
437
465
|
if (file === undefined || offset < 0 || offset > file.text.length) {
|
package/src/source-files.ts
CHANGED
|
@@ -6,12 +6,18 @@
|
|
|
6
6
|
|
|
7
7
|
import type { SourceFile } from "@typescript/native/ast";
|
|
8
8
|
import { commonDirectory, emittedFrom } from "./emit-map.ts";
|
|
9
|
-
import { dirnamePath, relativePath, resolvePath } from "./paths.ts";
|
|
9
|
+
import { dirnamePath, joinPath, relativePath, resolvePath } from "./paths.ts";
|
|
10
10
|
import type { ProjectView } from "./session.ts";
|
|
11
11
|
|
|
12
12
|
/** The one character a subpath pattern of a manifest's `exports` holds. */
|
|
13
13
|
const SUBPATH_WILDCARD = "*";
|
|
14
14
|
|
|
15
|
+
/** The extension a source file's name drops when the project writes it out. */
|
|
16
|
+
const SOURCE_EXTENSION = /\.[cm]?[jt]sx?$/u;
|
|
17
|
+
|
|
18
|
+
/** The extensions the project may write a source file's output under. */
|
|
19
|
+
const OUTPUT_EXTENSIONS = [".js", ".mjs", ".cjs", ".jsx", ".d.ts", ".d.mts", ".d.cts"] as const;
|
|
20
|
+
|
|
15
21
|
/**
|
|
16
22
|
* Whether one path is what a subpath pattern stands for: the text before the
|
|
17
23
|
* wildcard, any run of characters including the solidus, then the text after it.
|
|
@@ -40,6 +46,47 @@ export interface SourceFiles {
|
|
|
40
46
|
named(path: string): readonly SourceFile[];
|
|
41
47
|
/** The directories the project writes its output below, absolute: its output and declaration directories. */
|
|
42
48
|
readonly outputDirs: readonly string[];
|
|
49
|
+
/** The output paths, absolute, the project writes one own file to, each of which {@link named} reads back to it. */
|
|
50
|
+
writtenAs(file: SourceFile): readonly string[];
|
|
51
|
+
/**
|
|
52
|
+
* The own files one absolute path names read as an output of the project written below
|
|
53
|
+
* `outputDir`: a manifest that ships from its build directory names its entries there.
|
|
54
|
+
*/
|
|
55
|
+
namedAsOutput(outputDir: string, path: string): readonly SourceFile[];
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** The first own file of the paths given that the project holds, or none. */
|
|
59
|
+
function heldAt(
|
|
60
|
+
byName: ReadonlyMap<string, SourceFile>,
|
|
61
|
+
paths: readonly string[],
|
|
62
|
+
): readonly SourceFile[] {
|
|
63
|
+
for (const candidate of paths) {
|
|
64
|
+
const file = byName.get(candidate);
|
|
65
|
+
if (file !== undefined) {
|
|
66
|
+
return [file];
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
return [];
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** Where one project writes its output, and the directory it writes it from, absolute. */
|
|
73
|
+
function layoutOf<Brand>(
|
|
74
|
+
project: ProjectView<Brand>,
|
|
75
|
+
fileNames: readonly string[],
|
|
76
|
+
): {
|
|
77
|
+
readonly outDir: string | undefined;
|
|
78
|
+
readonly declarationDir: string | undefined;
|
|
79
|
+
readonly sourceDir: string;
|
|
80
|
+
} {
|
|
81
|
+
const options = project.program.getCompilerOptions();
|
|
82
|
+
const configDir = dirnamePath(project.configFile);
|
|
83
|
+
const at = (option: string | undefined): string | undefined =>
|
|
84
|
+
option === undefined ? undefined : resolvePath(configDir, option);
|
|
85
|
+
return {
|
|
86
|
+
outDir: at(options.outDir),
|
|
87
|
+
declarationDir: at(options.declarationDir),
|
|
88
|
+
sourceDir: at(options.rootDir) ?? commonDirectory(fileNames),
|
|
89
|
+
};
|
|
43
90
|
}
|
|
44
91
|
|
|
45
92
|
/**
|
|
@@ -62,14 +109,7 @@ export function sourceFilesOf<Brand>(project: ProjectView<Brand>, targetRoot: st
|
|
|
62
109
|
}
|
|
63
110
|
}
|
|
64
111
|
|
|
65
|
-
const
|
|
66
|
-
const configDir = dirnamePath(project.configFile);
|
|
67
|
-
const at = (option: string | undefined): string | undefined =>
|
|
68
|
-
option === undefined ? undefined : resolvePath(configDir, option);
|
|
69
|
-
const outDir = at(options.outDir);
|
|
70
|
-
const declarationDir = at(options.declarationDir);
|
|
71
|
-
const sourceDir = at(options.rootDir) ?? commonDirectory([...byName.keys()]);
|
|
72
|
-
|
|
112
|
+
const { outDir, declarationDir, sourceDir } = layoutOf(project, [...byName.keys()]);
|
|
73
113
|
const layout = { outDir, declarationDir, sourceDirs: [sourceDir] };
|
|
74
114
|
|
|
75
115
|
/** The source paths one written path may stand for, the path itself first. */
|
|
@@ -79,15 +119,25 @@ export function sourceFilesOf<Brand>(project: ProjectView<Brand>, targetRoot: st
|
|
|
79
119
|
byPath,
|
|
80
120
|
byName,
|
|
81
121
|
outputDirs: [...new Set([outDir, declarationDir])].filter((dir) => dir !== undefined),
|
|
122
|
+
writtenAs: (file) => {
|
|
123
|
+
const below = relativePath(sourceDir, file.fileName);
|
|
124
|
+
if (below === undefined) {
|
|
125
|
+
return [];
|
|
126
|
+
}
|
|
127
|
+
const written = [outDir, declarationDir].flatMap((dir) =>
|
|
128
|
+
dir === undefined
|
|
129
|
+
? []
|
|
130
|
+
: OUTPUT_EXTENSIONS.map((extension) =>
|
|
131
|
+
joinPath(dir, below.replace(SOURCE_EXTENSION, extension)),
|
|
132
|
+
),
|
|
133
|
+
);
|
|
134
|
+
return [...new Set(written)].filter((path) => heldAt(byName, candidates(path))[0] === file);
|
|
135
|
+
},
|
|
136
|
+
namedAsOutput: (outputDir, path) =>
|
|
137
|
+
heldAt(byName, emittedFrom(path, { outDir: outputDir, sourceDirs: [sourceDir] })),
|
|
82
138
|
named: (path) => {
|
|
83
139
|
if (!path.includes(SUBPATH_WILDCARD)) {
|
|
84
|
-
|
|
85
|
-
const held = byName.get(candidate);
|
|
86
|
-
if (held !== undefined) {
|
|
87
|
-
return [held];
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
return [];
|
|
140
|
+
return heldAt(byName, candidates(path));
|
|
91
141
|
}
|
|
92
142
|
const patterns = candidates(path).filter((candidate) => candidate.includes(SUBPATH_WILDCARD));
|
|
93
143
|
return [...byName.values()].filter((file) =>
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import {
|
|
2
|
+
isBindingElement,
|
|
3
|
+
isBreakStatement,
|
|
4
|
+
isClassDeclaration,
|
|
5
|
+
isClassExpression,
|
|
6
|
+
isContinueStatement,
|
|
7
|
+
isEnumDeclaration,
|
|
8
|
+
isEnumMember,
|
|
9
|
+
isExportDeclaration,
|
|
10
|
+
isExportSpecifier,
|
|
11
|
+
isFunctionDeclaration,
|
|
12
|
+
isFunctionExpression,
|
|
13
|
+
isGetAccessorDeclaration,
|
|
14
|
+
isImportClause,
|
|
15
|
+
isImportEqualsDeclaration,
|
|
16
|
+
isImportSpecifier,
|
|
17
|
+
isInterfaceDeclaration,
|
|
18
|
+
isJsxAttribute,
|
|
19
|
+
isLabeledStatement,
|
|
20
|
+
isMetaProperty,
|
|
21
|
+
isMethodDeclaration,
|
|
22
|
+
isMethodSignatureDeclaration,
|
|
23
|
+
isModuleDeclaration,
|
|
24
|
+
isNamespaceExport,
|
|
25
|
+
isNamespaceExportDeclaration,
|
|
26
|
+
isNamespaceImport,
|
|
27
|
+
isParameterDeclaration,
|
|
28
|
+
isPropertyAccessExpression,
|
|
29
|
+
isPropertyAssignment,
|
|
30
|
+
isPropertyDeclaration,
|
|
31
|
+
isPropertySignatureDeclaration,
|
|
32
|
+
isQualifiedName,
|
|
33
|
+
isSetAccessorDeclaration,
|
|
34
|
+
isTypeAliasDeclaration,
|
|
35
|
+
isTypeParameterDeclaration,
|
|
36
|
+
isVariableDeclaration,
|
|
37
|
+
type Identifier,
|
|
38
|
+
} from "@typescript/native/ast";
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Whether one identifier spells a name without referring to a binding of it: a member's
|
|
42
|
+
* name after a dot, a property or member an object literal, a class, a type or an enum
|
|
43
|
+
* declares, the name a declaration binds, the property a binding element destructures,
|
|
44
|
+
* an import or export specifier's name in another module or under a rename, a JSX
|
|
45
|
+
* attribute's name, a statement label, or the name of a meta-property. A shorthand
|
|
46
|
+
* property and an unrenamed local export are no such spelling: each reads the binding.
|
|
47
|
+
*/
|
|
48
|
+
export function spellsNoBinding(node: Identifier): boolean {
|
|
49
|
+
const parent = node.parent;
|
|
50
|
+
if (isPropertyAccessExpression(parent) || isMetaProperty(parent)) {
|
|
51
|
+
return parent.name === node;
|
|
52
|
+
}
|
|
53
|
+
if (isQualifiedName(parent)) {
|
|
54
|
+
return parent.right === node;
|
|
55
|
+
}
|
|
56
|
+
if (isBindingElement(parent)) {
|
|
57
|
+
return parent.propertyName === node || parent.name === node;
|
|
58
|
+
}
|
|
59
|
+
if (isImportSpecifier(parent)) {
|
|
60
|
+
return true;
|
|
61
|
+
}
|
|
62
|
+
if (isExportSpecifier(parent)) {
|
|
63
|
+
return (
|
|
64
|
+
(isExportDeclaration(parent.parent.parent) &&
|
|
65
|
+
parent.parent.parent.moduleSpecifier !== undefined) ||
|
|
66
|
+
(parent.propertyName !== undefined && parent.name === node)
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
if (
|
|
70
|
+
isPropertyAssignment(parent) ||
|
|
71
|
+
isPropertyDeclaration(parent) ||
|
|
72
|
+
isMethodDeclaration(parent) ||
|
|
73
|
+
isPropertySignatureDeclaration(parent) ||
|
|
74
|
+
isMethodSignatureDeclaration(parent) ||
|
|
75
|
+
isGetAccessorDeclaration(parent) ||
|
|
76
|
+
isSetAccessorDeclaration(parent) ||
|
|
77
|
+
isEnumMember(parent) ||
|
|
78
|
+
isJsxAttribute(parent) ||
|
|
79
|
+
isVariableDeclaration(parent) ||
|
|
80
|
+
isParameterDeclaration(parent) ||
|
|
81
|
+
isFunctionDeclaration(parent) ||
|
|
82
|
+
isFunctionExpression(parent) ||
|
|
83
|
+
isClassDeclaration(parent) ||
|
|
84
|
+
isClassExpression(parent) ||
|
|
85
|
+
isInterfaceDeclaration(parent) ||
|
|
86
|
+
isTypeAliasDeclaration(parent) ||
|
|
87
|
+
isEnumDeclaration(parent) ||
|
|
88
|
+
isModuleDeclaration(parent) ||
|
|
89
|
+
isTypeParameterDeclaration(parent) ||
|
|
90
|
+
isImportClause(parent) ||
|
|
91
|
+
isNamespaceImport(parent) ||
|
|
92
|
+
isImportEqualsDeclaration(parent) ||
|
|
93
|
+
isNamespaceExport(parent) ||
|
|
94
|
+
isNamespaceExportDeclaration(parent)
|
|
95
|
+
) {
|
|
96
|
+
return parent.name === node;
|
|
97
|
+
}
|
|
98
|
+
return (
|
|
99
|
+
(isLabeledStatement(parent) || isBreakStatement(parent) || isContinueStatement(parent)) &&
|
|
100
|
+
parent.label === node
|
|
101
|
+
);
|
|
102
|
+
}
|
package/src/suppress-file.ts
CHANGED
|
@@ -19,6 +19,7 @@ import {
|
|
|
19
19
|
UNSCOPED_ENTRY,
|
|
20
20
|
blankReason,
|
|
21
21
|
isEntryPath,
|
|
22
|
+
isPartKind,
|
|
22
23
|
isSymbolRef,
|
|
23
24
|
siteText,
|
|
24
25
|
type Mechanism,
|
|
@@ -98,7 +99,7 @@ type ByReference = ReadonlyMap<string, readonly InventorySymbol[]>;
|
|
|
98
99
|
*/
|
|
99
100
|
function readRecord(value: unknown, site: Position, byReference: ByReference, shape: Shape): Read {
|
|
100
101
|
const refuse = (text: string, want: string): never => {
|
|
101
|
-
throw new SuppressionError(shape.mechanism,
|
|
102
|
+
throw new SuppressionError(shape.mechanism, siteText(site), text, want);
|
|
102
103
|
};
|
|
103
104
|
if (!isObject(value)) {
|
|
104
105
|
return refuse(JSON.stringify(value), shape.recordWant);
|
|
@@ -155,13 +156,23 @@ function readRecord(value: unknown, site: Position, byReference: ByReference, sh
|
|
|
155
156
|
if (refusals.length > 0) {
|
|
156
157
|
return { records: [], refusals };
|
|
157
158
|
}
|
|
158
|
-
const named =
|
|
159
|
+
const named =
|
|
160
|
+
byReference.get(declarationKey(symbol, written.path)) ??
|
|
161
|
+
(isPartKind(code)
|
|
162
|
+
? byReference.get(declarationKey(parameterHolder(symbol), written.path))
|
|
163
|
+
: undefined) ??
|
|
164
|
+
[];
|
|
159
165
|
if (named.length === 0) {
|
|
160
166
|
return { records: [{ ...written, bound: "" }], refusals: [] };
|
|
161
167
|
}
|
|
162
168
|
return { records: named.map((one) => ({ ...written, bound: one.id })), refusals: [] };
|
|
163
169
|
}
|
|
164
170
|
|
|
171
|
+
/** The reference of the declaration a parameter's reference names it under, or `ref` itself. */
|
|
172
|
+
function parameterHolder(ref: string): string {
|
|
173
|
+
return ref.endsWith(")") ? ref.slice(0, ref.lastIndexOf("(")) : ref;
|
|
174
|
+
}
|
|
175
|
+
|
|
165
176
|
function byReferenceOf(symbols: readonly InventorySymbol[]): ByReference {
|
|
166
177
|
const byReference = new Map<string, InventorySymbol[]>();
|
|
167
178
|
for (const symbol of symbols) {
|
|
@@ -217,7 +228,7 @@ function readDocument(
|
|
|
217
228
|
const records: SuppressionRecord[] = [];
|
|
218
229
|
const refusals: Refusal[] = [];
|
|
219
230
|
(held as readonly unknown[]).forEach((value, index) => {
|
|
220
|
-
const offset = walk.
|
|
231
|
+
const offset = walk.starts[index];
|
|
221
232
|
const site = offset === undefined ? documentSite(shape) : positionAt(text, shape.file, offset);
|
|
222
233
|
const read = readRecord(value, site, byReference, shape);
|
|
223
234
|
records.push(...read.records);
|
package/src/suppress-inline.ts
CHANGED
|
@@ -10,8 +10,10 @@ import {
|
|
|
10
10
|
type Node,
|
|
11
11
|
type SourceFile,
|
|
12
12
|
} from "@typescript/native/ast";
|
|
13
|
+
import { importBindings } from "./import-bindings.ts";
|
|
13
14
|
import { isParameterProperty, nodeKey, type Inventory } from "./inventory.ts";
|
|
14
15
|
import { byPosition, positionKey, renderPosition, type Position } from "./position.ts";
|
|
16
|
+
import { importRef } from "./ref.ts";
|
|
15
17
|
import type { ProjectView } from "./session.ts";
|
|
16
18
|
import { signatureOf } from "./signatures.ts";
|
|
17
19
|
import {
|
|
@@ -134,7 +136,9 @@ function lineComments(file: SourceFile): readonly LineComment[] {
|
|
|
134
136
|
const spans: { readonly start: number; readonly end: number }[] = [];
|
|
135
137
|
const visit = (node: Node): void => {
|
|
136
138
|
if (LITERALS.has(node.kind)) {
|
|
137
|
-
|
|
139
|
+
// JSX text carries no trivia: its first code unit is at `pos`, even a `//`.
|
|
140
|
+
const start = node.kind === SyntaxKind.JsxText ? node.pos : tokenStart(text, node.pos);
|
|
141
|
+
spans.push({ start, end: node.end });
|
|
138
142
|
}
|
|
139
143
|
node.forEachChild(visit);
|
|
140
144
|
};
|
|
@@ -198,6 +202,7 @@ function declarationLines(
|
|
|
198
202
|
file: SourceFile,
|
|
199
203
|
held: Inventory,
|
|
200
204
|
symbols: ReadonlyMap<string, Declared>,
|
|
205
|
+
targetRoot: string,
|
|
201
206
|
): ReadonlyMap<number, readonly Binding[]> {
|
|
202
207
|
const below = new Map<number, Binding[]>();
|
|
203
208
|
const add = (offset: number, one: Binding): void => {
|
|
@@ -243,6 +248,20 @@ function declarationLines(
|
|
|
243
248
|
node.forEachChild(visit);
|
|
244
249
|
};
|
|
245
250
|
file.forEachChild(visit);
|
|
251
|
+
// An import binding is a subject of its own, bound on its line as a declaration is.
|
|
252
|
+
const fileId = held.declarations.get(nodeKey(file, file));
|
|
253
|
+
const moduleRef = held.symbols.find((symbol) => symbol.id === fileId)?.ref ?? "";
|
|
254
|
+
for (const name of moduleRef === "" ? [] : importBindings(file)) {
|
|
255
|
+
const position = renderPosition(file, targetRoot, name.getStart());
|
|
256
|
+
const one = { id: positionKey(position), ref: importRef(moduleRef, name.text), position };
|
|
257
|
+
let statement: Node = name;
|
|
258
|
+
while (statement.parent.kind !== SyntaxKind.SourceFile) {
|
|
259
|
+
statement = statement.parent;
|
|
260
|
+
}
|
|
261
|
+
for (const at of [statement.pos, name.getStart()]) {
|
|
262
|
+
add(at, { declared: one, part: one });
|
|
263
|
+
}
|
|
264
|
+
}
|
|
246
265
|
return below;
|
|
247
266
|
}
|
|
248
267
|
|
|
@@ -283,7 +302,7 @@ export function inlineDirectives<Brand>(
|
|
|
283
302
|
if (comments.length === 0) {
|
|
284
303
|
continue;
|
|
285
304
|
}
|
|
286
|
-
const below = declarationLines(file, held, symbols);
|
|
305
|
+
const below = declarationLines(file, held, symbols, targetRoot);
|
|
287
306
|
for (const comment of comments) {
|
|
288
307
|
const site = renderPosition(file, targetRoot, comment.offset);
|
|
289
308
|
const target = trails(file, comment.offset) ? site.line : site.line + 1;
|
package/src/suppress.ts
CHANGED
|
@@ -243,10 +243,11 @@ function claimKey(finding: Finding): string {
|
|
|
243
243
|
|
|
244
244
|
/**
|
|
245
245
|
* Whether one record names one finding. A record reaches a finding in two ways: it bound
|
|
246
|
-
* the declaration at the finding's position, or it names the finding's reference
|
|
247
|
-
*
|
|
248
|
-
* finding
|
|
249
|
-
*
|
|
246
|
+
* the declaration at the finding's position, or it names the finding's reference, or the
|
|
247
|
+
* reference of the declaration holding a part that has one of its own, and the file the
|
|
248
|
+
* finding is reported in, each exactly. The second is how an entry copied from a finding
|
|
249
|
+
* about a part of a declaration reaches the part, and the only way any record reaches a
|
|
250
|
+
* dependency row.
|
|
250
251
|
*/
|
|
251
252
|
export function pairs(record: SuppressionRecord, finding: Finding): boolean {
|
|
252
253
|
if (record.code !== finding.code) {
|
|
@@ -265,7 +266,10 @@ export function pairs(record: SuppressionRecord, finding: Finding): boolean {
|
|
|
265
266
|
if (record.bound !== "" && record.bound === positionKey(finding.position)) {
|
|
266
267
|
return true;
|
|
267
268
|
}
|
|
268
|
-
return
|
|
269
|
+
return (
|
|
270
|
+
(record.symbol === finding.symbol.ref || record.symbol === finding.symbol.holder) &&
|
|
271
|
+
record.path === finding.position.path
|
|
272
|
+
);
|
|
269
273
|
}
|
|
270
274
|
|
|
271
275
|
/**
|