@descent-vtt/spec-guard 0.27.0 → 0.28.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/CHANGELOG.md +107 -0
- package/README.md +73 -29
- package/dist/cites.d.ts.map +1 -1
- package/dist/cites.js +11 -1
- package/dist/cites.js.map +1 -1
- package/dist/cli.d.ts +15 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +25 -6
- package/dist/cli.js.map +1 -1
- package/dist/engine.d.ts +48 -1
- package/dist/engine.d.ts.map +1 -1
- package/dist/engine.js +224 -13
- package/dist/engine.js.map +1 -1
- package/dist/glob.d.ts +9 -0
- package/dist/glob.d.ts.map +1 -1
- package/dist/glob.js +23 -0
- package/dist/glob.js.map +1 -1
- package/dist/impact.d.ts +13 -9
- package/dist/impact.d.ts.map +1 -1
- package/dist/impact.js +5 -3
- package/dist/impact.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/polyglot.d.ts +4 -1
- package/dist/polyglot.d.ts.map +1 -1
- package/dist/polyglot.js +448 -69
- package/dist/polyglot.js.map +1 -1
- package/dist/prove.d.ts.map +1 -1
- package/dist/prove.js +42 -14
- package/dist/prove.js.map +1 -1
- package/dist/reporter.d.ts.map +1 -1
- package/dist/reporter.js +13 -0
- package/dist/reporter.js.map +1 -1
- package/dist/runner.d.ts.map +1 -1
- package/dist/runner.js +17 -13
- package/dist/runner.js.map +1 -1
- package/dist/scope.d.ts +22 -2
- package/dist/scope.d.ts.map +1 -1
- package/dist/scope.js +10 -2
- package/dist/scope.js.map +1 -1
- package/dist/structure.d.ts +3 -1
- package/dist/structure.d.ts.map +1 -1
- package/dist/structure.js +15 -4
- package/dist/structure.js.map +1 -1
- package/dist/types.d.ts +7 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/polyglot.js
CHANGED
|
@@ -81,7 +81,7 @@ function mask(source, language) {
|
|
|
81
81
|
// The opening delimiter is left visible so a reader can see that a literal
|
|
82
82
|
// starts here; everything inside it is blanked so no keyword can hide there.
|
|
83
83
|
const blanks = [...comments, ...strings.map(([start, end]) => [start + 1, end])];
|
|
84
|
-
return { code: maskRanges(source, blanks), strings, source, starts: lineStarts(source), unterminated };
|
|
84
|
+
return { code: maskRanges(source, blanks), strings, comments, source, starts: lineStarts(source), unterminated };
|
|
85
85
|
}
|
|
86
86
|
/** The literal that starts exactly at `offset`, or null. */
|
|
87
87
|
function literalAt(masked, offset) {
|
|
@@ -132,7 +132,18 @@ export function literalValue(raw) {
|
|
|
132
132
|
return text;
|
|
133
133
|
}
|
|
134
134
|
/* ------------------------------------------------------------------ reading */
|
|
135
|
-
|
|
135
|
+
/**
|
|
136
|
+
* A Go identifier: a letter, then letters and decimal digits, where a letter
|
|
137
|
+
* is `_` or a character of a Letter category, Lu, Ll, Lt, Lm or Lo, and a
|
|
138
|
+
* digit one of Nd (The Go Programming Language Specification, "Characters",
|
|
139
|
+
* "Letters and digits" and "Identifiers", go1.27, read on 2026-10-19). The
|
|
140
|
+
* page has those categories as Unicode 8.0 gives them; it follows from it,
|
|
141
|
+
* and is not on it, that a later Unicode's letters, which the Node that runs
|
|
142
|
+
* spec-guard has, are read as letters. A mark is no letter, so `cafe` with a
|
|
143
|
+
* combining accent is no identifier, and the text is not normalised: `café`
|
|
144
|
+
* written so is two characters of which the second is none.
|
|
145
|
+
*/
|
|
146
|
+
const IDENTIFIER = /[\p{L}_][\p{L}\p{Nd}_]*/uy;
|
|
136
147
|
/**
|
|
137
148
|
* Whitespace-skipping cursor over masked code, which a Go import and a Python
|
|
138
149
|
* dynamic import are read with.
|
|
@@ -412,6 +423,11 @@ function readPython(collector) {
|
|
|
412
423
|
}
|
|
413
424
|
/* ----------------------------------------------------------------------- go */
|
|
414
425
|
const GO_IMPORT = /\bimport\b/g;
|
|
426
|
+
/** Go's keywords, which no identifier is, so no package name ("Keywords"). */
|
|
427
|
+
const GO_KEYWORDS = new Set([
|
|
428
|
+
...['break', 'case', 'chan', 'const', 'continue', 'default', 'defer', 'else', 'fallthrough', 'for', 'func', 'go', 'goto'],
|
|
429
|
+
...['if', 'import', 'interface', 'map', 'package', 'range', 'return', 'select', 'struct', 'switch', 'type', 'var'],
|
|
430
|
+
]);
|
|
415
431
|
function readGo(collector) {
|
|
416
432
|
const { code } = collector.masked;
|
|
417
433
|
GO_IMPORT.lastIndex = 0;
|
|
@@ -428,11 +444,29 @@ function readGo(collector) {
|
|
|
428
444
|
GO_IMPORT.lastIndex = close;
|
|
429
445
|
continue;
|
|
430
446
|
}
|
|
431
|
-
// A single import may carry
|
|
432
|
-
|
|
447
|
+
// A single import may carry a package name, `import fmt2 "fmt"`, `import
|
|
448
|
+
// 名 "fmt"` or `import _ "x"`, or a `.` ("Import declarations"). A name at
|
|
449
|
+
// the end of its line has a semicolon put after it, and a comment that
|
|
450
|
+
// holds a line break ends the line too ("Semicolons", "Comments"): the
|
|
451
|
+
// import ends there, with no path, which Go refuses.
|
|
452
|
+
if (reader.peek() === '.') {
|
|
433
453
|
reader.index += 1;
|
|
434
|
-
|
|
435
|
-
|
|
454
|
+
}
|
|
455
|
+
else {
|
|
456
|
+
const name = reader.identifier();
|
|
457
|
+
// On from the keyword, so that `import import "x"` is read as no
|
|
458
|
+
// import, and not as a second.
|
|
459
|
+
if (GO_KEYWORDS.has(name)) {
|
|
460
|
+
GO_IMPORT.lastIndex = reader.index;
|
|
461
|
+
continue;
|
|
462
|
+
}
|
|
463
|
+
// With no name the space after `import` was passed over already, so
|
|
464
|
+
// this finds a line break only after one.
|
|
465
|
+
const after = reader.index;
|
|
466
|
+
reader.skipSpace();
|
|
467
|
+
if (code.slice(after, reader.index).includes('\n'))
|
|
468
|
+
continue;
|
|
469
|
+
}
|
|
436
470
|
reader.skipSpace();
|
|
437
471
|
const literal = literalAt(collector.masked, reader.index);
|
|
438
472
|
if (literal !== null)
|
|
@@ -457,8 +491,26 @@ const RUST_TOKEN = new RegExp(String.raw `[${RUST_SPACE}]*(?:(r#)?([\p{XID_Start
|
|
|
457
491
|
const RUST_BLANK = new RegExp(String.raw `^[${RUST_SPACE}]*$`, 'u');
|
|
458
492
|
/** What `r#` cannot make a raw identifier of. */
|
|
459
493
|
const RUST_NOT_RAW = new Set(['_', 'crate', 'self', 'Self', 'super']);
|
|
460
|
-
/**
|
|
461
|
-
|
|
494
|
+
/**
|
|
495
|
+
* What no segment of a path, alias or crate name is unless written raw:
|
|
496
|
+
* `_`, and Rust's keywords in every edition, strict and reserved (Keywords,
|
|
497
|
+
* as read on 2026-10-19), `Self` among them, which no use path holds
|
|
498
|
+
* (Use declarations, "use Paths"). So a declaration left open is never read
|
|
499
|
+
* on into the next, whose `use` or `extern` is one. `crate`, `self` and
|
|
500
|
+
* `super` are not here: they stand where `rustPathRefused` lets them.
|
|
501
|
+
* `async`, `await` and `dyn` are keywords from the 2018 edition on, `try`
|
|
502
|
+
* from 2018 and `gen` from 2024, and the edition is in `Cargo.toml`, which
|
|
503
|
+
* the reader does not open, so each is read as the name it is before them.
|
|
504
|
+
*/
|
|
505
|
+
const RUST_NOT_SEGMENT = new Set([
|
|
506
|
+
'_',
|
|
507
|
+
...['as', 'break', 'const', 'continue', 'else', 'enum', 'extern', 'false', 'fn', 'for', 'if', 'impl', 'in', 'let', 'loop'],
|
|
508
|
+
...['match', 'mod', 'move', 'mut', 'pub', 'ref', 'return', 'Self', 'static', 'struct', 'trait', 'true', 'type', 'unsafe'],
|
|
509
|
+
...['use', 'where', 'while', 'abstract', 'become', 'box', 'do', 'final', 'macro', 'override', 'priv', 'typeof', 'unsized'],
|
|
510
|
+
...['virtual', 'yield'],
|
|
511
|
+
]);
|
|
512
|
+
/** The words a path starts with to name a module, which an import binds only under a name of its own. */
|
|
513
|
+
const RUST_PATH_KEYWORDS = new Set(['crate', 'self', 'super', '$crate']);
|
|
462
514
|
/** The token at `index`, or null where none starts. */
|
|
463
515
|
function rustToken(text, index) {
|
|
464
516
|
RUST_TOKEN.lastIndex = index;
|
|
@@ -471,6 +523,36 @@ function rustToken(text, index) {
|
|
|
471
523
|
return null;
|
|
472
524
|
return { text: word?.normalize('NFC') ?? mark, name: word !== undefined, raw: raw !== undefined, end: RUST_TOKEN.lastIndex };
|
|
473
525
|
}
|
|
526
|
+
/**
|
|
527
|
+
* Whether rustc refuses a path of a use tree for where `crate`, `self`,
|
|
528
|
+
* `super` or `$crate` stands in it (Paths, "Path qualifiers"), or for
|
|
529
|
+
* importing a module that it binds only under a name given with `as` (Use
|
|
530
|
+
* declarations, "Restrictions"), each as read on 2026-10-19, and each held
|
|
531
|
+
* to rustc 1.97.1 in every edition.
|
|
532
|
+
*/
|
|
533
|
+
function rustPathRefused({ segments, global, bare }) {
|
|
534
|
+
const last = segments.length - 1;
|
|
535
|
+
const misplaced = segments.some((segment, at) => {
|
|
536
|
+
// First, and not after a `::`.
|
|
537
|
+
if (segment === 'crate' || segment === '$crate')
|
|
538
|
+
return at > 0 || global;
|
|
539
|
+
// First and not after a `::`, or last after one: `a::self` names `a`.
|
|
540
|
+
// `::{self as root}` imports the crate root in the 2015 edition and is
|
|
541
|
+
// refused from 2018, which the reader cannot tell apart, so it is read.
|
|
542
|
+
if (segment === 'self')
|
|
543
|
+
return at < last && (at > 0 || global);
|
|
544
|
+
// Only after other `super`s, or after a first `self`: a `self` anywhere
|
|
545
|
+
// else before it is refused where it stands.
|
|
546
|
+
if (segment === 'super')
|
|
547
|
+
return global || !segments.slice(0, at).every((before) => before === 'super' || before === 'self');
|
|
548
|
+
return false;
|
|
549
|
+
});
|
|
550
|
+
// `use crate;`, `use self;`, `use super::super;` and `use a::{self}` with
|
|
551
|
+
// `a` one of them: the module is bound only under a name given with `as`.
|
|
552
|
+
// `use crate::{};` binds nothing, and rustc takes it. A trailing `self`
|
|
553
|
+
// is one of those words too, so `use crate::self;` is refused alike.
|
|
554
|
+
return misplaced || (bare && segments.every((segment) => RUST_PATH_KEYWORDS.has(segment)));
|
|
555
|
+
}
|
|
474
556
|
/**
|
|
475
557
|
* The paths one use tree names, segment by segment, read as the Rust
|
|
476
558
|
* Reference's grammar has it:
|
|
@@ -495,28 +577,36 @@ function readUseTree(text, from) {
|
|
|
495
577
|
// A name, or `$crate` or another name of a macro's, which rustc reads
|
|
496
578
|
// where the macro is expanded.
|
|
497
579
|
const named = (token) => token.name || token.text.startsWith('$');
|
|
498
|
-
// `_` is a token of its own, and stands in a path only after `as`.
|
|
499
|
-
// segment is never the keyword a declaration starts with, so that a
|
|
500
|
-
// declaration left open is never read on into the next.
|
|
580
|
+
// `_` is a token of its own, and stands in a path only after `as`.
|
|
501
581
|
const segment = () => take((token) => named(token) && (token.raw || !RUST_NOT_SEGMENT.has(token.text)))?.text ?? null;
|
|
582
|
+
// A name given with `as`: `_`, or a name that is no keyword and no word a path starts with.
|
|
583
|
+
const alias = (token) => named(token) && (token.raw || token.text === '_' || !(RUST_NOT_SEGMENT.has(token.text) || RUST_PATH_KEYWORDS.has(token.text)));
|
|
502
584
|
const paths = [];
|
|
503
|
-
const tree = (head, depth) => {
|
|
585
|
+
const tree = (head, rooted, depth) => {
|
|
504
586
|
if (depth > MAX_EXPANSION)
|
|
505
587
|
return 'overflow';
|
|
506
588
|
const path = [...head];
|
|
507
|
-
|
|
589
|
+
// A `::` before the first segment of all is the global root, and rustc
|
|
590
|
+
// refuses one anywhere else: `use a::{::b};`.
|
|
591
|
+
const leading = mark('::');
|
|
592
|
+
if (leading && head.length > 0)
|
|
593
|
+
return 'refused';
|
|
594
|
+
const global = rooted || leading;
|
|
508
595
|
for (let name = segment(); name !== null; name = segment()) {
|
|
509
596
|
path.push(name);
|
|
510
597
|
if (mark('::'))
|
|
511
598
|
continue;
|
|
512
|
-
|
|
599
|
+
const renamed = take((token) => !token.raw && token.text === 'as') !== null;
|
|
600
|
+
if (renamed && take(alias) === null)
|
|
513
601
|
return 'refused';
|
|
514
|
-
paths.push(path);
|
|
602
|
+
paths.push({ segments: path, global, bare: !renamed });
|
|
515
603
|
return 'read';
|
|
516
604
|
}
|
|
517
605
|
// A separator was the last token read, or nothing was.
|
|
518
606
|
if (mark('*')) {
|
|
519
|
-
|
|
607
|
+
// A glob binds no module of its own; its `*` is no word a path starts
|
|
608
|
+
// with either, so `bare` is never what decides it.
|
|
609
|
+
paths.push({ segments: [...path, '*'], global, bare: false });
|
|
520
610
|
return 'read';
|
|
521
611
|
}
|
|
522
612
|
if (!mark('{'))
|
|
@@ -524,11 +614,11 @@ function readUseTree(text, from) {
|
|
|
524
614
|
if (mark('}')) {
|
|
525
615
|
// An empty group imports nothing, and rustc still reads the path before
|
|
526
616
|
// it as an import: its lint names `use a::{};` an unused one.
|
|
527
|
-
paths.push(path);
|
|
617
|
+
paths.push({ segments: path, global, bare: false });
|
|
528
618
|
return 'read';
|
|
529
619
|
}
|
|
530
620
|
do {
|
|
531
|
-
const read = tree(path, depth + 1);
|
|
621
|
+
const read = tree(path, global, depth + 1);
|
|
532
622
|
if (read !== 'read')
|
|
533
623
|
return read;
|
|
534
624
|
if (paths.length > MAX_EXPANSION)
|
|
@@ -536,11 +626,11 @@ function readUseTree(text, from) {
|
|
|
536
626
|
} while (mark(',') && rustToken(text, index)?.text !== '}');
|
|
537
627
|
return mark('}') ? 'read' : 'refused';
|
|
538
628
|
};
|
|
539
|
-
const read = tree([], 0);
|
|
540
|
-
return { paths: read
|
|
629
|
+
const read = tree([], false, 0);
|
|
630
|
+
return { paths: read !== 'read' ? read : paths.some(rustPathRefused) ? 'refused' : paths, end: index };
|
|
541
631
|
}
|
|
542
632
|
/** The module a path of a use tree names, in the notation a rule writes, or nothing. */
|
|
543
|
-
function usePath(segments) {
|
|
633
|
+
function usePath({ segments }) {
|
|
544
634
|
// `a::b::self` names `a::b`, as `a::{self}` names `a`. A glob of nothing,
|
|
545
635
|
// the one path a `*` starts, names no module.
|
|
546
636
|
const named = segments.length > 1 && segments.at(-1) === 'self' ? segments.slice(0, -1) : segments;
|
|
@@ -555,7 +645,10 @@ function usePath(segments) {
|
|
|
555
645
|
* space and comments between its tokens, a raw identifier without its `r#`, a
|
|
556
646
|
* name in its NFC form, and `self` at the end of a path naming what is before
|
|
557
647
|
* it. Text that is no use tree, which rustc refuses, names nothing (ADR-0008,
|
|
558
|
-
* amended 2026-10-18)
|
|
648
|
+
* amended 2026-10-18), and so does a tree with a keyword for a segment, or
|
|
649
|
+
* with `crate`, `self` or `super` where rustc refuses it (amended
|
|
650
|
+
* 2026-10-19). A `$` name is read as a macro's, as a tree alone says nothing
|
|
651
|
+
* of where it stands. Returns null when the tree would name more than
|
|
559
652
|
* MAX_EXPANSION paths, or nest its groups deeper than that, which the caller
|
|
560
653
|
* reports rather than truncating.
|
|
561
654
|
*/
|
|
@@ -570,38 +663,135 @@ export function expandUsePath(input) {
|
|
|
570
663
|
/**
|
|
571
664
|
* The crate an extern crate declaration names, read from after `crate`, or
|
|
572
665
|
* null where rustc refuses it: `extern crate name (as alias)? ;`, the name an
|
|
573
|
-
* identifier or `self
|
|
666
|
+
* identifier that is no keyword, or `self` with an alias, which rustc takes
|
|
667
|
+
* only so, and the end of its `;`.
|
|
574
668
|
*/
|
|
575
669
|
function externCrate(text, from) {
|
|
670
|
+
// A name that is no keyword and no word a path starts with, raw or not.
|
|
671
|
+
const name = (token) => token.name && (token.raw || !(RUST_NOT_SEGMENT.has(token.text) || RUST_PATH_KEYWORDS.has(token.text)));
|
|
576
672
|
const crate = rustToken(text, from);
|
|
577
|
-
if (crate === null || !crate
|
|
673
|
+
if (crate === null || !(name(crate) || crate.text === 'self'))
|
|
578
674
|
return null;
|
|
579
675
|
let next = rustToken(text, crate.end);
|
|
580
|
-
|
|
676
|
+
const renamed = next !== null && !next.raw && next.text === 'as';
|
|
677
|
+
if (renamed) {
|
|
581
678
|
const alias = rustToken(text, next.end);
|
|
582
|
-
next = alias !== null && alias.
|
|
679
|
+
next = alias !== null && (name(alias) || alias.text === '_') ? rustToken(text, alias.end) : null;
|
|
680
|
+
}
|
|
681
|
+
return next?.text === ';' && (renamed || crate.text !== 'self') ? { crate: crate.text, end: next.end } : null;
|
|
682
|
+
}
|
|
683
|
+
/**
|
|
684
|
+
* Where a macro's tokens stand: the group of an invocation, `m!(...)`,
|
|
685
|
+
* `path::m![...]` or `m! {...}`, of a definition, `macro_rules! m {...}`
|
|
686
|
+
* and `macro m(...) {...}`, and of an attribute, `#[...]` and `#![...]`.
|
|
687
|
+
* rustc reads them as tokens and refuses none of them for not being a
|
|
688
|
+
* declaration: a matcher holds the word `use`, and `quote!` writes
|
|
689
|
+
* `use #name::Thing;`. Too wide a reach costs a note, never a reading: a
|
|
690
|
+
* declaration in one is read as everywhere else.
|
|
691
|
+
*/
|
|
692
|
+
const RUST_MACRO = new RegExp(String.raw `(?:(?<!\p{XID_Continue})macro_rules[${RUST_SPACE}]*![${RUST_SPACE}]*[\p{XID_Start}_]\p{XID_Continue}*|(?<![\p{XID_Continue}#])(macro)[${RUST_SPACE}]+[\p{XID_Start}_]\p{XID_Continue}*|\p{XID_Continue}!|#!?)[${RUST_SPACE}]*([(\[{])`,
|
|
693
|
+
// Emptied, the flags leave each `exec` in `macroRanges` reading from the
|
|
694
|
+
// start, and on a file with an attribute its loop pushes a range a turn
|
|
695
|
+
// for ever. The thread runs out of memory, and no test's timer fires
|
|
696
|
+
// inside the loop, so a sweep counts an error: the tests of a macro's
|
|
697
|
+
// tokens see the mutant where they run before any test that meets it.
|
|
698
|
+
'gu');
|
|
699
|
+
const RUST_CLOSERS = { '(': ')', '[': ']', '{': '}' };
|
|
700
|
+
/** The brace a `macro` definition's body opens after its parameters. */
|
|
701
|
+
const RUST_BODY = new RegExp(String.raw `[${RUST_SPACE}]*\{`, 'uy');
|
|
702
|
+
/** The half-open ranges of a file's macro tokens, outermost only, ascending. */
|
|
703
|
+
function macroRanges(code) {
|
|
704
|
+
const ranges = [];
|
|
705
|
+
RUST_MACRO.lastIndex = 0;
|
|
706
|
+
let match;
|
|
707
|
+
while ((match = RUST_MACRO.exec(code)) !== null) {
|
|
708
|
+
const opener = match[2];
|
|
709
|
+
let end = matchingClose(code, RUST_MACRO.lastIndex - 1, opener, RUST_CLOSERS[opener]) + 1;
|
|
710
|
+
// The body ends at its `}`, and `- 1` for its `+ 1` is an equivalent
|
|
711
|
+
// mutant: no `use` starts in the body's last two characters.
|
|
712
|
+
RUST_BODY.lastIndex = end;
|
|
713
|
+
if (match[1] !== undefined && RUST_BODY.test(code))
|
|
714
|
+
end = matchingClose(code, RUST_BODY.lastIndex - 1, '{', '}') + 1;
|
|
715
|
+
ranges.push([match.index, end]);
|
|
716
|
+
// Outermost only: each is read to its end once, so nesting costs nothing.
|
|
717
|
+
RUST_MACRO.lastIndex = end;
|
|
583
718
|
}
|
|
584
|
-
return
|
|
719
|
+
return ranges;
|
|
585
720
|
}
|
|
721
|
+
const RUST_SPACE_CHARACTER = new RegExp(`[${RUST_SPACE}]`, 'u');
|
|
722
|
+
/** Whether `at` is where an item or a statement may start: at the head of the file, or after `;`, a brace, an attribute's `]`, `pub` or its `(...)`. */
|
|
723
|
+
function startsAnItem(code, at) {
|
|
724
|
+
let before = at;
|
|
725
|
+
while (RUST_SPACE_CHARACTER.test(code[before - 1] ?? ''))
|
|
726
|
+
before -= 1;
|
|
727
|
+
return before === 0 || ';{}])'.includes(code[before - 1]) || /(?<!\p{XID_Continue})pub$/u.test(code.slice(Math.max(0, before - 4), before));
|
|
728
|
+
}
|
|
729
|
+
/**
|
|
730
|
+
* A doc comment, which rustc reads as an attribute: three slashes and no
|
|
731
|
+
* fourth, `//!`, a block opened with two stars and no third that holds
|
|
732
|
+
* something, and a block opened with `/*!` (Comments, as read on
|
|
733
|
+
* 2026-10-19).
|
|
734
|
+
*/
|
|
735
|
+
const RUST_DOC = /^(?:\/\/\/(?!\/)|\/\/!|\/\*\*(?![*/])|\/\*!)/;
|
|
586
736
|
function readRust(collector) {
|
|
587
|
-
const { code } = collector.masked;
|
|
737
|
+
const { code, comments, source } = collector.masked;
|
|
738
|
+
const macros = macroRanges(code);
|
|
739
|
+
let macro = 0;
|
|
740
|
+
let comment = 0;
|
|
588
741
|
RUST_USE.lastIndex = 0;
|
|
589
742
|
let match;
|
|
590
743
|
while ((match = RUST_USE.exec(code)) !== null) {
|
|
591
|
-
const
|
|
744
|
+
const at = match.index;
|
|
745
|
+
while (macro < macros.length && macros[macro][1] <= at)
|
|
746
|
+
macro += 1;
|
|
747
|
+
// A range starts at a macro's name or an attribute's `#`, never where a
|
|
748
|
+
// `use` does, so `<` for `<=` is an equivalent mutant; and no comment
|
|
749
|
+
// starts where a `use` does, so `<=` for the `<` after it is one too.
|
|
750
|
+
const inMacro = macro < macros.length && macros[macro][0] <= at;
|
|
751
|
+
while (comment < comments.length && comments[comment][0] < at)
|
|
752
|
+
comment += 1;
|
|
753
|
+
// A doc comment between `use` and the end of the declaration, which rustc
|
|
754
|
+
// reads as an attribute where no attribute may stand.
|
|
755
|
+
const documented = (end) => {
|
|
756
|
+
for (let next = comment; next < comments.length && comments[next][0] < end; next++) {
|
|
757
|
+
if (RUST_DOC.test(source.slice(comments[next][0], comments[next][0] + 4)))
|
|
758
|
+
return true;
|
|
759
|
+
}
|
|
760
|
+
return false;
|
|
761
|
+
};
|
|
762
|
+
// What rustc refuses is said where it starts as a declaration: where an
|
|
763
|
+
// item or a statement may start, and not among a macro's tokens, which
|
|
764
|
+
// rustc does not read as one (ADR-0008, amended 2026-10-19).
|
|
765
|
+
const refused = () => {
|
|
766
|
+
if (!inMacro && startsAnItem(code, at)) {
|
|
767
|
+
note(collector, 'refused', at, 'a declaration that starts as a use or an extern crate is one rustc refuses, and no module is read from it');
|
|
768
|
+
}
|
|
769
|
+
};
|
|
770
|
+
const start = at + match[0].length;
|
|
592
771
|
if (match[0] !== 'use') {
|
|
593
|
-
const
|
|
594
|
-
if (
|
|
595
|
-
emit(collector, crate, 'use',
|
|
772
|
+
const extern = externCrate(code, start);
|
|
773
|
+
if (extern !== null && !documented(extern.end))
|
|
774
|
+
emit(collector, extern.crate, 'use', at);
|
|
775
|
+
else
|
|
776
|
+
refused();
|
|
596
777
|
continue;
|
|
597
778
|
}
|
|
598
779
|
const { paths, end } = readUseTree(code, start);
|
|
780
|
+
const semicolon = rustToken(code, end);
|
|
599
781
|
if (paths === 'overflow') {
|
|
600
|
-
note(collector, 'truncated',
|
|
782
|
+
note(collector, 'truncated', at, `a use declaration expands to more than ${MAX_EXPANSION} module paths, or nests its groups deeper than that`);
|
|
601
783
|
}
|
|
602
|
-
else if (paths !== 'refused' &&
|
|
784
|
+
else if (paths !== 'refused' &&
|
|
785
|
+
semicolon?.text === ';' &&
|
|
786
|
+
!documented(semicolon.end) &&
|
|
787
|
+
// A `$` name stands only in a macro's tokens, where it is read as the
|
|
788
|
+
// macro is expanded.
|
|
789
|
+
(inMacro || !paths.some(({ segments }) => segments.some((segment) => segment.startsWith('$'))))) {
|
|
603
790
|
for (const path of paths)
|
|
604
|
-
emit(collector, usePath(path), 'use',
|
|
791
|
+
emit(collector, usePath(path), 'use', at);
|
|
792
|
+
}
|
|
793
|
+
else {
|
|
794
|
+
refused();
|
|
605
795
|
}
|
|
606
796
|
// On from where reading stopped, past the tree or at the token rustc
|
|
607
797
|
// would refuse, so that no token is read twice. No segment is `use` or
|
|
@@ -634,6 +824,26 @@ const CSHARP_DIRECTIVE = new RegExp(String.raw `^[${CSHARP_BLANK}]*#[^${CSHARP_B
|
|
|
634
824
|
const CSHARP_WORD = new RegExp(String.raw `[${CSHARP_BLANK}${CSHARP_BREAK}]*(?:(@?(?:[${CSHARP_PART}]|\\u[\dA-Fa-f]{4}|\\U[\dA-Fa-f]{8})+)|(::|[.;=<>,?*\[\](){}]))`, 'uy');
|
|
635
825
|
const CSHARP_ESCAPE = /\\u([\dA-Fa-f]{4})|\\U([\dA-Fa-f]{8})/g;
|
|
636
826
|
const CSHARP_IDENTIFIER = new RegExp(String.raw `^[\p{L}\p{Nl}_][${CSHARP_PART}]*$`, 'u');
|
|
827
|
+
/**
|
|
828
|
+
* C#'s keywords (6.4.4, as read on 2026-10-19), which no identifier is
|
|
829
|
+
* unless written with `@`, or with an escape in it, which no keyword holds.
|
|
830
|
+
* Its contextual keywords, `global`, `var` and `dynamic` among them, are
|
|
831
|
+
* identifiers where a name stands.
|
|
832
|
+
*/
|
|
833
|
+
const CSHARP_KEYWORDS = new Set([
|
|
834
|
+
...['abstract', 'as', 'base', 'bool', 'break', 'byte', 'case', 'catch', 'char', 'checked', 'class', 'const', 'continue'],
|
|
835
|
+
...['decimal', 'default', 'delegate', 'do', 'double', 'else', 'enum', 'event', 'explicit', 'extern', 'false', 'finally'],
|
|
836
|
+
...['fixed', 'float', 'for', 'foreach', 'goto', 'if', 'implicit', 'in', 'int', 'interface', 'internal', 'is', 'lock'],
|
|
837
|
+
...['long', 'namespace', 'new', 'null', 'object', 'operator', 'out', 'override', 'params', 'private', 'protected'],
|
|
838
|
+
...['public', 'readonly', 'ref', 'return', 'sbyte', 'sealed', 'short', 'sizeof', 'stackalloc', 'static', 'string'],
|
|
839
|
+
...['struct', 'switch', 'this', 'throw', 'true', 'try', 'typeof', 'uint', 'ulong', 'unchecked', 'unsafe', 'ushort'],
|
|
840
|
+
...['using', 'virtual', 'void', 'volatile', 'while'],
|
|
841
|
+
]);
|
|
842
|
+
/** The keywords that name a type (8.2.1, 8.3.1): `void` is one only before a `*`. */
|
|
843
|
+
const CSHARP_TYPE_KEYWORDS = new Set([
|
|
844
|
+
...['bool', 'byte', 'char', 'decimal', 'double', 'float', 'int', 'long', 'object', 'sbyte', 'short', 'string', 'uint'],
|
|
845
|
+
...['ulong', 'ushort'],
|
|
846
|
+
]);
|
|
637
847
|
/**
|
|
638
848
|
* The identifier a word is, as C# compares two: with no `@`, each escape the
|
|
639
849
|
* character it stands for, and no formatting character. Null for a word that
|
|
@@ -652,12 +862,16 @@ function csharpIdentifier(word) {
|
|
|
652
862
|
});
|
|
653
863
|
return CSHARP_IDENTIFIER.test(decoded) ? decoded.replace(/\p{Cf}/gu, '') : null;
|
|
654
864
|
}
|
|
655
|
-
/** What a type argument list holds beside names and angle brackets: no pointer, which no type argument is. */
|
|
656
|
-
const TYPE_MARKS = ['.', ',', '?', '[', ']', '(', ')', '::'];
|
|
657
865
|
/** A cursor over the tokens of one directive or declaration. */
|
|
658
866
|
class CsharpTokens {
|
|
659
867
|
text;
|
|
660
868
|
index;
|
|
869
|
+
/** How deep the type being read is. */
|
|
870
|
+
nesting = 0;
|
|
871
|
+
/** Whether a type nested deeper than MAX_EXPANSION, and was not read. */
|
|
872
|
+
overflow = false;
|
|
873
|
+
/** The name of each type read, in the order written: a type is its name, then its arguments. */
|
|
874
|
+
names = [];
|
|
661
875
|
constructor(text, index) {
|
|
662
876
|
this.text = text;
|
|
663
877
|
this.index = index;
|
|
@@ -683,12 +897,13 @@ class CsharpTokens {
|
|
|
683
897
|
return true;
|
|
684
898
|
}
|
|
685
899
|
/**
|
|
686
|
-
* An identifier, which is never
|
|
687
|
-
* directive left open is never read on
|
|
900
|
+
* An identifier, which is never a keyword as it is written: so `using
|
|
901
|
+
* class.N;` names nothing, and a directive left open is never read on
|
|
902
|
+
* into the next, whose `using` or `namespace` is one.
|
|
688
903
|
*/
|
|
689
904
|
identifier() {
|
|
690
905
|
const token = this.next();
|
|
691
|
-
const name = token?.word === undefined ||
|
|
906
|
+
const name = token?.word === undefined || CSHARP_KEYWORDS.has(token.word) ? null : csharpIdentifier(token.word);
|
|
692
907
|
if (name !== null)
|
|
693
908
|
this.index = token.end;
|
|
694
909
|
return name;
|
|
@@ -724,70 +939,213 @@ class CsharpTokens {
|
|
|
724
939
|
return null;
|
|
725
940
|
}
|
|
726
941
|
/**
|
|
727
|
-
* Passes over a type argument list, `<` to the `>` that closes it,
|
|
728
|
-
*
|
|
729
|
-
*
|
|
942
|
+
* Passes over a type argument list, `<` to the `>` that closes it, each
|
|
943
|
+
* argument a type and none a pointer, which the compiler refuses there;
|
|
944
|
+
* false where something else stands. Token by token, so it never reads
|
|
945
|
+
* past a `;`, a brace or the next directive.
|
|
730
946
|
*/
|
|
731
947
|
typeArguments() {
|
|
732
948
|
if (!this.mark('<'))
|
|
733
949
|
return true;
|
|
734
|
-
|
|
735
|
-
if (this.
|
|
736
|
-
depth += 1;
|
|
737
|
-
else if (this.mark('>'))
|
|
738
|
-
depth -= 1;
|
|
739
|
-
else if (this.identifier() === null && !TYPE_MARKS.some((mark) => this.mark(mark)))
|
|
950
|
+
do {
|
|
951
|
+
if (!this.type(false))
|
|
740
952
|
return false;
|
|
953
|
+
} while (this.mark(','));
|
|
954
|
+
return this.mark('>');
|
|
955
|
+
}
|
|
956
|
+
/**
|
|
957
|
+
* Passes over a type, as the specification has one (8.1 and 24.3): a
|
|
958
|
+
* tuple of two or more, a keyword that names a type, a function pointer
|
|
959
|
+
* or a name with its type arguments, then the marks of a nullable, an
|
|
960
|
+
* array and a pointer. A pointer, a function pointer among them, only where
|
|
961
|
+
* `pointers` lets one stand: in an alias that is `unsafe`, and never in a
|
|
962
|
+
* tuple or a type argument. False where the text is none, and where it
|
|
963
|
+
* nests deeper than MAX_EXPANSION, as a Rust use group may not, with
|
|
964
|
+
* `overflow` set: so no type nested twenty thousand deep runs the reader
|
|
965
|
+
* out of stack.
|
|
966
|
+
*/
|
|
967
|
+
type(pointers) {
|
|
968
|
+
if (this.nesting >= MAX_EXPANSION) {
|
|
969
|
+
this.overflow = true;
|
|
970
|
+
return false;
|
|
741
971
|
}
|
|
742
|
-
|
|
972
|
+
this.nesting += 1;
|
|
973
|
+
const read = this.typeWithin(pointers);
|
|
974
|
+
this.nesting -= 1;
|
|
975
|
+
return read;
|
|
743
976
|
}
|
|
744
|
-
|
|
745
|
-
|
|
977
|
+
typeWithin(pointers) {
|
|
978
|
+
let pointer = false;
|
|
979
|
+
// Where no word is next, at the end of the text or before a mark, `''`
|
|
980
|
+
// is none of what a word is compared with, `delegate`, `void` and the
|
|
981
|
+
// type keywords: any other such string for it is an equivalent mutant.
|
|
982
|
+
const word = this.next()?.word ?? '';
|
|
983
|
+
if (this.mark('(')) {
|
|
984
|
+
let elements = 0;
|
|
985
|
+
do {
|
|
986
|
+
if (!this.type(false))
|
|
987
|
+
return false;
|
|
988
|
+
// The name an element may have.
|
|
989
|
+
this.identifier();
|
|
990
|
+
elements += 1;
|
|
991
|
+
} while (this.mark(','));
|
|
992
|
+
if (elements < 2 || !this.mark(')'))
|
|
993
|
+
return false;
|
|
994
|
+
}
|
|
995
|
+
else if (word === 'delegate' || word === 'void') {
|
|
996
|
+
this.keyword(word);
|
|
997
|
+
if (!this.mark('*') || (word === 'delegate' && !this.functionPointer()))
|
|
998
|
+
return false;
|
|
999
|
+
pointer = true;
|
|
1000
|
+
}
|
|
1001
|
+
else if (CSHARP_TYPE_KEYWORDS.has(word)) {
|
|
1002
|
+
this.keyword(word);
|
|
1003
|
+
}
|
|
1004
|
+
else {
|
|
1005
|
+
// Before the names its arguments hold, which reading it put here.
|
|
1006
|
+
const at = this.names.length;
|
|
1007
|
+
const name = this.name(true);
|
|
1008
|
+
if (name === null)
|
|
1009
|
+
return false;
|
|
1010
|
+
this.names.splice(at, 0, name);
|
|
1011
|
+
}
|
|
746
1012
|
for (;;) {
|
|
747
|
-
if (this.mark('?')
|
|
1013
|
+
if (this.mark('?'))
|
|
1014
|
+
continue;
|
|
1015
|
+
if (this.mark('*')) {
|
|
1016
|
+
pointer = true;
|
|
748
1017
|
continue;
|
|
1018
|
+
}
|
|
749
1019
|
if (!this.mark('['))
|
|
750
|
-
return
|
|
1020
|
+
return pointers || !pointer;
|
|
751
1021
|
while (this.mark(','))
|
|
752
1022
|
continue;
|
|
753
1023
|
if (!this.mark(']'))
|
|
754
1024
|
return false;
|
|
755
1025
|
}
|
|
756
1026
|
}
|
|
1027
|
+
/**
|
|
1028
|
+
* What follows `delegate*`: a calling convention or none, then `<`, the
|
|
1029
|
+
* parameters, each `ref`, `ref readonly`, `in` or `out` or none, and the
|
|
1030
|
+
* return type, `void` among them, then `>` (24.3.3).
|
|
1031
|
+
*/
|
|
1032
|
+
functionPointer() {
|
|
1033
|
+
if (this.keyword('unmanaged') && this.mark('[')) {
|
|
1034
|
+
do {
|
|
1035
|
+
if (this.identifier() === null)
|
|
1036
|
+
return false;
|
|
1037
|
+
} while (this.mark(','));
|
|
1038
|
+
if (!this.mark(']'))
|
|
1039
|
+
return false;
|
|
1040
|
+
}
|
|
1041
|
+
else {
|
|
1042
|
+
this.keyword('managed');
|
|
1043
|
+
}
|
|
1044
|
+
if (!this.mark('<'))
|
|
1045
|
+
return false;
|
|
1046
|
+
do {
|
|
1047
|
+
if (this.keyword('ref'))
|
|
1048
|
+
this.keyword('readonly');
|
|
1049
|
+
else if (!this.keyword('in'))
|
|
1050
|
+
this.keyword('out');
|
|
1051
|
+
if (!this.keyword('void') && !this.type(true))
|
|
1052
|
+
return false;
|
|
1053
|
+
} while (this.mark(','));
|
|
1054
|
+
return this.mark('>');
|
|
1055
|
+
}
|
|
757
1056
|
}
|
|
758
1057
|
/**
|
|
759
|
-
* The
|
|
760
|
-
* or null where C# would refuse it:
|
|
1058
|
+
* The namespaces and types a using directive names, read from after
|
|
1059
|
+
* `using`, or null where C# would refuse it:
|
|
761
1060
|
*
|
|
762
1061
|
* using static unsafe? type ; using unsafe? alias = name-or-type ; using namespace ;
|
|
763
1062
|
*
|
|
764
|
-
* `global` before `using` changes nothing of it. A
|
|
765
|
-
*
|
|
1063
|
+
* `global` before `using` changes nothing of it. A type is read whole: a
|
|
1064
|
+
* name, then each type its arguments name, and each element of a tuple; a
|
|
1065
|
+
* keyword that names a type, `int` or `string`, names nothing, and nor does
|
|
1066
|
+
* a tuple element's own name, and an alias of a type that names nothing is
|
|
1067
|
+
* no refusal. A using statement and a using declaration are none of these,
|
|
1068
|
+
* and are read as nothing.
|
|
766
1069
|
*/
|
|
767
1070
|
function usingDirective(tokens) {
|
|
768
1071
|
let target;
|
|
769
1072
|
if (tokens.keyword('static')) {
|
|
770
1073
|
tokens.keyword('unsafe');
|
|
771
|
-
|
|
1074
|
+
const name = tokens.name(true);
|
|
1075
|
+
target = name === null ? null : [name, ...tokens.names];
|
|
772
1076
|
}
|
|
773
1077
|
else {
|
|
774
1078
|
const unsafe = tokens.keyword('unsafe');
|
|
775
1079
|
const start = tokens.index;
|
|
776
1080
|
if (tokens.identifier() !== null && tokens.mark('=')) {
|
|
777
|
-
|
|
778
|
-
//
|
|
779
|
-
if (!tokens.
|
|
1081
|
+
// An alias may name any type (14.6.2), and a pointer of one where it
|
|
1082
|
+
// is `unsafe`.
|
|
1083
|
+
if (!tokens.type(unsafe))
|
|
780
1084
|
return null;
|
|
1085
|
+
target = tokens.names;
|
|
781
1086
|
}
|
|
782
1087
|
else if (unsafe) {
|
|
783
1088
|
return null;
|
|
784
1089
|
}
|
|
785
1090
|
else {
|
|
786
1091
|
tokens.index = start;
|
|
787
|
-
|
|
1092
|
+
const name = tokens.name(false);
|
|
1093
|
+
target = name === null ? null : [name];
|
|
788
1094
|
}
|
|
789
1095
|
}
|
|
790
|
-
|
|
1096
|
+
// A type named twice, `G.L<G.L<int>>`, is one dependency. A refused
|
|
1097
|
+
// directive stays null, not an empty list, which is what says it refused.
|
|
1098
|
+
return tokens.mark(';') && target !== null ? [...new Set(target)] : null;
|
|
1099
|
+
}
|
|
1100
|
+
/**
|
|
1101
|
+
* Whether the tokens after a `using` start a using statement or a using
|
|
1102
|
+
* declaration, which top-level statements may hold where a directive may
|
|
1103
|
+
* stand: `(`, or a type and the name of a local, `using var x = ...;` and
|
|
1104
|
+
* `using N.D? d = null;` among them. Each is a statement the compiler reads
|
|
1105
|
+
* as one, refused or not, and no directive it refuses.
|
|
1106
|
+
*/
|
|
1107
|
+
function startsAStatement(code, from) {
|
|
1108
|
+
const tokens = new CsharpTokens(code, from);
|
|
1109
|
+
return tokens.mark('(') || (tokens.type(true) && tokens.identifier() !== null);
|
|
1110
|
+
}
|
|
1111
|
+
const CSHARP_SPACE_CHARACTER = new RegExp(`[${CSHARP_BLANK}${CSHARP_BREAK}]`, 'u');
|
|
1112
|
+
const CSHARP_GLOBAL_BEFORE = new RegExp(String.raw `(?<![${CSHARP_PART}@\\])global$`, 'u');
|
|
1113
|
+
/**
|
|
1114
|
+
* Whether a `using` at `at` stands where a directive may start: at the head
|
|
1115
|
+
* of the file, after `;` or a brace, or after `global`. Where it follows
|
|
1116
|
+
* anything else, `await` or an `)`, it starts a statement.
|
|
1117
|
+
*/
|
|
1118
|
+
function startsADirective(code, at) {
|
|
1119
|
+
let before = at;
|
|
1120
|
+
while (CSHARP_SPACE_CHARACTER.test(code[before - 1] ?? ''))
|
|
1121
|
+
before -= 1;
|
|
1122
|
+
return before === 0 || ';{}'.includes(code[before - 1]) || CSHARP_GLOBAL_BEFORE.test(code.slice(Math.max(0, before - 7), before));
|
|
1123
|
+
}
|
|
1124
|
+
/** The `#if` or `#endif` a pre-processing line is, or neither. */
|
|
1125
|
+
const CSHARP_CONDITION = new RegExp(String.raw `^[${CSHARP_BLANK}]*#[${CSHARP_BLANK}]*(if|endif)(?![${CSHARP_PART}])`, 'u');
|
|
1126
|
+
/**
|
|
1127
|
+
* The half-open ranges from each outermost `#if` to the end of its `#endif`.
|
|
1128
|
+
* The compiler skips the text of a section whose condition is false and reads
|
|
1129
|
+
* none of it (6.5.5), and which condition holds is the build's to say: so a
|
|
1130
|
+
* directive there is read as one anywhere else, and none is said to be
|
|
1131
|
+
* refused. One left open runs to the end of the file.
|
|
1132
|
+
*/
|
|
1133
|
+
function conditionalRanges(code) {
|
|
1134
|
+
const ranges = [];
|
|
1135
|
+
let depth = 0;
|
|
1136
|
+
let start = 0;
|
|
1137
|
+
CSHARP_DIRECTIVE.lastIndex = 0;
|
|
1138
|
+
let line;
|
|
1139
|
+
while ((line = CSHARP_DIRECTIVE.exec(code)) !== null) {
|
|
1140
|
+
const kind = CSHARP_CONDITION.exec(line[0])?.[1];
|
|
1141
|
+
if (kind === 'if' && depth++ === 0)
|
|
1142
|
+
start = line.index;
|
|
1143
|
+
if (kind === 'endif' && depth > 0 && --depth === 0)
|
|
1144
|
+
ranges.push([start, line.index + line[0].length]);
|
|
1145
|
+
}
|
|
1146
|
+
if (depth > 0)
|
|
1147
|
+
ranges.push([start, code.length]);
|
|
1148
|
+
return ranges;
|
|
791
1149
|
}
|
|
792
1150
|
/**
|
|
793
1151
|
* Reads using directives and the namespaces a file declares, in one pass.
|
|
@@ -808,11 +1166,18 @@ function usingDirective(tokens) {
|
|
|
808
1166
|
* A directive and a declaration are read token by token, as the C#
|
|
809
1167
|
* specification has them (ADR-0008, amended 2026-10-18): white space and
|
|
810
1168
|
* comments between the tokens, an identifier as C# compares two, and what
|
|
811
|
-
* C# refuses read as nothing.
|
|
1169
|
+
* C# refuses read as nothing. Each type a directive names is read whole,
|
|
1170
|
+
* the types its arguments name and a tuple's elements among them, and each
|
|
1171
|
+
* is a reference. What it refuses is said where it starts as a directive:
|
|
1172
|
+
* in no brace but a namespace's, where a directive may start, where no
|
|
1173
|
+
* statement does, and outside every `#if` (amended 2026-10-19).
|
|
812
1174
|
*/
|
|
813
1175
|
function readCsharp(collector) {
|
|
814
1176
|
// A byte order mark at the head of a file is no character of its text.
|
|
815
|
-
const
|
|
1177
|
+
const marked = collector.masked.code.replace(/^\u{feff}/u, ' ');
|
|
1178
|
+
const conditions = conditionalRanges(marked);
|
|
1179
|
+
let condition = 0;
|
|
1180
|
+
const code = marked.replace(CSHARP_DIRECTIVE, (line) => ' '.repeat(line.length));
|
|
816
1181
|
const declared = new Set();
|
|
817
1182
|
const open = [];
|
|
818
1183
|
let fileScoped;
|
|
@@ -851,9 +1216,23 @@ function readCsharp(collector) {
|
|
|
851
1216
|
}
|
|
852
1217
|
}
|
|
853
1218
|
else {
|
|
854
|
-
const
|
|
855
|
-
if
|
|
856
|
-
|
|
1219
|
+
const targets = usingDirective(tokens);
|
|
1220
|
+
// A section starts at the head of its `#if` line and ends at its
|
|
1221
|
+
// `#endif` line's end, where no `using` starts: `<` for either `<=`
|
|
1222
|
+
// is an equivalent mutant.
|
|
1223
|
+
while (condition < conditions.length && conditions[condition][1] <= match.index)
|
|
1224
|
+
condition += 1;
|
|
1225
|
+
const conditional = condition < conditions.length && conditions[condition][0] <= match.index;
|
|
1226
|
+
if (targets !== null) {
|
|
1227
|
+
for (const target of targets)
|
|
1228
|
+
emit(collector, target, 'using', match.index, open.at(-1)?.name ?? fileScoped);
|
|
1229
|
+
}
|
|
1230
|
+
else if (tokens.overflow) {
|
|
1231
|
+
note(collector, 'truncated', match.index, `a using directive nests its types deeper than ${MAX_EXPANSION}`);
|
|
1232
|
+
}
|
|
1233
|
+
else if (!conditional && depth === (open.at(-1)?.depth ?? 0) && startsADirective(code, match.index) && !startsAStatement(code, CSHARP_TOKEN.lastIndex)) {
|
|
1234
|
+
note(collector, 'refused', match.index, 'a directive that starts as a using is one the C# compiler refuses, and no namespace is read from it');
|
|
1235
|
+
}
|
|
857
1236
|
}
|
|
858
1237
|
// On from where reading stopped, so that no token is read twice. No name
|
|
859
1238
|
// is `using` or `namespace`, so no directive is read into the next.
|