@cplieger/deadset-ts 7.2.0-dev.3 → 7.2.0-dev.4
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 +10 -6
- package/dist/src/component-files.js +96 -31
- package/dist/src/configuration-files.js +4 -3
- package/dist/src/configured-declarations.js +24 -2
- package/dist/src/conformance-record.js +1 -1
- package/dist/src/contextual-uses.js +26 -26
- package/dist/src/dependencies.js +2 -2
- package/dist/src/destructuring.js +77 -2
- package/dist/src/discover.js +68 -86
- package/dist/src/edges.js +1 -1
- package/dist/src/intra-function-parts.js +17 -6
- package/dist/src/inventory.js +11 -0
- package/dist/src/json-document.js +5 -5
- package/dist/src/namespace-objects.js +28 -18
- package/dist/src/overrides.js +7 -2
- package/dist/src/paths.js +9 -0
- package/dist/src/references.js +25 -12
- package/dist/src/reflective-lookup.js +8 -6
- package/dist/src/reporters/template.js +22 -13
- package/dist/src/serialization-contract.js +43 -12
- package/dist/src/session.js +19 -1
- package/dist/src/spelled-names.js +58 -0
- package/dist/src/suppress-file.js +2 -2
- package/dist/src/suppress-inline.js +3 -1
- package/dist/src/template-field.js +9 -6
- package/dist/src/type-errors.js +82 -28
- package/dist/src/value-reach.js +17 -5
- package/dist/src/verbs/analyze.js +3 -1
- package/dist/src/version-range.js +10 -5
- package/dist/src/workspace-resolution.js +19 -3
- package/dist/src/workspace.js +1 -1
- package/jsr.json +1 -1
- package/package.json +1 -1
- package/src/analysis.ts +32 -6
- package/src/component-files.ts +116 -31
- package/src/configuration-files.ts +14 -6
- package/src/configured-declarations.ts +33 -4
- package/src/conformance-record.ts +1 -1
- package/src/contextual-uses.ts +27 -27
- package/src/dependencies.ts +2 -2
- package/src/destructuring.ts +93 -2
- package/src/discover.ts +92 -87
- package/src/edges.ts +1 -1
- package/src/intra-function-parts.ts +21 -5
- package/src/inventory.ts +12 -0
- package/src/json-document.ts +7 -7
- package/src/namespace-objects.ts +34 -21
- package/src/overrides.ts +10 -2
- package/src/paths.ts +10 -0
- package/src/references.ts +28 -16
- package/src/reflective-lookup.ts +9 -7
- package/src/reporters/template.ts +21 -13
- package/src/serialization-contract.ts +53 -13
- package/src/session.ts +22 -1
- package/src/spelled-names.ts +102 -0
- package/src/suppress-file.ts +2 -2
- package/src/suppress-inline.ts +3 -1
- package/src/template-field.ts +9 -6
- package/src/type-errors.ts +94 -35
- package/src/value-reach.ts +19 -6
- package/src/verbs/analyze.ts +6 -1
- package/src/version-range.ts +10 -5
- package/src/workspace-resolution.ts +24 -4
- package/src/workspace.ts +1 -1
|
@@ -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
|
@@ -98,7 +98,7 @@ type ByReference = ReadonlyMap<string, readonly InventorySymbol[]>;
|
|
|
98
98
|
*/
|
|
99
99
|
function readRecord(value: unknown, site: Position, byReference: ByReference, shape: Shape): Read {
|
|
100
100
|
const refuse = (text: string, want: string): never => {
|
|
101
|
-
throw new SuppressionError(shape.mechanism,
|
|
101
|
+
throw new SuppressionError(shape.mechanism, siteText(site), text, want);
|
|
102
102
|
};
|
|
103
103
|
if (!isObject(value)) {
|
|
104
104
|
return refuse(JSON.stringify(value), shape.recordWant);
|
|
@@ -217,7 +217,7 @@ function readDocument(
|
|
|
217
217
|
const records: SuppressionRecord[] = [];
|
|
218
218
|
const refusals: Refusal[] = [];
|
|
219
219
|
(held as readonly unknown[]).forEach((value, index) => {
|
|
220
|
-
const offset = walk.
|
|
220
|
+
const offset = walk.starts[index];
|
|
221
221
|
const site = offset === undefined ? documentSite(shape) : positionAt(text, shape.file, offset);
|
|
222
222
|
const read = readRecord(value, site, byReference, shape);
|
|
223
223
|
records.push(...read.records);
|
package/src/suppress-inline.ts
CHANGED
|
@@ -134,7 +134,9 @@ function lineComments(file: SourceFile): readonly LineComment[] {
|
|
|
134
134
|
const spans: { readonly start: number; readonly end: number }[] = [];
|
|
135
135
|
const visit = (node: Node): void => {
|
|
136
136
|
if (LITERALS.has(node.kind)) {
|
|
137
|
-
|
|
137
|
+
// JSX text carries no trivia: its first code unit is at `pos`, even a `//`.
|
|
138
|
+
const start = node.kind === SyntaxKind.JsxText ? node.pos : tokenStart(text, node.pos);
|
|
139
|
+
spans.push({ start, end: node.end });
|
|
138
140
|
}
|
|
139
141
|
node.forEachChild(visit);
|
|
140
142
|
};
|
package/src/template-field.ts
CHANGED
|
@@ -18,7 +18,7 @@ import type { DetectorInput, Evidence } from "./exempt.ts";
|
|
|
18
18
|
import type { Host } from "./host.ts";
|
|
19
19
|
import type { SymbolKind } from "./inventory.ts";
|
|
20
20
|
import { memberNames } from "./member-names.ts";
|
|
21
|
-
import { isAbsolutePath, joinPath, normalizePath, relativePath } from "./paths.ts";
|
|
21
|
+
import { extensionOf, isAbsolutePath, joinPath, normalizePath, relativePath } from "./paths.ts";
|
|
22
22
|
import { isComponentFile, type Position } from "./position.ts";
|
|
23
23
|
|
|
24
24
|
/** One template, by its path below the target root. */
|
|
@@ -125,7 +125,10 @@ function stringEnd(text: string, from: number): number {
|
|
|
125
125
|
return at + 1;
|
|
126
126
|
}
|
|
127
127
|
|
|
128
|
-
/**
|
|
128
|
+
/**
|
|
129
|
+
* The end of the `${` substitution whose expression starts at `from`: past its closing brace,
|
|
130
|
+
* or one past the text's end where none closes it, so the expression is the text to its end.
|
|
131
|
+
*/
|
|
129
132
|
function substitutionEnd(text: string, from: number): number {
|
|
130
133
|
let depth = 1;
|
|
131
134
|
let at = from;
|
|
@@ -149,7 +152,7 @@ function substitutionEnd(text: string, from: number): number {
|
|
|
149
152
|
}
|
|
150
153
|
at += 1;
|
|
151
154
|
}
|
|
152
|
-
return
|
|
155
|
+
return text.length + 1;
|
|
153
156
|
}
|
|
154
157
|
|
|
155
158
|
/** The end of the template literal at `from`; each `${…}` expression goes to `substitution`. */
|
|
@@ -243,8 +246,8 @@ function namesOf(text: string, delimiters: TemplateDelimiters): readonly Named[]
|
|
|
243
246
|
}
|
|
244
247
|
|
|
245
248
|
/** Every identifier the markup of one component file writes in an action. */
|
|
246
|
-
function markupNamesOf(
|
|
247
|
-
return readComponent(text).markup.flatMap((one) =>
|
|
249
|
+
function markupNamesOf(file: TemplateFile): readonly Named[] {
|
|
250
|
+
return readComponent(file.text, extensionOf(file.path)).markup.flatMap((one) =>
|
|
248
251
|
identifiersOf(one.text, true).map(({ name, offset }) => ({
|
|
249
252
|
name,
|
|
250
253
|
offset: one.start + offset,
|
|
@@ -287,7 +290,7 @@ export function templateField<Brand>(input: DetectorInput<Brand>): readonly Evid
|
|
|
287
290
|
names: namesOf(file.text, templates.delimiters),
|
|
288
291
|
markup: false,
|
|
289
292
|
})),
|
|
290
|
-
...components.map((file) => ({ file, names: markupNamesOf(file
|
|
293
|
+
...components.map((file) => ({ file, names: markupNamesOf(file), markup: true })),
|
|
291
294
|
];
|
|
292
295
|
for (const { file, names, markup } of scanned) {
|
|
293
296
|
for (const named of names) {
|
package/src/type-errors.ts
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
* What the errors a project's own files carry do to the run. An import of a module the
|
|
3
3
|
* project expects and nothing provides is a setup failure. Any other error in an own file
|
|
4
4
|
* is a type error: the function, method or file-level statement holding it is skipped,
|
|
5
|
-
* which only ever withholds a finding.
|
|
6
|
-
*
|
|
5
|
+
* which only ever withholds a finding. `projectErrors` decides, before any of these, which
|
|
6
|
+
* errors the compiler places in no file leave the project unloaded.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
import {
|
|
@@ -69,8 +69,6 @@ interface ProjectErrorReading {
|
|
|
69
69
|
readonly missing: readonly SetupFailure[];
|
|
70
70
|
/** The type errors, each with the unit it skips. */
|
|
71
71
|
readonly skips: readonly Skip[];
|
|
72
|
-
/** The errors placed in no file, which leave the project unanalyzable. */
|
|
73
|
-
readonly failing: readonly LocatedDiagnostic[];
|
|
74
72
|
}
|
|
75
73
|
|
|
76
74
|
/** Where one project reads what its imports may name. */
|
|
@@ -78,11 +76,17 @@ interface ErrorContext {
|
|
|
78
76
|
readonly host: Host;
|
|
79
77
|
/** The target root, absolute. */
|
|
80
78
|
readonly targetRoot: string;
|
|
81
|
-
/** The
|
|
82
|
-
readonly paths:
|
|
79
|
+
/** The project configuration's `paths` mapping. */
|
|
80
|
+
readonly paths: PathsMapping;
|
|
83
81
|
}
|
|
84
82
|
|
|
85
|
-
/**
|
|
83
|
+
/** One configuration's `paths` mapping, its targets read against `base`. */
|
|
84
|
+
interface PathsMapping {
|
|
85
|
+
readonly base: string;
|
|
86
|
+
readonly entries: readonly { readonly key: string; readonly targets: readonly string[] }[];
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** The string literal holding one offset, innermost first, a doc comment's included. */
|
|
86
90
|
function literalAt(file: SourceFile, offset: number): string | undefined {
|
|
87
91
|
let found: string | undefined;
|
|
88
92
|
const visit = (node: Node): void => {
|
|
@@ -92,36 +96,59 @@ function literalAt(file: SourceFile, offset: number): string | undefined {
|
|
|
92
96
|
if (isStringLiteral(node) || isNoSubstitutionTemplateLiteral(node)) {
|
|
93
97
|
found = node.text;
|
|
94
98
|
}
|
|
99
|
+
// `forEachChild` does not enter a doc comment, where a JavaScript file imports types.
|
|
100
|
+
for (const doc of (node as { readonly jsDoc?: readonly Node[] }).jsDoc ?? []) {
|
|
101
|
+
visit(doc);
|
|
102
|
+
}
|
|
95
103
|
node.forEachChild(visit);
|
|
96
104
|
};
|
|
97
105
|
file.forEachChild(visit);
|
|
98
106
|
return found;
|
|
99
107
|
}
|
|
100
108
|
|
|
101
|
-
/**
|
|
102
|
-
|
|
109
|
+
/**
|
|
110
|
+
* What one `paths` key's `*` matches in one specifier, `""` for a key with no `*` that
|
|
111
|
+
* is the specifier, or `undefined` where the key does not match it.
|
|
112
|
+
*/
|
|
113
|
+
function mappedBy(key: string, specifier: string): string | undefined {
|
|
103
114
|
const star = key.indexOf("*");
|
|
104
115
|
if (star < 0) {
|
|
105
|
-
return key === specifier;
|
|
116
|
+
return key === specifier ? "" : undefined;
|
|
106
117
|
}
|
|
107
118
|
const prefix = key.slice(0, star);
|
|
108
119
|
const suffix = key.slice(star + 1);
|
|
109
|
-
return
|
|
110
|
-
specifier.length >= prefix.length + suffix.length &&
|
|
120
|
+
return specifier.length >= prefix.length + suffix.length &&
|
|
111
121
|
specifier.startsWith(prefix) &&
|
|
112
122
|
specifier.endsWith(suffix)
|
|
113
|
-
|
|
123
|
+
? specifier.slice(prefix.length, specifier.length - suffix.length)
|
|
124
|
+
: undefined;
|
|
114
125
|
}
|
|
115
126
|
|
|
116
|
-
/**
|
|
117
|
-
|
|
118
|
-
|
|
127
|
+
/**
|
|
128
|
+
* One `paths` target, its first `*` replaced by `star` taken literally, as the compiler does:
|
|
129
|
+
* https://github.com/microsoft/typescript-go/blob/89d5d5b2849a0db0957065889ca58536fa6d2e4a/internal/module/resolver.go#L1282
|
|
130
|
+
*/
|
|
131
|
+
function substituted(target: string, star: string): string {
|
|
132
|
+
const at = target.indexOf("*");
|
|
133
|
+
return at < 0 ? target : target.slice(0, at) + star + target.slice(at + 1);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Whether a path names something on disk, as written or with any extension; under
|
|
138
|
+
* `fileOnly`, only a file counts.
|
|
139
|
+
*/
|
|
140
|
+
function provided(host: Host, path: string, fileOnly: boolean): boolean {
|
|
141
|
+
const counts = (at: string): boolean =>
|
|
142
|
+
fileOnly ? host.kindOf(at) === "file" : host.kindOf(at) !== "absent";
|
|
143
|
+
if (counts(path)) {
|
|
119
144
|
return true;
|
|
120
145
|
}
|
|
121
146
|
const dir = dirnamePath(path);
|
|
122
147
|
const stem = `${path.slice(dir.length + 1)}.`;
|
|
123
148
|
try {
|
|
124
|
-
return host
|
|
149
|
+
return host
|
|
150
|
+
.readDirectory(dir)
|
|
151
|
+
.some((entry) => entry.name.startsWith(stem) && counts(joinPath(dir, entry.name)));
|
|
125
152
|
} catch {
|
|
126
153
|
return false;
|
|
127
154
|
}
|
|
@@ -158,6 +185,21 @@ function declaringManifest(
|
|
|
158
185
|
}
|
|
159
186
|
}
|
|
160
187
|
|
|
188
|
+
/**
|
|
189
|
+
* Whether a package is installed for an importer in `dir`: in the `node_modules` of
|
|
190
|
+
* `dir` or of any ancestor, above the target root too, as Node and the compiler look.
|
|
191
|
+
*/
|
|
192
|
+
function installed(host: Host, dir: string, name: string): boolean {
|
|
193
|
+
for (let at = dir; ; at = dirnamePath(at)) {
|
|
194
|
+
if (host.kindOf(joinPath(at, "node_modules", name)) === "directory") {
|
|
195
|
+
return true;
|
|
196
|
+
}
|
|
197
|
+
if (dirnamePath(at) === at) {
|
|
198
|
+
return false;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
161
203
|
/**
|
|
162
204
|
* Why one import that the compiler cannot resolve names a module the project expects,
|
|
163
205
|
* with the fix, or undefined where the module is one the project does not expect.
|
|
@@ -170,17 +212,31 @@ function expectedModule(
|
|
|
170
212
|
const { host, targetRoot } = context;
|
|
171
213
|
const fix = "run the generator or the build that writes it";
|
|
172
214
|
if (specifier.startsWith(".") || isAbsolutePath(specifier)) {
|
|
173
|
-
return provided(host, resolvePath(dirnamePath(importer), specifier))
|
|
215
|
+
return provided(host, resolvePath(dirnamePath(importer), specifier), false)
|
|
174
216
|
? undefined
|
|
175
217
|
: `which names no file of the target: ${fix}`;
|
|
176
218
|
}
|
|
177
219
|
if (specifier.startsWith("#")) {
|
|
178
220
|
return `which the manifest's imports map to no file: ${fix}`;
|
|
179
221
|
}
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
222
|
+
const mapped = context.paths.entries.flatMap(({ key, targets }) => {
|
|
223
|
+
const star = mappedBy(key, specifier);
|
|
224
|
+
return star === undefined
|
|
225
|
+
? []
|
|
226
|
+
: targets.map((target) => resolvePath(context.paths.base, substituted(target, star)));
|
|
227
|
+
});
|
|
183
228
|
const name = packageOfSpecifier(specifier);
|
|
229
|
+
// The compiler looks up in `node_modules` what `paths` does not resolve, so the error
|
|
230
|
+
// of an installed package's import is the import's own, a subpath its exports omit:
|
|
231
|
+
// https://github.com/microsoft/typescript-go/blob/89d5d5b2849a0db0957065889ca58536fa6d2e4a/internal/module/resolver.go#L548-L575
|
|
232
|
+
if (name !== undefined && installed(host, dirnamePath(importer), name)) {
|
|
233
|
+
return undefined;
|
|
234
|
+
}
|
|
235
|
+
if (mapped.length > 0) {
|
|
236
|
+
return mapped.some((path) => provided(host, path, true))
|
|
237
|
+
? undefined
|
|
238
|
+
: `which the compiler configuration's paths map to no file: ${fix}`;
|
|
239
|
+
}
|
|
184
240
|
const manifest =
|
|
185
241
|
name === undefined
|
|
186
242
|
? undefined
|
|
@@ -223,14 +279,8 @@ export function readErrors<Brand>(
|
|
|
223
279
|
const files = new Map(project.ownSourceFiles().map((file) => [file.fileName, file]));
|
|
224
280
|
const missing: SetupFailure[] = [];
|
|
225
281
|
const skips: Skip[] = [];
|
|
226
|
-
const
|
|
227
|
-
|
|
228
|
-
const { diagnostic, at } = located;
|
|
229
|
-
if (diagnostic.fileName === undefined) {
|
|
230
|
-
failing.push(located);
|
|
231
|
-
continue;
|
|
232
|
-
}
|
|
233
|
-
const file = files.get(diagnostic.fileName);
|
|
282
|
+
for (const { diagnostic, at } of errors) {
|
|
283
|
+
const file = diagnostic.fileName === undefined ? undefined : files.get(diagnostic.fileName);
|
|
234
284
|
if (file === undefined || diagnostic.reportsUnnecessary === true) {
|
|
235
285
|
continue;
|
|
236
286
|
}
|
|
@@ -257,7 +307,7 @@ export function readErrors<Brand>(
|
|
|
257
307
|
});
|
|
258
308
|
}
|
|
259
309
|
}
|
|
260
|
-
return { missing, skips
|
|
310
|
+
return { missing, skips };
|
|
261
311
|
}
|
|
262
312
|
|
|
263
313
|
/** The lines each skip withholds, below the target root. */
|
|
@@ -330,12 +380,21 @@ export function skippedRoots<Brand>(
|
|
|
330
380
|
return [...kept];
|
|
331
381
|
}
|
|
332
382
|
|
|
333
|
-
/**
|
|
334
|
-
export function pathsOf(engine: Engine, configFile: string):
|
|
383
|
+
/** One compiler configuration's `paths` mapping, empty where it cannot be read. */
|
|
384
|
+
export function pathsOf(engine: Engine, configFile: string): PathsMapping {
|
|
385
|
+
const base = dirnamePath(configFile);
|
|
335
386
|
try {
|
|
336
|
-
const
|
|
337
|
-
|
|
387
|
+
const { options } = engine.parseConfigFile(configFile);
|
|
388
|
+
const { paths } = options;
|
|
389
|
+
// The parse answers `pathsBasePath`, the directory of the configuration that writes
|
|
390
|
+
// `paths` (an extended one included), though the client's option type omits it:
|
|
391
|
+
// https://github.com/microsoft/typescript-go/blob/89d5d5b2849a0db0957065889ca58536fa6d2e4a/internal/tsoptions/tsconfigparsing.go#L1120
|
|
392
|
+
const declaredAt = (options as { readonly pathsBasePath?: unknown }).pathsBasePath;
|
|
393
|
+
return {
|
|
394
|
+
base: typeof declaredAt === "string" ? declaredAt : base,
|
|
395
|
+
entries: Object.entries(paths ?? {}).map(([key, targets]) => ({ key, targets })),
|
|
396
|
+
};
|
|
338
397
|
} catch {
|
|
339
|
-
return [];
|
|
398
|
+
return { base, entries: [] };
|
|
340
399
|
}
|
|
341
400
|
}
|
package/src/value-reach.ts
CHANGED
|
@@ -26,8 +26,12 @@ import type { Inventory } from "./inventory.ts";
|
|
|
26
26
|
import { must } from "./query.ts";
|
|
27
27
|
import type { ProjectView } from "./session.ts";
|
|
28
28
|
|
|
29
|
-
/**
|
|
30
|
-
|
|
29
|
+
/**
|
|
30
|
+
* The object types whose members the walk reads whatever declares them. A reverse-mapped
|
|
31
|
+
* type has no symbol: it is the shape the checker infers from an argument the target
|
|
32
|
+
* writes against a homomorphic mapped type.
|
|
33
|
+
*/
|
|
34
|
+
const WRITTEN = ObjectFlags.ObjectLiteral | ObjectFlags.Mapped | ObjectFlags.ReverseMapped;
|
|
31
35
|
|
|
32
36
|
/** The symbols of an anonymous object type the target writes as a type. */
|
|
33
37
|
const LITERAL_SYMBOLS = SymbolFlags.TypeLiteral | SymbolFlags.ObjectLiteral;
|
|
@@ -38,7 +42,7 @@ export interface Reached {
|
|
|
38
42
|
readonly classes: ReadonlySet<string>;
|
|
39
43
|
/**
|
|
40
44
|
* The members of the interfaces and object types the target writes, other than its
|
|
41
|
-
* classes: each data member, and each method named `toJSON` or `toString`.
|
|
45
|
+
* classes: each data member, and each method or getter named `toJSON` or `toString`.
|
|
42
46
|
*/
|
|
43
47
|
readonly members: readonly TSSymbol[];
|
|
44
48
|
}
|
|
@@ -51,6 +55,13 @@ interface ValueReach {
|
|
|
51
55
|
/** The two conversion methods a serializer or a formatter resolves on a value's own shape. */
|
|
52
56
|
const CONVERSION_NAMES: ReadonlySet<string> = new Set(["toJSON", "toString"]);
|
|
53
57
|
|
|
58
|
+
/**
|
|
59
|
+
* The members that can answer a conversion: a serializer reads the name with `[[Get]]`
|
|
60
|
+
* and calls what it answers, so a getter answers it as a method does:
|
|
61
|
+
* https://tc39.es/ecma262/#sec-serializejsonproperty (step 2).
|
|
62
|
+
*/
|
|
63
|
+
export const CONVERSION_MEMBERS = SymbolFlags.Method | SymbolFlags.GetAccessor;
|
|
64
|
+
|
|
54
65
|
/**
|
|
55
66
|
* The types each value is checked against by the `satisfies` expressions and type assertions
|
|
56
67
|
* it is written as, through parentheses, and the innermost operand's own type where there is
|
|
@@ -147,8 +158,10 @@ export function valueReach<Brand>(
|
|
|
147
158
|
if ((type.objectFlags & ObjectFlags.Anonymous) !== 0) {
|
|
148
159
|
return (symbol.flags & LITERAL_SYMBOLS) !== 0;
|
|
149
160
|
}
|
|
161
|
+
// A class the inventory holds is read as a class before this, so an own class
|
|
162
|
+
// here is one it does not hold, such as a class declared in a function.
|
|
150
163
|
return (
|
|
151
|
-
(symbol.flags & SymbolFlags.Interface) !== 0 &&
|
|
164
|
+
(symbol.flags & (SymbolFlags.Interface | SymbolFlags.Class)) !== 0 &&
|
|
152
165
|
symbol.declarations.every((handle) => own.has(handle.path))
|
|
153
166
|
);
|
|
154
167
|
};
|
|
@@ -178,12 +191,12 @@ export function valueReach<Brand>(
|
|
|
178
191
|
return !isList && isWritten(type);
|
|
179
192
|
};
|
|
180
193
|
|
|
181
|
-
/** The data members and conversion
|
|
194
|
+
/** The data members and conversion members of one written object type. */
|
|
182
195
|
const carried = (type: Type): readonly TSSymbol[] =>
|
|
183
196
|
must(queries.propertiesOf(type)).filter(
|
|
184
197
|
(property) =>
|
|
185
198
|
(property.flags & SymbolFlags.Property) !== 0 ||
|
|
186
|
-
((property.flags &
|
|
199
|
+
((property.flags & CONVERSION_MEMBERS) !== 0 && CONVERSION_NAMES.has(property.name)),
|
|
187
200
|
);
|
|
188
201
|
|
|
189
202
|
/** The types a value of one type holds, read once per type. */
|
package/src/verbs/analyze.ts
CHANGED
|
@@ -229,6 +229,7 @@ export const analyzeVerb: Verb = ({ err, host, inputs, scope, openClient, option
|
|
|
229
229
|
let partial: readonly string[];
|
|
230
230
|
let warnings: readonly ComponentWarning[];
|
|
231
231
|
let unread: readonly string[];
|
|
232
|
+
let unfound: readonly string[];
|
|
232
233
|
try {
|
|
233
234
|
const root = runRelative(invoked, targetRoot);
|
|
234
235
|
const mode = { production: true };
|
|
@@ -256,6 +257,10 @@ export const analyzeVerb: Verb = ({ err, host, inputs, scope, openClient, option
|
|
|
256
257
|
({ configFile, reason }) =>
|
|
257
258
|
`${relativePath(resolvePath(invoked, targetRoot), configFile) ?? configFile}: its component files are not read: ${reason}`,
|
|
258
259
|
);
|
|
260
|
+
unfound = run.librariesUnfound.map(
|
|
261
|
+
({ configFile, error }) =>
|
|
262
|
+
`${relativePath(resolvePath(invoked, targetRoot), configFile) ?? configFile}: ${error}: the configuration is analyzed without that library`,
|
|
263
|
+
);
|
|
259
264
|
partial = partialNotes({
|
|
260
265
|
notBuilt: run.notBuilt,
|
|
261
266
|
unanswered: run.unanswered,
|
|
@@ -325,7 +330,7 @@ export const analyzeVerb: Verb = ({ err, host, inputs, scope, openClient, option
|
|
|
325
330
|
for (const note of partial) {
|
|
326
331
|
err.write(`deadset-ts: ${note}\n`);
|
|
327
332
|
}
|
|
328
|
-
for (const line of unread) {
|
|
333
|
+
for (const line of [...unread, ...unfound]) {
|
|
329
334
|
err.write(`deadset-ts: ${line}\n`);
|
|
330
335
|
}
|
|
331
336
|
for (const warning of warnings) {
|
package/src/version-range.ts
CHANGED
|
@@ -66,10 +66,12 @@ function partialOf(text: string): Partial | undefined {
|
|
|
66
66
|
parts.push(Number(part));
|
|
67
67
|
}
|
|
68
68
|
const prerelease = prereleaseOf(match[4]);
|
|
69
|
-
if (prerelease === undefined
|
|
69
|
+
if (prerelease === undefined) {
|
|
70
70
|
return undefined;
|
|
71
71
|
}
|
|
72
|
-
|
|
72
|
+
// node-semver drops the prerelease of a version with a wildcard part (`1.2.x-beta` is `1.2.x`):
|
|
73
|
+
// https://github.com/npm/node-semver/blob/v7.8.5/classes/range.js#L409-L430
|
|
74
|
+
return { major: parts[0], minor: parts[1], patch: parts[2], prerelease: wild ? [] : prerelease };
|
|
73
75
|
}
|
|
74
76
|
|
|
75
77
|
/** One exact version, or `undefined` where the text is not one. */
|
|
@@ -152,11 +154,14 @@ function passes(version: Version, comparator: Comparator): boolean {
|
|
|
152
154
|
}
|
|
153
155
|
}
|
|
154
156
|
|
|
155
|
-
/**
|
|
157
|
+
/**
|
|
158
|
+
* The comparators a version with missing parts stands for: any value of each. A wildcard
|
|
159
|
+
* major is no comparator, so it never refuses a 0.0.0 prerelease another comparator admits.
|
|
160
|
+
*/
|
|
156
161
|
function anyOf(partial: Partial): Comparator[] {
|
|
157
162
|
const { major, minor } = partial;
|
|
158
163
|
if (major === undefined) {
|
|
159
|
-
return [
|
|
164
|
+
return [];
|
|
160
165
|
}
|
|
161
166
|
if (minor === undefined) {
|
|
162
167
|
return [
|
|
@@ -210,7 +215,7 @@ function primitive(operator: "<" | "<=" | ">" | ">=", partial: Partial): Compara
|
|
|
210
215
|
if (major === undefined) {
|
|
211
216
|
return operator === "<" || operator === ">"
|
|
212
217
|
? [{ operator: "<", version: floorOf(0, 0, 0) }]
|
|
213
|
-
: [
|
|
218
|
+
: [];
|
|
214
219
|
}
|
|
215
220
|
if (minor !== undefined && patch !== undefined) {
|
|
216
221
|
return [{ operator, version: lowest(partial) }];
|
|
@@ -45,6 +45,7 @@ const DEPENDENCY_MEMBERS = [
|
|
|
45
45
|
] as const;
|
|
46
46
|
|
|
47
47
|
const SOURCE = /(?<!\.d)\.(?:[mc]?ts|tsx)$/u;
|
|
48
|
+
const OUTPUT = /(?:\.d\.[mc]?ts|\.[mc]?js|\.jsx)$/u;
|
|
48
49
|
|
|
49
50
|
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
50
51
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
@@ -77,10 +78,17 @@ function exportsEntry(
|
|
|
77
78
|
exportsField: unknown,
|
|
78
79
|
subpath: string,
|
|
79
80
|
): { readonly value: unknown; readonly match?: string } | undefined {
|
|
80
|
-
const
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
81
|
+
const keys = isRecord(exportsField) ? Object.keys(exportsField) : [];
|
|
82
|
+
const dotted = keys.filter((key) => key.startsWith(".")).length;
|
|
83
|
+
// An object mixing subpath keys with others is invalid to Node; the compiler still
|
|
84
|
+
// answers `.` from its `"."` key and no other subpath:
|
|
85
|
+
// https://github.com/microsoft/typescript-go/blob/89d5d5b2849a0db0957065889ca58536fa6d2e4a/internal/module/resolver.go#L673-L695
|
|
86
|
+
if (isRecord(exportsField) && dotted > 0 && dotted < keys.length) {
|
|
87
|
+
return subpath === "." && Object.hasOwn(exportsField, ".")
|
|
88
|
+
? { value: exportsField["."] }
|
|
89
|
+
: undefined;
|
|
90
|
+
}
|
|
91
|
+
const map = isRecord(exportsField) && dotted > 0 ? exportsField : { ".": exportsField };
|
|
84
92
|
if (Object.hasOwn(map, subpath)) {
|
|
85
93
|
return { value: map[subpath] };
|
|
86
94
|
}
|
|
@@ -371,6 +379,18 @@ export function workspaceResolver(host: Host, workspace: Workspace): WorkspaceRe
|
|
|
371
379
|
if (answered !== undefined) {
|
|
372
380
|
return { kind: "default" };
|
|
373
381
|
}
|
|
382
|
+
// A stylesheet or another asset is no module with a source, so the import of one
|
|
383
|
+
// keeps the compiler's resolution.
|
|
384
|
+
if (
|
|
385
|
+
targets.some(
|
|
386
|
+
(target) =>
|
|
387
|
+
!SOURCE.test(target) &&
|
|
388
|
+
!OUTPUT.test(target) &&
|
|
389
|
+
host.kindOf(joinPath(member.dir, target)) === "file",
|
|
390
|
+
)
|
|
391
|
+
) {
|
|
392
|
+
return { kind: "default" };
|
|
393
|
+
}
|
|
374
394
|
const named = targets.length === 0 ? "no target" : targets.join(", ");
|
|
375
395
|
const built = targets.some((target) => host.kindOf(joinPath(member.dir, target)) === "file");
|
|
376
396
|
return {
|
package/src/workspace.ts
CHANGED
|
@@ -274,7 +274,7 @@ function memberDirectories(
|
|
|
274
274
|
/** Whether a workspace's patterns name one directory below its root, read without listing any. */
|
|
275
275
|
function namesDirectory(root: string, patterns: readonly string[], dir: string): boolean {
|
|
276
276
|
const below = relativePath(root, dir);
|
|
277
|
-
if (below === undefined
|
|
277
|
+
if (below === undefined) {
|
|
278
278
|
return false;
|
|
279
279
|
}
|
|
280
280
|
const path = below.split("/");
|