@cplieger/deadset-ts 8.0.0-dev.5 → 8.0.0-dev.7
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 +3 -2
- package/dist/src/analysis.js +28 -46
- 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 +3 -2
- package/dist/src/configuration-files.js +27 -168
- package/dist/src/conformance-record.js +1 -1
- package/dist/src/contract/contract.generated.js +1084 -0
- package/dist/src/contract/report.generated.js +796 -0
- package/dist/src/conventions.js +20 -60
- package/dist/src/dependencies.js +25 -43
- package/dist/src/discover.js +13 -39
- package/dist/src/edges.js +7 -15
- 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 +3 -63
- package/dist/src/exempt.js +13 -40
- package/dist/src/file-facts.js +2 -25
- package/dist/src/findings/completion.js +11 -20
- package/dist/src/findings/dependencies-and-module-machinery.js +4 -5
- package/dist/src/findings/emitters.js +4 -4
- package/dist/src/findings/interfaces.js +4 -5
- package/dist/src/findings/intra-function.js +8 -7
- package/dist/src/findings/non-code-artifacts.js +5 -6
- package/dist/src/findings/reads-and-writes.js +11 -9
- package/dist/src/findings/self-check.js +10 -12
- package/dist/src/findings/unused-declarations.js +28 -27
- package/dist/src/findings/visibility-narrowing.js +9 -11
- package/dist/src/findings-pass.js +7 -14
- 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/index.js +1 -1
- package/dist/src/intra-function-parts.js +18 -26
- package/dist/src/inventory.js +2 -21
- package/dist/src/json-document.js +4 -0
- package/dist/src/keyof-comparisons.js +4 -6
- package/dist/src/kinds.js +4 -360
- 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 +3 -2
- package/dist/src/product.js +13 -0
- package/dist/src/references.js +14 -27
- package/dist/src/report-schema.js +272 -0
- package/dist/src/report.js +39 -42
- package/dist/src/reporters/sarif.js +4 -2
- package/dist/src/reporters/template.js +4 -19
- package/dist/src/resolve.js +12 -26
- package/dist/src/roots.js +7 -17
- package/dist/src/run.js +10 -8
- package/dist/src/schema.js +4 -3
- package/dist/src/scope.js +4 -5
- package/dist/src/serialization-contract.js +4 -8
- package/dist/src/session.js +2 -1
- package/dist/src/setup-failure.js +2 -6
- package/dist/src/suppress-file.js +12 -13
- package/dist/src/suppress-inline.js +3 -2
- package/dist/src/suppress.js +29 -48
- package/dist/src/symbol-keys.js +3 -5
- package/dist/src/template-field.js +5 -15
- 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/verbs/analyze.js +22 -15
- package/dist/src/verbs/describe.js +7 -7
- package/dist/src/verbs/explain.js +7 -4
- package/dist/src/verbs/print-config.js +2 -1
- package/dist/src/verbs/print-projects.js +4 -2
- package/dist/src/verbs/print-retained.js +4 -2
- package/dist/src/verbs/print-roots.js +4 -2
- package/dist/src/verbs/verb.js +1 -5
- package/dist/src/verbs/version.js +4 -3
- 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 +2 -1
- package/src/analysis.ts +29 -54
- 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 +11 -15
- package/src/configuration-files.ts +27 -170
- package/src/conformance-record.ts +1 -1
- package/src/contract/contract.generated.ts +1148 -0
- package/src/contract/report.generated.ts +1157 -0
- package/src/conventions.ts +26 -74
- package/src/dependencies.ts +32 -50
- package/src/discover.ts +14 -39
- package/src/edges.ts +7 -21
- 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 +5 -74
- package/src/exempt.ts +17 -47
- package/src/file-facts.ts +6 -24
- package/src/finding.ts +9 -9
- package/src/findings/completion.ts +22 -23
- package/src/findings/dependencies-and-module-machinery.ts +4 -7
- package/src/findings/emitter.ts +6 -7
- package/src/findings/emitters.ts +6 -6
- package/src/findings/interfaces.ts +4 -6
- package/src/findings/intra-function.ts +8 -8
- package/src/findings/non-code-artifacts.ts +5 -7
- package/src/findings/reads-and-writes.ts +15 -12
- package/src/findings/self-check.ts +11 -17
- package/src/findings/unused-declarations.ts +28 -27
- package/src/findings/visibility-narrowing.ts +9 -12
- package/src/findings-pass.ts +7 -16
- package/src/fs/manifest.ts +52 -0
- package/src/fs/tree.ts +110 -0
- package/src/git-ignore.ts +38 -25
- package/src/html-entries.ts +6 -33
- package/src/in-operands.ts +2 -1
- package/src/index.ts +1 -1
- package/src/intra-function-parts.ts +18 -28
- package/src/inventory.ts +2 -20
- package/src/json-document.ts +5 -0
- package/src/keyof-comparisons.ts +3 -7
- package/src/kinds.ts +6 -406
- 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 +3 -2
- package/src/product.ts +18 -0
- package/src/references.ts +14 -29
- package/src/report-schema.ts +364 -0
- package/src/report.ts +66 -229
- package/src/reporters/annotations.ts +2 -2
- package/src/reporters/json.ts +1 -1
- package/src/reporters/reporter.ts +2 -2
- package/src/reporters/reporters.ts +1 -1
- package/src/reporters/sarif.ts +17 -12
- package/src/reporters/template.ts +4 -20
- package/src/reporters/text.ts +3 -3
- package/src/resolve.ts +31 -38
- package/src/roots.ts +7 -20
- package/src/run.ts +10 -14
- package/src/schema.ts +4 -3
- package/src/scope.ts +4 -10
- package/src/serialization-contract.ts +4 -9
- package/src/session.ts +2 -1
- package/src/setup-failure.ts +5 -7
- package/src/suppress-file.ts +11 -15
- package/src/suppress-inline.ts +2 -2
- package/src/suppress.ts +39 -55
- package/src/symbol-keys.ts +2 -5
- package/src/template-field.ts +5 -18
- 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/verbs/analyze.ts +38 -25
- package/src/verbs/describe.ts +13 -7
- package/src/verbs/explain.ts +7 -4
- package/src/verbs/print-config.ts +2 -1
- package/src/verbs/print-projects.ts +4 -2
- package/src/verbs/print-retained.ts +4 -2
- package/src/verbs/print-roots.ts +4 -2
- package/src/verbs/verb.ts +0 -6
- package/src/verbs/version.ts +4 -3
- 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/dist/src/version.js +0 -7
- package/src/glob.ts +0 -67
- package/src/pnpm-workspace.ts +0 -135
- package/src/version.ts +0 -7
package/dist/src/suppress.js
CHANGED
|
@@ -6,15 +6,10 @@
|
|
|
6
6
|
* withholds a finding its code would have produced there, dormant when the configuration
|
|
7
7
|
* withholds every such finding anyway, and stale otherwise.
|
|
8
8
|
*/
|
|
9
|
+
import { KIND_ROWS, SYMBOL_SUBJECTS, } from "./contract/contract.generated.js";
|
|
9
10
|
import { ConfigError } from "./config.js";
|
|
10
11
|
import { positionKey } from "./position.js";
|
|
11
12
|
import { isRef } from "./ref.js";
|
|
12
|
-
/** The code a suppression that carries no reason is reported under. */
|
|
13
|
-
export const SUPPRESSION_WITHOUT_REASON = "DS1701";
|
|
14
|
-
/** The code an ignore entry or a baseline row that names no path is reported under. */
|
|
15
|
-
export const UNSCOPED_ENTRY = "DS1702";
|
|
16
|
-
/** The code a suppression that withholds nothing is reported under. */
|
|
17
|
-
export const STALE_SUPPRESSION = "DS1703";
|
|
18
13
|
/**
|
|
19
14
|
* A directive, an entry or a document the grammar refuses before any finding exists: an
|
|
20
15
|
* instruction the analysis cannot carry out. It is a malformed input, so the command line
|
|
@@ -71,73 +66,59 @@ export function isSymbolRef(value) {
|
|
|
71
66
|
export function blankReason(reason) {
|
|
72
67
|
return reason === undefined || /^[ \t\r\n]*$/u.test(reason);
|
|
73
68
|
}
|
|
69
|
+
/** The subject of the kind each live code names. */
|
|
70
|
+
const SUBJECT_OF_CODE = new Map(KIND_ROWS.map((row) => [row.code, row.subject]));
|
|
71
|
+
/** The subject a finding about one symbol kind is about. */
|
|
72
|
+
function subjectOf(kind) {
|
|
73
|
+
return Object.hasOwn(SYMBOL_SUBJECTS, kind)
|
|
74
|
+
? SYMBOL_SUBJECTS[kind]
|
|
75
|
+
: undefined;
|
|
76
|
+
}
|
|
77
|
+
/** Whether a subject is a row of a document rather than a declaration of the program. */
|
|
78
|
+
function rowOf(subject) {
|
|
79
|
+
return subject === "file" || subject === "dependency" || subject === "record";
|
|
80
|
+
}
|
|
74
81
|
/**
|
|
75
|
-
*
|
|
76
|
-
*
|
|
77
|
-
* and marks nothing, and an inline directive never binds one.
|
|
78
|
-
*/
|
|
79
|
-
const DEPENDENCY_SUBJECTS = new Map([
|
|
80
|
-
["dependency", ["DS1601"]],
|
|
81
|
-
["module-directive", ["DS1605"]],
|
|
82
|
-
]);
|
|
83
|
-
/**
|
|
84
|
-
* The subject kinds that are a row of a document rather than a declaration of the program,
|
|
85
|
-
* each with the codes that report one. Apart from a dependency row, nothing withholds a
|
|
86
|
-
* finding about one.
|
|
82
|
+
* Whether a finding's subject is a row of a document rather than a declaration. Apart
|
|
83
|
+
* from a dependency row, nothing withholds a finding about one.
|
|
87
84
|
*/
|
|
88
|
-
const ROW_SUBJECTS = new Map([
|
|
89
|
-
["file", ["DS1501", "DS1502"]],
|
|
90
|
-
...DEPENDENCY_SUBJECTS,
|
|
91
|
-
["root", ["DS1704"]],
|
|
92
|
-
["configured-declaration", ["DS1706"]],
|
|
93
|
-
["suppression", ["DS1701", "DS1702", "DS1703"]],
|
|
94
|
-
["edge", ["DS1705"]],
|
|
95
|
-
]);
|
|
96
|
-
/** Whether a finding's subject is a row of a document rather than a declaration. */
|
|
97
85
|
export function isRowSubject(kind) {
|
|
98
|
-
return
|
|
86
|
+
return rowOf(subjectOf(kind));
|
|
99
87
|
}
|
|
100
88
|
/** Whether a code reports a row of a document. */
|
|
101
89
|
export function isRowCode(code) {
|
|
102
|
-
return
|
|
90
|
+
return rowOf(SUBJECT_OF_CODE.get(code));
|
|
103
91
|
}
|
|
104
|
-
/**
|
|
92
|
+
/**
|
|
93
|
+
* Whether a finding's subject is a dependency row. An ignore entry or a baseline row
|
|
94
|
+
* naming the row's reference and file withholds its finding and marks nothing, and an
|
|
95
|
+
* inline directive never binds one.
|
|
96
|
+
*/
|
|
105
97
|
export function isDependencySubject(kind) {
|
|
106
|
-
return
|
|
98
|
+
return subjectOf(kind) === "dependency";
|
|
107
99
|
}
|
|
108
100
|
/** Whether a code reports a dependency row, which only an ignore entry or a baseline row names. */
|
|
109
101
|
export function isDependencyCode(code) {
|
|
110
|
-
return
|
|
102
|
+
return SUBJECT_OF_CODE.get(code) === "dependency";
|
|
111
103
|
}
|
|
112
104
|
/** Whether a code reports a row of a document that no record of any mechanism can name. */
|
|
113
105
|
export function isUnsuppressibleCode(code) {
|
|
114
106
|
return isRowCode(code) && !isDependencyCode(code);
|
|
115
107
|
}
|
|
116
108
|
/**
|
|
117
|
-
*
|
|
109
|
+
* Whether a finding's subject is a part of the declaration its reference names: a part
|
|
118
110
|
* has no reference of its own, so a finding about one names the declaration that holds
|
|
119
111
|
* it, and the part is decided inside that declaration whatever uses it.
|
|
120
112
|
*/
|
|
121
|
-
const PART_SUBJECTS = new Set([
|
|
122
|
-
"parameter",
|
|
123
|
-
"receiver",
|
|
124
|
-
"result",
|
|
125
|
-
"statement",
|
|
126
|
-
"case",
|
|
127
|
-
"store",
|
|
128
|
-
]);
|
|
129
|
-
/** Whether a finding's subject is a part of the declaration its reference names. */
|
|
130
113
|
export function isPartSubject(kind) {
|
|
131
|
-
return
|
|
114
|
+
return subjectOf(kind) === "part";
|
|
132
115
|
}
|
|
133
|
-
/** The prefix of the codes of the part kinds, the intra-function range. */
|
|
134
|
-
const PART_KIND_PREFIX = "DS18";
|
|
135
116
|
/**
|
|
136
117
|
* Whether a code is a part kind, whose record withholds its finding and marks nothing:
|
|
137
118
|
* the record says the part is wanted, not that the declaration holding it is.
|
|
138
119
|
*/
|
|
139
120
|
export function isPartKind(code) {
|
|
140
|
-
return
|
|
121
|
+
return SUBJECT_OF_CODE.get(code) === "part";
|
|
141
122
|
}
|
|
142
123
|
/** One finding's identity: its code and the position of the thing it names. */
|
|
143
124
|
function claimKey(finding) {
|
|
@@ -155,12 +136,12 @@ export function pairs(record, finding) {
|
|
|
155
136
|
if (record.code !== finding.code) {
|
|
156
137
|
return false;
|
|
157
138
|
}
|
|
158
|
-
if (
|
|
139
|
+
if (isDependencySubject(finding.symbol.kind)) {
|
|
159
140
|
return (record.mechanism !== "inline" &&
|
|
160
141
|
record.symbol === finding.symbol.ref &&
|
|
161
142
|
record.path === finding.position.path);
|
|
162
143
|
}
|
|
163
|
-
if (
|
|
144
|
+
if (isRowSubject(finding.symbol.kind)) {
|
|
164
145
|
return false;
|
|
165
146
|
}
|
|
166
147
|
if (record.bound !== "" && record.bound === positionKey(finding.position)) {
|
package/dist/src/symbol-keys.js
CHANGED
|
@@ -5,16 +5,14 @@
|
|
|
5
5
|
* No name node resolves to such a member: the key's name resolves to the constant. An
|
|
6
6
|
* iteration reads the member its well-known iteration symbol keys.
|
|
7
7
|
*/
|
|
8
|
-
import { isComputedPropertyName, isForOfStatement, isIdentifier, isNoSubstitutionTemplateLiteral, isNumericLiteral,
|
|
8
|
+
import { isComputedPropertyName, isForOfStatement, isIdentifier, isNoSubstitutionTemplateLiteral, isNumericLiteral, isPropertyAccessExpression, isSpreadElement, isStringLiteral, } from "@typescript/native/ast";
|
|
9
|
+
import { parenthesesRemoved } from "./unwrap.js";
|
|
9
10
|
import { nodeKey } from "./inventory.js";
|
|
10
11
|
import { renderPosition } from "./position.js";
|
|
11
12
|
import { isAnswered } from "./query.js";
|
|
12
13
|
/** The identifier an element access's argument names its key by, where it is a name. */
|
|
13
14
|
export function keyNameOf(argument) {
|
|
14
|
-
|
|
15
|
-
while (isParenthesizedExpression(at)) {
|
|
16
|
-
at = at.expression;
|
|
17
|
-
}
|
|
15
|
+
const at = parenthesesRemoved(argument);
|
|
18
16
|
if (isIdentifier(at)) {
|
|
19
17
|
return at;
|
|
20
18
|
}
|
|
@@ -16,6 +16,8 @@ import { ConfigError } from "./config.js";
|
|
|
16
16
|
import { memberNames } from "./member-names.js";
|
|
17
17
|
import { extensionOf, isAbsolutePath, joinPath, normalizePath, relativePath } from "./paths.js";
|
|
18
18
|
import { isComponentFile } from "./position.js";
|
|
19
|
+
import { bytewise } from "./text/compare.js";
|
|
20
|
+
import { filesBelow } from "./fs/tree.js";
|
|
19
21
|
/** The kinds of declaration an action can name. */
|
|
20
22
|
const READ_BY_A_TEMPLATE = new Set([
|
|
21
23
|
"method",
|
|
@@ -30,13 +32,6 @@ const READ_BY_A_TEMPLATE = new Set([
|
|
|
30
32
|
const READ_BY_MARKUP = new Set([...READ_BY_A_TEMPLATE, "enum-member"]);
|
|
31
33
|
/** One identifier, as the grammar of the analyzed language spells one. */
|
|
32
34
|
const IDENTIFIER = /[\p{ID_Start}$_][\p{ID_Continue}$\u200C\u200D]*/uy;
|
|
33
|
-
/** Two strings ordered bytewise. */
|
|
34
|
-
function compare(a, b) {
|
|
35
|
-
if (a === b) {
|
|
36
|
-
return 0;
|
|
37
|
-
}
|
|
38
|
-
return a < b ? -1 : 1;
|
|
39
|
-
}
|
|
40
35
|
/**
|
|
41
36
|
* Every template under the configured directories, read once for the run, in path
|
|
42
37
|
* order and each file once. A directory is named relative to the target root, and
|
|
@@ -48,13 +43,8 @@ function compare(a, b) {
|
|
|
48
43
|
export function readTemplates(host, targetRoot, analysis) {
|
|
49
44
|
const files = new Map();
|
|
50
45
|
const read = (dir) => {
|
|
51
|
-
const
|
|
52
|
-
|
|
53
|
-
const path = joinPath(dir, entry.name);
|
|
54
|
-
if (entry.directory) {
|
|
55
|
-
read(path);
|
|
56
|
-
continue;
|
|
57
|
-
}
|
|
46
|
+
for (const below of filesBelow(host, joinPath(targetRoot, dir), () => true)) {
|
|
47
|
+
const path = joinPath(dir, below);
|
|
58
48
|
const absolute = joinPath(targetRoot, path);
|
|
59
49
|
if (!files.has(path) && host.kindOf(absolute) === "file") {
|
|
60
50
|
files.set(path, host.readFile(absolute));
|
|
@@ -75,7 +65,7 @@ export function readTemplates(host, targetRoot, analysis) {
|
|
|
75
65
|
delimiters: analysis.templateDelimiters,
|
|
76
66
|
files: [...files]
|
|
77
67
|
.map(([path, text]) => ({ path, text }))
|
|
78
|
-
.sort((a, b) =>
|
|
68
|
+
.sort((a, b) => bytewise(a.path, b.path)),
|
|
79
69
|
};
|
|
80
70
|
}
|
|
81
71
|
/** The end of the quoted string that opens at `from`, past its closing quote. */
|
package/dist/src/test-globals.js
CHANGED
|
@@ -7,9 +7,11 @@
|
|
|
7
7
|
* written for the runner, as one that imports it is.
|
|
8
8
|
*/
|
|
9
9
|
import { isAsExpression, isBinaryExpression, isCallExpression, isExportAssignment, isExpressionStatement, isIdentifier, isObjectLiteralExpression, isParenthesizedExpression, isPropertyAccessExpression, isPropertyAssignment, isSatisfiesExpression, SyntaxKind, } from "@typescript/native/ast";
|
|
10
|
-
import { dirnamePath, joinPath } from "./paths.js";
|
|
10
|
+
import { ancestors, dirnamePath, joinPath } from "./paths.js";
|
|
11
11
|
import { UNANSWERED } from "./query.js";
|
|
12
12
|
import { writtenPath } from "./workspace-programs.js";
|
|
13
|
+
import { filesIn } from "./fs/tree.js";
|
|
14
|
+
import { holdsManifest, INSTALLED_PACKAGES } from "./fs/manifest.js";
|
|
13
15
|
/** The test runner whose configuration injects the globals, and the `types` entry declaring them. */
|
|
14
16
|
export const GLOBALS_RUNNER = "vitest";
|
|
15
17
|
const GLOBALS_TYPES = "vitest/globals";
|
|
@@ -93,17 +95,7 @@ function setsGlobals(file) {
|
|
|
93
95
|
}
|
|
94
96
|
/** Whether the runner's configuration in one manifest's directory injects the globals. */
|
|
95
97
|
function injectsGlobals(host, parse, dir) {
|
|
96
|
-
|
|
97
|
-
try {
|
|
98
|
-
names = host
|
|
99
|
-
.readDirectory(dir)
|
|
100
|
-
.filter((entry) => !entry.directory && RUNNER_CONFIG.test(entry.name))
|
|
101
|
-
.map((entry) => entry.name);
|
|
102
|
-
}
|
|
103
|
-
catch {
|
|
104
|
-
return false;
|
|
105
|
-
}
|
|
106
|
-
return names.some((name) => {
|
|
98
|
+
return filesIn(host, dir, (name) => RUNNER_CONFIG.test(name)).some((name) => {
|
|
107
99
|
const path = joinPath(dir, name);
|
|
108
100
|
try {
|
|
109
101
|
return setsGlobals(parse(path, host.readFile(path)));
|
|
@@ -115,25 +107,11 @@ function injectsGlobals(host, parse, dir) {
|
|
|
115
107
|
}
|
|
116
108
|
/** Whether a file declaring the globals is installed where the configuration resolves packages from. */
|
|
117
109
|
function globalsInstalled(host, configFile) {
|
|
118
|
-
|
|
119
|
-
if (host.kindOf(joinPath(dir, "node_modules", GLOBALS_RUNNER, "globals.d.ts")) === "file") {
|
|
120
|
-
return true;
|
|
121
|
-
}
|
|
122
|
-
if (dirnamePath(dir) === dir) {
|
|
123
|
-
return false;
|
|
124
|
-
}
|
|
125
|
-
}
|
|
110
|
+
return ancestors(dirnamePath(configFile)).some((dir) => host.kindOf(joinPath(dir, INSTALLED_PACKAGES, GLOBALS_RUNNER, "globals.d.ts")) === "file");
|
|
126
111
|
}
|
|
127
112
|
/** The directory of the manifest nearest one configuration file, at or above its own. */
|
|
128
113
|
function manifestDir(host, configFile) {
|
|
129
|
-
|
|
130
|
-
if (host.kindOf(joinPath(dir, "package.json")) === "file") {
|
|
131
|
-
return dir;
|
|
132
|
-
}
|
|
133
|
-
if (dirnamePath(dir) === dir) {
|
|
134
|
-
return undefined;
|
|
135
|
-
}
|
|
136
|
-
}
|
|
114
|
+
return ancestors(dirnamePath(configFile)).find((dir) => holdsManifest(host, dir));
|
|
137
115
|
}
|
|
138
116
|
function typesOf(value) {
|
|
139
117
|
return Array.isArray(value) && value.every((one) => typeof one === "string") ? value : undefined;
|
package/dist/src/test-joins.js
CHANGED
|
@@ -6,14 +6,16 @@
|
|
|
6
6
|
* file another package's, which its own test runner compiles.
|
|
7
7
|
*/
|
|
8
8
|
import { sourcePaths } from "./file-facts.js";
|
|
9
|
-
import { dirnamePath, joinPath
|
|
9
|
+
import { ancestors, dirnamePath, joinPath } from "./paths.js";
|
|
10
10
|
import { writtenPath } from "./workspace-programs.js";
|
|
11
|
+
import { bytewise } from "./text/compare.js";
|
|
12
|
+
import { holdsManifest } from "./fs/manifest.js";
|
|
11
13
|
const PREFERRED = "/tsconfig.json";
|
|
12
14
|
/** The configurations of one directory in the order a file joins them: `tsconfig.json` first. */
|
|
13
15
|
function byPreference(a, b) {
|
|
14
16
|
const first = a.endsWith(PREFERRED) ? 0 : 1;
|
|
15
17
|
const second = b.endsWith(PREFERRED) ? 0 : 1;
|
|
16
|
-
return first - second || (a
|
|
18
|
+
return first - second || bytewise(a, b);
|
|
17
19
|
}
|
|
18
20
|
/**
|
|
19
21
|
* Per configuration of `configFiles`, the absolute paths of the test files that join it:
|
|
@@ -43,15 +45,13 @@ export function joinedTestFiles(engine, host, root, configFiles, patterns) {
|
|
|
43
45
|
if (listed.has(file) || !patterns.some((pattern) => pattern.test(path))) {
|
|
44
46
|
continue;
|
|
45
47
|
}
|
|
46
|
-
for (
|
|
48
|
+
for (const dir of ancestors(dirnamePath(file), root)) {
|
|
47
49
|
const [nearest] = byDir.get(dir) ?? [];
|
|
48
50
|
if (nearest !== undefined) {
|
|
49
51
|
joins.set(nearest, [...(joins.get(nearest) ?? []), file]);
|
|
50
52
|
break;
|
|
51
53
|
}
|
|
52
|
-
if (host
|
|
53
|
-
dir === root ||
|
|
54
|
-
relativePath(root, dir) === undefined) {
|
|
54
|
+
if (holdsManifest(host, dir)) {
|
|
55
55
|
break;
|
|
56
56
|
}
|
|
57
57
|
}
|
|
@@ -0,0 +1,26 @@
|
|
|
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, b) {
|
|
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
|
+
/** One UTF-16 unit moved so that a surrogate ranks above every unit of U+E000 to U+FFFF. */
|
|
21
|
+
function ranked(unit) {
|
|
22
|
+
if (unit >= 0xe000) {
|
|
23
|
+
return unit - 0x800;
|
|
24
|
+
}
|
|
25
|
+
return unit >= 0xd800 ? unit + 0x2000 : unit;
|
|
26
|
+
}
|
|
@@ -0,0 +1,194 @@
|
|
|
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
|
+
* One character of a pattern as the expression syntax reads it literally. The set escaped
|
|
10
|
+
* is closed: under the unicode flag an escape of any other character is refused outright.
|
|
11
|
+
*/
|
|
12
|
+
function quoted(character) {
|
|
13
|
+
return /[.*+?^${}()|[\]\\/]/u.test(character) ? `\\${character}` : character;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* The ASCII ranges each POSIX class a bracket expression may name holds, as git's own
|
|
17
|
+
* character table reads them, each range written as its first and last character.
|
|
18
|
+
*/
|
|
19
|
+
const CLASSES = new Map([
|
|
20
|
+
["alnum", "09AZaz"],
|
|
21
|
+
["alpha", "AZaz"],
|
|
22
|
+
["blank", "\t\t "],
|
|
23
|
+
["cntrl", "\0\x1f\x7f\x7f"],
|
|
24
|
+
["digit", "09"],
|
|
25
|
+
["graph", "!~"],
|
|
26
|
+
["lower", "az"],
|
|
27
|
+
["print", " ~"],
|
|
28
|
+
["punct", "!/:@[`{~"],
|
|
29
|
+
["space", "\t\n\r\r "],
|
|
30
|
+
["upper", "AZ"],
|
|
31
|
+
["xdigit", "09AFaf"],
|
|
32
|
+
]);
|
|
33
|
+
/** What a pattern git cannot read matches: nothing, as git aborts the whole match. */
|
|
34
|
+
const NOTHING = "(?!)";
|
|
35
|
+
/** One code point as a member of an expression class. */
|
|
36
|
+
function member(point) {
|
|
37
|
+
return `\\u{${point.toString(16)}}`;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* The class a bracket expression opening at `open` denotes, and the index past its `]`,
|
|
41
|
+
* read as git's wildmatch reads it: a leading `!` or `^` negates it, a `]` first is a
|
|
42
|
+
* member, `\` escapes one character, `x-y` is a range (one whose `y` is below `x` holds
|
|
43
|
+
* `x` alone) and `[:name:]` a POSIX class. An unclosed bracket or an unknown class name
|
|
44
|
+
* matches nothing. Neither form matches the solidus.
|
|
45
|
+
*/
|
|
46
|
+
function bracket(pattern, open) {
|
|
47
|
+
let at = open + 1;
|
|
48
|
+
const negated = pattern[at] === "!" || pattern[at] === "^";
|
|
49
|
+
if (negated) {
|
|
50
|
+
at += 1;
|
|
51
|
+
}
|
|
52
|
+
const next = () => {
|
|
53
|
+
const point = pattern.codePointAt(at);
|
|
54
|
+
at += point !== undefined && point > 0xffff ? 2 : 1;
|
|
55
|
+
return point;
|
|
56
|
+
};
|
|
57
|
+
let members = "";
|
|
58
|
+
let low;
|
|
59
|
+
for (let first = true;; first = false) {
|
|
60
|
+
let point = next();
|
|
61
|
+
if (point === undefined) {
|
|
62
|
+
return { source: NOTHING, end: pattern.length };
|
|
63
|
+
}
|
|
64
|
+
if (point === 0x5d && !first) {
|
|
65
|
+
return { source: negated ? `[^/${members}]` : `(?!/)[${members}]`, end: at };
|
|
66
|
+
}
|
|
67
|
+
if (point === 0x2d && low !== undefined && at < pattern.length && pattern[at] !== "]") {
|
|
68
|
+
let high = next();
|
|
69
|
+
if (high === 0x5c) {
|
|
70
|
+
high = next();
|
|
71
|
+
}
|
|
72
|
+
if (high === undefined) {
|
|
73
|
+
return { source: NOTHING, end: pattern.length };
|
|
74
|
+
}
|
|
75
|
+
members += high >= low ? `${member(low)}-${member(high)}` : "";
|
|
76
|
+
low = undefined;
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
79
|
+
if (point === 0x5b && pattern[at] === ":") {
|
|
80
|
+
const close = pattern.indexOf("]", at + 1);
|
|
81
|
+
if (close < 0) {
|
|
82
|
+
return { source: NOTHING, end: pattern.length };
|
|
83
|
+
}
|
|
84
|
+
if (close > at + 1 && pattern[close - 1] === ":") {
|
|
85
|
+
const ranges = CLASSES.get(pattern.slice(at + 1, close - 1));
|
|
86
|
+
if (ranges === undefined) {
|
|
87
|
+
return { source: NOTHING, end: pattern.length };
|
|
88
|
+
}
|
|
89
|
+
for (let one = 0; one < ranges.length; one += 2) {
|
|
90
|
+
members += `${member(ranges.charCodeAt(one))}-${member(ranges.charCodeAt(one + 1))}`;
|
|
91
|
+
}
|
|
92
|
+
low = undefined;
|
|
93
|
+
at = close + 1;
|
|
94
|
+
continue;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
if (point === 0x5c) {
|
|
98
|
+
point = next();
|
|
99
|
+
if (point === undefined) {
|
|
100
|
+
return { source: NOTHING, end: pattern.length };
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
members += member(point);
|
|
104
|
+
low = point;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
/** The expression source of one pattern with no brace list. */
|
|
108
|
+
function source(pattern, form) {
|
|
109
|
+
let text = "";
|
|
110
|
+
let at = 0;
|
|
111
|
+
while (at < pattern.length) {
|
|
112
|
+
if (pattern.startsWith("**/", at)) {
|
|
113
|
+
text += "(?:[^/]*/)*";
|
|
114
|
+
at += 3;
|
|
115
|
+
continue;
|
|
116
|
+
}
|
|
117
|
+
if (pattern.startsWith("**", at)) {
|
|
118
|
+
text += ".*";
|
|
119
|
+
at += 2;
|
|
120
|
+
continue;
|
|
121
|
+
}
|
|
122
|
+
const character = pattern[at] ?? "";
|
|
123
|
+
if (form === "gitignore" && character === "[") {
|
|
124
|
+
const read = bracket(pattern, at);
|
|
125
|
+
text += read.source;
|
|
126
|
+
at = read.end;
|
|
127
|
+
continue;
|
|
128
|
+
}
|
|
129
|
+
if (form === "gitignore" && character === "\\" && at + 1 < pattern.length) {
|
|
130
|
+
text += quoted(pattern[at + 1] ?? "");
|
|
131
|
+
at += 2;
|
|
132
|
+
continue;
|
|
133
|
+
}
|
|
134
|
+
at += 1;
|
|
135
|
+
text += character === "*" ? "[^/]*" : character === "?" ? "[^/]" : quoted(character);
|
|
136
|
+
}
|
|
137
|
+
return text;
|
|
138
|
+
}
|
|
139
|
+
/** A pattern with every brace list expanded into its alternatives; a lone `{` stands for itself. */
|
|
140
|
+
export function braceExpanded(pattern) {
|
|
141
|
+
const open = pattern.indexOf("{");
|
|
142
|
+
const close = open < 0 ? -1 : pattern.indexOf("}", open);
|
|
143
|
+
if (close < 0) {
|
|
144
|
+
return [pattern];
|
|
145
|
+
}
|
|
146
|
+
const before = pattern.slice(0, open);
|
|
147
|
+
const after = pattern.slice(close + 1);
|
|
148
|
+
return pattern
|
|
149
|
+
.slice(open + 1, close)
|
|
150
|
+
.split(",")
|
|
151
|
+
.flatMap((member) => braceExpanded(before + member + after));
|
|
152
|
+
}
|
|
153
|
+
/** The expression one whole-path pattern of the configured form denotes. */
|
|
154
|
+
export function globExpression(pattern) {
|
|
155
|
+
const alternatives = braceExpanded(pattern).map((one) => source(one, "configured"));
|
|
156
|
+
return new RegExp(`^(?:${alternatives.join("|")})$`, "u");
|
|
157
|
+
}
|
|
158
|
+
/** The expression one `.gitignore` pattern denotes over a path below its file's directory. */
|
|
159
|
+
export function gitignoreExpression(pattern) {
|
|
160
|
+
return new RegExp(`^${source(pattern, "gitignore")}$`, "u");
|
|
161
|
+
}
|
|
162
|
+
const SEGMENTS = new Map();
|
|
163
|
+
/**
|
|
164
|
+
* Whether one directory name matches one segment of a workspace's member pattern, its brace
|
|
165
|
+
* lists already expanded. Neither `*` nor `?` matches a leading dot, as npm's workspaces read
|
|
166
|
+
* them: https://docs.npmjs.com/cli/using-npm/workspaces
|
|
167
|
+
*/
|
|
168
|
+
export function segmentMatches(segment, name) {
|
|
169
|
+
if (name.startsWith(".") && !segment.startsWith(".")) {
|
|
170
|
+
return false;
|
|
171
|
+
}
|
|
172
|
+
let expression = SEGMENTS.get(segment);
|
|
173
|
+
if (expression === undefined) {
|
|
174
|
+
expression = globExpression(segment);
|
|
175
|
+
SEGMENTS.set(segment, expression);
|
|
176
|
+
}
|
|
177
|
+
return expression.test(name);
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* Whether one path's segments match a member pattern's, `**` standing for any run of segments
|
|
181
|
+
* that starts with no dot.
|
|
182
|
+
*/
|
|
183
|
+
export function pathMatches(pattern, path) {
|
|
184
|
+
const [head, ...rest] = pattern;
|
|
185
|
+
if (head === undefined) {
|
|
186
|
+
return path.length === 0;
|
|
187
|
+
}
|
|
188
|
+
if (head === "**") {
|
|
189
|
+
return (pathMatches(rest, path) ||
|
|
190
|
+
(path.length > 0 && !(path[0] ?? "").startsWith(".") && pathMatches(pattern, path.slice(1))));
|
|
191
|
+
}
|
|
192
|
+
const [name, ...below] = path;
|
|
193
|
+
return name !== undefined && segmentMatches(head, name) && pathMatches(rest, below);
|
|
194
|
+
}
|
|
@@ -0,0 +1,159 @@
|
|
|
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
|
+
* The shell's list and pipeline operators, each of which ends one command. An `&` beside a
|
|
7
|
+
* redirection's `<` or `>` (`2>&1`, and bash's `&>log`) is part of the redirection.
|
|
8
|
+
*/
|
|
9
|
+
const COMMAND_OPERATORS = /&&|\|\||;|\||(?<![<>])&(?!>)/u;
|
|
10
|
+
/** Where a command splits into tokens: white space and the operators the Contract names. */
|
|
11
|
+
const TOKEN_SEPARATORS = /\s+|&&|\|\||;|\|/u;
|
|
12
|
+
/** A text written inside one pair of quotes. */
|
|
13
|
+
const QUOTED = /^(["'])(.*)\1$/su;
|
|
14
|
+
/** The commands one line runs, the text between two of the shell's list and pipeline operators. */
|
|
15
|
+
export function commandsOf(line) {
|
|
16
|
+
return line.split(COMMAND_OPERATORS);
|
|
17
|
+
}
|
|
18
|
+
/** One text with one pair of enclosing quotes removed. */
|
|
19
|
+
export function unquoted(text) {
|
|
20
|
+
return text.replace(QUOTED, "$2");
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Every token one script's command line holds, each with one pair of enclosing quotes
|
|
24
|
+
* removed. A token holding a wildcard is a pattern rather than a path, and names no
|
|
25
|
+
* entry point.
|
|
26
|
+
*/
|
|
27
|
+
export function scriptTokens(command) {
|
|
28
|
+
return command
|
|
29
|
+
.split(TOKEN_SEPARATORS)
|
|
30
|
+
.map(unquoted)
|
|
31
|
+
.filter((token) => token !== "" && !token.includes("*"));
|
|
32
|
+
}
|
|
33
|
+
/** One shell line up to its comment: a `#` that starts a word outside quotes. */
|
|
34
|
+
export function withoutComment(line) {
|
|
35
|
+
let quote = "";
|
|
36
|
+
for (let at = 0; at < line.length; at += 1) {
|
|
37
|
+
const char = line[at];
|
|
38
|
+
if (quote !== "") {
|
|
39
|
+
if (char === quote) {
|
|
40
|
+
quote = "";
|
|
41
|
+
}
|
|
42
|
+
else if (char === "\\" && quote === '"') {
|
|
43
|
+
at += 1;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
else if (char === "'" || char === '"') {
|
|
47
|
+
quote = char;
|
|
48
|
+
}
|
|
49
|
+
else if (char === "\\") {
|
|
50
|
+
at += 1;
|
|
51
|
+
}
|
|
52
|
+
else if (char === "#" && (at === 0 || /\s/u.test(line[at - 1] ?? ""))) {
|
|
53
|
+
return line.slice(0, at);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return line;
|
|
57
|
+
}
|
|
58
|
+
/** The characters that end a word outside quotes and separate nothing: blanks. */
|
|
59
|
+
// 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
|
|
60
|
+
const BLANK = /[ \t\r\f\v]/u;
|
|
61
|
+
/** The characters that end a word outside quotes and begin an operator: newline included. */
|
|
62
|
+
const OPERATOR = /[\n;&|()<>]/u;
|
|
63
|
+
/** The characters a backslash escapes inside double quotes; before any other it stands for itself. */
|
|
64
|
+
const DOUBLE_QUOTE_ESCAPES = new Set(["$", "`", '"', "\\"]);
|
|
65
|
+
/**
|
|
66
|
+
* The words of one command line, as POSIX token recognition reads quotes, backslashes,
|
|
67
|
+
* line continuations, comments and operators. A word an expansion may change (an unquoted
|
|
68
|
+
* `$`, backquote, glob character or leading `~`, or a `$` or backquote in double quotes)
|
|
69
|
+
* is computed, and so is the rest of a line whose quote is never closed. A computed word
|
|
70
|
+
* and each operator are undefined, so a flag's file is never read across them.
|
|
71
|
+
*/
|
|
72
|
+
export function shellWords(command) {
|
|
73
|
+
const words = [];
|
|
74
|
+
let word;
|
|
75
|
+
let computed = false;
|
|
76
|
+
const end = () => {
|
|
77
|
+
if (word !== undefined) {
|
|
78
|
+
words.push(computed ? undefined : word);
|
|
79
|
+
}
|
|
80
|
+
word = undefined;
|
|
81
|
+
computed = false;
|
|
82
|
+
};
|
|
83
|
+
for (let at = 0; at < command.length; at += 1) {
|
|
84
|
+
const char = command.charAt(at);
|
|
85
|
+
if (char === "\\" && command.charAt(at + 1) === "\n") {
|
|
86
|
+
at += 1;
|
|
87
|
+
}
|
|
88
|
+
else if (BLANK.test(char)) {
|
|
89
|
+
end();
|
|
90
|
+
}
|
|
91
|
+
else if (OPERATOR.test(char)) {
|
|
92
|
+
end();
|
|
93
|
+
words.push(undefined);
|
|
94
|
+
}
|
|
95
|
+
else if (char === "#" && word === undefined) {
|
|
96
|
+
const newline = command.indexOf("\n", at);
|
|
97
|
+
at = newline < 0 ? command.length : newline - 1;
|
|
98
|
+
}
|
|
99
|
+
else if (char === "\\") {
|
|
100
|
+
at += 1;
|
|
101
|
+
word = (word ?? "") + command.charAt(at);
|
|
102
|
+
}
|
|
103
|
+
else if (char === "'" || char === '"') {
|
|
104
|
+
const close = closingQuote(command, at);
|
|
105
|
+
if (close < 0) {
|
|
106
|
+
word ??= "";
|
|
107
|
+
computed = true;
|
|
108
|
+
break;
|
|
109
|
+
}
|
|
110
|
+
const quoted = char === "'"
|
|
111
|
+
? { text: command.slice(at + 1, close), computed: false }
|
|
112
|
+
: doubleQuoted(command.slice(at + 1, close));
|
|
113
|
+
word = (word ?? "") + quoted.text;
|
|
114
|
+
computed ||= quoted.computed;
|
|
115
|
+
at = close;
|
|
116
|
+
}
|
|
117
|
+
else {
|
|
118
|
+
computed ||= /[$`*?[]/u.test(char) || (char === "~" && word === undefined);
|
|
119
|
+
word = (word ?? "") + char;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
end();
|
|
123
|
+
return words;
|
|
124
|
+
}
|
|
125
|
+
/** The text between one pair of double quotes, unescaped, and whether an expansion may change it. */
|
|
126
|
+
function doubleQuoted(inner) {
|
|
127
|
+
let text = "";
|
|
128
|
+
let computed = false;
|
|
129
|
+
for (let at = 0; at < inner.length; at += 1) {
|
|
130
|
+
const char = inner.charAt(at);
|
|
131
|
+
const next = inner.charAt(at + 1);
|
|
132
|
+
if (char === "\\" && next === "\n") {
|
|
133
|
+
at += 1;
|
|
134
|
+
}
|
|
135
|
+
else if (char === "\\" && DOUBLE_QUOTE_ESCAPES.has(next)) {
|
|
136
|
+
text += next;
|
|
137
|
+
at += 1;
|
|
138
|
+
}
|
|
139
|
+
else {
|
|
140
|
+
computed ||= char === "$" || char === "`";
|
|
141
|
+
text += char;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
return { text, computed };
|
|
145
|
+
}
|
|
146
|
+
/** The index of the quote that closes the one at `open`, or -1 where none does. */
|
|
147
|
+
function closingQuote(command, open) {
|
|
148
|
+
const quote = command.charAt(open);
|
|
149
|
+
for (let at = open + 1; at < command.length; at += 1) {
|
|
150
|
+
const char = command.charAt(at);
|
|
151
|
+
if (char === quote) {
|
|
152
|
+
return at;
|
|
153
|
+
}
|
|
154
|
+
if (quote === '"' && char === "\\") {
|
|
155
|
+
at += 1;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
return -1;
|
|
159
|
+
}
|