@cplieger/deadset-ts 8.0.0-dev.6 → 8.0.0-dev.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin/mapper-connection.js +2 -1
- package/dist/bin/node-host.js +24 -5
- package/dist/src/analysis.js +37 -56
- package/dist/src/automatic-types.js +10 -29
- package/dist/src/build-scripts.js +17 -29
- package/dist/src/component-layer.js +4 -2
- package/dist/src/components.js +2 -3
- package/dist/src/config.js +6 -82
- package/dist/src/configuration-files.js +27 -168
- package/dist/src/configured-declarations.js +15 -20
- package/dist/src/consumers.js +2 -2
- package/dist/src/contract/config.generated.js +544 -0
- package/dist/src/contract/contract.generated.js +13 -0
- package/dist/src/contract/report.generated.js +3 -2
- package/dist/src/conventions.js +20 -60
- package/dist/src/dependencies.js +25 -43
- package/dist/src/discover.js +14 -40
- package/dist/src/edges.js +38 -122
- package/dist/src/element-access.js +4 -6
- package/dist/src/entry-point-gaps.js +2 -8
- package/dist/src/entry-points.js +3 -10
- package/dist/src/enum-group.js +3 -10
- package/dist/src/erased-stores.js +4 -6
- package/dist/src/exempt-classes.js +0 -4
- package/dist/src/exempt.js +6 -22
- package/dist/src/file-facts.js +2 -25
- package/dist/src/findings/completion.js +5 -4
- package/dist/src/findings/emitters.js +1 -1
- package/dist/src/findings/intra-function.js +1 -1
- package/dist/src/findings/non-code-artifacts.js +2 -2
- package/dist/src/findings/reads-and-writes.js +2 -1
- package/dist/src/findings/self-check.js +3 -2
- package/dist/src/findings/unused-declarations.js +1 -1
- package/dist/src/findings/visibility-narrowing.js +2 -2
- package/dist/src/findings-pass.js +7 -12
- package/dist/src/framework-lifecycle.js +7 -7
- package/dist/src/fs/document.js +59 -0
- package/dist/src/fs/manifest.js +43 -0
- package/dist/src/fs/tree.js +88 -0
- package/dist/src/git-ignore.js +34 -25
- package/dist/src/html-entries.js +6 -33
- package/dist/src/in-operands.js +2 -1
- package/dist/src/injection-container.js +1 -1
- package/dist/src/intra-function-parts.js +10 -13
- package/dist/src/inventory.js +2 -21
- package/dist/src/json-document.js +198 -67
- package/dist/src/keyof-comparisons.js +4 -6
- package/dist/src/kinds.js +0 -7
- package/dist/src/manifest.js +5 -43
- package/dist/src/mdx-entries.js +3 -26
- package/dist/src/partial.js +2 -1
- package/dist/src/paths.js +26 -15
- package/dist/src/position.js +2 -1
- package/dist/src/print.js +11 -79
- package/dist/src/product.js +13 -0
- package/dist/src/references.js +14 -27
- package/dist/src/report-schema.js +3 -225
- package/dist/src/report.js +22 -31
- package/dist/src/reporters/sarif.js +4 -2
- package/dist/src/reporters/template.js +4 -19
- package/dist/src/resolve.js +245 -724
- package/dist/src/roots.js +7 -17
- package/dist/src/run.js +55 -61
- package/dist/src/schema.js +320 -343
- package/dist/src/scope.js +34 -103
- package/dist/src/serialization-contract.js +5 -9
- package/dist/src/session.js +2 -1
- package/dist/src/suppress-file.js +21 -25
- package/dist/src/suppress.js +3 -3
- package/dist/src/symbol-keys.js +3 -5
- package/dist/src/template-field.js +9 -19
- package/dist/src/test-globals.js +6 -28
- package/dist/src/test-joins.js +6 -6
- package/dist/src/text/compare.js +26 -0
- package/dist/src/text/glob.js +194 -0
- package/dist/src/text/shell.js +159 -0
- package/dist/src/{yaml-document.js → text/yaml.js} +38 -0
- package/dist/src/type-errors.js +14 -38
- package/dist/src/unseen-loads.js +2 -9
- package/dist/src/unwrap.js +23 -0
- package/dist/src/usage.js +10 -0
- package/dist/src/verbs/analyze.js +25 -26
- package/dist/src/verbs/describe.js +4 -4
- package/dist/src/verbs/explain.js +8 -6
- package/dist/src/verbs/print-projects.js +4 -3
- package/dist/src/verbs/print-retained.js +2 -1
- package/dist/src/verbs/print-roots.js +2 -1
- package/dist/src/verbs/version.js +2 -1
- package/dist/src/workflow-steps.js +9 -46
- package/dist/src/workspace-manifests.js +2 -2
- package/dist/src/workspace-programs.js +5 -3
- package/dist/src/workspace-resolution.js +13 -17
- package/dist/src/workspace.js +28 -116
- package/jsr.json +1 -1
- package/package.json +1 -1
- package/src/analysis.ts +55 -76
- package/src/automatic-types.ts +11 -29
- package/src/build-scripts.ts +17 -38
- package/src/component-layer.ts +4 -2
- package/src/components.ts +2 -4
- package/src/config.ts +31 -296
- package/src/configuration-files.ts +27 -170
- package/src/configured-declarations.ts +18 -23
- package/src/consumers.ts +4 -4
- package/src/contract/config.generated.ts +798 -0
- package/src/contract/contract.generated.ts +25 -0
- package/src/contract/report.generated.ts +3 -2
- package/src/conventions.ts +26 -74
- package/src/dependencies.ts +32 -50
- package/src/discover.ts +15 -40
- package/src/edges.ts +41 -140
- package/src/element-access.ts +4 -7
- package/src/entry-point-gaps.ts +3 -9
- package/src/entry-points.ts +2 -13
- package/src/enum-group.ts +2 -11
- package/src/erased-stores.ts +3 -7
- package/src/exempt-classes.ts +0 -5
- package/src/exempt.ts +8 -30
- package/src/file-facts.ts +6 -24
- package/src/findings/completion.ts +5 -6
- package/src/findings/emitter.ts +2 -2
- package/src/findings/emitters.ts +1 -1
- package/src/findings/intra-function.ts +1 -1
- package/src/findings/non-code-artifacts.ts +4 -4
- package/src/findings/reads-and-writes.ts +2 -1
- package/src/findings/self-check.ts +3 -2
- package/src/findings/unused-declarations.ts +1 -1
- package/src/findings/visibility-narrowing.ts +2 -2
- package/src/findings-pass.ts +9 -15
- package/src/framework-lifecycle.ts +7 -7
- package/src/fs/document.ts +74 -0
- package/src/fs/manifest.ts +52 -0
- package/src/fs/tree.ts +110 -0
- package/src/git-ignore.ts +38 -25
- package/src/host.ts +12 -5
- package/src/html-entries.ts +6 -33
- package/src/in-operands.ts +2 -1
- package/src/injection-container.ts +1 -1
- package/src/intra-function-parts.ts +10 -14
- package/src/inventory.ts +2 -20
- package/src/json-document.ts +241 -76
- package/src/keyof-comparisons.ts +3 -7
- package/src/kinds.ts +0 -8
- package/src/manifest.ts +5 -46
- package/src/mdx-entries.ts +3 -26
- package/src/partial.ts +2 -1
- package/src/paths.ts +27 -15
- package/src/position.ts +2 -1
- package/src/print.ts +12 -92
- package/src/product.ts +18 -0
- package/src/references.ts +14 -29
- package/src/report-schema.ts +3 -301
- package/src/report.ts +24 -34
- package/src/reporters/sarif.ts +4 -4
- package/src/reporters/template.ts +4 -20
- package/src/resolve.ts +283 -1114
- package/src/roots.ts +7 -20
- package/src/run.ts +57 -75
- package/src/schema.ts +391 -388
- package/src/scope.ts +40 -126
- package/src/serialization-contract.ts +5 -10
- package/src/session.ts +2 -1
- package/src/suppress-file.ts +21 -29
- package/src/suppress.ts +3 -3
- package/src/symbol-keys.ts +2 -5
- package/src/template-field.ts +15 -25
- package/src/test-globals.ts +9 -27
- package/src/test-joins.ts +6 -8
- package/src/text/compare.ts +27 -0
- package/src/text/glob.ts +210 -0
- package/src/text/shell.ts +161 -0
- package/src/{yaml-document.ts → text/yaml.ts} +46 -0
- package/src/type-errors.ts +25 -36
- package/src/unseen-loads.ts +1 -12
- package/src/unwrap.ts +34 -0
- package/src/usage.ts +10 -0
- package/src/verbs/analyze.ts +32 -42
- package/src/verbs/describe.ts +4 -4
- package/src/verbs/explain.ts +8 -10
- package/src/verbs/print-projects.ts +4 -3
- package/src/verbs/print-retained.ts +2 -1
- package/src/verbs/print-roots.ts +2 -1
- package/src/verbs/verb.ts +2 -2
- package/src/verbs/version.ts +2 -1
- package/src/workflow-steps.ts +9 -44
- package/src/workspace-manifests.ts +2 -2
- package/src/workspace-programs.ts +5 -3
- package/src/workspace-resolution.ts +21 -17
- package/src/workspace.ts +35 -121
- package/dist/src/glob.js +0 -65
- package/dist/src/pnpm-workspace.js +0 -120
- package/src/glob.ts +0 -67
- package/src/pnpm-workspace.ts +0 -135
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Two strings in the order of their UTF-8 bytes, the order every set of a run is read in.
|
|
3
|
+
* That is code point order, which UTF-16 code unit order (`<` on strings) breaks above
|
|
4
|
+
* U+FFFF: a surrogate unit is below U+E000 while the code point it encodes is above U+FFFF.
|
|
5
|
+
*/
|
|
6
|
+
export function bytewise(a: string, b: string): number {
|
|
7
|
+
if (a === b) {
|
|
8
|
+
return 0;
|
|
9
|
+
}
|
|
10
|
+
const length = Math.min(a.length, b.length);
|
|
11
|
+
for (let at = 0; at < length; at += 1) {
|
|
12
|
+
const x = a.charCodeAt(at);
|
|
13
|
+
const y = b.charCodeAt(at);
|
|
14
|
+
if (x !== y) {
|
|
15
|
+
return ranked(x) < ranked(y) ? -1 : 1;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
return a.length < b.length ? -1 : 1;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/** One UTF-16 unit moved so that a surrogate ranks above every unit of U+E000 to U+FFFF. */
|
|
22
|
+
function ranked(unit: number): number {
|
|
23
|
+
if (unit >= 0xe000) {
|
|
24
|
+
return unit - 0x800;
|
|
25
|
+
}
|
|
26
|
+
return unit >= 0xd800 ? unit + 0x2000 : unit;
|
|
27
|
+
}
|
package/src/text/glob.ts
ADDED
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Glob patterns over paths in two forms. The configured form (the configuration, convention
|
|
3
|
+
* rows, bundler entries, workspace member patterns) has `*` and `?` within a segment, `**`
|
|
4
|
+
* across segments and brace lists. A `.gitignore` line has git's form: bracket expressions
|
|
5
|
+
* and backslash escapes, no brace lists (https://git-scm.com/docs/gitignore#_pattern_format).
|
|
6
|
+
* A configured root is not a glob: it names a symbol reference, and is read where it is used.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
type GlobForm = "configured" | "gitignore";
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* One character of a pattern as the expression syntax reads it literally. The set escaped
|
|
13
|
+
* is closed: under the unicode flag an escape of any other character is refused outright.
|
|
14
|
+
*/
|
|
15
|
+
function quoted(character: string): string {
|
|
16
|
+
return /[.*+?^${}()|[\]\\/]/u.test(character) ? `\\${character}` : character;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* The ASCII ranges each POSIX class a bracket expression may name holds, as git's own
|
|
21
|
+
* character table reads them, each range written as its first and last character.
|
|
22
|
+
*/
|
|
23
|
+
const CLASSES: ReadonlyMap<string, string> = new Map([
|
|
24
|
+
["alnum", "09AZaz"],
|
|
25
|
+
["alpha", "AZaz"],
|
|
26
|
+
["blank", "\t\t "],
|
|
27
|
+
["cntrl", "\0\x1f\x7f\x7f"],
|
|
28
|
+
["digit", "09"],
|
|
29
|
+
["graph", "!~"],
|
|
30
|
+
["lower", "az"],
|
|
31
|
+
["print", " ~"],
|
|
32
|
+
["punct", "!/:@[`{~"],
|
|
33
|
+
["space", "\t\n\r\r "],
|
|
34
|
+
["upper", "AZ"],
|
|
35
|
+
["xdigit", "09AFaf"],
|
|
36
|
+
]);
|
|
37
|
+
|
|
38
|
+
/** What a pattern git cannot read matches: nothing, as git aborts the whole match. */
|
|
39
|
+
const NOTHING = "(?!)";
|
|
40
|
+
|
|
41
|
+
/** One code point as a member of an expression class. */
|
|
42
|
+
function member(point: number): string {
|
|
43
|
+
return `\\u{${point.toString(16)}}`;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* The class a bracket expression opening at `open` denotes, and the index past its `]`,
|
|
48
|
+
* read as git's wildmatch reads it: a leading `!` or `^` negates it, a `]` first is a
|
|
49
|
+
* member, `\` escapes one character, `x-y` is a range (one whose `y` is below `x` holds
|
|
50
|
+
* `x` alone) and `[:name:]` a POSIX class. An unclosed bracket or an unknown class name
|
|
51
|
+
* matches nothing. Neither form matches the solidus.
|
|
52
|
+
*/
|
|
53
|
+
function bracket(pattern: string, open: number): { source: string; end: number } {
|
|
54
|
+
let at = open + 1;
|
|
55
|
+
const negated = pattern[at] === "!" || pattern[at] === "^";
|
|
56
|
+
if (negated) {
|
|
57
|
+
at += 1;
|
|
58
|
+
}
|
|
59
|
+
const next = (): number | undefined => {
|
|
60
|
+
const point = pattern.codePointAt(at);
|
|
61
|
+
at += point !== undefined && point > 0xffff ? 2 : 1;
|
|
62
|
+
return point;
|
|
63
|
+
};
|
|
64
|
+
let members = "";
|
|
65
|
+
let low: number | undefined;
|
|
66
|
+
for (let first = true; ; first = false) {
|
|
67
|
+
let point = next();
|
|
68
|
+
if (point === undefined) {
|
|
69
|
+
return { source: NOTHING, end: pattern.length };
|
|
70
|
+
}
|
|
71
|
+
if (point === 0x5d && !first) {
|
|
72
|
+
return { source: negated ? `[^/${members}]` : `(?!/)[${members}]`, end: at };
|
|
73
|
+
}
|
|
74
|
+
if (point === 0x2d && low !== undefined && at < pattern.length && pattern[at] !== "]") {
|
|
75
|
+
let high = next();
|
|
76
|
+
if (high === 0x5c) {
|
|
77
|
+
high = next();
|
|
78
|
+
}
|
|
79
|
+
if (high === undefined) {
|
|
80
|
+
return { source: NOTHING, end: pattern.length };
|
|
81
|
+
}
|
|
82
|
+
members += high >= low ? `${member(low)}-${member(high)}` : "";
|
|
83
|
+
low = undefined;
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
if (point === 0x5b && pattern[at] === ":") {
|
|
87
|
+
const close = pattern.indexOf("]", at + 1);
|
|
88
|
+
if (close < 0) {
|
|
89
|
+
return { source: NOTHING, end: pattern.length };
|
|
90
|
+
}
|
|
91
|
+
if (close > at + 1 && pattern[close - 1] === ":") {
|
|
92
|
+
const ranges = CLASSES.get(pattern.slice(at + 1, close - 1));
|
|
93
|
+
if (ranges === undefined) {
|
|
94
|
+
return { source: NOTHING, end: pattern.length };
|
|
95
|
+
}
|
|
96
|
+
for (let one = 0; one < ranges.length; one += 2) {
|
|
97
|
+
members += `${member(ranges.charCodeAt(one))}-${member(ranges.charCodeAt(one + 1))}`;
|
|
98
|
+
}
|
|
99
|
+
low = undefined;
|
|
100
|
+
at = close + 1;
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
if (point === 0x5c) {
|
|
105
|
+
point = next();
|
|
106
|
+
if (point === undefined) {
|
|
107
|
+
return { source: NOTHING, end: pattern.length };
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
members += member(point);
|
|
111
|
+
low = point;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/** The expression source of one pattern with no brace list. */
|
|
116
|
+
function source(pattern: string, form: GlobForm): string {
|
|
117
|
+
let text = "";
|
|
118
|
+
let at = 0;
|
|
119
|
+
while (at < pattern.length) {
|
|
120
|
+
if (pattern.startsWith("**/", at)) {
|
|
121
|
+
text += "(?:[^/]*/)*";
|
|
122
|
+
at += 3;
|
|
123
|
+
continue;
|
|
124
|
+
}
|
|
125
|
+
if (pattern.startsWith("**", at)) {
|
|
126
|
+
text += ".*";
|
|
127
|
+
at += 2;
|
|
128
|
+
continue;
|
|
129
|
+
}
|
|
130
|
+
const character = pattern[at] ?? "";
|
|
131
|
+
if (form === "gitignore" && character === "[") {
|
|
132
|
+
const read = bracket(pattern, at);
|
|
133
|
+
text += read.source;
|
|
134
|
+
at = read.end;
|
|
135
|
+
continue;
|
|
136
|
+
}
|
|
137
|
+
if (form === "gitignore" && character === "\\" && at + 1 < pattern.length) {
|
|
138
|
+
text += quoted(pattern[at + 1] ?? "");
|
|
139
|
+
at += 2;
|
|
140
|
+
continue;
|
|
141
|
+
}
|
|
142
|
+
at += 1;
|
|
143
|
+
text += character === "*" ? "[^/]*" : character === "?" ? "[^/]" : quoted(character);
|
|
144
|
+
}
|
|
145
|
+
return text;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/** A pattern with every brace list expanded into its alternatives; a lone `{` stands for itself. */
|
|
149
|
+
export function braceExpanded(pattern: string): readonly string[] {
|
|
150
|
+
const open = pattern.indexOf("{");
|
|
151
|
+
const close = open < 0 ? -1 : pattern.indexOf("}", open);
|
|
152
|
+
if (close < 0) {
|
|
153
|
+
return [pattern];
|
|
154
|
+
}
|
|
155
|
+
const before = pattern.slice(0, open);
|
|
156
|
+
const after = pattern.slice(close + 1);
|
|
157
|
+
return pattern
|
|
158
|
+
.slice(open + 1, close)
|
|
159
|
+
.split(",")
|
|
160
|
+
.flatMap((member) => braceExpanded(before + member + after));
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/** The expression one whole-path pattern of the configured form denotes. */
|
|
164
|
+
export function globExpression(pattern: string): RegExp {
|
|
165
|
+
const alternatives = braceExpanded(pattern).map((one) => source(one, "configured"));
|
|
166
|
+
return new RegExp(`^(?:${alternatives.join("|")})$`, "u");
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/** The expression one `.gitignore` pattern denotes over a path below its file's directory. */
|
|
170
|
+
export function gitignoreExpression(pattern: string): RegExp {
|
|
171
|
+
return new RegExp(`^${source(pattern, "gitignore")}$`, "u");
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
const SEGMENTS = new Map<string, RegExp>();
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Whether one directory name matches one segment of a workspace's member pattern, its brace
|
|
178
|
+
* lists already expanded. Neither `*` nor `?` matches a leading dot, as npm's workspaces read
|
|
179
|
+
* them: https://docs.npmjs.com/cli/using-npm/workspaces
|
|
180
|
+
*/
|
|
181
|
+
export function segmentMatches(segment: string, name: string): boolean {
|
|
182
|
+
if (name.startsWith(".") && !segment.startsWith(".")) {
|
|
183
|
+
return false;
|
|
184
|
+
}
|
|
185
|
+
let expression = SEGMENTS.get(segment);
|
|
186
|
+
if (expression === undefined) {
|
|
187
|
+
expression = globExpression(segment);
|
|
188
|
+
SEGMENTS.set(segment, expression);
|
|
189
|
+
}
|
|
190
|
+
return expression.test(name);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Whether one path's segments match a member pattern's, `**` standing for any run of segments
|
|
195
|
+
* that starts with no dot.
|
|
196
|
+
*/
|
|
197
|
+
export function pathMatches(pattern: readonly string[], path: readonly string[]): boolean {
|
|
198
|
+
const [head, ...rest] = pattern;
|
|
199
|
+
if (head === undefined) {
|
|
200
|
+
return path.length === 0;
|
|
201
|
+
}
|
|
202
|
+
if (head === "**") {
|
|
203
|
+
return (
|
|
204
|
+
pathMatches(rest, path) ||
|
|
205
|
+
(path.length > 0 && !(path[0] ?? "").startsWith(".") && pathMatches(pattern, path.slice(1)))
|
|
206
|
+
);
|
|
207
|
+
}
|
|
208
|
+
const [name, ...below] = path;
|
|
209
|
+
return name !== undefined && segmentMatches(head, name) && pathMatches(rest, below);
|
|
210
|
+
}
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shell command lines read as text, never run: the commands a line runs, the tokens the
|
|
3
|
+
* Contract splits a command into, and the words POSIX token recognition reads.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* The shell's list and pipeline operators, each of which ends one command. An `&` beside a
|
|
8
|
+
* redirection's `<` or `>` (`2>&1`, and bash's `&>log`) is part of the redirection.
|
|
9
|
+
*/
|
|
10
|
+
const COMMAND_OPERATORS = /&&|\|\||;|\||(?<![<>])&(?!>)/u;
|
|
11
|
+
|
|
12
|
+
/** Where a command splits into tokens: white space and the operators the Contract names. */
|
|
13
|
+
const TOKEN_SEPARATORS = /\s+|&&|\|\||;|\|/u;
|
|
14
|
+
|
|
15
|
+
/** A text written inside one pair of quotes. */
|
|
16
|
+
const QUOTED = /^(["'])(.*)\1$/su;
|
|
17
|
+
|
|
18
|
+
/** The commands one line runs, the text between two of the shell's list and pipeline operators. */
|
|
19
|
+
export function commandsOf(line: string): readonly string[] {
|
|
20
|
+
return line.split(COMMAND_OPERATORS);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** One text with one pair of enclosing quotes removed. */
|
|
24
|
+
export function unquoted(text: string): string {
|
|
25
|
+
return text.replace(QUOTED, "$2");
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Every token one script's command line holds, each with one pair of enclosing quotes
|
|
30
|
+
* removed. A token holding a wildcard is a pattern rather than a path, and names no
|
|
31
|
+
* entry point.
|
|
32
|
+
*/
|
|
33
|
+
export function scriptTokens(command: string): readonly string[] {
|
|
34
|
+
return command
|
|
35
|
+
.split(TOKEN_SEPARATORS)
|
|
36
|
+
.map(unquoted)
|
|
37
|
+
.filter((token) => token !== "" && !token.includes("*"));
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** One shell line up to its comment: a `#` that starts a word outside quotes. */
|
|
41
|
+
export function withoutComment(line: string): string {
|
|
42
|
+
let quote = "";
|
|
43
|
+
for (let at = 0; at < line.length; at += 1) {
|
|
44
|
+
const char = line[at];
|
|
45
|
+
if (quote !== "") {
|
|
46
|
+
if (char === quote) {
|
|
47
|
+
quote = "";
|
|
48
|
+
} else if (char === "\\" && quote === '"') {
|
|
49
|
+
at += 1;
|
|
50
|
+
}
|
|
51
|
+
} else if (char === "'" || char === '"') {
|
|
52
|
+
quote = char;
|
|
53
|
+
} else if (char === "\\") {
|
|
54
|
+
at += 1;
|
|
55
|
+
} else if (char === "#" && (at === 0 || /\s/u.test(line[at - 1] ?? ""))) {
|
|
56
|
+
return line.slice(0, at);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return line;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** The characters that end a word outside quotes and separate nothing: blanks. */
|
|
63
|
+
// Stryker disable next-line Regex: its \v to \V mutant is no valid escape under the u flag, so the whole module fails to parse
|
|
64
|
+
const BLANK = /[ \t\r\f\v]/u;
|
|
65
|
+
|
|
66
|
+
/** The characters that end a word outside quotes and begin an operator: newline included. */
|
|
67
|
+
const OPERATOR = /[\n;&|()<>]/u;
|
|
68
|
+
|
|
69
|
+
/** The characters a backslash escapes inside double quotes; before any other it stands for itself. */
|
|
70
|
+
const DOUBLE_QUOTE_ESCAPES = new Set(["$", "`", '"', "\\"]);
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* The words of one command line, as POSIX token recognition reads quotes, backslashes,
|
|
74
|
+
* line continuations, comments and operators. A word an expansion may change (an unquoted
|
|
75
|
+
* `$`, backquote, glob character or leading `~`, or a `$` or backquote in double quotes)
|
|
76
|
+
* is computed, and so is the rest of a line whose quote is never closed. A computed word
|
|
77
|
+
* and each operator are undefined, so a flag's file is never read across them.
|
|
78
|
+
*/
|
|
79
|
+
export function shellWords(command: string): readonly (string | undefined)[] {
|
|
80
|
+
const words: (string | undefined)[] = [];
|
|
81
|
+
let word: string | undefined;
|
|
82
|
+
let computed = false;
|
|
83
|
+
const end = (): void => {
|
|
84
|
+
if (word !== undefined) {
|
|
85
|
+
words.push(computed ? undefined : word);
|
|
86
|
+
}
|
|
87
|
+
word = undefined;
|
|
88
|
+
computed = false;
|
|
89
|
+
};
|
|
90
|
+
for (let at = 0; at < command.length; at += 1) {
|
|
91
|
+
const char = command.charAt(at);
|
|
92
|
+
if (char === "\\" && command.charAt(at + 1) === "\n") {
|
|
93
|
+
at += 1;
|
|
94
|
+
} else if (BLANK.test(char)) {
|
|
95
|
+
end();
|
|
96
|
+
} else if (OPERATOR.test(char)) {
|
|
97
|
+
end();
|
|
98
|
+
words.push(undefined);
|
|
99
|
+
} else if (char === "#" && word === undefined) {
|
|
100
|
+
const newline = command.indexOf("\n", at);
|
|
101
|
+
at = newline < 0 ? command.length : newline - 1;
|
|
102
|
+
} else if (char === "\\") {
|
|
103
|
+
at += 1;
|
|
104
|
+
word = (word ?? "") + command.charAt(at);
|
|
105
|
+
} else if (char === "'" || char === '"') {
|
|
106
|
+
const close = closingQuote(command, at);
|
|
107
|
+
if (close < 0) {
|
|
108
|
+
word ??= "";
|
|
109
|
+
computed = true;
|
|
110
|
+
break;
|
|
111
|
+
}
|
|
112
|
+
const quoted =
|
|
113
|
+
char === "'"
|
|
114
|
+
? { text: command.slice(at + 1, close), computed: false }
|
|
115
|
+
: doubleQuoted(command.slice(at + 1, close));
|
|
116
|
+
word = (word ?? "") + quoted.text;
|
|
117
|
+
computed ||= quoted.computed;
|
|
118
|
+
at = close;
|
|
119
|
+
} else {
|
|
120
|
+
computed ||= /[$`*?[]/u.test(char) || (char === "~" && word === undefined);
|
|
121
|
+
word = (word ?? "") + char;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
end();
|
|
125
|
+
return words;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/** The text between one pair of double quotes, unescaped, and whether an expansion may change it. */
|
|
129
|
+
function doubleQuoted(inner: string): { readonly text: string; readonly computed: boolean } {
|
|
130
|
+
let text = "";
|
|
131
|
+
let computed = false;
|
|
132
|
+
for (let at = 0; at < inner.length; at += 1) {
|
|
133
|
+
const char = inner.charAt(at);
|
|
134
|
+
const next = inner.charAt(at + 1);
|
|
135
|
+
if (char === "\\" && next === "\n") {
|
|
136
|
+
at += 1;
|
|
137
|
+
} else if (char === "\\" && DOUBLE_QUOTE_ESCAPES.has(next)) {
|
|
138
|
+
text += next;
|
|
139
|
+
at += 1;
|
|
140
|
+
} else {
|
|
141
|
+
computed ||= char === "$" || char === "`";
|
|
142
|
+
text += char;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
return { text, computed };
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/** The index of the quote that closes the one at `open`, or -1 where none does. */
|
|
149
|
+
function closingQuote(command: string, open: number): number {
|
|
150
|
+
const quote = command.charAt(open);
|
|
151
|
+
for (let at = open + 1; at < command.length; at += 1) {
|
|
152
|
+
const char = command.charAt(at);
|
|
153
|
+
if (char === quote) {
|
|
154
|
+
return at;
|
|
155
|
+
}
|
|
156
|
+
if (quote === '"' && char === "\\") {
|
|
157
|
+
at += 1;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
return -1;
|
|
161
|
+
}
|
|
@@ -6,6 +6,8 @@
|
|
|
6
6
|
* as a JSON document that does not parse has none.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
+
import { isRecord } from "../json-document.ts";
|
|
10
|
+
|
|
9
11
|
/** One line of the document with its comment removed, by its indentation and its content. */
|
|
10
12
|
interface Line {
|
|
11
13
|
readonly indent: number;
|
|
@@ -375,3 +377,47 @@ export function yamlDocument(text: string): unknown {
|
|
|
375
377
|
throw error;
|
|
376
378
|
}
|
|
377
379
|
}
|
|
380
|
+
|
|
381
|
+
/** What one top-level key of a YAML document holds, read by {@link yamlKey}. */
|
|
382
|
+
type YamlKey =
|
|
383
|
+
| { readonly kind: "value"; readonly value: unknown }
|
|
384
|
+
| { readonly kind: "absent" }
|
|
385
|
+
| { readonly kind: "refused"; readonly reason: string };
|
|
386
|
+
|
|
387
|
+
/** The key one line of the document's top-level mapping opens, or undefined where it opens none. */
|
|
388
|
+
function topLevelKey(raw: string): string | undefined {
|
|
389
|
+
const content = withoutComment(raw).trimEnd();
|
|
390
|
+
const end = /^\s/u.test(content) ? -1 : keyEnd(content);
|
|
391
|
+
if (end < 0) {
|
|
392
|
+
return undefined;
|
|
393
|
+
}
|
|
394
|
+
try {
|
|
395
|
+
const key = scalar(content.slice(0, end));
|
|
396
|
+
return typeof key === "object" && key !== null ? undefined : String(key);
|
|
397
|
+
} catch {
|
|
398
|
+
return undefined;
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
/**
|
|
403
|
+
* The value of one key of a document's top-level block mapping, read from that key's lines
|
|
404
|
+
* alone, so a document whose other keys lie outside the subset still yields it. A key
|
|
405
|
+
* declared twice, or one whose own lines lie outside the subset, is refused.
|
|
406
|
+
*/
|
|
407
|
+
export function yamlKey(text: string, key: string): YamlKey {
|
|
408
|
+
const lines = text.replace(/^\uFEFF/u, "").split(/\r?\n/u);
|
|
409
|
+
const starts = lines.flatMap((line, at) => (topLevelKey(line) === undefined ? [] : [at]));
|
|
410
|
+
const named = starts.filter((at) => topLevelKey(lines[at] ?? "") === key);
|
|
411
|
+
const [start] = named;
|
|
412
|
+
if (start === undefined) {
|
|
413
|
+
return { kind: "absent" };
|
|
414
|
+
}
|
|
415
|
+
if (named.length > 1) {
|
|
416
|
+
return { kind: "refused", reason: `the ${key} key is declared more than once` };
|
|
417
|
+
}
|
|
418
|
+
const end = starts.find((at) => at > start) ?? lines.length;
|
|
419
|
+
const value = yamlDocument(lines.slice(start, end).join("\n"));
|
|
420
|
+
return isRecord(value)
|
|
421
|
+
? { kind: "value", value: value[key] }
|
|
422
|
+
: { kind: "refused", reason: `the value of ${key} is outside the YAML subset read here` };
|
|
423
|
+
}
|
package/src/type-errors.ts
CHANGED
|
@@ -21,12 +21,21 @@ import { packageOfSpecifier } from "./dependencies.ts";
|
|
|
21
21
|
import type { LocatedDiagnostic } from "./discover.ts";
|
|
22
22
|
import type { Host } from "./host.ts";
|
|
23
23
|
import { nodeKey, type Inventory } from "./inventory.ts";
|
|
24
|
-
import {
|
|
24
|
+
import {
|
|
25
|
+
ancestors,
|
|
26
|
+
dirnamePath,
|
|
27
|
+
isAbsolutePath,
|
|
28
|
+
joinPath,
|
|
29
|
+
relativePath,
|
|
30
|
+
resolvePath,
|
|
31
|
+
} from "./paths.ts";
|
|
25
32
|
import { isAnswered } from "./query.ts";
|
|
26
33
|
import type { TypeErrorSkip } from "./report.ts";
|
|
27
34
|
import type { Engine, ProjectView } from "./session.ts";
|
|
28
35
|
import type { SetupFailure } from "./setup-failure.ts";
|
|
29
36
|
import { findWorkspace, type Workspace } from "./workspace.ts";
|
|
37
|
+
import { entriesOf } from "./fs/tree.ts";
|
|
38
|
+
import { INSTALLED_PACKAGES, MANIFEST, manifestAt } from "./fs/manifest.ts";
|
|
30
39
|
|
|
31
40
|
/** The compiler's codes for an import whose module it cannot find. */
|
|
32
41
|
const MODULE_NOT_FOUND: ReadonlySet<number> = new Set([2307, 2792]);
|
|
@@ -38,7 +47,6 @@ const MODULE_NOT_FOUND: ReadonlySet<number> = new Set([2307, 2792]);
|
|
|
38
47
|
*/
|
|
39
48
|
const CONFIGURATION_LEVEL: ReadonlySet<number> = new Set([5055, 5056, 6059, 6307]);
|
|
40
49
|
|
|
41
|
-
const MANIFEST = "package.json";
|
|
42
50
|
const MANIFEST_SECTIONS = [
|
|
43
51
|
"dependencies",
|
|
44
52
|
"devDependencies",
|
|
@@ -156,13 +164,9 @@ function provided(host: Host, path: string, fileOnly: boolean): boolean {
|
|
|
156
164
|
}
|
|
157
165
|
const dir = dirnamePath(path);
|
|
158
166
|
const stem = `${path.slice(dir.length + 1)}.`;
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
.some((entry) => entry.name.startsWith(stem) && counts(joinPath(dir, entry.name)));
|
|
163
|
-
} catch {
|
|
164
|
-
return false;
|
|
165
|
-
}
|
|
167
|
+
return entriesOf(host, dir).some(
|
|
168
|
+
(entry) => entry.name.startsWith(stem) && counts(joinPath(dir, entry.name)),
|
|
169
|
+
);
|
|
166
170
|
}
|
|
167
171
|
|
|
168
172
|
/** The manifest at or above `dir`, no higher than the target root, that declares `name`. */
|
|
@@ -172,28 +176,18 @@ function declaringManifest(
|
|
|
172
176
|
targetRoot: string,
|
|
173
177
|
name: string,
|
|
174
178
|
): string | undefined {
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
if (typeof manifest === "object" && manifest !== null) {
|
|
184
|
-
const sections = manifest as Record<string, unknown>;
|
|
185
|
-
for (const section of MANIFEST_SECTIONS) {
|
|
186
|
-
const declared = sections[section];
|
|
187
|
-
if (typeof declared === "object" && declared !== null && Object.hasOwn(declared, name)) {
|
|
188
|
-
return path;
|
|
189
|
-
}
|
|
179
|
+
const chain = ancestors(dir, targetRoot);
|
|
180
|
+
// A directory outside the target root reads its own manifest alone.
|
|
181
|
+
for (const at of chain.length > 0 ? chain : [dir]) {
|
|
182
|
+
const manifest = manifestAt(host, at);
|
|
183
|
+
for (const section of MANIFEST_SECTIONS) {
|
|
184
|
+
const declared = manifest?.[section];
|
|
185
|
+
if (typeof declared === "object" && declared !== null && Object.hasOwn(declared, name)) {
|
|
186
|
+
return joinPath(at, MANIFEST);
|
|
190
187
|
}
|
|
191
188
|
}
|
|
192
|
-
// The target root is not below itself, so the walk stops after reading its manifest.
|
|
193
|
-
if (relativePath(targetRoot, at) === undefined) {
|
|
194
|
-
return undefined;
|
|
195
|
-
}
|
|
196
189
|
}
|
|
190
|
+
return undefined;
|
|
197
191
|
}
|
|
198
192
|
|
|
199
193
|
/**
|
|
@@ -201,14 +195,9 @@ function declaringManifest(
|
|
|
201
195
|
* `dir` or of any ancestor, above the target root too, as Node and the compiler look.
|
|
202
196
|
*/
|
|
203
197
|
function installed(host: Host, dir: string, name: string): boolean {
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
}
|
|
208
|
-
if (dirnamePath(at) === at) {
|
|
209
|
-
return false;
|
|
210
|
-
}
|
|
211
|
-
}
|
|
198
|
+
return ancestors(dir).some(
|
|
199
|
+
(at) => host.kindOf(joinPath(at, INSTALLED_PACKAGES, name)) === "directory",
|
|
200
|
+
);
|
|
212
201
|
}
|
|
213
202
|
|
|
214
203
|
/**
|
package/src/unseen-loads.ts
CHANGED
|
@@ -6,13 +6,10 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import {
|
|
9
|
-
isAsExpression,
|
|
10
9
|
isBinaryExpression,
|
|
11
10
|
isCallExpression,
|
|
12
11
|
isIdentifier,
|
|
13
12
|
isNoSubstitutionTemplateLiteral,
|
|
14
|
-
isParenthesizedExpression,
|
|
15
|
-
isSatisfiesExpression,
|
|
16
13
|
isStringLiteral,
|
|
17
14
|
isTemplateExpression,
|
|
18
15
|
SyntaxKind,
|
|
@@ -23,6 +20,7 @@ import { renderPosition, type Position } from "./position.ts";
|
|
|
23
20
|
import { UNANSWERED } from "./query.ts";
|
|
24
21
|
import type { ProjectView } from "./session.ts";
|
|
25
22
|
import { sourceFilesOf } from "./source-files.ts";
|
|
23
|
+
import { unwrapped } from "./unwrap.ts";
|
|
26
24
|
|
|
27
25
|
/** One load the analysis cannot resolve, in the configuration whose production file writes it. */
|
|
28
26
|
export interface UnseenLoad {
|
|
@@ -31,15 +29,6 @@ export interface UnseenLoad {
|
|
|
31
29
|
readonly position: Position;
|
|
32
30
|
}
|
|
33
31
|
|
|
34
|
-
/** The expression a wrapper that changes no value holds: parentheses and type assertions. */
|
|
35
|
-
function unwrapped(node: Node): Node {
|
|
36
|
-
let at = node;
|
|
37
|
-
while (isParenthesizedExpression(at) || isAsExpression(at) || isSatisfiesExpression(at)) {
|
|
38
|
-
at = at.expression;
|
|
39
|
-
}
|
|
40
|
-
return at;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
32
|
/** The literal text a computed path starts with: a template's head, or a concatenation's first literal. */
|
|
44
33
|
export function leadingLiteral(node: Node): string | undefined {
|
|
45
34
|
const at = unwrapped(node);
|
package/src/unwrap.ts
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import {
|
|
2
|
+
isAsExpression,
|
|
3
|
+
isNonNullExpression,
|
|
4
|
+
isParenthesizedExpression,
|
|
5
|
+
isSatisfiesExpression,
|
|
6
|
+
type Expression,
|
|
7
|
+
type Node,
|
|
8
|
+
} from "@typescript/native/ast";
|
|
9
|
+
|
|
10
|
+
/** The expression inside every pair of parentheses around `node`. */
|
|
11
|
+
export function parenthesesRemoved<T extends Node>(node: T): T | Expression {
|
|
12
|
+
let at: T | Expression = node;
|
|
13
|
+
while (isParenthesizedExpression(at)) {
|
|
14
|
+
at = at.expression;
|
|
15
|
+
}
|
|
16
|
+
return at;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* The expression inside every wrapper around `node` that changes no value it evaluates to:
|
|
21
|
+
* parentheses, `as` and `satisfies` assertions and the non-null assertion.
|
|
22
|
+
*/
|
|
23
|
+
export function unwrapped<T extends Node>(node: T): T | Expression {
|
|
24
|
+
let at: T | Expression = node;
|
|
25
|
+
while (
|
|
26
|
+
isParenthesizedExpression(at) ||
|
|
27
|
+
isAsExpression(at) ||
|
|
28
|
+
isSatisfiesExpression(at) ||
|
|
29
|
+
isNonNullExpression(at)
|
|
30
|
+
) {
|
|
31
|
+
at = at.expression;
|
|
32
|
+
}
|
|
33
|
+
return at;
|
|
34
|
+
}
|
package/src/usage.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A refusal of the invocation: the fix is to the command line or to a document it
|
|
3
|
+
* names, so the run ends with the usage code before any analysis.
|
|
4
|
+
*/
|
|
5
|
+
export class UsageError extends Error {
|
|
6
|
+
constructor(message: string) {
|
|
7
|
+
super(message);
|
|
8
|
+
this.name = "UsageError";
|
|
9
|
+
}
|
|
10
|
+
}
|