@velarscript/compiler 0.12.0 → 0.13.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/dist/advisory-suppression.d.ts +62 -0
- package/dist/advisory-suppression.d.ts.map +1 -0
- package/dist/advisory-suppression.js +179 -0
- package/dist/advisory-suppression.js.map +1 -0
- package/dist/analyzer.d.ts +405 -17
- package/dist/analyzer.d.ts.map +1 -1
- package/dist/analyzer.js +1755 -211
- package/dist/analyzer.js.map +1 -1
- package/dist/ast.d.ts +6 -3
- package/dist/ast.d.ts.map +1 -1
- package/dist/ast.js.map +1 -1
- package/dist/binding-stability.d.ts +22 -0
- package/dist/binding-stability.d.ts.map +1 -0
- package/dist/binding-stability.js +63 -0
- package/dist/binding-stability.js.map +1 -0
- package/dist/collection-lowering-runtime.d.ts +1 -1
- package/dist/collection-lowering-runtime.d.ts.map +1 -1
- package/dist/collection-lowering-runtime.js +183 -56
- package/dist/collection-lowering-runtime.js.map +1 -1
- package/dist/diagnostic.d.ts +34 -0
- package/dist/diagnostic.d.ts.map +1 -1
- package/dist/diagnostic.js +11 -1
- package/dist/diagnostic.js.map +1 -1
- package/dist/embedded-javascript.d.ts +12 -1
- package/dist/embedded-javascript.d.ts.map +1 -1
- package/dist/embedded-javascript.js +111 -2
- package/dist/embedded-javascript.js.map +1 -1
- package/dist/embedded-module.d.ts.map +1 -1
- package/dist/embedded-module.js +22 -0
- package/dist/embedded-module.js.map +1 -1
- package/dist/emitter.d.ts +83 -1
- package/dist/emitter.d.ts.map +1 -1
- package/dist/emitter.js +518 -41
- package/dist/emitter.js.map +1 -1
- package/dist/error-runtime.d.ts +9 -0
- package/dist/error-runtime.d.ts.map +1 -1
- package/dist/error-runtime.js +21 -0
- package/dist/error-runtime.js.map +1 -1
- package/dist/extension.d.ts +5 -2
- package/dist/extension.d.ts.map +1 -1
- package/dist/extension.js +2 -1
- package/dist/extension.js.map +1 -1
- package/dist/formatter.d.ts.map +1 -1
- package/dist/formatter.js +222 -91
- package/dist/formatter.js.map +1 -1
- package/dist/index.d.ts +10 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +43 -5
- package/dist/index.js.map +1 -1
- package/dist/interpolated-string.d.ts.map +1 -1
- package/dist/interpolated-string.js +29 -2
- package/dist/interpolated-string.js.map +1 -1
- package/dist/json-runtime.d.ts.map +1 -1
- package/dist/json-runtime.js +27 -3
- package/dist/json-runtime.js.map +1 -1
- package/dist/lexer.d.ts +217 -1
- package/dist/lexer.d.ts.map +1 -1
- package/dist/lexer.js +799 -37
- package/dist/lexer.js.map +1 -1
- package/dist/limits.d.ts +9 -0
- package/dist/limits.d.ts.map +1 -1
- package/dist/limits.js +9 -0
- package/dist/limits.js.map +1 -1
- package/dist/mechanical-fix.d.ts.map +1 -1
- package/dist/mechanical-fix.js +38 -0
- package/dist/mechanical-fix.js.map +1 -1
- package/dist/parser.d.ts +30 -1
- package/dist/parser.d.ts.map +1 -1
- package/dist/parser.js +137 -21
- package/dist/parser.js.map +1 -1
- package/dist/promise-runtime.d.ts +20 -1
- package/dist/promise-runtime.d.ts.map +1 -1
- package/dist/promise-runtime.js +38 -3
- package/dist/promise-runtime.js.map +1 -1
- package/dist/reactive-bridge-runtime.d.ts.map +1 -1
- package/dist/reactive-bridge-runtime.js +7 -1
- package/dist/reactive-bridge-runtime.js.map +1 -1
- package/dist/runtime-abi.d.ts +21 -1
- package/dist/runtime-abi.d.ts.map +1 -1
- package/dist/runtime-abi.js +23 -3
- package/dist/runtime-abi.js.map +1 -1
- package/dist/source-names.d.ts +26 -0
- package/dist/source-names.d.ts.map +1 -1
- package/dist/source-names.js +49 -5
- package/dist/source-names.js.map +1 -1
- package/dist/source.d.ts.map +1 -1
- package/dist/source.js +13 -0
- package/dist/source.js.map +1 -1
- package/dist/stable-order.d.ts +16 -0
- package/dist/stable-order.d.ts.map +1 -0
- package/dist/stable-order.js +18 -0
- package/dist/stable-order.js.map +1 -0
- package/dist/text-runtime.d.ts.map +1 -1
- package/dist/text-runtime.js +182 -41
- package/dist/text-runtime.js.map +1 -1
- package/dist/token.d.ts +11 -0
- package/dist/token.d.ts.map +1 -1
- package/dist/token.js.map +1 -1
- package/dist/type-registry-runtime.d.ts.map +1 -1
- package/dist/type-registry-runtime.js +8 -1
- package/dist/type-registry-runtime.js.map +1 -1
- package/dist/type-validation-runtime.d.ts.map +1 -1
- package/dist/type-validation-runtime.js +129 -1
- package/dist/type-validation-runtime.js.map +1 -1
- package/dist/types.d.ts +17 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +161 -17
- package/dist/types.js.map +1 -1
- package/package.json +1 -1
package/dist/formatter.js
CHANGED
|
@@ -2,7 +2,7 @@ import { CORE_STATEMENT_HEAD_KEYWORDS } from "./core-vocabulary.js";
|
|
|
2
2
|
import { scanEmbeddedJavaScriptLiteral } from "./embedded-javascript.js";
|
|
3
3
|
import { MAX_VELAR_SOURCE_CODE_UNITS } from "./limits.js";
|
|
4
4
|
import { findInterpolatedExpressionEnd, scanStringEscape, scanStringLiteral } from "./interpolated-string.js";
|
|
5
|
-
import { isSourceIdentifierPart, isSourceIdentifierStart } from "./source-names.js";
|
|
5
|
+
import { isSourceIdentifierPart, isSourceIdentifierStart, isTypeEvidenceName } from "./source-names.js";
|
|
6
6
|
import { keywordKinds } from "./token.js";
|
|
7
7
|
const multiCharacterOperators = [">>>=", "<<=", ">>=", ">>>", "<<", ">>", "...", "?.", "??", "->", "=>", "==", "!=", "<=", ">=", "**", "+=", "-=", "*=", "/=", "%=", "|=", "&=", "^="];
|
|
8
8
|
const binaryWords = new Set(["and", "or", "in", "is"]);
|
|
@@ -49,7 +49,7 @@ export function formatSource(text, options = {}) {
|
|
|
49
49
|
const lines = protectedStrings.text.replaceAll("\r\n", "\n").replaceAll("\r", "\n").split("\n");
|
|
50
50
|
const indentation = [0];
|
|
51
51
|
const formatted = [];
|
|
52
|
-
let
|
|
52
|
+
let embedded = closedEmbeddedScan;
|
|
53
53
|
let statementLevel = 0;
|
|
54
54
|
/** The last token of the previous line — the context a continuation reads. */
|
|
55
55
|
let preceding;
|
|
@@ -66,7 +66,7 @@ export function formatSource(text, options = {}) {
|
|
|
66
66
|
// A leading-dot chain continuation keeps its own canonical indentation —
|
|
67
67
|
// one level past the statement it continues — without opening a block for
|
|
68
68
|
// the lines that follow it.
|
|
69
|
-
if (
|
|
69
|
+
if (!isEmbeddedLine(embedded) && isChainContinuationLine(content) && formatted.length > 0) {
|
|
70
70
|
const column = (statementLevel + 1) * indentWidth;
|
|
71
71
|
const line = formatInlineLine(content, angleEmbedding, markupLayout(indentWidth, column, angleEmbedding), preceding);
|
|
72
72
|
formatted.push(`${" ".repeat(column)}${line.text}`);
|
|
@@ -86,7 +86,7 @@ export function formatSource(text, options = {}) {
|
|
|
86
86
|
statementLevel = indentation.length - 1;
|
|
87
87
|
const indent = " ".repeat(statementLevel * indentWidth);
|
|
88
88
|
const layout = markupLayout(indentWidth, statementLevel * indentWidth, angleEmbedding);
|
|
89
|
-
if (
|
|
89
|
+
if (isEmbeddedLine(embedded)) {
|
|
90
90
|
formatted.push(`${indent}${formatEmbeddedContent(content, angleEmbedding, layout, layout.column)}`);
|
|
91
91
|
preceding = undefined;
|
|
92
92
|
}
|
|
@@ -95,14 +95,55 @@ export function formatSource(text, options = {}) {
|
|
|
95
95
|
formatted.push(`${indent}${line.text}`);
|
|
96
96
|
preceding = line.trailing ?? preceding;
|
|
97
97
|
}
|
|
98
|
-
|
|
98
|
+
embedded = nextEmbeddedScan(content, embedded, angleEmbedding);
|
|
99
99
|
}
|
|
100
100
|
while (formatted.at(-1) === "")
|
|
101
101
|
formatted.pop();
|
|
102
|
-
|
|
102
|
+
// The terminating newline is settled on the restored text. An unterminated
|
|
103
|
+
// block comment or layout string runs to the end of the module, so its
|
|
104
|
+
// placeholder value carries the file's final newline where neither the trim
|
|
105
|
+
// above nor an append here can see it — appending one to the joined lines
|
|
106
|
+
// adds a newline on every run, and charter line 422 makes formatting
|
|
107
|
+
// idempotent.
|
|
108
|
+
const restored = protectedStrings.restore(formatted.join("\n"));
|
|
109
|
+
return restored.endsWith("\n") || restored.endsWith("\r") ? restored : `${restored}\n`;
|
|
103
110
|
}
|
|
104
111
|
function protectMultilineStrings(source, opaqueSourceScanners) {
|
|
105
112
|
const replacements = [];
|
|
113
|
+
// One scan settles a prefix the module does not already contain, so every
|
|
114
|
+
// placeholder built from it is unique by construction. Asking the same
|
|
115
|
+
// question again per replacement re-reads the whole module each time, which
|
|
116
|
+
// costs O(placeholders x module) on a file full of layout strings.
|
|
117
|
+
//
|
|
118
|
+
// The prefix is the marker plus the smallest serial the module does not
|
|
119
|
+
// already write after it. Growing the marker by a character per collision
|
|
120
|
+
// instead would let a module spelling the marker followed by a long run of
|
|
121
|
+
// underscores grow the prefix without bound, and `placeholderPattern` below
|
|
122
|
+
// then builds a pattern the regular-expression engine refuses — a crashed
|
|
123
|
+
// formatter rather than a diagnostic. A serial cannot collide: an occurrence
|
|
124
|
+
// of `marker + serial + "_"` is an occurrence of the marker whose digit run
|
|
125
|
+
// is exactly that serial, which this scan already recorded.
|
|
126
|
+
const marker = "__velar_formatter_";
|
|
127
|
+
const writtenSerials = new Set();
|
|
128
|
+
for (let at = source.indexOf(marker); at !== -1; at = source.indexOf(marker, at + 1)) {
|
|
129
|
+
let end = at + marker.length;
|
|
130
|
+
while (end < source.length && source[end] >= "0" && source[end] <= "9")
|
|
131
|
+
end += 1;
|
|
132
|
+
writtenSerials.add(source.slice(at + marker.length, end));
|
|
133
|
+
}
|
|
134
|
+
let serial = 0;
|
|
135
|
+
while (writtenSerials.has(String(serial)))
|
|
136
|
+
serial += 1;
|
|
137
|
+
const prefix = `${marker}${serial}_`;
|
|
138
|
+
// Walking back to the line break costs a column, while asking the module for
|
|
139
|
+
// its last `\r` before an index costs the whole prefix of the module when it
|
|
140
|
+
// has none — which is every module with Unix line endings.
|
|
141
|
+
const lineStartBefore = (position) => {
|
|
142
|
+
let start = position;
|
|
143
|
+
while (start > 0 && source[start - 1] !== "\n" && source[start - 1] !== "\r")
|
|
144
|
+
start -= 1;
|
|
145
|
+
return start;
|
|
146
|
+
};
|
|
106
147
|
let output = "";
|
|
107
148
|
let index = 0;
|
|
108
149
|
while (index < source.length) {
|
|
@@ -121,12 +162,8 @@ function protectMultilineStrings(source, opaqueSourceScanners) {
|
|
|
121
162
|
index = end;
|
|
122
163
|
continue;
|
|
123
164
|
}
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
marker += "_";
|
|
127
|
-
const placeholder = JSON.stringify(marker);
|
|
128
|
-
const lineStart = Math.max(source.lastIndexOf("\n", index - 1), source.lastIndexOf("\r", index - 1)) + 1;
|
|
129
|
-
const originalIndent = /^[ \t]*/u.exec(source.slice(lineStart, index))?.[0] ?? "";
|
|
165
|
+
const placeholder = JSON.stringify(`${prefix}multiline_comment_${replacements.length}__`);
|
|
166
|
+
const originalIndent = /^[ \t]*/u.exec(source.slice(lineStartBefore(index), index))?.[0] ?? "";
|
|
130
167
|
replacements.push({ placeholder, value, kind: "blockComment", originalIndent });
|
|
131
168
|
output += placeholder;
|
|
132
169
|
index = end;
|
|
@@ -137,14 +174,10 @@ function protectMultilineStrings(source, opaqueSourceScanners) {
|
|
|
137
174
|
const start = index;
|
|
138
175
|
index = opaqueSource.end;
|
|
139
176
|
const value = source.slice(start, index);
|
|
140
|
-
|
|
141
|
-
?
|
|
142
|
-
:
|
|
143
|
-
|
|
144
|
-
marker += "_";
|
|
145
|
-
const placeholder = JSON.stringify(marker);
|
|
146
|
-
const lineStart = Math.max(source.lastIndexOf("\n", start - 1), source.lastIndexOf("\r", start - 1)) + 1;
|
|
147
|
-
const originalIndent = /^[ \t]*/u.exec(source.slice(lineStart, start))?.[0] ?? "";
|
|
177
|
+
const placeholder = JSON.stringify(opaqueSource.attachedToPrevious
|
|
178
|
+
? `${prefix}attached_opaque_source_${replacements.length}__`
|
|
179
|
+
: `${prefix}opaque_source_${replacements.length}__`);
|
|
180
|
+
const originalIndent = /^[ \t]*/u.exec(source.slice(lineStartBefore(start), start))?.[0] ?? "";
|
|
148
181
|
replacements.push({ placeholder, value, kind: "opaqueString", originalIndent });
|
|
149
182
|
output += placeholder;
|
|
150
183
|
continue;
|
|
@@ -163,30 +196,45 @@ function protectMultilineStrings(source, opaqueSourceScanners) {
|
|
|
163
196
|
output += value;
|
|
164
197
|
continue;
|
|
165
198
|
}
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
marker += "_";
|
|
169
|
-
const placeholder = JSON.stringify(marker);
|
|
170
|
-
const lineStart = Math.max(source.lastIndexOf("\n", start - 1), source.lastIndexOf("\r", start - 1)) + 1;
|
|
171
|
-
const originalIndent = /^[ \t]*/u.exec(source.slice(lineStart, start))?.[0] ?? "";
|
|
199
|
+
const placeholder = JSON.stringify(`${prefix}multiline_string_${replacements.length}__`);
|
|
200
|
+
const originalIndent = /^[ \t]*/u.exec(source.slice(lineStartBefore(start), start))?.[0] ?? "";
|
|
172
201
|
replacements.push({ placeholder, value, kind: scanned.layout ? "layout" : "opaqueString", originalIndent });
|
|
173
202
|
output += placeholder;
|
|
174
203
|
}
|
|
204
|
+
// Every placeholder shares the settled prefix, so one pass over the formatted
|
|
205
|
+
// text finds them all wherever the printer moved them. Searching for each
|
|
206
|
+
// placeholder from the start and splicing it into a rebuilt string is the
|
|
207
|
+
// other half of the same quadratic cost.
|
|
208
|
+
const byPlaceholder = new Map(replacements.map((replacement) => [replacement.placeholder, replacement]));
|
|
209
|
+
const placeholderPattern = new RegExp(`"${prefix}[A-Za-z0-9_]*"`, "gu");
|
|
175
210
|
return {
|
|
176
211
|
text: output,
|
|
177
|
-
restore: (formatted) =>
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
const
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
:
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
212
|
+
restore: (formatted) => {
|
|
213
|
+
let restored = "";
|
|
214
|
+
/** The restored text after its last line break — the indent a value reads. */
|
|
215
|
+
let lineTail = "";
|
|
216
|
+
let cursor = 0;
|
|
217
|
+
const append = (segment) => {
|
|
218
|
+
restored += segment;
|
|
219
|
+
const lineStart = Math.max(segment.lastIndexOf("\n"), segment.lastIndexOf("\r"));
|
|
220
|
+
lineTail = lineStart === -1 ? lineTail + segment : segment.slice(lineStart + 1);
|
|
221
|
+
};
|
|
222
|
+
for (const match of formatted.matchAll(placeholderPattern)) {
|
|
223
|
+
const replacement = byPlaceholder.get(match[0]);
|
|
224
|
+
if (!replacement)
|
|
225
|
+
continue;
|
|
226
|
+
append(formatted.slice(cursor, match.index));
|
|
227
|
+
const formattedIndent = /^[ \t]*/u.exec(lineTail)?.[0] ?? "";
|
|
228
|
+
append(replacement.kind === "layout"
|
|
229
|
+
? reindentLayoutLiteral(replacement.value, replacement.originalIndent, formattedIndent)
|
|
230
|
+
: replacement.kind === "blockComment"
|
|
231
|
+
? reindentBlockComment(replacement.value, replacement.originalIndent, formattedIndent)
|
|
232
|
+
: replacement.value);
|
|
233
|
+
cursor = match.index + match[0].length;
|
|
234
|
+
}
|
|
235
|
+
append(formatted.slice(cursor));
|
|
236
|
+
return restored;
|
|
237
|
+
},
|
|
190
238
|
};
|
|
191
239
|
}
|
|
192
240
|
function scanFormattingOpaqueSource(source, start, extensionScanners) {
|
|
@@ -270,8 +318,11 @@ function reindentLayoutLiteral(value, originalIndent, formattedIndent) {
|
|
|
270
318
|
// Blank layout-string lines participate in the literal without needing
|
|
271
319
|
// an indentation payload. Reintroducing the content margin here writes
|
|
272
320
|
// trailing spaces into otherwise canonical source and makes a formatter
|
|
273
|
-
// result fail `git diff --check`.
|
|
274
|
-
|
|
321
|
+
// result fail `git diff --check`. That holds only for a line whose whole
|
|
322
|
+
// indentation is the margin or less: whitespace past the margin is the
|
|
323
|
+
// string's own value (charter lines 381-384 preserve it exactly), so a
|
|
324
|
+
// line carrying it is re-margined like any other and keeps its payload.
|
|
325
|
+
if (originalMargin.startsWith(text))
|
|
275
326
|
text = "";
|
|
276
327
|
else if (text.startsWith(originalMargin))
|
|
277
328
|
text = `${formattedMargin}${text.slice(originalMargin.length)}`;
|
|
@@ -283,72 +334,102 @@ function isChainContinuationLine(content) {
|
|
|
283
334
|
const member = content.startsWith("?.") ? content[2] : content[1];
|
|
284
335
|
return (content.startsWith(".") || content.startsWith("?.")) && Boolean(member && isSourceIdentifierStart(member));
|
|
285
336
|
}
|
|
286
|
-
|
|
337
|
+
const closedEmbeddedScan = { depth: 0, pending: null };
|
|
338
|
+
/** A line inside an embedding, or inside an open tag, is copied, not formatted. */
|
|
339
|
+
function isEmbeddedLine(scan) {
|
|
340
|
+
return scan.depth > 0 || scan.pending !== null;
|
|
341
|
+
}
|
|
342
|
+
function nextEmbeddedScan(source, current, embedding) {
|
|
287
343
|
if (!embedding)
|
|
288
|
-
return
|
|
289
|
-
let depth =
|
|
344
|
+
return closedEmbeddedScan;
|
|
345
|
+
let depth = current.depth;
|
|
346
|
+
let pending = current.pending;
|
|
290
347
|
let index = 0;
|
|
291
|
-
if (depth === 0) {
|
|
348
|
+
if (depth === 0 && pending === null) {
|
|
292
349
|
const start = embeddedStart(source);
|
|
293
350
|
if (start === -1)
|
|
294
|
-
return
|
|
351
|
+
return closedEmbeddedScan;
|
|
295
352
|
index = start;
|
|
296
353
|
}
|
|
297
354
|
while (index < source.length) {
|
|
298
|
-
if (
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
index
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
cursor
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
355
|
+
if (pending === null) {
|
|
356
|
+
if (source.startsWith("<!--", index)) {
|
|
357
|
+
const end = source.indexOf("-->", index + 4);
|
|
358
|
+
if (end === -1)
|
|
359
|
+
break;
|
|
360
|
+
index = end + 3;
|
|
361
|
+
continue;
|
|
362
|
+
}
|
|
363
|
+
if (source.startsWith("/*", index)) {
|
|
364
|
+
index = blockCommentEnd(source, index);
|
|
365
|
+
continue;
|
|
366
|
+
}
|
|
367
|
+
if (source[index] !== "<" || !/[A-Za-z/>]/u.test(source[index + 1] ?? "")) {
|
|
368
|
+
index += 1;
|
|
369
|
+
continue;
|
|
370
|
+
}
|
|
371
|
+
const closing = source[index + 1] === "/";
|
|
372
|
+
let cursor = index + (closing ? 2 : 1);
|
|
373
|
+
const nameStart = cursor;
|
|
374
|
+
while (/[A-Za-z0-9_.:-]/u.test(source[cursor] ?? ""))
|
|
375
|
+
cursor += 1;
|
|
376
|
+
const name = source.slice(nameStart, cursor);
|
|
377
|
+
const fragment = name === "" && source[cursor] === ">";
|
|
378
|
+
if (!name && !fragment) {
|
|
379
|
+
index += 1;
|
|
380
|
+
continue;
|
|
381
|
+
}
|
|
382
|
+
pending = { name, closing, slash: false };
|
|
383
|
+
index = cursor;
|
|
323
384
|
}
|
|
324
385
|
let quote = "";
|
|
325
386
|
let braces = 0;
|
|
326
|
-
|
|
327
|
-
|
|
387
|
+
let slash = pending.slash;
|
|
388
|
+
let terminated = false;
|
|
389
|
+
while (index < source.length) {
|
|
390
|
+
const character = source[index++];
|
|
328
391
|
if (quote) {
|
|
329
392
|
if (character === "\\")
|
|
330
|
-
|
|
393
|
+
index += 1;
|
|
331
394
|
else if (character === quote)
|
|
332
395
|
quote = "";
|
|
396
|
+
continue;
|
|
333
397
|
}
|
|
334
|
-
|
|
398
|
+
if (character === '"' || character === "'") {
|
|
335
399
|
quote = character;
|
|
336
|
-
|
|
400
|
+
slash = false;
|
|
401
|
+
}
|
|
402
|
+
else if (character === "{") {
|
|
337
403
|
braces += 1;
|
|
338
|
-
|
|
404
|
+
slash = false;
|
|
405
|
+
}
|
|
406
|
+
else if (character === "}") {
|
|
339
407
|
braces = Math.max(0, braces - 1);
|
|
340
|
-
|
|
408
|
+
slash = false;
|
|
409
|
+
}
|
|
410
|
+
else if (braces > 0)
|
|
411
|
+
continue;
|
|
412
|
+
else if (character === ">") {
|
|
413
|
+
terminated = true;
|
|
341
414
|
break;
|
|
415
|
+
}
|
|
416
|
+
else if (character === "/")
|
|
417
|
+
slash = true;
|
|
418
|
+
else if (!/\s/u.test(character))
|
|
419
|
+
slash = false;
|
|
342
420
|
}
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
421
|
+
if (!terminated) {
|
|
422
|
+
pending = { ...pending, slash };
|
|
423
|
+
break;
|
|
424
|
+
}
|
|
425
|
+
const selfClosing = slash || embedding.voidElements?.has(pending.name) === true;
|
|
426
|
+
if (pending.closing)
|
|
346
427
|
depth = Math.max(0, depth - 1);
|
|
347
428
|
else if (!selfClosing)
|
|
348
429
|
depth += 1;
|
|
349
|
-
|
|
430
|
+
pending = null;
|
|
350
431
|
}
|
|
351
|
-
return depth;
|
|
432
|
+
return { depth, pending };
|
|
352
433
|
}
|
|
353
434
|
function embeddedStart(source) {
|
|
354
435
|
for (let index = 0; index < source.length - 1; index += 1) {
|
|
@@ -526,7 +607,8 @@ function tokenizeInline(source, embedding, layout = heldLayoutFor(embedding)) {
|
|
|
526
607
|
const generic = previous?.kind === "word"
|
|
527
608
|
&& (genericStack.at(-1) === true
|
|
528
609
|
|| beginsTypeBracket(tokens)
|
|
529
|
-
|| (opensAnnotatedType(tokens) && closesAsTypeArguments(source, index))
|
|
610
|
+
|| (opensAnnotatedType(tokens) && closesAsTypeArguments(source, index))
|
|
611
|
+
|| opensCallTypeArguments(source, index));
|
|
530
612
|
if (generic)
|
|
531
613
|
genericStack.push(true);
|
|
532
614
|
tokens.push({ kind: generic ? "open" : "operator", text: character, generic });
|
|
@@ -613,14 +695,43 @@ function isTypeAliasLine(tokens) {
|
|
|
613
695
|
* comparisons, so the paren depth is what separates them.
|
|
614
696
|
*/
|
|
615
697
|
function closesAsTypeArguments(source, start) {
|
|
698
|
+
return scanTypeArguments(source, start) !== null;
|
|
699
|
+
}
|
|
700
|
+
/**
|
|
701
|
+
* D90 (compiler-front-15): the type argument list a TypeScript habit puts on a
|
|
702
|
+
* call — `Map<string, number>()`, `id<string>("a")`. VelarScript infers type
|
|
703
|
+
* arguments, so the spelling is always an error and the parser reads it without
|
|
704
|
+
* regard to spacing. The formatter has to read it too: respacing it into
|
|
705
|
+
* `Map < string, number > ()` rewrites the author's line into a spelling that
|
|
706
|
+
* appears nowhere in their source, in exactly the file `velar format` is
|
|
707
|
+
* pointed at while the teaching diagnostic is on screen.
|
|
708
|
+
*
|
|
709
|
+
* The evidence is the parser's (`explicitTypeArgumentsEnd`) minus the same-file
|
|
710
|
+
* generic roster a line-based formatter cannot see: the brackets close on this
|
|
711
|
+
* line with `(` directly behind them, and every `,`-separated argument carries
|
|
712
|
+
* type evidence of its own — so `two(a < Limit, g > (c))`, two comparisons and
|
|
713
|
+
* a working program, keeps its operator spacing. Being narrower than the parser
|
|
714
|
+
* can only cost a respace on a line that is an error either way; it can never
|
|
715
|
+
* change which reading the compiler gives a line, because the parser does not
|
|
716
|
+
* consult the spacing.
|
|
717
|
+
*/
|
|
718
|
+
function opensCallTypeArguments(source, start) {
|
|
719
|
+
const scanned = scanTypeArguments(source, start);
|
|
720
|
+
return scanned !== null && scanned.typed && source[scanned.end] === "(";
|
|
721
|
+
}
|
|
722
|
+
function scanTypeArguments(source, start) {
|
|
616
723
|
let depth = 0;
|
|
617
724
|
let parenthesized = 0;
|
|
618
725
|
let index = start;
|
|
726
|
+
let everyArgumentIsTyped = true;
|
|
727
|
+
let argumentIsTyped = false;
|
|
619
728
|
while (index < source.length) {
|
|
620
729
|
const character = source[index];
|
|
621
730
|
if (character === "<") {
|
|
622
731
|
depth += 1;
|
|
623
732
|
index += 1;
|
|
733
|
+
if (depth >= 2)
|
|
734
|
+
argumentIsTyped = true;
|
|
624
735
|
continue;
|
|
625
736
|
}
|
|
626
737
|
if (character === ">") {
|
|
@@ -629,11 +740,12 @@ function closesAsTypeArguments(source, start) {
|
|
|
629
740
|
depth -= 1;
|
|
630
741
|
index += 1;
|
|
631
742
|
if (depth === 0)
|
|
632
|
-
return
|
|
743
|
+
return { end: index, typed: everyArgumentIsTyped && argumentIsTyped };
|
|
633
744
|
continue;
|
|
634
745
|
}
|
|
635
746
|
if (source.startsWith("->", index)) {
|
|
636
747
|
index += 2;
|
|
748
|
+
argumentIsTyped = true;
|
|
637
749
|
continue;
|
|
638
750
|
}
|
|
639
751
|
if (isSourceIdentifierStart(character)) {
|
|
@@ -643,7 +755,9 @@ function closesAsTypeArguments(source, start) {
|
|
|
643
755
|
index += 1;
|
|
644
756
|
const word = source.slice(wordStart, index);
|
|
645
757
|
if (binaryWords.has(word) || prefixWords.has(word))
|
|
646
|
-
return
|
|
758
|
+
return null;
|
|
759
|
+
if (isTypeEvidenceName(word))
|
|
760
|
+
argumentIsTyped = true;
|
|
647
761
|
continue;
|
|
648
762
|
}
|
|
649
763
|
if (character === "(") {
|
|
@@ -654,7 +768,7 @@ function closesAsTypeArguments(source, start) {
|
|
|
654
768
|
if (character === ")") {
|
|
655
769
|
parenthesized -= 1;
|
|
656
770
|
if (parenthesized < 0)
|
|
657
|
-
return
|
|
771
|
+
return null;
|
|
658
772
|
index += 1;
|
|
659
773
|
continue;
|
|
660
774
|
}
|
|
@@ -662,13 +776,24 @@ function closesAsTypeArguments(source, start) {
|
|
|
662
776
|
index += 1;
|
|
663
777
|
continue;
|
|
664
778
|
}
|
|
665
|
-
if ("
|
|
779
|
+
if (character === "," && depth === 1) {
|
|
780
|
+
everyArgumentIsTyped &&= argumentIsTyped;
|
|
781
|
+
argumentIsTyped = false;
|
|
666
782
|
index += 1;
|
|
667
783
|
continue;
|
|
668
784
|
}
|
|
669
|
-
|
|
785
|
+
if (character === "?" || character === "|") {
|
|
786
|
+
argumentIsTyped = true;
|
|
787
|
+
index += 1;
|
|
788
|
+
continue;
|
|
789
|
+
}
|
|
790
|
+
if (" \t,.".includes(character)) {
|
|
791
|
+
index += 1;
|
|
792
|
+
continue;
|
|
793
|
+
}
|
|
794
|
+
return null;
|
|
670
795
|
}
|
|
671
|
-
return
|
|
796
|
+
return null;
|
|
672
797
|
}
|
|
673
798
|
function formatInterpolatedString(source, start, scanned, embedding) {
|
|
674
799
|
if (!scanned.closed)
|
|
@@ -1131,7 +1256,13 @@ function needsSpace(previous, current, next, tokens, index, preceding) {
|
|
|
1131
1256
|
return true;
|
|
1132
1257
|
}
|
|
1133
1258
|
function isAttachedOpaqueSourcePlaceholder(token) {
|
|
1134
|
-
|
|
1259
|
+
// The marker carries a settled serial, so the placeholder is recognised by
|
|
1260
|
+
// the fixed middle `protectMultilineStrings` gives it rather than by the
|
|
1261
|
+
// marker spelled out — which would stop matching the moment a module made
|
|
1262
|
+
// the marker settle on anything but its first spelling.
|
|
1263
|
+
return token.kind === "string"
|
|
1264
|
+
&& token.text.includes("__velar_formatter_")
|
|
1265
|
+
&& token.text.includes("attached_opaque_source_");
|
|
1135
1266
|
}
|
|
1136
1267
|
function isUnaryOperator(token, previous, statementHead = false) {
|
|
1137
1268
|
if (prefixWords.has(token.text))
|