@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
|
@@ -38,10 +38,12 @@ function partialOf(text) {
|
|
|
38
38
|
parts.push(Number(part));
|
|
39
39
|
}
|
|
40
40
|
const prerelease = prereleaseOf(match[4]);
|
|
41
|
-
if (prerelease === undefined
|
|
41
|
+
if (prerelease === undefined) {
|
|
42
42
|
return undefined;
|
|
43
43
|
}
|
|
44
|
-
|
|
44
|
+
// node-semver drops the prerelease of a version with a wildcard part (`1.2.x-beta` is `1.2.x`):
|
|
45
|
+
// https://github.com/npm/node-semver/blob/v7.8.5/classes/range.js#L409-L430
|
|
46
|
+
return { major: parts[0], minor: parts[1], patch: parts[2], prerelease: wild ? [] : prerelease };
|
|
45
47
|
}
|
|
46
48
|
/** One exact version, or `undefined` where the text is not one. */
|
|
47
49
|
function versionOf(text) {
|
|
@@ -111,11 +113,14 @@ function passes(version, comparator) {
|
|
|
111
113
|
return order === 0;
|
|
112
114
|
}
|
|
113
115
|
}
|
|
114
|
-
/**
|
|
116
|
+
/**
|
|
117
|
+
* The comparators a version with missing parts stands for: any value of each. A wildcard
|
|
118
|
+
* major is no comparator, so it never refuses a 0.0.0 prerelease another comparator admits.
|
|
119
|
+
*/
|
|
115
120
|
function anyOf(partial) {
|
|
116
121
|
const { major, minor } = partial;
|
|
117
122
|
if (major === undefined) {
|
|
118
|
-
return [
|
|
123
|
+
return [];
|
|
119
124
|
}
|
|
120
125
|
if (minor === undefined) {
|
|
121
126
|
return [
|
|
@@ -165,7 +170,7 @@ function primitive(operator, partial) {
|
|
|
165
170
|
if (major === undefined) {
|
|
166
171
|
return operator === "<" || operator === ">"
|
|
167
172
|
? [{ operator: "<", version: floorOf(0, 0, 0) }]
|
|
168
|
-
: [
|
|
173
|
+
: [];
|
|
169
174
|
}
|
|
170
175
|
if (minor !== undefined && patch !== undefined) {
|
|
171
176
|
return [{ operator, version: lowest(partial) }];
|
|
@@ -19,6 +19,7 @@ const DEPENDENCY_MEMBERS = [
|
|
|
19
19
|
"optionalDependencies",
|
|
20
20
|
];
|
|
21
21
|
const SOURCE = /(?<!\.d)\.(?:[mc]?ts|tsx)$/u;
|
|
22
|
+
const OUTPUT = /(?:\.d\.[mc]?ts|\.[mc]?js|\.jsx)$/u;
|
|
22
23
|
function isRecord(value) {
|
|
23
24
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
24
25
|
}
|
|
@@ -42,9 +43,17 @@ function inside(dir, path) {
|
|
|
42
43
|
}
|
|
43
44
|
/** The `exports` entry one subpath selects, with what a pattern's `*` matched. */
|
|
44
45
|
function exportsEntry(exportsField, subpath) {
|
|
45
|
-
const
|
|
46
|
-
|
|
47
|
-
|
|
46
|
+
const keys = isRecord(exportsField) ? Object.keys(exportsField) : [];
|
|
47
|
+
const dotted = keys.filter((key) => key.startsWith(".")).length;
|
|
48
|
+
// An object mixing subpath keys with others is invalid to Node; the compiler still
|
|
49
|
+
// answers `.` from its `"."` key and no other subpath:
|
|
50
|
+
// https://github.com/microsoft/typescript-go/blob/89d5d5b2849a0db0957065889ca58536fa6d2e4a/internal/module/resolver.go#L673-L695
|
|
51
|
+
if (isRecord(exportsField) && dotted > 0 && dotted < keys.length) {
|
|
52
|
+
return subpath === "." && Object.hasOwn(exportsField, ".")
|
|
53
|
+
? { value: exportsField["."] }
|
|
54
|
+
: undefined;
|
|
55
|
+
}
|
|
56
|
+
const map = isRecord(exportsField) && dotted > 0 ? exportsField : { ".": exportsField };
|
|
48
57
|
if (Object.hasOwn(map, subpath)) {
|
|
49
58
|
return { value: map[subpath] };
|
|
50
59
|
}
|
|
@@ -286,6 +295,13 @@ export function workspaceResolver(host, workspace) {
|
|
|
286
295
|
if (answered !== undefined) {
|
|
287
296
|
return { kind: "default" };
|
|
288
297
|
}
|
|
298
|
+
// A stylesheet or another asset is no module with a source, so the import of one
|
|
299
|
+
// keeps the compiler's resolution.
|
|
300
|
+
if (targets.some((target) => !SOURCE.test(target) &&
|
|
301
|
+
!OUTPUT.test(target) &&
|
|
302
|
+
host.kindOf(joinPath(member.dir, target)) === "file")) {
|
|
303
|
+
return { kind: "default" };
|
|
304
|
+
}
|
|
289
305
|
const named = targets.length === 0 ? "no target" : targets.join(", ");
|
|
290
306
|
const built = targets.some((target) => host.kindOf(joinPath(member.dir, target)) === "file");
|
|
291
307
|
return {
|
package/dist/src/workspace.js
CHANGED
|
@@ -205,7 +205,7 @@ function memberDirectories(host, root, patterns) {
|
|
|
205
205
|
/** Whether a workspace's patterns name one directory below its root, read without listing any. */
|
|
206
206
|
function namesDirectory(root, patterns, dir) {
|
|
207
207
|
const below = relativePath(root, dir);
|
|
208
|
-
if (below === undefined
|
|
208
|
+
if (below === undefined) {
|
|
209
209
|
return false;
|
|
210
210
|
}
|
|
211
211
|
const path = below.split("/");
|
package/jsr.json
CHANGED
package/package.json
CHANGED
package/src/analysis.ts
CHANGED
|
@@ -80,7 +80,14 @@ import {
|
|
|
80
80
|
type Matrix,
|
|
81
81
|
type SweepResult,
|
|
82
82
|
} from "./matrix.ts";
|
|
83
|
-
import {
|
|
83
|
+
import {
|
|
84
|
+
dirnamePath,
|
|
85
|
+
extensionOf,
|
|
86
|
+
joinPath,
|
|
87
|
+
normalizePath,
|
|
88
|
+
relativePath,
|
|
89
|
+
resolvePath,
|
|
90
|
+
} from "./paths.ts";
|
|
84
91
|
import type { UnansweredQuestion } from "./query.ts";
|
|
85
92
|
import { byPosition, isComponentFile, type Position } from "./position.ts";
|
|
86
93
|
import { LANGUAGE, type TypeErrorSkip, type UnansweredCount } from "./report.ts";
|
|
@@ -232,6 +239,8 @@ interface ReadProjects<Answer> {
|
|
|
232
239
|
readonly unanswered: readonly UnansweredQuestion[];
|
|
233
240
|
/** Per configuration whose component files the run cannot read, the reason. */
|
|
234
241
|
readonly componentsUnread: readonly ComponentsUnread[];
|
|
242
|
+
/** Per analyzed configuration, each library it names that the compiler cannot find. */
|
|
243
|
+
readonly librariesUnfound: readonly LibraryUnfound[];
|
|
235
244
|
/** The convention rows the run applied. */
|
|
236
245
|
readonly conventionsApplied: readonly AppliedConvention[];
|
|
237
246
|
/** The dependencies the applied rows' short names use. */
|
|
@@ -394,6 +403,7 @@ function readProjects<Answer>(
|
|
|
394
403
|
const failures: LocatedDiagnostic[] = [];
|
|
395
404
|
const unbuildable: SetupFailure[] = [];
|
|
396
405
|
const setupDropped: SetupFailure[] = [];
|
|
406
|
+
const librariesUnfound: LibraryUnfound[] = [];
|
|
397
407
|
const consumed = new Map<string, Reference>();
|
|
398
408
|
const heldFiles = new Set<string>();
|
|
399
409
|
let outsideTests: readonly OutsideTest[] = [];
|
|
@@ -458,10 +468,6 @@ function readProjects<Answer>(
|
|
|
458
468
|
: resolvePath(host.workingDirectory(), consumer.path),
|
|
459
469
|
paths: pathsOf(engine, project.configFile),
|
|
460
470
|
});
|
|
461
|
-
if (reading.failing.length > 0) {
|
|
462
|
-
failures.push(...reading.failing);
|
|
463
|
-
return undefined;
|
|
464
|
-
}
|
|
465
471
|
if (consumer !== undefined && reading.missing.length > 0) {
|
|
466
472
|
unbuildable.push(
|
|
467
473
|
...reading.missing.map((one) => ({
|
|
@@ -502,6 +508,12 @@ function readProjects<Answer>(
|
|
|
502
508
|
}
|
|
503
509
|
return undefined;
|
|
504
510
|
}
|
|
511
|
+
librariesUnfound.push(
|
|
512
|
+
...raw.unfound.map((one) => ({
|
|
513
|
+
configFile: project.configFile,
|
|
514
|
+
error: renderDiagnostic(one, (path) => relativePath(absoluteRoot, path) ?? path),
|
|
515
|
+
})),
|
|
516
|
+
);
|
|
505
517
|
const configuration = ids.get(project.configFile) ?? project.configFile;
|
|
506
518
|
const read = (): ProjectRead => {
|
|
507
519
|
const held = inventory(project, host, targetRoot);
|
|
@@ -589,6 +601,7 @@ function readProjects<Answer>(
|
|
|
589
601
|
notBuilt,
|
|
590
602
|
unanswered: session.unanswered,
|
|
591
603
|
componentsUnread: workspace?.componentsUnread ?? [],
|
|
604
|
+
librariesUnfound,
|
|
592
605
|
conventionsApplied: conventions.applied,
|
|
593
606
|
conventionUses: conventions.uses,
|
|
594
607
|
memberDirs,
|
|
@@ -775,6 +788,8 @@ interface ReadRun<Extra> {
|
|
|
775
788
|
readonly unanswered: readonly UnansweredQuestion[];
|
|
776
789
|
/** Per configuration whose component files the run cannot read, the reason. */
|
|
777
790
|
readonly componentsUnread: readonly ComponentsUnread[];
|
|
791
|
+
/** Per analyzed configuration, each library it names that the compiler cannot find. */
|
|
792
|
+
readonly librariesUnfound: readonly LibraryUnfound[];
|
|
778
793
|
/** The convention rows the run applied. */
|
|
779
794
|
readonly conventionsApplied: readonly AppliedConvention[];
|
|
780
795
|
/** The dependencies the applied rows' short names use. */
|
|
@@ -842,7 +857,7 @@ function readRun<Extra>(
|
|
|
842
857
|
evidence,
|
|
843
858
|
componentFiles: components.map(pathOf),
|
|
844
859
|
componentWarnings: components.flatMap((file) =>
|
|
845
|
-
readComponent(file.originalText).warnings.map((warning) => ({
|
|
860
|
+
readComponent(file.originalText, extensionOf(file.fileName)).warnings.map((warning) => ({
|
|
846
861
|
path: pathOf(file),
|
|
847
862
|
line: lineOf(file.originalText, warning.offset),
|
|
848
863
|
reason: warning.reason,
|
|
@@ -942,6 +957,7 @@ function readRun<Extra>(
|
|
|
942
957
|
notBuilt: read.notBuilt,
|
|
943
958
|
unanswered: read.unanswered,
|
|
944
959
|
componentsUnread: read.componentsUnread,
|
|
960
|
+
librariesUnfound: read.librariesUnfound,
|
|
945
961
|
conventionsApplied: read.conventionsApplied,
|
|
946
962
|
conventionUses: read.conventionUses,
|
|
947
963
|
memberDirs: read.memberDirs,
|
|
@@ -1341,6 +1357,7 @@ function readAnalysis(
|
|
|
1341
1357
|
notBuilt: read.notBuilt,
|
|
1342
1358
|
unanswered: read.unanswered,
|
|
1343
1359
|
componentsUnread: read.componentsUnread,
|
|
1360
|
+
librariesUnfound: read.librariesUnfound,
|
|
1344
1361
|
conventionsApplied: read.conventionsApplied,
|
|
1345
1362
|
},
|
|
1346
1363
|
configured: read.projects.map((one) => one.configured),
|
|
@@ -1418,6 +1435,8 @@ interface RunFacts {
|
|
|
1418
1435
|
readonly componentWarnings: readonly ComponentWarning[];
|
|
1419
1436
|
/** Per configuration whose component files the run cannot read, the reason. */
|
|
1420
1437
|
readonly componentsUnread: readonly ComponentsUnread[];
|
|
1438
|
+
/** Per analyzed configuration, each library it names that the compiler cannot find. */
|
|
1439
|
+
readonly librariesUnfound: readonly LibraryUnfound[];
|
|
1421
1440
|
/** The convention rows the run applied. */
|
|
1422
1441
|
readonly conventionsApplied: readonly AppliedConvention[];
|
|
1423
1442
|
}
|
|
@@ -1428,6 +1447,13 @@ interface ComponentsUnread {
|
|
|
1428
1447
|
readonly reason: string;
|
|
1429
1448
|
}
|
|
1430
1449
|
|
|
1450
|
+
/** One library an analyzed configuration names that the compiler cannot find. */
|
|
1451
|
+
interface LibraryUnfound {
|
|
1452
|
+
readonly configFile: string;
|
|
1453
|
+
/** The compiler's error, rendered as a dropped configuration's is. */
|
|
1454
|
+
readonly error: string;
|
|
1455
|
+
}
|
|
1456
|
+
|
|
1431
1457
|
/** The findings of one run, beside what a report states about the run itself. */
|
|
1432
1458
|
export interface RunAnalysis {
|
|
1433
1459
|
readonly result: PassResult;
|
package/src/component-files.ts
CHANGED
|
@@ -188,18 +188,25 @@ function scriptTagAt(text: string, offset: number): boolean {
|
|
|
188
188
|
);
|
|
189
189
|
}
|
|
190
190
|
|
|
191
|
+
/** Where the braces opening at one offset end, and whether a `}` matched them. */
|
|
192
|
+
interface Braces {
|
|
193
|
+
/** Past the matching `}`, or where the scan stopped: a line-start `<script` or the end. */
|
|
194
|
+
readonly end: number;
|
|
195
|
+
readonly closed: boolean;
|
|
196
|
+
}
|
|
197
|
+
|
|
191
198
|
/**
|
|
192
|
-
* The
|
|
193
|
-
*
|
|
194
|
-
*
|
|
199
|
+
* The braces opening at `from`, quoted strings skipped. The scan stops unclosed at the
|
|
200
|
+
* end, or before a `<script` start tag that begins a line, so a stray brace in markup
|
|
201
|
+
* never hides a block.
|
|
195
202
|
*/
|
|
196
|
-
function
|
|
203
|
+
function scanBraces(text: string, from: number): Braces {
|
|
197
204
|
let depth = 0;
|
|
198
205
|
let at = from;
|
|
199
206
|
while (at < text.length) {
|
|
200
207
|
const code = text.charCodeAt(at);
|
|
201
208
|
if (scriptTagAt(text, at)) {
|
|
202
|
-
return
|
|
209
|
+
return { end: at, closed: false };
|
|
203
210
|
}
|
|
204
211
|
if (code === 0x22 || code === 0x27 || code === 0x60) {
|
|
205
212
|
at = pastQuoted(text, at);
|
|
@@ -210,12 +217,59 @@ function pastBraces(text: string, from: number): number {
|
|
|
210
217
|
} else if (code === 0x7d) {
|
|
211
218
|
depth -= 1;
|
|
212
219
|
if (depth === 0) {
|
|
213
|
-
return at + 1;
|
|
220
|
+
return { end: at + 1, closed: true };
|
|
214
221
|
}
|
|
215
222
|
}
|
|
216
223
|
at += 1;
|
|
217
224
|
}
|
|
218
|
-
return
|
|
225
|
+
return { end: text.length, closed: false };
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/** The offset past the `}` matching the `{` at `from`, or -1 where {@link scanBraces} finds none. */
|
|
229
|
+
function pastBraces(text: string, from: number): number {
|
|
230
|
+
const braces = scanBraces(text, from);
|
|
231
|
+
return braces.closed ? braces.end : -1;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/** How one framework's parser reads the forms the frameworks disagree on. */
|
|
235
|
+
interface Dialect {
|
|
236
|
+
/** Whether `<!-->`, `<!--->` and `--!>` end a comment, the opening dashes counted. */
|
|
237
|
+
readonly shortComments: boolean;
|
|
238
|
+
/** Whether a start tag's unmatched `{` is a character of an attribute name or value. */
|
|
239
|
+
readonly braceIsText: boolean;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* The dialect each framework's parser reads: Vue's tokenizer, Svelte's parser and Astro's
|
|
244
|
+
* tokenizer, the last for any other extension too. `.vue` also closes `--!>`, which Vue
|
|
245
|
+
* does not, so a block Vue drops there is still read.
|
|
246
|
+
* https://github.com/vuejs/core/blob/main/packages/compiler-core/src/tokenizer.ts
|
|
247
|
+
* https://github.com/sveltejs/svelte/blob/main/packages/svelte/src/compiler/phases/1-parse/state/element.js
|
|
248
|
+
* https://github.com/withastro/compiler/blob/main/internal/token.go
|
|
249
|
+
*/
|
|
250
|
+
function dialectOf(extension: string): Dialect {
|
|
251
|
+
switch (extension.toLowerCase()) {
|
|
252
|
+
case ".svelte":
|
|
253
|
+
return { shortComments: false, braceIsText: false };
|
|
254
|
+
case ".vue":
|
|
255
|
+
return { shortComments: true, braceIsText: true };
|
|
256
|
+
default:
|
|
257
|
+
return { shortComments: true, braceIsText: false };
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/** The offset past the comment whose `<!--` is at `lt`, or the text's length where none ends it. */
|
|
262
|
+
function pastComment(text: string, lt: number, dialect: Dialect): number {
|
|
263
|
+
if (!dialect.shortComments) {
|
|
264
|
+
const close = text.indexOf("-->", lt + 4);
|
|
265
|
+
return close < 0 ? text.length : close + 3;
|
|
266
|
+
}
|
|
267
|
+
const dashes = text.indexOf("-->", lt + 2);
|
|
268
|
+
const bang = text.indexOf("--!>", lt + 2);
|
|
269
|
+
if (bang >= 0 && (dashes < 0 || bang < dashes)) {
|
|
270
|
+
return bang + 4;
|
|
271
|
+
}
|
|
272
|
+
return dashes < 0 ? text.length : dashes + 3;
|
|
219
273
|
}
|
|
220
274
|
|
|
221
275
|
interface Attribute {
|
|
@@ -234,17 +288,43 @@ interface StartTag {
|
|
|
234
288
|
readonly attributes: readonly Attribute[];
|
|
235
289
|
}
|
|
236
290
|
|
|
237
|
-
/**
|
|
238
|
-
|
|
291
|
+
/**
|
|
292
|
+
* Where an unmatched `{` of a start tag ends its braced value: at the first `>`, or where
|
|
293
|
+
* the brace scan stopped where that comes first.
|
|
294
|
+
*/
|
|
295
|
+
function unmatchedBraceEnd(text: string, from: number, braces: Braces): number {
|
|
296
|
+
const close = text.indexOf(">", from);
|
|
297
|
+
return close >= 0 && close < braces.end ? close : braces.end;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* The start tag whose `<` is at `from`, to its `>`. A quoted or braced value may hold `>`.
|
|
302
|
+
* Past an unmatched `{`, the tag also ends before a `<script` start tag that begins a line.
|
|
303
|
+
*/
|
|
304
|
+
function readStartTag(text: string, from: number, nameStop: number, dialect: Dialect): StartTag {
|
|
239
305
|
const name = text.slice(from + 1, nameStop).toLowerCase();
|
|
240
306
|
const attributes: Attribute[] = [];
|
|
241
307
|
let at = nameStop;
|
|
242
308
|
let selfClosing = false;
|
|
309
|
+
let unmatched = false;
|
|
310
|
+
/** The braces opening at `at`, and whether they make a braced value rather than text. */
|
|
311
|
+
const bracesAt = (): { readonly braces: Braces; readonly value: boolean } | undefined => {
|
|
312
|
+
if (text.charCodeAt(at) !== 0x7b) {
|
|
313
|
+
return undefined;
|
|
314
|
+
}
|
|
315
|
+
const braces = scanBraces(text, at);
|
|
316
|
+
return { braces, value: braces.closed || !dialect.braceIsText };
|
|
317
|
+
};
|
|
318
|
+
const bracedEnd = (braces: Braces): number =>
|
|
319
|
+
braces.closed ? braces.end : unmatchedBraceEnd(text, at, braces);
|
|
243
320
|
while (at < text.length) {
|
|
244
321
|
const code = text.charCodeAt(at);
|
|
245
322
|
if (code === 0x3e) {
|
|
246
323
|
return { name, end: at + 1, selfClosing, attributes };
|
|
247
324
|
}
|
|
325
|
+
if (unmatched && scriptTagAt(text, at)) {
|
|
326
|
+
return { name, end: at, selfClosing, attributes };
|
|
327
|
+
}
|
|
248
328
|
if (isSpace(code)) {
|
|
249
329
|
at += 1;
|
|
250
330
|
continue;
|
|
@@ -256,12 +336,13 @@ function readStartTag(text: string, from: number, nameStop: number): StartTag {
|
|
|
256
336
|
}
|
|
257
337
|
selfClosing = false;
|
|
258
338
|
const start = at;
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
339
|
+
const bare = bracesAt();
|
|
340
|
+
unmatched ||= bare?.braces.closed === false;
|
|
341
|
+
if (bare?.value === true) {
|
|
342
|
+
at = bracedEnd(bare.braces);
|
|
262
343
|
attributes.push({
|
|
263
344
|
name: "",
|
|
264
|
-
value: text.slice(start + 1, at - 1),
|
|
345
|
+
value: text.slice(start + 1, bare.braces.closed ? at - 1 : at),
|
|
265
346
|
start,
|
|
266
347
|
end: at,
|
|
267
348
|
valueStart: start + 1,
|
|
@@ -287,16 +368,18 @@ function readStartTag(text: string, from: number, nameStop: number): StartTag {
|
|
|
287
368
|
at += 1;
|
|
288
369
|
}
|
|
289
370
|
const opening = text.charCodeAt(at);
|
|
371
|
+
const braced = bracesAt();
|
|
372
|
+
unmatched ||= braced?.braces.closed === false;
|
|
290
373
|
if (opening === 0x22 || opening === 0x27) {
|
|
291
|
-
|
|
374
|
+
// An attribute value has no escapes: a backslash before its quote is text.
|
|
375
|
+
const close = text.indexOf(text.charAt(at), at + 1);
|
|
292
376
|
valueStart = at + 1;
|
|
293
|
-
value = text.slice(at + 1,
|
|
294
|
-
at =
|
|
295
|
-
} else if (
|
|
296
|
-
const
|
|
297
|
-
const stop = past < 0 ? text.length : past;
|
|
377
|
+
value = text.slice(at + 1, close < 0 ? text.length : close);
|
|
378
|
+
at = close < 0 ? text.length : close + 1;
|
|
379
|
+
} else if (braced?.value === true) {
|
|
380
|
+
const stop = bracedEnd(braced.braces);
|
|
298
381
|
valueStart = at + 1;
|
|
299
|
-
value = text.slice(at + 1,
|
|
382
|
+
value = text.slice(at + 1, braced.braces.closed ? stop - 1 : stop);
|
|
300
383
|
at = stop;
|
|
301
384
|
} else {
|
|
302
385
|
valueStart = at;
|
|
@@ -410,9 +493,11 @@ function scriptsInside(text: string, from: number, to: number): number[] {
|
|
|
410
493
|
* The scan keeps a stack of open elements. A comment, a doctype or processing instruction and
|
|
411
494
|
* an expression in braces are text, and an end tag closes back to the nearest open element of
|
|
412
495
|
* its name. A raw-text element at the top level runs to its own end tag, and only a `script`
|
|
413
|
-
* element whose start tag begins a line at the top level is a block.
|
|
496
|
+
* element whose start tag begins a line at the top level is a block. The file's extension
|
|
497
|
+
* names the framework whose reading of a comment and of a stray brace applies.
|
|
414
498
|
*/
|
|
415
|
-
export function readComponent(text: string): ComponentFile {
|
|
499
|
+
export function readComponent(text: string, extension: string): ComponentFile {
|
|
500
|
+
const dialect = dialectOf(extension);
|
|
416
501
|
const blocks: Block[] = [];
|
|
417
502
|
const imports: SourceImport[] = [];
|
|
418
503
|
const warnings: ComponentWarning[] = [];
|
|
@@ -442,8 +527,7 @@ export function readComponent(text: string): ComponentFile {
|
|
|
442
527
|
}
|
|
443
528
|
const lt = at;
|
|
444
529
|
if (text.startsWith("<!--", lt)) {
|
|
445
|
-
|
|
446
|
-
at = close < 0 ? text.length : close + 3;
|
|
530
|
+
at = pastComment(text, lt, dialect);
|
|
447
531
|
continue;
|
|
448
532
|
}
|
|
449
533
|
const next = text.charCodeAt(lt + 1);
|
|
@@ -467,7 +551,7 @@ export function readComponent(text: string): ComponentFile {
|
|
|
467
551
|
at = lt + 1;
|
|
468
552
|
continue;
|
|
469
553
|
}
|
|
470
|
-
const tag = readStartTag(text, lt, nameStop);
|
|
554
|
+
const tag = readStartTag(text, lt, nameStop, dialect);
|
|
471
555
|
const rawText = open.length === 0 && RAW_TEXT.has(tag.name);
|
|
472
556
|
for (const attribute of rawText ? [] : tag.attributes) {
|
|
473
557
|
if (attribute.value !== "") {
|
|
@@ -542,9 +626,9 @@ export function readComponent(text: string): ComponentFile {
|
|
|
542
626
|
}
|
|
543
627
|
|
|
544
628
|
/**
|
|
545
|
-
* Whether the module's text exports a default at its top level: an `export default
|
|
546
|
-
* or an export clause naming `default`, outside every comment,
|
|
547
|
-
* regular expression, and outside every bracket.
|
|
629
|
+
* Whether the module's text exports a default at its top level: an `export default`, an
|
|
630
|
+
* `export * as default`, or an export clause naming `default`, outside every comment,
|
|
631
|
+
* string, template and regular expression, and outside every bracket.
|
|
548
632
|
*/
|
|
549
633
|
export function exportsDefault(text: string): boolean {
|
|
550
634
|
const words: string[] = [];
|
|
@@ -633,7 +717,8 @@ export function exportsDefault(text: string): boolean {
|
|
|
633
717
|
const matched = word.exec(text);
|
|
634
718
|
if (matched !== null) {
|
|
635
719
|
const name = matched[0];
|
|
636
|
-
|
|
720
|
+
const starAs = words.at(-3) === "export" && words.at(-2) === "*" && words.at(-1) === "as";
|
|
721
|
+
if (depth === 0 && (words.at(-1) === "export" || starAs) && name === "default") {
|
|
637
722
|
return true;
|
|
638
723
|
}
|
|
639
724
|
if (clause && name === "default") {
|
|
@@ -662,8 +747,8 @@ export function exportsDefault(text: string): boolean {
|
|
|
662
747
|
}
|
|
663
748
|
|
|
664
749
|
/** The module one component file's text is read as, and the span map back to the file. */
|
|
665
|
-
export function componentModule(text: string): ComponentModule {
|
|
666
|
-
const { blocks, imports } = readComponent(text);
|
|
750
|
+
export function componentModule(text: string, fileExtension: string): ComponentModule {
|
|
751
|
+
const { blocks, imports } = readComponent(text, fileExtension);
|
|
667
752
|
const out: string[] = [];
|
|
668
753
|
let at = 0;
|
|
669
754
|
let language: BlockLanguage | undefined;
|
|
@@ -278,8 +278,15 @@ function readDocument(host: Host, path: string): unknown {
|
|
|
278
278
|
|
|
279
279
|
/** The configuration files one project holds and sits beside. */
|
|
280
280
|
interface ConfigurationFiles {
|
|
281
|
-
/**
|
|
282
|
-
|
|
281
|
+
/**
|
|
282
|
+
* Every own file that is a configuration module, with the form its name has and the
|
|
283
|
+
* directory of the manifest whose dependencies its strings are read against.
|
|
284
|
+
*/
|
|
285
|
+
readonly modules: readonly {
|
|
286
|
+
readonly file: SourceFile;
|
|
287
|
+
readonly form: string;
|
|
288
|
+
readonly manifestDir: string;
|
|
289
|
+
}[];
|
|
283
290
|
/**
|
|
284
291
|
* Every configuration module in a directory of the project that the program does not
|
|
285
292
|
* hold, and every file such a file imports or re-exports by a relative specifier that
|
|
@@ -345,12 +352,13 @@ export function configurationFiles(
|
|
|
345
352
|
return held;
|
|
346
353
|
};
|
|
347
354
|
|
|
348
|
-
const modules: { file: SourceFile; form: string }[] = [];
|
|
355
|
+
const modules: { file: SourceFile; form: string; manifestDir: string }[] = [];
|
|
349
356
|
const dirs = new Set<string>([root]);
|
|
350
357
|
for (const file of files.byPath.values()) {
|
|
351
358
|
const form = configurationModuleForm(basename(file.fileName));
|
|
352
|
-
|
|
353
|
-
|
|
359
|
+
const fileDir = dirnamePath(file.fileName);
|
|
360
|
+
if (form !== undefined && holdsManifest(fileDir)) {
|
|
361
|
+
modules.push({ file, form, manifestDir: fileDir });
|
|
354
362
|
}
|
|
355
363
|
for (
|
|
356
364
|
let dir = dirnamePath(file.fileName);
|
|
@@ -383,7 +391,7 @@ export function configurationFiles(
|
|
|
383
391
|
if (path.endsWith(".json")) {
|
|
384
392
|
pending.push({ path, manifestDir: dir });
|
|
385
393
|
} else if (held !== undefined) {
|
|
386
|
-
modules.push({ file: held, form: "a script's configuration flag" });
|
|
394
|
+
modules.push({ file: held, form: "a script's configuration flag", manifestDir: dir });
|
|
387
395
|
} else {
|
|
388
396
|
named.push({ path, manifestDir: dir });
|
|
389
397
|
}
|
|
@@ -20,7 +20,7 @@ import { SymbolFlags, type NodeHandle, type Symbol as TSSymbol } from "@typescri
|
|
|
20
20
|
import type { AliasChains } from "./alias-chain.ts";
|
|
21
21
|
import { resolvedTargets } from "./calls.ts";
|
|
22
22
|
import type { Config, DeclarationEntry } from "./config.ts";
|
|
23
|
-
import type
|
|
23
|
+
import { declaredComponent, type Inventory } from "./inventory.ts";
|
|
24
24
|
import { must, Unanswerable } from "./query.ts";
|
|
25
25
|
import { nameComponent } from "./ref.ts";
|
|
26
26
|
import type { ProjectView } from "./session.ts";
|
|
@@ -118,15 +118,44 @@ function candidatesOf<Brand>(
|
|
|
118
118
|
];
|
|
119
119
|
}
|
|
120
120
|
|
|
121
|
-
/**
|
|
121
|
+
/**
|
|
122
|
+
* The properties of the type one container declares, or of its constructor's type for a
|
|
123
|
+
* static member. A member a symbol keys is bound to that type later than the container's
|
|
124
|
+
* symbol table is built, so the table does not hold it.
|
|
125
|
+
*/
|
|
126
|
+
function typeMembersOf<Brand>(
|
|
127
|
+
project: ProjectView<Brand>,
|
|
128
|
+
container: TSSymbol,
|
|
129
|
+
isStatic: boolean,
|
|
130
|
+
): readonly TSSymbol[] {
|
|
131
|
+
const queries = project.queries;
|
|
132
|
+
const type = must(isStatic ? queries.typeOfSymbol(container) : queries.declaredTypeOf(container));
|
|
133
|
+
return type === undefined ? [] : must(queries.propertiesOf(type));
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* The member of one container a path component spells, followed through any alias. A
|
|
138
|
+
* component in brackets is a computed key, compared with the key each declaration of a
|
|
139
|
+
* candidate writes: the checker names a member a symbol keys by no spelling of its own.
|
|
140
|
+
*/
|
|
122
141
|
function memberOf<Brand>(
|
|
123
142
|
project: ProjectView<Brand>,
|
|
124
143
|
container: TSSymbol,
|
|
125
144
|
component: string,
|
|
126
145
|
isStatic: boolean,
|
|
127
146
|
): TSSymbol | undefined {
|
|
128
|
-
const
|
|
129
|
-
|
|
147
|
+
const computed = component.startsWith("[");
|
|
148
|
+
const candidates = candidatesOf(project, container, isStatic);
|
|
149
|
+
const member = (
|
|
150
|
+
computed ? [...candidates, ...typeMembersOf(project, container, isStatic)] : candidates
|
|
151
|
+
).find(
|
|
152
|
+
(candidate) =>
|
|
153
|
+
nameComponent(candidate.name) === component ||
|
|
154
|
+
(computed &&
|
|
155
|
+
candidate.declarations.some((handle) => {
|
|
156
|
+
const node = project.declarationAt(handle)?.node;
|
|
157
|
+
return node !== undefined && declaredComponent(node) === component;
|
|
158
|
+
})),
|
|
130
159
|
);
|
|
131
160
|
return member === undefined ? undefined : unaliased(project, member);
|
|
132
161
|
}
|
|
@@ -4,7 +4,7 @@ export const RECORD = {
|
|
|
4
4
|
conformance: {
|
|
5
5
|
corpusVersion: "5.2.0",
|
|
6
6
|
result: "pass",
|
|
7
|
-
digest: "sha256:
|
|
7
|
+
digest: "sha256:012872542679539ec105f49b5ee502b0a9fa35c6bbd9ef3983b5f6e53af83d8e",
|
|
8
8
|
},
|
|
9
9
|
gaps: [],
|
|
10
10
|
} as const;
|