@tsuzuri-lab/sed-language-server 0.1.2 → 0.3.0
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/README.md +50 -39
- package/package.json +8 -4
- package/src/definition.js +30 -32
- package/src/diagnostics.js +83 -1829
- package/src/formatting.js +97 -0
- package/src/server.js +56 -46
- package/src/syntax.js +124 -0
- package/vendor/tree-sitter-sed-gnu.wasm +0 -0
- package/vendor/tree-sitter-sed-posix.wasm +0 -0
- package/src/completion.js +0 -148
- package/src/document-structure.js +0 -774
- package/src/sed-syntax.js +0 -2321
- package/src/syntax-profile.js +0 -95
package/src/diagnostics.js
CHANGED
|
@@ -1,1865 +1,119 @@
|
|
|
1
1
|
import { DiagnosticSeverity } from "vscode-languageserver/node";
|
|
2
2
|
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
findLineEnd,
|
|
8
|
-
findReplacementDelimiter,
|
|
9
|
-
findTransliterateDelimiter,
|
|
10
|
-
hasUnescapedTrailingBackslash,
|
|
11
|
-
iterateRegexpTokens,
|
|
12
|
-
scanAddressSyntax,
|
|
13
|
-
scanCommandEndSyntax,
|
|
14
|
-
scanCommandRecoverySyntax,
|
|
15
|
-
scanFilenameArgumentSyntax,
|
|
16
|
-
scanLabelArgumentSyntax,
|
|
17
|
-
scanOptionalNumericArgumentSyntax,
|
|
18
|
-
scanRegexDelimiter,
|
|
19
|
-
scanShellCommandSyntax,
|
|
20
|
-
scanSubstituteFlagTokenSyntax,
|
|
21
|
-
scanTextCommandSyntax,
|
|
22
|
-
scanVersionArgumentSyntax,
|
|
23
|
-
skipBlanks,
|
|
24
|
-
syntaxPolicyFor,
|
|
25
|
-
} from "./sed-syntax.js";
|
|
26
|
-
import {
|
|
27
|
-
defaultSyntaxProfile,
|
|
28
|
-
requireSyntaxProfile,
|
|
29
|
-
} from "./syntax-profile.js";
|
|
30
|
-
|
|
31
|
-
const SOURCE = "sed-language-server";
|
|
32
|
-
const DIALECT_NAME = "\0sed-dialect\0";
|
|
3
|
+
collectSyntaxIssueNodes,
|
|
4
|
+
rangeForNode,
|
|
5
|
+
syntaxTreeFor,
|
|
6
|
+
} from "./syntax.js";
|
|
33
7
|
|
|
34
|
-
const
|
|
35
|
-
missingDelimiter: `Expected a delimiter after the ${DIALECT_NAME} sed \`s\` command.`,
|
|
36
|
-
invalidDelimiter: `A ${DIALECT_NAME} sed \`s\` command cannot use a backslash or newline as its delimiter.`,
|
|
37
|
-
unterminatedPattern: `The ${DIALECT_NAME} sed substitute pattern is not terminated.`,
|
|
38
|
-
unterminatedReplacement: `The ${DIALECT_NAME} sed substitute replacement is not terminated.`,
|
|
39
|
-
invalidFlag: (flag) =>
|
|
40
|
-
`Invalid ${DIALECT_NAME} sed substitute flag: \`${flag}\`.`,
|
|
41
|
-
repeatedOccurrence:
|
|
42
|
-
"A GNU sed `s` command accepts only one occurrence-number option.",
|
|
43
|
-
zeroOccurrence: "A GNU sed `s` command occurrence number must not be zero.",
|
|
44
|
-
repeatedSubstituteFlag: (flag) =>
|
|
45
|
-
`The GNU sed \`${flag}\` substitute flag may only be specified once.`,
|
|
46
|
-
missingWriteFileSeparator: `Expected a blank between the ${DIALECT_NAME} sed \`w\` flag and its filename.`,
|
|
47
|
-
missingWriteFile: `Expected a filename after the ${DIALECT_NAME} sed substitute \`w\` flag.`,
|
|
48
|
-
missingFileSeparator: (command) =>
|
|
49
|
-
`Expected a blank between the ${DIALECT_NAME} sed \`${command}\` command and its filename.`,
|
|
50
|
-
missingFile: (command) =>
|
|
51
|
-
`Expected a filename after the ${DIALECT_NAME} sed \`${command}\` command.`,
|
|
52
|
-
missingLabel: `Expected a label after the ${DIALECT_NAME} sed \`:\` command.`,
|
|
53
|
-
missingLabelSeparator: (command) =>
|
|
54
|
-
`Expected a space between the ${DIALECT_NAME} sed \`${command}\` command and its label.`,
|
|
55
|
-
unexpectedClosingBrace: `This ${DIALECT_NAME} sed closing brace has no matching opening brace.`,
|
|
56
|
-
missingClosingBraceSeparator: `Expected a newline or semicolon before this ${DIALECT_NAME} sed closing brace.`,
|
|
57
|
-
unclosedOpeningBrace: `This ${DIALECT_NAME} sed opening brace is not closed.`,
|
|
58
|
-
missingTransliterateDelimiter: `Expected a delimiter after the ${DIALECT_NAME} sed \`y\` command.`,
|
|
59
|
-
invalidTransliterateDelimiter: `A ${DIALECT_NAME} sed \`y\` command cannot use a backslash or newline as its delimiter.`,
|
|
60
|
-
unterminatedFirstTransliterateString: `The first string in this ${DIALECT_NAME} sed \`y\` command is not terminated.`,
|
|
61
|
-
unterminatedSecondTransliterateString: `The second string in this ${DIALECT_NAME} sed \`y\` command is not terminated.`,
|
|
62
|
-
missingContextAddressDelimiter: `Expected a delimiter after the backslash in this ${DIALECT_NAME} sed context address.`,
|
|
63
|
-
invalidContextAddressDelimiter: `A ${DIALECT_NAME} sed context address cannot use a backslash or newline as its delimiter.`,
|
|
64
|
-
unterminatedContextAddress: `This ${DIALECT_NAME} sed context address is not terminated.`,
|
|
65
|
-
relativeAddressFirst:
|
|
66
|
-
"A GNU sed `+N` or `~N` address can only be the second address in a range.",
|
|
67
|
-
missingRangeAddress:
|
|
68
|
-
"Expected an address after this comma in a GNU sed address range.",
|
|
69
|
-
emptyRegexpModifiers:
|
|
70
|
-
"GNU sed regexp modifiers cannot be used with an empty regexp.",
|
|
71
|
-
emptySubstituteRegexpModifiers:
|
|
72
|
-
"GNU sed substitute regexp modifiers cannot be used with an empty regexp.",
|
|
73
|
-
invalidZeroAddress: "GNU sed line address 0 is not valid in this context.",
|
|
74
|
-
invalidRegexpBackReference: (regexpMode, number) =>
|
|
75
|
-
`The ${DIALECT_NAME} ${regexpMode.toUpperCase()} back-reference \`\\${number}\` does not refer to a preceding subexpression.`,
|
|
76
|
-
invalidControlEscape:
|
|
77
|
-
"GNU sed does not allow recursive escaping after `\\c`.",
|
|
78
|
-
unclosedRegexpBracket: `The ${DIALECT_NAME} sed regular expression has an unmatched \`[\`.`,
|
|
79
|
-
missingCommand: `Expected a ${DIALECT_NAME} sed command.`,
|
|
80
|
-
unknownCommand: (command) =>
|
|
81
|
-
`Unknown ${DIALECT_NAME} sed command: \`${command}\`.`,
|
|
82
|
-
unexpectedCommandText: (command) =>
|
|
83
|
-
`Unexpected text after the ${DIALECT_NAME} sed \`${command}\` command.`,
|
|
84
|
-
missingTextBackslash: (command) =>
|
|
85
|
-
`Expected a backslash immediately after the ${DIALECT_NAME} sed \`${command}\` command.`,
|
|
86
|
-
unexpectedTextAfterTextBackslash: (command) =>
|
|
87
|
-
`Unexpected text after the backslash in the ${DIALECT_NAME} sed \`${command}\` command.`,
|
|
88
|
-
missingTextNewline: (command) =>
|
|
89
|
-
`Expected a newline after the backslash in the ${DIALECT_NAME} sed \`${command}\` command.`,
|
|
90
|
-
missingTextLine: (command) =>
|
|
91
|
-
`Expected a line of text for the ${DIALECT_NAME} sed \`${command}\` command.`,
|
|
92
|
-
missingTextArgument: (command) =>
|
|
93
|
-
`Expected text or a backslash after the ${DIALECT_NAME} sed \`${command}\` command.`,
|
|
94
|
-
unsupportedVersion: (requiredVersion) =>
|
|
95
|
-
`The GNU sed 4.10 target does not satisfy the required version \`${requiredVersion}\`.`,
|
|
96
|
-
tooManyAddressesBeforeCommand: `A ${DIALECT_NAME} sed command accepts at most two addresses.`,
|
|
97
|
-
emptyCommandHasAddresses: `An empty ${DIALECT_NAME} sed command does not accept addresses.`,
|
|
98
|
-
tooManyAddresses: (command, maximum) => {
|
|
99
|
-
if (maximum === 0) {
|
|
100
|
-
return `The ${DIALECT_NAME} sed \`${command}\` command does not accept addresses.`;
|
|
101
|
-
}
|
|
8
|
+
const diagnosticSource = "sed-language-server";
|
|
102
9
|
|
|
103
|
-
|
|
104
|
-
|
|
10
|
+
const issueDescriptions = {
|
|
11
|
+
incomplete_escape: {
|
|
12
|
+
code: "incomplete-escape",
|
|
13
|
+
message: "This escape sequence is incomplete.",
|
|
14
|
+
},
|
|
15
|
+
incomplete_regex: {
|
|
16
|
+
code: "unterminated-regular-expression",
|
|
17
|
+
message: "This regular expression is not terminated.",
|
|
18
|
+
},
|
|
19
|
+
incomplete_replacement: {
|
|
20
|
+
code: "unterminated-replacement",
|
|
21
|
+
message: "This replacement is not terminated.",
|
|
105
22
|
},
|
|
23
|
+
incomplete_translate: {
|
|
24
|
+
code: "unterminated-translation",
|
|
25
|
+
message: "This translation is not terminated.",
|
|
26
|
+
},
|
|
27
|
+
invalid_control_escape: {
|
|
28
|
+
code: "invalid-control-escape",
|
|
29
|
+
message: "This GNU sed control escape is invalid.",
|
|
30
|
+
},
|
|
31
|
+
unclosed_bracket: {
|
|
32
|
+
code: "unclosed-bracket-expression",
|
|
33
|
+
message: "This bracket expression is not terminated.",
|
|
34
|
+
},
|
|
35
|
+
invalid_command: (node, dialect) => ({
|
|
36
|
+
code: "invalid-command",
|
|
37
|
+
message: `Unknown ${dialectName(dialect)} sed command: \`${node.text}\`.`,
|
|
38
|
+
}),
|
|
39
|
+
invalid_flag: (node, dialect) => ({
|
|
40
|
+
code: "invalid-substitution-flag",
|
|
41
|
+
message: `Unknown ${dialectName(dialect)} sed substitution flag: \`${node.text}\`.`,
|
|
42
|
+
}),
|
|
43
|
+
unexpected_text: (_node, dialect) => ({
|
|
44
|
+
code: "unexpected-text",
|
|
45
|
+
message: `Unexpected text after this ${dialectName(dialect)} sed command.`,
|
|
46
|
+
}),
|
|
106
47
|
};
|
|
107
48
|
|
|
108
|
-
function
|
|
109
|
-
|
|
110
|
-
return message.replaceAll(DIALECT_NAME, dialectName);
|
|
49
|
+
function dialectName(dialect) {
|
|
50
|
+
return dialect === "gnu" ? "GNU" : "POSIX";
|
|
111
51
|
}
|
|
112
52
|
|
|
113
|
-
function
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
let nextSubexpression = 1;
|
|
119
|
-
let subexpressionDepth = 0;
|
|
120
|
-
const trackedSubexpressionsByDepth = new Map();
|
|
121
|
-
const closedSubexpressions = new Set();
|
|
122
|
-
const problems = [];
|
|
123
|
-
|
|
124
|
-
for (const token of iterateRegexpTokens(
|
|
125
|
-
text,
|
|
126
|
-
offset,
|
|
127
|
-
end,
|
|
128
|
-
delimiter,
|
|
129
|
-
syntaxProfile,
|
|
130
|
-
)) {
|
|
131
|
-
if (token.kind === "invalid-control-escape") {
|
|
132
|
-
problems.push(
|
|
133
|
-
issue(
|
|
134
|
-
"regexp-invalid-control-escape",
|
|
135
|
-
messages.invalidControlEscape,
|
|
136
|
-
token.offset,
|
|
137
|
-
token.endOffset,
|
|
138
|
-
),
|
|
139
|
-
);
|
|
140
|
-
} else if (token.kind === "unclosed-bracket-expression") {
|
|
141
|
-
problems.push(
|
|
142
|
-
issue(
|
|
143
|
-
"regexp-unclosed-bracket-expression",
|
|
144
|
-
messages.unclosedRegexpBracket,
|
|
145
|
-
token.offset,
|
|
146
|
-
token.endOffset,
|
|
147
|
-
),
|
|
148
|
-
);
|
|
149
|
-
} else if (token.kind === "subexpression-open") {
|
|
150
|
-
subexpressionDepth += 1;
|
|
151
|
-
if (nextSubexpression <= 9) {
|
|
152
|
-
trackedSubexpressionsByDepth.set(subexpressionDepth, nextSubexpression);
|
|
153
|
-
}
|
|
154
|
-
nextSubexpression += 1;
|
|
155
|
-
} else if (token.kind === "subexpression-close") {
|
|
156
|
-
const subexpression =
|
|
157
|
-
trackedSubexpressionsByDepth.get(subexpressionDepth);
|
|
158
|
-
if (subexpression !== undefined) {
|
|
159
|
-
closedSubexpressions.add(subexpression);
|
|
160
|
-
trackedSubexpressionsByDepth.delete(subexpressionDepth);
|
|
161
|
-
}
|
|
162
|
-
subexpressionDepth = Math.max(0, subexpressionDepth - 1);
|
|
163
|
-
} else if (
|
|
164
|
-
token.kind === "back-reference" &&
|
|
165
|
-
!closedSubexpressions.has(Number(token.value))
|
|
166
|
-
) {
|
|
167
|
-
problems.push(
|
|
168
|
-
issue(
|
|
169
|
-
`${syntaxProfile.regexpMode}-invalid-back-reference`,
|
|
170
|
-
messages.invalidRegexpBackReference(
|
|
171
|
-
syntaxProfile.regexpMode,
|
|
172
|
-
token.value,
|
|
173
|
-
),
|
|
174
|
-
token.offset,
|
|
175
|
-
token.endOffset,
|
|
176
|
-
),
|
|
177
|
-
);
|
|
178
|
-
}
|
|
53
|
+
function descriptionFor(node, dialect) {
|
|
54
|
+
const description = issueDescriptions[node.type];
|
|
55
|
+
if (typeof description === "function") {
|
|
56
|
+
return description(node, dialect);
|
|
179
57
|
}
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
const commandDefinitionsByProfile = new Map();
|
|
185
|
-
|
|
186
|
-
function commandDefinitionsFor(syntaxProfile) {
|
|
187
|
-
let definitions = commandDefinitionsByProfile.get(syntaxProfile);
|
|
188
|
-
if (definitions !== undefined) {
|
|
189
|
-
return definitions;
|
|
58
|
+
if (description !== undefined) {
|
|
59
|
+
return description;
|
|
190
60
|
}
|
|
191
61
|
|
|
192
|
-
|
|
193
|
-
commandSpecificationsFor(syntaxProfile).map(
|
|
194
|
-
({ command, kind, maximumAddresses }) => [
|
|
195
|
-
command,
|
|
196
|
-
{ kind, maximumAddresses },
|
|
197
|
-
],
|
|
198
|
-
),
|
|
199
|
-
);
|
|
200
|
-
commandDefinitionsByProfile.set(syntaxProfile, definitions);
|
|
201
|
-
return definitions;
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
function scanAddress(text, offset, end, syntaxProfile) {
|
|
205
|
-
const result = scanAddressSyntax(text, offset, end, syntaxProfile);
|
|
206
|
-
if (result.kind === "none") {
|
|
207
|
-
return result;
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
if (result.kind === "indeterminate") {
|
|
62
|
+
if (node.isMissing) {
|
|
211
63
|
return {
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
problem: null,
|
|
64
|
+
code: "missing-syntax",
|
|
65
|
+
message: `Expected ${node.type.replaceAll("_", " ")}.`,
|
|
215
66
|
};
|
|
216
67
|
}
|
|
217
68
|
|
|
218
|
-
if (result.kind === "invalid") {
|
|
219
|
-
if (result.reason === "missing-delimiter") {
|
|
220
|
-
return {
|
|
221
|
-
endOffset: result.endOffset ?? result.delimiterOffset ?? end,
|
|
222
|
-
kind: "invalid",
|
|
223
|
-
problem: issue(
|
|
224
|
-
"address-missing-delimiter",
|
|
225
|
-
messages.missingContextAddressDelimiter,
|
|
226
|
-
offset,
|
|
227
|
-
offset + 1,
|
|
228
|
-
),
|
|
229
|
-
};
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
if (result.reason === "invalid-delimiter") {
|
|
233
|
-
return {
|
|
234
|
-
endOffset: result.endOffset ?? result.delimiterOffset ?? end,
|
|
235
|
-
kind: "invalid",
|
|
236
|
-
problem: issue(
|
|
237
|
-
"address-invalid-delimiter",
|
|
238
|
-
messages.invalidContextAddressDelimiter,
|
|
239
|
-
result.delimiterOffset,
|
|
240
|
-
result.delimiterOffset + (result.delimiter?.width ?? 1),
|
|
241
|
-
),
|
|
242
|
-
};
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
return {
|
|
246
|
-
endOffset: result.endOffset ?? result.delimiterOffset ?? end,
|
|
247
|
-
kind: "invalid",
|
|
248
|
-
problem: issue(
|
|
249
|
-
"address-unterminated-context",
|
|
250
|
-
messages.unterminatedContextAddress,
|
|
251
|
-
offset,
|
|
252
|
-
result.endOffset ?? end,
|
|
253
|
-
),
|
|
254
|
-
};
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
const addressProblems = [];
|
|
258
|
-
|
|
259
|
-
if (
|
|
260
|
-
result.addressKind === "regular-expression" &&
|
|
261
|
-
result.value === "" &&
|
|
262
|
-
result.modifiers.length > 0
|
|
263
|
-
) {
|
|
264
|
-
addressProblems.push(
|
|
265
|
-
issue(
|
|
266
|
-
"address-empty-regexp-modifiers",
|
|
267
|
-
messages.emptyRegexpModifiers,
|
|
268
|
-
result.modifiers[0].startOffset,
|
|
269
|
-
result.modifiers.at(-1).endOffset,
|
|
270
|
-
),
|
|
271
|
-
);
|
|
272
|
-
}
|
|
273
|
-
|
|
274
69
|
return {
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
addressKind: result.addressKind,
|
|
278
|
-
endOffset: result.endOffset,
|
|
279
|
-
modifiers: result.modifiers,
|
|
280
|
-
startOffset: result.startOffset,
|
|
281
|
-
value: result.value,
|
|
282
|
-
regexpProblems:
|
|
283
|
-
result.addressKind !== "regular-expression" || result.isUndefined
|
|
284
|
-
? []
|
|
285
|
-
: findRegexpProblems(
|
|
286
|
-
text,
|
|
287
|
-
result.patternStartOffset,
|
|
288
|
-
result.patternEndOffset,
|
|
289
|
-
result.delimiter,
|
|
290
|
-
syntaxProfile,
|
|
291
|
-
),
|
|
70
|
+
code: "syntax-error",
|
|
71
|
+
message: `Invalid ${dialectName(dialect)} sed syntax.`,
|
|
292
72
|
};
|
|
293
73
|
}
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
return false;
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
for (const digit of value) {
|
|
301
|
-
if (digit !== "0") {
|
|
302
|
-
return false;
|
|
303
|
-
}
|
|
304
|
-
}
|
|
305
|
-
return true;
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
function addressUsageProblems(
|
|
309
|
-
addresses,
|
|
310
|
-
command,
|
|
311
|
-
hasOmittedAddress,
|
|
312
|
-
negationOffset,
|
|
313
|
-
syntaxProfile,
|
|
314
|
-
) {
|
|
315
|
-
if (syntaxProfile.dialect !== "gnu") {
|
|
316
|
-
return [];
|
|
74
|
+
function issuePriority(node) {
|
|
75
|
+
if (node.isError) {
|
|
76
|
+
return 0;
|
|
317
77
|
}
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
const firstAddress = addresses[0];
|
|
321
|
-
// GNU sed represents an omitted or zero relative value as a null address.
|
|
322
|
-
// Only a positive relative value is invalid in the first position.
|
|
323
|
-
if (
|
|
324
|
-
firstAddress !== undefined &&
|
|
325
|
-
(firstAddress.addressKind === "relative-line-count" ||
|
|
326
|
-
firstAddress.addressKind === "relative-line-multiple") &&
|
|
327
|
-
firstAddress.value !== null &&
|
|
328
|
-
!isZeroDecimal(firstAddress.value)
|
|
329
|
-
) {
|
|
330
|
-
problems.push(
|
|
331
|
-
issue(
|
|
332
|
-
"address-relative-first",
|
|
333
|
-
messages.relativeAddressFirst,
|
|
334
|
-
firstAddress.startOffset,
|
|
335
|
-
firstAddress.endOffset,
|
|
336
|
-
),
|
|
337
|
-
);
|
|
338
|
-
}
|
|
339
|
-
|
|
340
|
-
if (firstAddress === undefined) {
|
|
341
|
-
return problems;
|
|
342
|
-
}
|
|
343
|
-
|
|
344
|
-
const firstLineNumber =
|
|
345
|
-
firstAddress.addressKind === "line-number"
|
|
346
|
-
? firstAddress.value
|
|
347
|
-
: firstAddress.addressKind === "line-number-step"
|
|
348
|
-
? firstAddress.value.first
|
|
349
|
-
: null;
|
|
350
|
-
if (firstLineNumber === null || !isZeroDecimal(firstLineNumber)) {
|
|
351
|
-
return problems;
|
|
78
|
+
if (node.isMissing) {
|
|
79
|
+
return 1;
|
|
352
80
|
}
|
|
353
|
-
|
|
354
|
-
const hasPositiveStep =
|
|
355
|
-
firstAddress.addressKind === "line-number-step" &&
|
|
356
|
-
firstAddress.value.step !== null &&
|
|
357
|
-
!isZeroDecimal(firstAddress.value.step);
|
|
358
|
-
const isZeroRegexpRange = addresses[1]?.addressKind === "regular-expression";
|
|
359
|
-
const isStandaloneRead =
|
|
360
|
-
addresses.length === 1 &&
|
|
361
|
-
!hasOmittedAddress &&
|
|
362
|
-
negationOffset === null &&
|
|
363
|
-
command === "r";
|
|
364
|
-
if (!hasPositiveStep && !isZeroRegexpRange && !isStandaloneRead) {
|
|
365
|
-
problems.push(
|
|
366
|
-
issue(
|
|
367
|
-
"address-zero-invalid",
|
|
368
|
-
messages.invalidZeroAddress,
|
|
369
|
-
firstAddress.startOffset,
|
|
370
|
-
firstAddress.endOffset,
|
|
371
|
-
),
|
|
372
|
-
);
|
|
373
|
-
}
|
|
374
|
-
|
|
375
|
-
return problems;
|
|
81
|
+
return 2;
|
|
376
82
|
}
|
|
377
83
|
|
|
378
|
-
function
|
|
379
|
-
const
|
|
380
|
-
const addressScanEnd = policy.regexp.escapedPhysicalNewlines
|
|
381
|
-
? text.length
|
|
382
|
-
: lineEnd;
|
|
383
|
-
let cursor = skipBlanks(text, lineStart, lineEnd);
|
|
384
|
-
let problem = null;
|
|
385
|
-
let negationOffset = null;
|
|
386
|
-
let hasOmittedAddress = false;
|
|
387
|
-
const addresses = [];
|
|
388
|
-
const addressProblems = [];
|
|
389
|
-
const regexpProblems = [];
|
|
390
|
-
|
|
391
|
-
if (text[cursor] === ",") {
|
|
392
|
-
if (syntaxProfile.dialect === "gnu") {
|
|
393
|
-
problem = issue(
|
|
394
|
-
"address-range-missing",
|
|
395
|
-
messages.missingRangeAddress,
|
|
396
|
-
cursor,
|
|
397
|
-
cursor + 1,
|
|
398
|
-
);
|
|
399
|
-
} else {
|
|
400
|
-
hasOmittedAddress = true;
|
|
401
|
-
}
|
|
402
|
-
cursor = skipBlanks(text, cursor + 1, lineEnd);
|
|
403
|
-
}
|
|
404
|
-
|
|
405
|
-
const firstAddress = scanAddress(text, cursor, addressScanEnd, syntaxProfile);
|
|
406
|
-
|
|
407
|
-
if (firstAddress.kind === "invalid") {
|
|
408
|
-
if (firstAddress.endOffset > lineEnd) {
|
|
409
|
-
lineEnd = findLineEnd(text, firstAddress.endOffset);
|
|
410
|
-
}
|
|
411
|
-
return {
|
|
412
|
-
addressProblems,
|
|
413
|
-
addresses,
|
|
414
|
-
regexpProblems,
|
|
415
|
-
commandOffset: null,
|
|
416
|
-
hasOmittedAddress: hasOmittedAddress || firstAddress.problem === null,
|
|
417
|
-
lineEnd,
|
|
418
|
-
negationOffset,
|
|
419
|
-
problem: hasOmittedAddress ? null : firstAddress.problem,
|
|
420
|
-
resumeOffset: null,
|
|
421
|
-
};
|
|
422
|
-
}
|
|
423
|
-
|
|
424
|
-
if (firstAddress.kind === "valid") {
|
|
425
|
-
addressProblems.push(...firstAddress.addressProblems);
|
|
426
|
-
regexpProblems.push(...(firstAddress.regexpProblems ?? []));
|
|
427
|
-
addresses.push({
|
|
428
|
-
addressKind: firstAddress.addressKind,
|
|
429
|
-
endOffset: firstAddress.endOffset,
|
|
430
|
-
modifiers: firstAddress.modifiers,
|
|
431
|
-
startOffset: firstAddress.startOffset,
|
|
432
|
-
value: firstAddress.value,
|
|
433
|
-
});
|
|
434
|
-
if (firstAddress.endOffset > lineEnd) {
|
|
435
|
-
lineEnd = findLineEnd(text, firstAddress.endOffset);
|
|
436
|
-
}
|
|
437
|
-
cursor = skipBlanks(text, firstAddress.endOffset, lineEnd);
|
|
438
|
-
|
|
439
|
-
while (text[cursor] === ",") {
|
|
440
|
-
const commaOffset = cursor;
|
|
441
|
-
cursor = skipBlanks(text, commaOffset + 1, lineEnd);
|
|
442
|
-
const nextAddress = scanAddress(
|
|
443
|
-
text,
|
|
444
|
-
cursor,
|
|
445
|
-
addressScanEnd,
|
|
446
|
-
syntaxProfile,
|
|
447
|
-
);
|
|
448
|
-
|
|
449
|
-
if (nextAddress.kind === "invalid") {
|
|
450
|
-
if (nextAddress.endOffset > lineEnd) {
|
|
451
|
-
lineEnd = findLineEnd(text, nextAddress.endOffset);
|
|
452
|
-
}
|
|
453
|
-
if (addresses.length >= 2) {
|
|
454
|
-
return {
|
|
455
|
-
addressProblems,
|
|
456
|
-
addresses,
|
|
457
|
-
regexpProblems,
|
|
458
|
-
commandOffset: null,
|
|
459
|
-
hasOmittedAddress,
|
|
460
|
-
lineEnd,
|
|
461
|
-
negationOffset,
|
|
462
|
-
problem: issue(
|
|
463
|
-
"address-too-many",
|
|
464
|
-
messages.tooManyAddressesBeforeCommand,
|
|
465
|
-
commaOffset,
|
|
466
|
-
commaOffset + 1,
|
|
467
|
-
),
|
|
468
|
-
resumeOffset: null,
|
|
469
|
-
};
|
|
470
|
-
}
|
|
471
|
-
|
|
472
|
-
return {
|
|
473
|
-
addressProblems,
|
|
474
|
-
addresses,
|
|
475
|
-
regexpProblems,
|
|
476
|
-
commandOffset: null,
|
|
477
|
-
hasOmittedAddress: hasOmittedAddress || nextAddress.problem === null,
|
|
478
|
-
lineEnd,
|
|
479
|
-
negationOffset,
|
|
480
|
-
problem: nextAddress.problem,
|
|
481
|
-
resumeOffset: null,
|
|
482
|
-
};
|
|
483
|
-
}
|
|
484
|
-
|
|
485
|
-
if (nextAddress.kind === "none") {
|
|
486
|
-
if (addresses.length >= 2) {
|
|
487
|
-
problem = issue(
|
|
488
|
-
"address-too-many",
|
|
489
|
-
messages.tooManyAddressesBeforeCommand,
|
|
490
|
-
commaOffset,
|
|
491
|
-
commaOffset + 1,
|
|
492
|
-
);
|
|
493
|
-
} else if (syntaxProfile.dialect === "gnu") {
|
|
494
|
-
problem = issue(
|
|
495
|
-
"address-range-missing",
|
|
496
|
-
messages.missingRangeAddress,
|
|
497
|
-
commaOffset,
|
|
498
|
-
commaOffset + 1,
|
|
499
|
-
);
|
|
500
|
-
} else {
|
|
501
|
-
hasOmittedAddress = true;
|
|
502
|
-
}
|
|
503
|
-
|
|
504
|
-
if (text[cursor] === ";") {
|
|
505
|
-
return {
|
|
506
|
-
addressProblems,
|
|
507
|
-
addresses,
|
|
508
|
-
regexpProblems,
|
|
509
|
-
commandOffset: null,
|
|
510
|
-
hasOmittedAddress,
|
|
511
|
-
lineEnd,
|
|
512
|
-
negationOffset,
|
|
513
|
-
problem,
|
|
514
|
-
resumeOffset: skipEmptyCommands(text, cursor, lineEnd),
|
|
515
|
-
};
|
|
516
|
-
}
|
|
517
|
-
|
|
518
|
-
break;
|
|
519
|
-
}
|
|
520
|
-
|
|
521
|
-
addressProblems.push(...nextAddress.addressProblems);
|
|
522
|
-
addresses.push({
|
|
523
|
-
addressKind: nextAddress.addressKind,
|
|
524
|
-
endOffset: nextAddress.endOffset,
|
|
525
|
-
modifiers: nextAddress.modifiers,
|
|
526
|
-
startOffset: nextAddress.startOffset,
|
|
527
|
-
value: nextAddress.value,
|
|
528
|
-
});
|
|
529
|
-
if (nextAddress.endOffset > lineEnd) {
|
|
530
|
-
lineEnd = findLineEnd(text, nextAddress.endOffset);
|
|
531
|
-
}
|
|
532
|
-
regexpProblems.push(...(nextAddress.regexpProblems ?? []));
|
|
533
|
-
cursor = skipBlanks(text, nextAddress.endOffset, lineEnd);
|
|
534
|
-
}
|
|
535
|
-
}
|
|
536
|
-
|
|
537
|
-
if (text[cursor] === "!") {
|
|
538
|
-
negationOffset = cursor;
|
|
539
|
-
cursor = skipBlanks(text, cursor + 1, lineEnd);
|
|
540
|
-
}
|
|
541
|
-
|
|
542
|
-
const commandOffset = cursor < lineEnd ? cursor : null;
|
|
543
|
-
if (problem === null) {
|
|
544
|
-
addressProblems.push(
|
|
545
|
-
...addressUsageProblems(
|
|
546
|
-
addresses,
|
|
547
|
-
commandOffset === null ? null : text[commandOffset],
|
|
548
|
-
hasOmittedAddress,
|
|
549
|
-
negationOffset,
|
|
550
|
-
syntaxProfile,
|
|
551
|
-
),
|
|
552
|
-
);
|
|
553
|
-
}
|
|
554
|
-
|
|
555
|
-
return {
|
|
556
|
-
addressProblems,
|
|
557
|
-
addresses,
|
|
558
|
-
regexpProblems,
|
|
559
|
-
commandOffset,
|
|
560
|
-
hasOmittedAddress,
|
|
561
|
-
lineEnd,
|
|
562
|
-
negationOffset,
|
|
563
|
-
problem,
|
|
564
|
-
resumeOffset: null,
|
|
565
|
-
};
|
|
566
|
-
}
|
|
567
|
-
|
|
568
|
-
function diagnoseTextCommand(
|
|
569
|
-
text,
|
|
570
|
-
commandOffset,
|
|
571
|
-
lineEnd,
|
|
572
|
-
hasPhysicalNewline,
|
|
573
|
-
syntaxProfile,
|
|
574
|
-
) {
|
|
575
|
-
const command = text[commandOffset];
|
|
576
|
-
const result = scanTextCommandSyntax(
|
|
577
|
-
text,
|
|
578
|
-
commandOffset,
|
|
579
|
-
lineEnd,
|
|
580
|
-
hasPhysicalNewline,
|
|
581
|
-
syntaxProfile,
|
|
582
|
-
);
|
|
583
|
-
let problem = null;
|
|
584
|
-
|
|
585
|
-
if (result.kind === "invalid") {
|
|
586
|
-
if (result.reason === "missing-backslash") {
|
|
587
|
-
problem = issue(
|
|
588
|
-
"text-missing-backslash",
|
|
589
|
-
messages.missingTextBackslash(command),
|
|
590
|
-
result.startOffset,
|
|
591
|
-
result.endOffset,
|
|
592
|
-
);
|
|
593
|
-
} else if (result.reason === "unexpected-after-backslash") {
|
|
594
|
-
problem = issue(
|
|
595
|
-
"text-unexpected-after-backslash",
|
|
596
|
-
messages.unexpectedTextAfterTextBackslash(command),
|
|
597
|
-
result.startOffset,
|
|
598
|
-
result.endOffset,
|
|
599
|
-
);
|
|
600
|
-
} else if (result.reason === "missing-newline") {
|
|
601
|
-
problem = issue(
|
|
602
|
-
"text-missing-newline",
|
|
603
|
-
messages.missingTextNewline(command),
|
|
604
|
-
result.startOffset,
|
|
605
|
-
result.endOffset,
|
|
606
|
-
);
|
|
607
|
-
} else {
|
|
608
|
-
problem = issue(
|
|
609
|
-
"text-missing-argument",
|
|
610
|
-
messages.missingTextArgument(command),
|
|
611
|
-
result.startOffset,
|
|
612
|
-
result.endOffset,
|
|
613
|
-
);
|
|
614
|
-
}
|
|
615
|
-
}
|
|
616
|
-
|
|
617
|
-
if (!result.consumesFollowingTextLine) {
|
|
618
|
-
return { problem, textArgument: null };
|
|
619
|
-
}
|
|
620
|
-
|
|
621
|
-
return {
|
|
622
|
-
problem,
|
|
623
|
-
textArgument: {
|
|
624
|
-
allowsMissingLine: result.allowsMissingFollowingTextLine ?? false,
|
|
625
|
-
command,
|
|
626
|
-
expectedLineOffset:
|
|
627
|
-
result.backslashOffset ?? Math.max(commandOffset, lineEnd - 1),
|
|
628
|
-
},
|
|
629
|
-
};
|
|
630
|
-
}
|
|
631
|
-
|
|
632
|
-
function scanShellCommand(
|
|
633
|
-
text,
|
|
634
|
-
commandOffset,
|
|
635
|
-
lineEnd,
|
|
636
|
-
hasPhysicalNewline,
|
|
637
|
-
syntaxProfile,
|
|
638
|
-
) {
|
|
639
|
-
const result = scanShellCommandSyntax(
|
|
640
|
-
text,
|
|
641
|
-
commandOffset,
|
|
642
|
-
lineEnd,
|
|
643
|
-
hasPhysicalNewline,
|
|
644
|
-
syntaxProfile,
|
|
645
|
-
);
|
|
646
|
-
if (!result.consumesFollowingTextLine) {
|
|
647
|
-
return null;
|
|
648
|
-
}
|
|
649
|
-
|
|
650
|
-
return {
|
|
651
|
-
allowsMissingLine: true,
|
|
652
|
-
command: text[commandOffset],
|
|
653
|
-
expectedLineOffset: Math.max(commandOffset, lineEnd - 1),
|
|
654
|
-
};
|
|
655
|
-
}
|
|
656
|
-
|
|
657
|
-
function diagnoseFileCommand(text, commandOffset, lineEnd, syntaxProfile) {
|
|
658
|
-
const command = text[commandOffset];
|
|
659
|
-
const argument = scanFilenameArgumentSyntax(
|
|
660
|
-
text,
|
|
661
|
-
commandOffset,
|
|
662
|
-
lineEnd,
|
|
663
|
-
syntaxProfile,
|
|
664
|
-
);
|
|
665
|
-
const problemKind = !argument.hasName
|
|
666
|
-
? "missing-name"
|
|
667
|
-
: argument.hasValidSeparator
|
|
668
|
-
? null
|
|
669
|
-
: "missing-separator";
|
|
670
|
-
if (problemKind === null) {
|
|
671
|
-
return null;
|
|
672
|
-
}
|
|
673
|
-
|
|
674
|
-
const codePrefix = command.toLowerCase() === "r" ? "read-file" : "write-file";
|
|
675
|
-
return issue(
|
|
676
|
-
`${codePrefix}-${problemKind}`,
|
|
677
|
-
problemKind === "missing-name"
|
|
678
|
-
? messages.missingFile(command)
|
|
679
|
-
: messages.missingFileSeparator(command),
|
|
680
|
-
commandOffset,
|
|
681
|
-
commandOffset + 1,
|
|
682
|
-
);
|
|
683
|
-
}
|
|
684
|
-
|
|
685
|
-
function diagnoseLabelCommand(
|
|
686
|
-
text,
|
|
687
|
-
commandOffset,
|
|
688
|
-
lineEnd,
|
|
689
|
-
insideBlock,
|
|
690
|
-
syntaxProfile,
|
|
691
|
-
) {
|
|
692
|
-
const command = text[commandOffset];
|
|
693
|
-
const policy = syntaxPolicyFor(syntaxProfile);
|
|
694
|
-
const argument = scanLabelArgumentSyntax(
|
|
695
|
-
text,
|
|
696
|
-
commandOffset,
|
|
697
|
-
lineEnd,
|
|
698
|
-
syntaxProfile,
|
|
699
|
-
);
|
|
700
|
-
let problem = null;
|
|
701
|
-
|
|
702
|
-
if (!argument.hasLabel) {
|
|
703
|
-
if (command === ":") {
|
|
704
|
-
problem = issue(
|
|
705
|
-
"label-missing",
|
|
706
|
-
messages.missingLabel,
|
|
707
|
-
commandOffset,
|
|
708
|
-
commandOffset + 1,
|
|
709
|
-
);
|
|
710
|
-
} else if (
|
|
711
|
-
insideBlock &&
|
|
712
|
-
policy.commandEnd.rejectsTrailingBlanksInBlock &&
|
|
713
|
-
argument.fieldStartOffset < lineEnd
|
|
714
|
-
) {
|
|
715
|
-
problem = issue(
|
|
716
|
-
"command-unexpected-text",
|
|
717
|
-
messages.unexpectedCommandText(command),
|
|
718
|
-
argument.fieldStartOffset,
|
|
719
|
-
lineEnd,
|
|
720
|
-
);
|
|
721
|
-
}
|
|
722
|
-
} else if (command !== ":" && !argument.hasValidSeparator) {
|
|
723
|
-
const codePrefix = command === "b" ? "branch-label" : "test-label";
|
|
724
|
-
problem = issue(
|
|
725
|
-
`${codePrefix}-missing-separator`,
|
|
726
|
-
messages.missingLabelSeparator(command),
|
|
727
|
-
commandOffset,
|
|
728
|
-
commandOffset + 1,
|
|
729
|
-
);
|
|
730
|
-
}
|
|
731
|
-
|
|
732
|
-
return {
|
|
733
|
-
problem,
|
|
734
|
-
nextCommandBoundary: argument.nextCommandBoundary,
|
|
735
|
-
nextCommandOffset: argument.nextCommandOffset,
|
|
736
|
-
};
|
|
737
|
-
}
|
|
738
|
-
|
|
739
|
-
function diagnoseVersionCommand(text, commandOffset, lineEnd, syntaxProfile) {
|
|
740
|
-
const argument = scanVersionArgumentSyntax(
|
|
741
|
-
text,
|
|
742
|
-
commandOffset,
|
|
743
|
-
lineEnd,
|
|
744
|
-
syntaxProfile,
|
|
745
|
-
);
|
|
746
|
-
|
|
747
|
-
return {
|
|
748
|
-
problem: argument.isSupported
|
|
749
|
-
? null
|
|
750
|
-
: issue(
|
|
751
|
-
"version-requires-newer-sed",
|
|
752
|
-
messages.unsupportedVersion(argument.comparedVersion),
|
|
753
|
-
argument.startOffset,
|
|
754
|
-
argument.endOffset,
|
|
755
|
-
),
|
|
756
|
-
nextCommandBoundary: argument.nextCommandBoundary,
|
|
757
|
-
nextCommandOffset: argument.nextCommandOffset,
|
|
758
|
-
};
|
|
759
|
-
}
|
|
760
|
-
|
|
761
|
-
function scanEmptyCommands(text, offset, lineEnd, boundary = "direct") {
|
|
762
|
-
let cursor = offset;
|
|
763
|
-
let nextBoundary = boundary;
|
|
764
|
-
|
|
765
|
-
while (cursor < lineEnd) {
|
|
766
|
-
cursor = skipBlanks(text, cursor, lineEnd);
|
|
767
|
-
if (text[cursor] !== ";") {
|
|
768
|
-
break;
|
|
769
|
-
}
|
|
770
|
-
nextBoundary = "separated";
|
|
771
|
-
cursor += 1;
|
|
772
|
-
}
|
|
773
|
-
|
|
774
|
-
return {
|
|
775
|
-
boundary: nextBoundary,
|
|
776
|
-
offset: skipBlanks(text, cursor, lineEnd),
|
|
777
|
-
};
|
|
778
|
-
}
|
|
779
|
-
|
|
780
|
-
function skipEmptyCommands(text, offset, lineEnd) {
|
|
781
|
-
return scanEmptyCommands(text, offset, lineEnd).offset;
|
|
782
|
-
}
|
|
783
|
-
|
|
784
|
-
function diagnoseCommandEnd(
|
|
785
|
-
text,
|
|
786
|
-
command,
|
|
787
|
-
commandEnd,
|
|
788
|
-
lineEnd,
|
|
789
|
-
syntaxProfile,
|
|
790
|
-
state = {},
|
|
791
|
-
) {
|
|
792
|
-
const result = scanCommandEndSyntax(
|
|
793
|
-
text,
|
|
794
|
-
commandEnd,
|
|
795
|
-
lineEnd,
|
|
796
|
-
syntaxProfile,
|
|
797
|
-
state,
|
|
798
|
-
);
|
|
799
|
-
return {
|
|
800
|
-
problem:
|
|
801
|
-
result.kind === "unexpected"
|
|
802
|
-
? issue(
|
|
803
|
-
"command-unexpected-text",
|
|
804
|
-
messages.unexpectedCommandText(command),
|
|
805
|
-
result.startOffset,
|
|
806
|
-
result.endOffset,
|
|
807
|
-
)
|
|
808
|
-
: null,
|
|
809
|
-
nextCommandOffset: result.nextCommandOffset,
|
|
810
|
-
nextCommandBoundary: result.nextCommandBoundary,
|
|
811
|
-
};
|
|
812
|
-
}
|
|
813
|
-
|
|
814
|
-
function simpleCommandEnd(
|
|
815
|
-
text,
|
|
816
|
-
command,
|
|
817
|
-
commandOffset,
|
|
818
|
-
lineEnd,
|
|
819
|
-
syntaxProfile,
|
|
820
|
-
state = {},
|
|
821
|
-
) {
|
|
822
|
-
return diagnoseCommandEnd(
|
|
823
|
-
text,
|
|
824
|
-
command,
|
|
825
|
-
commandOffset + 1,
|
|
826
|
-
lineEnd,
|
|
827
|
-
syntaxProfile,
|
|
828
|
-
state,
|
|
829
|
-
);
|
|
830
|
-
}
|
|
831
|
-
|
|
832
|
-
function diagnoseTransliterate(
|
|
833
|
-
text,
|
|
834
|
-
commandOffset,
|
|
835
|
-
lineEnd,
|
|
836
|
-
recoverAtClosingBrace,
|
|
837
|
-
syntaxProfile,
|
|
838
|
-
) {
|
|
839
|
-
const delimiterOffset = commandOffset + 1;
|
|
840
|
-
if (delimiterOffset >= lineEnd) {
|
|
841
|
-
return {
|
|
842
|
-
problem: issue(
|
|
843
|
-
"transliterate-missing-delimiter",
|
|
844
|
-
messages.missingTransliterateDelimiter,
|
|
845
|
-
commandOffset,
|
|
846
|
-
commandOffset + 1,
|
|
847
|
-
),
|
|
848
|
-
nextCommandOffset: null,
|
|
849
|
-
};
|
|
850
|
-
}
|
|
851
|
-
|
|
852
|
-
const delimiter = characterAt(text, delimiterOffset);
|
|
853
|
-
if (
|
|
854
|
-
delimiter === null ||
|
|
855
|
-
delimiter.value === "\\" ||
|
|
856
|
-
delimiter.value === "\n"
|
|
857
|
-
) {
|
|
858
|
-
return {
|
|
859
|
-
problem: issue(
|
|
860
|
-
"transliterate-invalid-delimiter",
|
|
861
|
-
messages.invalidTransliterateDelimiter,
|
|
862
|
-
delimiterOffset,
|
|
863
|
-
delimiterOffset + (delimiter?.width ?? 1),
|
|
864
|
-
),
|
|
865
|
-
nextCommandOffset: null,
|
|
866
|
-
};
|
|
867
|
-
}
|
|
868
|
-
|
|
869
|
-
const firstStringEnd = findTransliterateDelimiter(
|
|
870
|
-
text,
|
|
871
|
-
delimiterOffset + delimiter.width,
|
|
872
|
-
lineEnd,
|
|
873
|
-
delimiter,
|
|
874
|
-
syntaxProfile,
|
|
875
|
-
);
|
|
876
|
-
if (firstStringEnd === null) {
|
|
877
|
-
return {
|
|
878
|
-
problem: issue(
|
|
879
|
-
"transliterate-unterminated-first-string",
|
|
880
|
-
messages.unterminatedFirstTransliterateString,
|
|
881
|
-
commandOffset,
|
|
882
|
-
lineEnd,
|
|
883
|
-
),
|
|
884
|
-
nextCommandOffset: null,
|
|
885
|
-
};
|
|
886
|
-
}
|
|
887
|
-
|
|
888
|
-
const secondStringEnd = findTransliterateDelimiter(
|
|
889
|
-
text,
|
|
890
|
-
firstStringEnd + delimiter.width,
|
|
891
|
-
lineEnd,
|
|
892
|
-
delimiter,
|
|
893
|
-
syntaxProfile,
|
|
894
|
-
);
|
|
895
|
-
if (secondStringEnd === null) {
|
|
896
|
-
return {
|
|
897
|
-
problem: issue(
|
|
898
|
-
"transliterate-unterminated-second-string",
|
|
899
|
-
messages.unterminatedSecondTransliterateString,
|
|
900
|
-
commandOffset,
|
|
901
|
-
lineEnd,
|
|
902
|
-
),
|
|
903
|
-
nextCommandOffset: null,
|
|
904
|
-
};
|
|
905
|
-
}
|
|
906
|
-
|
|
907
|
-
const commandEnd = secondStringEnd + delimiter.width;
|
|
908
|
-
return diagnoseCommandEnd(text, "y", commandEnd, lineEnd, syntaxProfile, {
|
|
909
|
-
insideBlock: recoverAtClosingBrace,
|
|
910
|
-
recoverAtClosingBrace,
|
|
911
|
-
});
|
|
912
|
-
}
|
|
913
|
-
|
|
914
|
-
function scanSubstituteFlags(
|
|
915
|
-
text,
|
|
916
|
-
offset,
|
|
917
|
-
lineEnd,
|
|
918
|
-
recoverAtClosingBrace,
|
|
919
|
-
syntaxProfile,
|
|
920
|
-
) {
|
|
921
|
-
const policy = syntaxPolicyFor(syntaxProfile);
|
|
922
|
-
const canRecoverAtClosingBrace =
|
|
923
|
-
recoverAtClosingBrace || policy.commandEnd.closingBraceTerminates;
|
|
924
|
-
let cursor = offset;
|
|
925
|
-
let flagState = createSubstituteFlagState();
|
|
926
|
-
const flags = [];
|
|
927
|
-
const regexpModifiers = [];
|
|
928
|
-
|
|
929
|
-
function result(fields) {
|
|
930
|
-
return { flagState, flags, regexpModifiers, ...fields };
|
|
931
|
-
}
|
|
932
|
-
|
|
933
|
-
while (cursor < lineEnd) {
|
|
934
|
-
const character = characterAt(text, cursor);
|
|
935
|
-
if (character === null) {
|
|
936
|
-
break;
|
|
937
|
-
}
|
|
938
|
-
|
|
939
|
-
if (character.value === ";") {
|
|
940
|
-
return result({
|
|
941
|
-
problem: null,
|
|
942
|
-
consumedUntil: cursor + character.width,
|
|
943
|
-
nextCommandOffset: cursor + character.width,
|
|
944
|
-
nextCommandBoundary: "separated",
|
|
945
|
-
});
|
|
946
|
-
}
|
|
947
|
-
|
|
948
|
-
if (policy.commandEnd.commentTerminates && character.value === "#") {
|
|
949
|
-
return result({
|
|
950
|
-
problem: null,
|
|
951
|
-
consumedUntil: lineEnd,
|
|
952
|
-
nextCommandOffset: null,
|
|
953
|
-
nextCommandBoundary: null,
|
|
954
|
-
});
|
|
955
|
-
}
|
|
956
|
-
|
|
957
|
-
if (canRecoverAtClosingBrace && character.value === "}") {
|
|
958
|
-
return result({
|
|
959
|
-
problem: null,
|
|
960
|
-
consumedUntil: lineEnd,
|
|
961
|
-
nextCommandOffset: cursor,
|
|
962
|
-
nextCommandBoundary: "direct",
|
|
963
|
-
});
|
|
964
|
-
}
|
|
84
|
+
function preferSpecificIssues(nodes) {
|
|
85
|
+
const issuesByRange = new Map();
|
|
965
86
|
|
|
87
|
+
for (const node of nodes) {
|
|
88
|
+
const key = `${node.startIndex}:${node.endIndex}`;
|
|
89
|
+
const existing = issuesByRange.get(key);
|
|
966
90
|
if (
|
|
967
|
-
|
|
968
|
-
(
|
|
91
|
+
existing === undefined ||
|
|
92
|
+
issuePriority(node) > issuePriority(existing)
|
|
969
93
|
) {
|
|
970
|
-
|
|
971
|
-
continue;
|
|
94
|
+
issuesByRange.set(key, node);
|
|
972
95
|
}
|
|
973
|
-
|
|
974
|
-
if (
|
|
975
|
-
recoverAtClosingBrace &&
|
|
976
|
-
(character.value === " " || character.value === "\t")
|
|
977
|
-
) {
|
|
978
|
-
const argumentOffset = skipBlanks(text, cursor, lineEnd);
|
|
979
|
-
if (text[argumentOffset] === "}") {
|
|
980
|
-
return result({
|
|
981
|
-
problem: null,
|
|
982
|
-
consumedUntil: lineEnd,
|
|
983
|
-
nextCommandOffset: argumentOffset,
|
|
984
|
-
nextCommandBoundary: "direct",
|
|
985
|
-
});
|
|
986
|
-
}
|
|
987
|
-
|
|
988
|
-
if (argumentOffset >= lineEnd || text[argumentOffset] === ";") {
|
|
989
|
-
return result({
|
|
990
|
-
problem: issue(
|
|
991
|
-
"command-unexpected-text",
|
|
992
|
-
messages.unexpectedCommandText("s"),
|
|
993
|
-
cursor,
|
|
994
|
-
argumentOffset,
|
|
995
|
-
),
|
|
996
|
-
consumedUntil: lineEnd,
|
|
997
|
-
nextCommandOffset:
|
|
998
|
-
argumentOffset >= lineEnd ? null : argumentOffset + 1,
|
|
999
|
-
nextCommandBoundary: argumentOffset >= lineEnd ? null : "separated",
|
|
1000
|
-
});
|
|
1001
|
-
}
|
|
1002
|
-
}
|
|
1003
|
-
|
|
1004
|
-
const token = scanSubstituteFlagTokenSyntax(
|
|
1005
|
-
text,
|
|
1006
|
-
cursor,
|
|
1007
|
-
lineEnd,
|
|
1008
|
-
flagState,
|
|
1009
|
-
syntaxProfile,
|
|
1010
|
-
);
|
|
1011
|
-
if (token.kind === "flag" || token.kind === "occurrence") {
|
|
1012
|
-
flags.push({
|
|
1013
|
-
endOffset: token.endOffset,
|
|
1014
|
-
kind: token.kind,
|
|
1015
|
-
startOffset: token.startOffset,
|
|
1016
|
-
value: token.value,
|
|
1017
|
-
});
|
|
1018
|
-
if (token.kind === "flag" && ["i", "I", "m", "M"].includes(token.value)) {
|
|
1019
|
-
regexpModifiers.push({
|
|
1020
|
-
endOffset: token.endOffset,
|
|
1021
|
-
startOffset: token.startOffset,
|
|
1022
|
-
value: token.value,
|
|
1023
|
-
});
|
|
1024
|
-
}
|
|
1025
|
-
flagState = token.state;
|
|
1026
|
-
cursor = token.endOffset;
|
|
1027
|
-
continue;
|
|
1028
|
-
}
|
|
1029
|
-
|
|
1030
|
-
if (token.kind === "write") {
|
|
1031
|
-
const filename = scanFilenameArgumentSyntax(
|
|
1032
|
-
text,
|
|
1033
|
-
cursor,
|
|
1034
|
-
lineEnd,
|
|
1035
|
-
syntaxProfile,
|
|
1036
|
-
);
|
|
1037
|
-
const filenameProblem = !filename.hasName
|
|
1038
|
-
? "missing-name"
|
|
1039
|
-
: filename.hasValidSeparator
|
|
1040
|
-
? null
|
|
1041
|
-
: "missing-separator";
|
|
1042
|
-
if (filenameProblem !== null) {
|
|
1043
|
-
return result({
|
|
1044
|
-
problem: issue(
|
|
1045
|
-
`substitute-write-file-${filenameProblem}`,
|
|
1046
|
-
filenameProblem === "missing-name"
|
|
1047
|
-
? messages.missingWriteFile
|
|
1048
|
-
: messages.missingWriteFileSeparator,
|
|
1049
|
-
cursor,
|
|
1050
|
-
cursor + character.width,
|
|
1051
|
-
),
|
|
1052
|
-
consumedUntil: lineEnd,
|
|
1053
|
-
nextCommandOffset: null,
|
|
1054
|
-
nextCommandBoundary: null,
|
|
1055
|
-
});
|
|
1056
|
-
}
|
|
1057
|
-
|
|
1058
|
-
return result({
|
|
1059
|
-
problem: null,
|
|
1060
|
-
consumedUntil: lineEnd,
|
|
1061
|
-
nextCommandOffset: null,
|
|
1062
|
-
nextCommandBoundary: null,
|
|
1063
|
-
});
|
|
1064
|
-
}
|
|
1065
|
-
|
|
1066
|
-
const recovery = scanCommandRecoverySyntax(
|
|
1067
|
-
text,
|
|
1068
|
-
token.endOffset,
|
|
1069
|
-
lineEnd,
|
|
1070
|
-
syntaxProfile,
|
|
1071
|
-
{ recoverAtClosingBrace: canRecoverAtClosingBrace },
|
|
1072
|
-
);
|
|
1073
|
-
const problem =
|
|
1074
|
-
syntaxProfile.dialect === "gnu" && token.reason === "repeated-occurrence"
|
|
1075
|
-
? issue(
|
|
1076
|
-
"substitute-occurrence-repeated",
|
|
1077
|
-
messages.repeatedOccurrence,
|
|
1078
|
-
token.startOffset,
|
|
1079
|
-
token.endOffset,
|
|
1080
|
-
)
|
|
1081
|
-
: syntaxProfile.dialect === "gnu" && token.reason === "zero-occurrence"
|
|
1082
|
-
? issue(
|
|
1083
|
-
"substitute-occurrence-zero",
|
|
1084
|
-
messages.zeroOccurrence,
|
|
1085
|
-
token.startOffset,
|
|
1086
|
-
token.endOffset,
|
|
1087
|
-
)
|
|
1088
|
-
: syntaxProfile.dialect === "gnu" &&
|
|
1089
|
-
(token.reason === "repeated-global" ||
|
|
1090
|
-
token.reason === "repeated-print")
|
|
1091
|
-
? issue(
|
|
1092
|
-
"substitute-flag-repeated",
|
|
1093
|
-
messages.repeatedSubstituteFlag(token.value),
|
|
1094
|
-
token.startOffset,
|
|
1095
|
-
token.endOffset,
|
|
1096
|
-
)
|
|
1097
|
-
: issue(
|
|
1098
|
-
"substitute-invalid-flag",
|
|
1099
|
-
messages.invalidFlag(token.value),
|
|
1100
|
-
token.startOffset,
|
|
1101
|
-
token.endOffset,
|
|
1102
|
-
);
|
|
1103
|
-
return result({
|
|
1104
|
-
problem,
|
|
1105
|
-
consumedUntil: lineEnd,
|
|
1106
|
-
nextCommandOffset: recovery.nextCommandOffset,
|
|
1107
|
-
nextCommandBoundary: recovery.nextCommandBoundary,
|
|
1108
|
-
});
|
|
1109
96
|
}
|
|
1110
97
|
|
|
1111
|
-
return
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
nextCommandBoundary: null,
|
|
1116
|
-
});
|
|
1117
|
-
}
|
|
1118
|
-
|
|
1119
|
-
function issue(code, message, startOffset, endOffset) {
|
|
1120
|
-
return {
|
|
1121
|
-
code,
|
|
1122
|
-
message,
|
|
1123
|
-
startOffset,
|
|
1124
|
-
endOffset: Math.max(startOffset + 1, endOffset),
|
|
1125
|
-
};
|
|
98
|
+
return [...issuesByRange.values()].sort(
|
|
99
|
+
(left, right) =>
|
|
100
|
+
left.startIndex - right.startIndex || left.endIndex - right.endIndex,
|
|
101
|
+
);
|
|
1126
102
|
}
|
|
1127
103
|
|
|
1128
|
-
function
|
|
1129
|
-
|
|
1130
|
-
commandOffset,
|
|
1131
|
-
lineEnd,
|
|
1132
|
-
recoverAtClosingBrace,
|
|
1133
|
-
syntaxProfile,
|
|
1134
|
-
) {
|
|
1135
|
-
const policy = syntaxPolicyFor(syntaxProfile);
|
|
1136
|
-
const delimiterOffset = commandOffset + 1;
|
|
1137
|
-
if (delimiterOffset >= lineEnd) {
|
|
1138
|
-
return {
|
|
1139
|
-
problem: issue(
|
|
1140
|
-
"substitute-missing-delimiter",
|
|
1141
|
-
messages.missingDelimiter,
|
|
1142
|
-
commandOffset,
|
|
1143
|
-
commandOffset + 1,
|
|
1144
|
-
),
|
|
1145
|
-
consumedUntil: lineEnd,
|
|
1146
|
-
nextCommandOffset: null,
|
|
1147
|
-
};
|
|
1148
|
-
}
|
|
1149
|
-
|
|
1150
|
-
const delimiter = characterAt(text, delimiterOffset);
|
|
1151
|
-
if (delimiter === null || delimiter.value === "\\") {
|
|
1152
|
-
return {
|
|
1153
|
-
problem: issue(
|
|
1154
|
-
"substitute-invalid-delimiter",
|
|
1155
|
-
messages.invalidDelimiter,
|
|
1156
|
-
delimiterOffset,
|
|
1157
|
-
delimiterOffset + (delimiter?.width ?? 1),
|
|
1158
|
-
),
|
|
1159
|
-
consumedUntil: lineEnd,
|
|
1160
|
-
nextCommandOffset: null,
|
|
1161
|
-
};
|
|
1162
|
-
}
|
|
104
|
+
export function createDiagnostics(document, dialect) {
|
|
105
|
+
const tree = syntaxTreeFor(document, dialect);
|
|
1163
106
|
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
policy.regexp.escapedPhysicalNewlines ? text.length : lineEnd,
|
|
1168
|
-
delimiter,
|
|
1169
|
-
syntaxProfile,
|
|
1170
|
-
);
|
|
1171
|
-
const patternEnd =
|
|
1172
|
-
pattern.closingOffset ??
|
|
1173
|
-
(syntaxProfile.dialect === "posix" && pattern.hasUnclosedBracketExpression
|
|
1174
|
-
? pattern.possibleClosingOffset
|
|
1175
|
-
: null);
|
|
1176
|
-
if (patternEnd === null) {
|
|
1177
|
-
if (
|
|
1178
|
-
syntaxProfile.dialect === "posix" &&
|
|
1179
|
-
(pattern.hasUnclosedBracketExpression ||
|
|
1180
|
-
pattern.isDelimiterInterpretationUnspecified)
|
|
1181
|
-
) {
|
|
107
|
+
return preferSpecificIssues(collectSyntaxIssueNodes(tree.rootNode)).map(
|
|
108
|
+
(node) => {
|
|
109
|
+
const { code, message } = descriptionFor(node, dialect);
|
|
1182
110
|
return {
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
111
|
+
severity: DiagnosticSeverity.Error,
|
|
112
|
+
range: rangeForNode(document, node),
|
|
113
|
+
message,
|
|
114
|
+
code,
|
|
115
|
+
source: diagnosticSource,
|
|
1186
116
|
};
|
|
1187
|
-
}
|
|
1188
|
-
|
|
1189
|
-
const failureOffset =
|
|
1190
|
-
pattern.failureOffset ??
|
|
1191
|
-
(policy.regexp.escapedPhysicalNewlines ? text.length : lineEnd);
|
|
1192
|
-
return {
|
|
1193
|
-
problem: issue(
|
|
1194
|
-
"substitute-unterminated-pattern",
|
|
1195
|
-
messages.unterminatedPattern,
|
|
1196
|
-
commandOffset,
|
|
1197
|
-
failureOffset,
|
|
1198
|
-
),
|
|
1199
|
-
consumedUntil: failureOffset,
|
|
1200
|
-
nextCommandOffset: null,
|
|
1201
|
-
};
|
|
1202
|
-
}
|
|
1203
|
-
|
|
1204
|
-
const patternProblems =
|
|
1205
|
-
pattern.closingOffset === null
|
|
1206
|
-
? []
|
|
1207
|
-
: findRegexpProblems(
|
|
1208
|
-
text,
|
|
1209
|
-
delimiterOffset + delimiter.width,
|
|
1210
|
-
patternEnd,
|
|
1211
|
-
delimiter,
|
|
1212
|
-
syntaxProfile,
|
|
1213
|
-
);
|
|
1214
|
-
const replacement = findReplacementDelimiter(
|
|
1215
|
-
text,
|
|
1216
|
-
patternEnd + delimiter.width,
|
|
1217
|
-
delimiter,
|
|
1218
|
-
syntaxProfile,
|
|
1219
|
-
);
|
|
1220
|
-
if (replacement.closingOffset === null) {
|
|
1221
|
-
return {
|
|
1222
|
-
problem: issue(
|
|
1223
|
-
"substitute-unterminated-replacement",
|
|
1224
|
-
messages.unterminatedReplacement,
|
|
1225
|
-
commandOffset,
|
|
1226
|
-
replacement.failureOffset,
|
|
1227
|
-
),
|
|
1228
|
-
consumedUntil: replacement.failureOffset,
|
|
1229
|
-
nextCommandOffset: null,
|
|
1230
|
-
};
|
|
1231
|
-
}
|
|
1232
|
-
|
|
1233
|
-
const invalidReplacementControlEscape = findInvalidReplacementControlEscape(
|
|
1234
|
-
text,
|
|
1235
|
-
patternEnd + delimiter.width,
|
|
1236
|
-
replacement.closingOffset,
|
|
1237
|
-
delimiter,
|
|
1238
|
-
syntaxProfile,
|
|
1239
|
-
);
|
|
1240
|
-
const flagsOffset = replacement.closingOffset + delimiter.width;
|
|
1241
|
-
const flagsLineEnd =
|
|
1242
|
-
flagsOffset <= lineEnd ? lineEnd : findLineEnd(text, flagsOffset);
|
|
1243
|
-
const result = scanSubstituteFlags(
|
|
1244
|
-
text,
|
|
1245
|
-
flagsOffset,
|
|
1246
|
-
flagsLineEnd,
|
|
1247
|
-
recoverAtClosingBrace,
|
|
1248
|
-
syntaxProfile,
|
|
1249
|
-
);
|
|
1250
|
-
if (invalidReplacementControlEscape !== null) {
|
|
1251
|
-
return {
|
|
1252
|
-
...result,
|
|
1253
|
-
problem: issue(
|
|
1254
|
-
"replacement-invalid-control-escape",
|
|
1255
|
-
messages.invalidControlEscape,
|
|
1256
|
-
invalidReplacementControlEscape.startOffset,
|
|
1257
|
-
invalidReplacementControlEscape.endOffset,
|
|
1258
|
-
),
|
|
1259
|
-
regexpProblems: patternProblems,
|
|
1260
|
-
};
|
|
1261
|
-
}
|
|
1262
|
-
|
|
1263
|
-
if (
|
|
1264
|
-
result.problem === null &&
|
|
1265
|
-
syntaxProfile.dialect === "gnu" &&
|
|
1266
|
-
patternEnd === delimiterOffset + delimiter.width &&
|
|
1267
|
-
result.regexpModifiers.length > 0
|
|
1268
|
-
) {
|
|
1269
|
-
return {
|
|
1270
|
-
...result,
|
|
1271
|
-
problem: issue(
|
|
1272
|
-
"substitute-empty-regexp-modifiers",
|
|
1273
|
-
messages.emptySubstituteRegexpModifiers,
|
|
1274
|
-
result.regexpModifiers[0].startOffset,
|
|
1275
|
-
result.regexpModifiers.at(-1).endOffset,
|
|
1276
|
-
),
|
|
1277
|
-
regexpProblems: patternProblems,
|
|
1278
|
-
};
|
|
1279
|
-
}
|
|
1280
|
-
|
|
1281
|
-
return {
|
|
1282
|
-
...result,
|
|
1283
|
-
regexpProblems: patternProblems,
|
|
1284
|
-
};
|
|
1285
|
-
}
|
|
1286
|
-
|
|
1287
|
-
function addressLimitProblem(command, definition, addresses) {
|
|
1288
|
-
if (addresses.length <= definition.maximumAddresses) {
|
|
1289
|
-
return null;
|
|
1290
|
-
}
|
|
1291
|
-
|
|
1292
|
-
const firstExcessAddress = addresses[definition.maximumAddresses];
|
|
1293
|
-
return issue(
|
|
1294
|
-
"address-too-many",
|
|
1295
|
-
messages.tooManyAddresses(command, definition.maximumAddresses),
|
|
1296
|
-
firstExcessAddress.startOffset,
|
|
1297
|
-
firstExcessAddress.endOffset,
|
|
1298
|
-
);
|
|
1299
|
-
}
|
|
1300
|
-
|
|
1301
|
-
function universalAddressLimitProblem(addresses) {
|
|
1302
|
-
if (addresses.length <= 2) {
|
|
1303
|
-
return null;
|
|
1304
|
-
}
|
|
1305
|
-
|
|
1306
|
-
const firstExcessAddress = addresses[2];
|
|
1307
|
-
return issue(
|
|
1308
|
-
"address-too-many",
|
|
1309
|
-
messages.tooManyAddressesBeforeCommand,
|
|
1310
|
-
firstExcessAddress.startOffset,
|
|
1311
|
-
firstExcessAddress.endOffset,
|
|
1312
|
-
);
|
|
1313
|
-
}
|
|
1314
|
-
|
|
1315
|
-
function emptyCommandAddressLimitProblem(addresses) {
|
|
1316
|
-
if (addresses.length === 0) {
|
|
1317
|
-
return null;
|
|
1318
|
-
}
|
|
1319
|
-
|
|
1320
|
-
return issue(
|
|
1321
|
-
"address-too-many",
|
|
1322
|
-
messages.emptyCommandHasAddresses,
|
|
1323
|
-
addresses[0].startOffset,
|
|
1324
|
-
addresses[0].endOffset,
|
|
1325
|
-
);
|
|
1326
|
-
}
|
|
1327
|
-
|
|
1328
|
-
function missingCommandProblem(command) {
|
|
1329
|
-
if (command.hasOmittedAddress) {
|
|
1330
|
-
return null;
|
|
1331
|
-
}
|
|
1332
|
-
|
|
1333
|
-
if (command.negationOffset !== null) {
|
|
1334
|
-
return issue(
|
|
1335
|
-
"command-missing",
|
|
1336
|
-
messages.missingCommand,
|
|
1337
|
-
command.negationOffset,
|
|
1338
|
-
command.negationOffset + 1,
|
|
1339
|
-
);
|
|
1340
|
-
}
|
|
1341
|
-
|
|
1342
|
-
const lastAddress = command.addresses.at(-1);
|
|
1343
|
-
if (lastAddress === undefined) {
|
|
1344
|
-
return null;
|
|
1345
|
-
}
|
|
1346
|
-
|
|
1347
|
-
return issue(
|
|
1348
|
-
"command-missing",
|
|
1349
|
-
messages.missingCommand,
|
|
1350
|
-
lastAddress.startOffset,
|
|
1351
|
-
lastAddress.endOffset,
|
|
1352
|
-
);
|
|
1353
|
-
}
|
|
1354
|
-
|
|
1355
|
-
export function analyze(text, options = defaultSyntaxProfile) {
|
|
1356
|
-
const syntaxProfile = requireSyntaxProfile(options);
|
|
1357
|
-
const syntaxPolicy = syntaxPolicyFor(syntaxProfile);
|
|
1358
|
-
const commandDefinitions = commandDefinitionsFor(syntaxProfile);
|
|
1359
|
-
const problems = [];
|
|
1360
|
-
const openingBraces = [];
|
|
1361
|
-
let lineStart = 0;
|
|
1362
|
-
let consumedUntil = -1;
|
|
1363
|
-
let continuedOpaqueArgument = null;
|
|
1364
|
-
let pendingCommandOffset = null;
|
|
1365
|
-
let pendingCommandBoundary = null;
|
|
1366
|
-
|
|
1367
|
-
while (lineStart <= text.length) {
|
|
1368
|
-
const newlineOffset = text.indexOf("\n", lineStart);
|
|
1369
|
-
const nextLineStart =
|
|
1370
|
-
newlineOffset === -1 ? text.length + 1 : newlineOffset + 1;
|
|
1371
|
-
let lineEnd = newlineOffset === -1 ? text.length : newlineOffset;
|
|
1372
|
-
if (lineEnd > lineStart && text[lineEnd - 1] === "\r") {
|
|
1373
|
-
lineEnd -= 1;
|
|
1374
|
-
}
|
|
1375
|
-
|
|
1376
|
-
const resumesOnThisLine =
|
|
1377
|
-
pendingCommandOffset !== null &&
|
|
1378
|
-
pendingCommandOffset >= lineStart &&
|
|
1379
|
-
pendingCommandOffset <= lineEnd;
|
|
1380
|
-
const initialCommandOffset = resumesOnThisLine
|
|
1381
|
-
? pendingCommandOffset
|
|
1382
|
-
: lineStart;
|
|
1383
|
-
const initialCommandBoundary = resumesOnThisLine
|
|
1384
|
-
? pendingCommandBoundary
|
|
1385
|
-
: "separated";
|
|
1386
|
-
if (resumesOnThisLine) {
|
|
1387
|
-
pendingCommandOffset = null;
|
|
1388
|
-
pendingCommandBoundary = null;
|
|
1389
|
-
}
|
|
1390
|
-
|
|
1391
|
-
if (!resumesOnThisLine && lineStart <= consumedUntil) {
|
|
1392
|
-
// A multiline construct may already have scanned a later physical line.
|
|
1393
|
-
} else if (!resumesOnThisLine && continuedOpaqueArgument !== null) {
|
|
1394
|
-
const hasPhysicalTextLine =
|
|
1395
|
-
newlineOffset !== -1 || lineStart < text.length;
|
|
1396
|
-
if (!hasPhysicalTextLine) {
|
|
1397
|
-
if (!continuedOpaqueArgument.allowsMissingLine) {
|
|
1398
|
-
problems.push(
|
|
1399
|
-
issue(
|
|
1400
|
-
"text-missing-line",
|
|
1401
|
-
messages.missingTextLine(continuedOpaqueArgument.command),
|
|
1402
|
-
continuedOpaqueArgument.expectedLineOffset,
|
|
1403
|
-
continuedOpaqueArgument.expectedLineOffset + 1,
|
|
1404
|
-
),
|
|
1405
|
-
);
|
|
1406
|
-
}
|
|
1407
|
-
continuedOpaqueArgument = null;
|
|
1408
|
-
} else if (
|
|
1409
|
-
newlineOffset !== -1 &&
|
|
1410
|
-
hasUnescapedTrailingBackslash(text, lineStart, lineEnd)
|
|
1411
|
-
) {
|
|
1412
|
-
continuedOpaqueArgument = {
|
|
1413
|
-
allowsMissingLine: continuedOpaqueArgument.allowsMissingLine,
|
|
1414
|
-
command: continuedOpaqueArgument.command,
|
|
1415
|
-
expectedLineOffset: lineEnd - 1,
|
|
1416
|
-
};
|
|
1417
|
-
} else {
|
|
1418
|
-
continuedOpaqueArgument = null;
|
|
1419
|
-
}
|
|
1420
|
-
} else {
|
|
1421
|
-
const initialCommand = scanEmptyCommands(
|
|
1422
|
-
text,
|
|
1423
|
-
initialCommandOffset,
|
|
1424
|
-
lineEnd,
|
|
1425
|
-
initialCommandBoundary,
|
|
1426
|
-
);
|
|
1427
|
-
let commandSearchOffset = initialCommand.offset;
|
|
1428
|
-
let commandBoundary = initialCommand.boundary;
|
|
1429
|
-
|
|
1430
|
-
while (commandSearchOffset < lineEnd) {
|
|
1431
|
-
const command = findCommand(
|
|
1432
|
-
text,
|
|
1433
|
-
commandSearchOffset,
|
|
1434
|
-
lineEnd,
|
|
1435
|
-
syntaxProfile,
|
|
1436
|
-
);
|
|
1437
|
-
if (command.lineEnd > lineEnd) {
|
|
1438
|
-
lineEnd = command.lineEnd;
|
|
1439
|
-
consumedUntil = Math.max(consumedUntil, lineEnd);
|
|
1440
|
-
}
|
|
1441
|
-
problems.push(...command.addressProblems);
|
|
1442
|
-
problems.push(...command.regexpProblems);
|
|
1443
|
-
|
|
1444
|
-
if (command.resumeOffset !== null) {
|
|
1445
|
-
if (command.problem !== null) {
|
|
1446
|
-
problems.push(command.problem);
|
|
1447
|
-
}
|
|
1448
|
-
commandSearchOffset = command.resumeOffset;
|
|
1449
|
-
commandBoundary = "separated";
|
|
1450
|
-
continue;
|
|
1451
|
-
}
|
|
1452
|
-
|
|
1453
|
-
const commandOffset = command.commandOffset;
|
|
1454
|
-
if (commandOffset === null) {
|
|
1455
|
-
if (command.problem !== null) {
|
|
1456
|
-
problems.push(command.problem);
|
|
1457
|
-
} else if (command.addressProblems.length === 0) {
|
|
1458
|
-
const addressProblem = command.hasOmittedAddress
|
|
1459
|
-
? null
|
|
1460
|
-
: universalAddressLimitProblem(command.addresses);
|
|
1461
|
-
if (addressProblem !== null) {
|
|
1462
|
-
problems.push(addressProblem);
|
|
1463
|
-
} else {
|
|
1464
|
-
const missingProblem = missingCommandProblem(command);
|
|
1465
|
-
if (missingProblem !== null) {
|
|
1466
|
-
problems.push(missingProblem);
|
|
1467
|
-
}
|
|
1468
|
-
}
|
|
1469
|
-
}
|
|
1470
|
-
break;
|
|
1471
|
-
}
|
|
1472
|
-
|
|
1473
|
-
const commandCharacter = characterAt(text, commandOffset);
|
|
1474
|
-
if (commandCharacter === null) {
|
|
1475
|
-
break;
|
|
1476
|
-
}
|
|
1477
|
-
|
|
1478
|
-
if (commandCharacter.value === ";") {
|
|
1479
|
-
if (command.problem !== null) {
|
|
1480
|
-
problems.push(command.problem);
|
|
1481
|
-
} else if (command.negationOffset !== null) {
|
|
1482
|
-
const missingProblem = missingCommandProblem(command);
|
|
1483
|
-
if (missingProblem !== null) {
|
|
1484
|
-
problems.push(missingProblem);
|
|
1485
|
-
}
|
|
1486
|
-
} else if (command.addressProblems.length === 0) {
|
|
1487
|
-
const addressProblem = command.hasOmittedAddress
|
|
1488
|
-
? null
|
|
1489
|
-
: emptyCommandAddressLimitProblem(command.addresses);
|
|
1490
|
-
if (addressProblem !== null) {
|
|
1491
|
-
problems.push(addressProblem);
|
|
1492
|
-
}
|
|
1493
|
-
}
|
|
1494
|
-
const nextCommand = scanEmptyCommands(
|
|
1495
|
-
text,
|
|
1496
|
-
commandOffset,
|
|
1497
|
-
lineEnd,
|
|
1498
|
-
commandBoundary,
|
|
1499
|
-
);
|
|
1500
|
-
commandSearchOffset = nextCommand.offset;
|
|
1501
|
-
commandBoundary = nextCommand.boundary;
|
|
1502
|
-
continue;
|
|
1503
|
-
}
|
|
1504
|
-
|
|
1505
|
-
const closesAfterMissingCommand =
|
|
1506
|
-
commandCharacter.value === "}" && command.negationOffset !== null;
|
|
1507
|
-
if (closesAfterMissingCommand) {
|
|
1508
|
-
if (command.problem !== null) {
|
|
1509
|
-
problems.push(command.problem);
|
|
1510
|
-
} else {
|
|
1511
|
-
const missingProblem = missingCommandProblem(command);
|
|
1512
|
-
if (missingProblem !== null) {
|
|
1513
|
-
problems.push(missingProblem);
|
|
1514
|
-
}
|
|
1515
|
-
}
|
|
1516
|
-
}
|
|
1517
|
-
|
|
1518
|
-
const definition = commandDefinitions.get(commandCharacter.value);
|
|
1519
|
-
if (definition === undefined) {
|
|
1520
|
-
if (command.problem !== null) {
|
|
1521
|
-
problems.push(command.problem);
|
|
1522
|
-
} else {
|
|
1523
|
-
const addressProblem = command.hasOmittedAddress
|
|
1524
|
-
? null
|
|
1525
|
-
: universalAddressLimitProblem(command.addresses);
|
|
1526
|
-
if (addressProblem !== null) {
|
|
1527
|
-
problems.push(addressProblem);
|
|
1528
|
-
}
|
|
1529
|
-
problems.push(
|
|
1530
|
-
issue(
|
|
1531
|
-
"command-unknown",
|
|
1532
|
-
messages.unknownCommand(commandCharacter.value),
|
|
1533
|
-
commandOffset,
|
|
1534
|
-
commandOffset + commandCharacter.width,
|
|
1535
|
-
),
|
|
1536
|
-
);
|
|
1537
|
-
}
|
|
1538
|
-
|
|
1539
|
-
const recovery = scanCommandRecoverySyntax(
|
|
1540
|
-
text,
|
|
1541
|
-
commandOffset + commandCharacter.width,
|
|
1542
|
-
lineEnd,
|
|
1543
|
-
syntaxProfile,
|
|
1544
|
-
{ recoverAtClosingBrace: openingBraces.length > 0 },
|
|
1545
|
-
);
|
|
1546
|
-
if (recovery.nextCommandOffset === null) {
|
|
1547
|
-
break;
|
|
1548
|
-
}
|
|
1549
|
-
|
|
1550
|
-
const nextCommand = scanEmptyCommands(
|
|
1551
|
-
text,
|
|
1552
|
-
recovery.nextCommandOffset,
|
|
1553
|
-
lineEnd,
|
|
1554
|
-
recovery.nextCommandBoundary,
|
|
1555
|
-
);
|
|
1556
|
-
commandSearchOffset = nextCommand.offset;
|
|
1557
|
-
commandBoundary = nextCommand.boundary;
|
|
1558
|
-
continue;
|
|
1559
|
-
}
|
|
1560
|
-
|
|
1561
|
-
if (!closesAfterMissingCommand) {
|
|
1562
|
-
const addressProblem = command.hasOmittedAddress
|
|
1563
|
-
? null
|
|
1564
|
-
: command.addressProblems.length === 0
|
|
1565
|
-
? addressLimitProblem(
|
|
1566
|
-
commandCharacter.value,
|
|
1567
|
-
definition,
|
|
1568
|
-
command.addresses,
|
|
1569
|
-
)
|
|
1570
|
-
: null;
|
|
1571
|
-
if (
|
|
1572
|
-
command.problem !== null &&
|
|
1573
|
-
(command.problem.code !== "address-too-many" ||
|
|
1574
|
-
addressProblem === null)
|
|
1575
|
-
) {
|
|
1576
|
-
problems.push(command.problem);
|
|
1577
|
-
} else if (addressProblem !== null) {
|
|
1578
|
-
problems.push(addressProblem);
|
|
1579
|
-
}
|
|
1580
|
-
}
|
|
1581
|
-
|
|
1582
|
-
if (definition.kind === "text") {
|
|
1583
|
-
const hasPhysicalNewline =
|
|
1584
|
-
text[lineEnd] === "\n" ||
|
|
1585
|
-
(text[lineEnd] === "\r" && text[lineEnd + 1] === "\n");
|
|
1586
|
-
const result = diagnoseTextCommand(
|
|
1587
|
-
text,
|
|
1588
|
-
commandOffset,
|
|
1589
|
-
lineEnd,
|
|
1590
|
-
hasPhysicalNewline,
|
|
1591
|
-
syntaxProfile,
|
|
1592
|
-
);
|
|
1593
|
-
if (result.problem !== null) {
|
|
1594
|
-
problems.push(result.problem);
|
|
1595
|
-
}
|
|
1596
|
-
continuedOpaqueArgument = result.textArgument;
|
|
1597
|
-
break;
|
|
1598
|
-
} else if (definition.kind === "shell") {
|
|
1599
|
-
const hasPhysicalNewline =
|
|
1600
|
-
text[lineEnd] === "\n" ||
|
|
1601
|
-
(text[lineEnd] === "\r" && text[lineEnd + 1] === "\n");
|
|
1602
|
-
continuedOpaqueArgument = scanShellCommand(
|
|
1603
|
-
text,
|
|
1604
|
-
commandOffset,
|
|
1605
|
-
lineEnd,
|
|
1606
|
-
hasPhysicalNewline,
|
|
1607
|
-
syntaxProfile,
|
|
1608
|
-
);
|
|
1609
|
-
break;
|
|
1610
|
-
} else if (definition.kind === "file") {
|
|
1611
|
-
const problem = diagnoseFileCommand(
|
|
1612
|
-
text,
|
|
1613
|
-
commandOffset,
|
|
1614
|
-
lineEnd,
|
|
1615
|
-
syntaxProfile,
|
|
1616
|
-
);
|
|
1617
|
-
if (problem !== null) {
|
|
1618
|
-
problems.push(problem);
|
|
1619
|
-
}
|
|
1620
|
-
break;
|
|
1621
|
-
} else if (definition.kind === "label") {
|
|
1622
|
-
const result = diagnoseLabelCommand(
|
|
1623
|
-
text,
|
|
1624
|
-
commandOffset,
|
|
1625
|
-
lineEnd,
|
|
1626
|
-
openingBraces.length > 0,
|
|
1627
|
-
syntaxProfile,
|
|
1628
|
-
);
|
|
1629
|
-
if (result.problem !== null) {
|
|
1630
|
-
problems.push(result.problem);
|
|
1631
|
-
}
|
|
1632
|
-
if (result.nextCommandOffset === null) {
|
|
1633
|
-
break;
|
|
1634
|
-
}
|
|
1635
|
-
const nextCommand = scanEmptyCommands(
|
|
1636
|
-
text,
|
|
1637
|
-
result.nextCommandOffset,
|
|
1638
|
-
lineEnd,
|
|
1639
|
-
result.nextCommandBoundary,
|
|
1640
|
-
);
|
|
1641
|
-
commandSearchOffset = nextCommand.offset;
|
|
1642
|
-
commandBoundary = nextCommand.boundary;
|
|
1643
|
-
} else if (definition.kind === "version") {
|
|
1644
|
-
const result = diagnoseVersionCommand(
|
|
1645
|
-
text,
|
|
1646
|
-
commandOffset,
|
|
1647
|
-
lineEnd,
|
|
1648
|
-
syntaxProfile,
|
|
1649
|
-
);
|
|
1650
|
-
if (result.problem !== null) {
|
|
1651
|
-
problems.push(result.problem);
|
|
1652
|
-
}
|
|
1653
|
-
if (result.nextCommandOffset === null) {
|
|
1654
|
-
break;
|
|
1655
|
-
}
|
|
1656
|
-
const nextCommand = scanEmptyCommands(
|
|
1657
|
-
text,
|
|
1658
|
-
result.nextCommandOffset,
|
|
1659
|
-
lineEnd,
|
|
1660
|
-
result.nextCommandBoundary,
|
|
1661
|
-
);
|
|
1662
|
-
commandSearchOffset = nextCommand.offset;
|
|
1663
|
-
commandBoundary = nextCommand.boundary;
|
|
1664
|
-
} else if (definition.kind === "substitute") {
|
|
1665
|
-
const result = diagnoseSubstitute(
|
|
1666
|
-
text,
|
|
1667
|
-
commandOffset,
|
|
1668
|
-
lineEnd,
|
|
1669
|
-
openingBraces.length > 0,
|
|
1670
|
-
syntaxProfile,
|
|
1671
|
-
);
|
|
1672
|
-
consumedUntil = result.consumedUntil;
|
|
1673
|
-
problems.push(...(result.regexpProblems ?? []));
|
|
1674
|
-
if (result.problem !== null) {
|
|
1675
|
-
problems.push(result.problem);
|
|
1676
|
-
}
|
|
1677
|
-
|
|
1678
|
-
if (result.nextCommandOffset === null) {
|
|
1679
|
-
break;
|
|
1680
|
-
}
|
|
1681
|
-
|
|
1682
|
-
if (result.nextCommandOffset > lineEnd) {
|
|
1683
|
-
pendingCommandOffset = result.nextCommandOffset;
|
|
1684
|
-
pendingCommandBoundary = result.nextCommandBoundary;
|
|
1685
|
-
break;
|
|
1686
|
-
}
|
|
1687
|
-
|
|
1688
|
-
const nextCommand = scanEmptyCommands(
|
|
1689
|
-
text,
|
|
1690
|
-
result.nextCommandOffset,
|
|
1691
|
-
lineEnd,
|
|
1692
|
-
result.nextCommandBoundary,
|
|
1693
|
-
);
|
|
1694
|
-
commandSearchOffset = nextCommand.offset;
|
|
1695
|
-
commandBoundary = nextCommand.boundary;
|
|
1696
|
-
} else if (definition.kind === "block-open") {
|
|
1697
|
-
openingBraces.push(commandOffset);
|
|
1698
|
-
const nextCommand = scanEmptyCommands(
|
|
1699
|
-
text,
|
|
1700
|
-
commandOffset + 1,
|
|
1701
|
-
lineEnd,
|
|
1702
|
-
"direct",
|
|
1703
|
-
);
|
|
1704
|
-
commandSearchOffset = nextCommand.offset;
|
|
1705
|
-
commandBoundary = nextCommand.boundary;
|
|
1706
|
-
} else if (definition.kind === "block-close") {
|
|
1707
|
-
const hasMatchingOpeningBrace = openingBraces.length > 0;
|
|
1708
|
-
const hasPrimaryCommandProblem =
|
|
1709
|
-
command.problem !== null || command.negationOffset !== null;
|
|
1710
|
-
|
|
1711
|
-
if (
|
|
1712
|
-
hasMatchingOpeningBrace &&
|
|
1713
|
-
commandBoundary === "direct" &&
|
|
1714
|
-
!syntaxPolicy.commandEnd.closingBraceTerminates &&
|
|
1715
|
-
!hasPrimaryCommandProblem
|
|
1716
|
-
) {
|
|
1717
|
-
problems.push(
|
|
1718
|
-
issue(
|
|
1719
|
-
"block-closing-brace-missing-separator",
|
|
1720
|
-
messages.missingClosingBraceSeparator,
|
|
1721
|
-
commandOffset,
|
|
1722
|
-
commandOffset + 1,
|
|
1723
|
-
),
|
|
1724
|
-
);
|
|
1725
|
-
}
|
|
1726
|
-
|
|
1727
|
-
if (!hasMatchingOpeningBrace) {
|
|
1728
|
-
problems.push(
|
|
1729
|
-
issue(
|
|
1730
|
-
"block-unexpected-closing-brace",
|
|
1731
|
-
messages.unexpectedClosingBrace,
|
|
1732
|
-
commandOffset,
|
|
1733
|
-
commandOffset + 1,
|
|
1734
|
-
),
|
|
1735
|
-
);
|
|
1736
|
-
} else {
|
|
1737
|
-
openingBraces.pop();
|
|
1738
|
-
}
|
|
1739
|
-
|
|
1740
|
-
const result = simpleCommandEnd(
|
|
1741
|
-
text,
|
|
1742
|
-
commandCharacter.value,
|
|
1743
|
-
commandOffset,
|
|
1744
|
-
lineEnd,
|
|
1745
|
-
syntaxProfile,
|
|
1746
|
-
{ recoverAtClosingBrace: true },
|
|
1747
|
-
);
|
|
1748
|
-
if (result.problem !== null) {
|
|
1749
|
-
problems.push(result.problem);
|
|
1750
|
-
}
|
|
1751
|
-
if (result.nextCommandOffset === null) {
|
|
1752
|
-
break;
|
|
1753
|
-
}
|
|
1754
|
-
const nextCommand = scanEmptyCommands(
|
|
1755
|
-
text,
|
|
1756
|
-
result.nextCommandOffset,
|
|
1757
|
-
lineEnd,
|
|
1758
|
-
result.nextCommandBoundary,
|
|
1759
|
-
);
|
|
1760
|
-
commandSearchOffset = nextCommand.offset;
|
|
1761
|
-
commandBoundary = nextCommand.boundary;
|
|
1762
|
-
} else if (definition.kind === "transliterate") {
|
|
1763
|
-
const transliterateLineEnd =
|
|
1764
|
-
commandOffset + 1 < lineEnd &&
|
|
1765
|
-
text[commandOffset + 1] === "\r" &&
|
|
1766
|
-
text[lineEnd] === "\r"
|
|
1767
|
-
? lineEnd + 1
|
|
1768
|
-
: lineEnd;
|
|
1769
|
-
const result = diagnoseTransliterate(
|
|
1770
|
-
text,
|
|
1771
|
-
commandOffset,
|
|
1772
|
-
transliterateLineEnd,
|
|
1773
|
-
openingBraces.length > 0,
|
|
1774
|
-
syntaxProfile,
|
|
1775
|
-
);
|
|
1776
|
-
if (result.problem !== null) {
|
|
1777
|
-
problems.push(result.problem);
|
|
1778
|
-
}
|
|
1779
|
-
if (result.nextCommandOffset === null) {
|
|
1780
|
-
break;
|
|
1781
|
-
}
|
|
1782
|
-
const nextCommand = scanEmptyCommands(
|
|
1783
|
-
text,
|
|
1784
|
-
result.nextCommandOffset,
|
|
1785
|
-
lineEnd,
|
|
1786
|
-
result.nextCommandBoundary,
|
|
1787
|
-
);
|
|
1788
|
-
commandSearchOffset = nextCommand.offset;
|
|
1789
|
-
commandBoundary = nextCommand.boundary;
|
|
1790
|
-
} else if (
|
|
1791
|
-
definition.kind === "simple" ||
|
|
1792
|
-
definition.kind === "numeric"
|
|
1793
|
-
) {
|
|
1794
|
-
const commandEnd =
|
|
1795
|
-
definition.kind === "numeric"
|
|
1796
|
-
? scanOptionalNumericArgumentSyntax(
|
|
1797
|
-
text,
|
|
1798
|
-
commandOffset,
|
|
1799
|
-
lineEnd,
|
|
1800
|
-
syntaxProfile,
|
|
1801
|
-
).commandEndOffset
|
|
1802
|
-
: commandOffset + commandCharacter.width;
|
|
1803
|
-
const result = diagnoseCommandEnd(
|
|
1804
|
-
text,
|
|
1805
|
-
commandCharacter.value,
|
|
1806
|
-
commandEnd,
|
|
1807
|
-
lineEnd,
|
|
1808
|
-
syntaxProfile,
|
|
1809
|
-
{ insideBlock: openingBraces.length > 0 },
|
|
1810
|
-
);
|
|
1811
|
-
if (result.problem !== null) {
|
|
1812
|
-
problems.push(result.problem);
|
|
1813
|
-
}
|
|
1814
|
-
if (result.nextCommandOffset === null) {
|
|
1815
|
-
break;
|
|
1816
|
-
}
|
|
1817
|
-
const nextCommand = scanEmptyCommands(
|
|
1818
|
-
text,
|
|
1819
|
-
result.nextCommandOffset,
|
|
1820
|
-
lineEnd,
|
|
1821
|
-
result.nextCommandBoundary,
|
|
1822
|
-
);
|
|
1823
|
-
commandSearchOffset = nextCommand.offset;
|
|
1824
|
-
commandBoundary = nextCommand.boundary;
|
|
1825
|
-
} else {
|
|
1826
|
-
break;
|
|
1827
|
-
}
|
|
1828
|
-
}
|
|
1829
|
-
}
|
|
1830
|
-
|
|
1831
|
-
if (newlineOffset === -1) {
|
|
1832
|
-
break;
|
|
1833
|
-
}
|
|
1834
|
-
lineStart = nextLineStart;
|
|
1835
|
-
}
|
|
1836
|
-
|
|
1837
|
-
for (const openingBrace of openingBraces) {
|
|
1838
|
-
problems.push(
|
|
1839
|
-
issue(
|
|
1840
|
-
"block-unclosed-opening-brace",
|
|
1841
|
-
messages.unclosedOpeningBrace,
|
|
1842
|
-
openingBrace,
|
|
1843
|
-
openingBrace + 1,
|
|
1844
|
-
),
|
|
1845
|
-
);
|
|
1846
|
-
}
|
|
1847
|
-
|
|
1848
|
-
return problems.map((problem) => ({
|
|
1849
|
-
...problem,
|
|
1850
|
-
message: renderMessage(problem.message, syntaxProfile),
|
|
1851
|
-
}));
|
|
1852
|
-
}
|
|
1853
|
-
|
|
1854
|
-
export function createDiagnostics(document, options = defaultSyntaxProfile) {
|
|
1855
|
-
return analyze(document.getText(), options).map((problem) => ({
|
|
1856
|
-
severity: DiagnosticSeverity.Error,
|
|
1857
|
-
range: {
|
|
1858
|
-
start: document.positionAt(problem.startOffset),
|
|
1859
|
-
end: document.positionAt(problem.endOffset),
|
|
1860
117
|
},
|
|
1861
|
-
|
|
1862
|
-
code: problem.code,
|
|
1863
|
-
source: SOURCE,
|
|
1864
|
-
}));
|
|
118
|
+
);
|
|
1865
119
|
}
|