mirror of
https://github.com/l0ng-ai/openidea.git
synced 2026-11-06 08:07:49 +00:00
feat(angular): highlight directive microsyntax and fold control-flow blocks (#458)
Angular templates left parts of their syntax uncoloured or misparsed, and control-flow blocks could not be folded. Binding names such as (keydown.escape) and [style.width.%] now take one colour and parse; structural directive values are parsed as microsyntax rather than JavaScript statements; block keywords are keywords only where one can appear; ?. is styled like . in templates and in JS and TS files; @if, @for, @switch, @defer and related blocks fold from { to } with the closing brace left visible.
Co-authored-by: nntndfrk <2458463+nntndfrk@users.noreply.github.com>
This commit is contained in:
co-authored by
nntndfrk
parent
971760f858
commit
2613e61ebf
@@ -2,7 +2,8 @@
|
||||
// From @codemirror/lang-angular 0.1.4 (MIT, see LICENSE). Changed by OpenIDEA:
|
||||
// control-flow blocks (`@if`, `@for`, `@switch`, `@defer`, ...) and `@let`
|
||||
// declarations in text, key and animation event names (`(keydown.enter)`,
|
||||
// `(@fade.done)`) and animation bindings (`[@fade]`). Regenerate with
|
||||
// `(@fade.done)`), animation bindings (`[@fade]`) and structural directives'
|
||||
// microsyntax (`*ngFor="let item of items; index as i"`). Regenerate with
|
||||
// `pnpm gen:angular-grammar`.
|
||||
|
||||
@top Content {
|
||||
@@ -22,12 +23,29 @@
|
||||
// `class.active`, `keydown.enter`, and animations' `@fade`, `@fade.done`.
|
||||
bindingName { "@"? Identifier ("." Identifier)* }
|
||||
|
||||
// A structural directive's value: an expression, then `let` declarations,
|
||||
// `as` aliases and keyed expressions (`of items`, `trackBy: byId`). A key's
|
||||
// expression is optional, like Angular's at the end and before `let`.
|
||||
@top Microsyntax {
|
||||
(BindingExpression alias?)? (binding | ";" | ",")*
|
||||
}
|
||||
|
||||
binding {
|
||||
reserved<"let"> VariableDefinition (Is VariableName)? |
|
||||
VariableName alias |
|
||||
BindingKey (":"? BindingExpression alias?)?
|
||||
}
|
||||
|
||||
alias { reserved<"as"> VariableDefinition }
|
||||
|
||||
BindingKey { jsIdentifier }
|
||||
|
||||
// The header of a block, up to its `{`: its body and `}` are content and
|
||||
// HTML elements around it. Both braces are a `Brace`, which CodeMirror
|
||||
// matches like plain text.
|
||||
Block {
|
||||
"@if" BlockParameters<Expression (";" kw<"as"> VariableDefinition)?> Brace |
|
||||
"@else" (kw<"if"> BlockParameters<Expression (";" kw<"as"> VariableDefinition)?>)? Brace |
|
||||
"@if" BlockParameters<Expression (";" alias)?> Brace |
|
||||
"@else" (kw<"if"> BlockParameters<Expression (";" alias)?>)? Brace |
|
||||
"@for" BlockParameters<VariableDefinition kw<"of"> Expression (";" forOption?)*> Brace |
|
||||
("@switch" | "@case") BlockParameters<Expression> Brace |
|
||||
("@default" | "@empty" | "@error") Brace |
|
||||
@@ -41,7 +59,7 @@
|
||||
|
||||
forOption {
|
||||
kw<"track"> Expression |
|
||||
kw<"let"> ForAlias ("," ForAlias)*
|
||||
reserved<"let"> ForAlias ("," ForAlias)*
|
||||
}
|
||||
|
||||
ForAlias { VariableDefinition Is VariableName }
|
||||
@@ -63,7 +81,12 @@
|
||||
|
||||
TriggerName { jsIdentifier }
|
||||
|
||||
kw<term> { @specialize[@name={term}]<jsIdentifier, term> }
|
||||
// Keywords that are also names where a keyword can't be (`@if (x; as
|
||||
// after)`). `let` and `as` never are: in microsyntax, `let x` and `as x`
|
||||
// would also read as a key and its expression.
|
||||
kw<term> { @extend[@name={term}]<jsIdentifier, term> }
|
||||
|
||||
reserved<term> { @specialize[@name={term}]<jsIdentifier, term> }
|
||||
}
|
||||
|
||||
AttributeValue[isolate] {
|
||||
@@ -86,7 +109,7 @@ Interpolation[isolate] {
|
||||
|
||||
@external tokens time from "./tokens" { Time }
|
||||
|
||||
@external tokens expression from "./tokens" { Expression }
|
||||
@external tokens expression from "./tokens" { Expression, BindingExpression }
|
||||
|
||||
@external tokens attrSingle from "./tokens" { attributeContentSingle }
|
||||
@external tokens attrDouble from "./tokens" { attributeContentDouble }
|
||||
@@ -101,7 +124,8 @@ Interpolation[isolate] {
|
||||
|
||||
InterpolationContent { (![}] | "}" ![}]) InterpolationContent? }
|
||||
|
||||
Identifier { (":" | "-" | @asciiLetter | @digit | "_" | $[À-\u{EFFFF}])+ }
|
||||
// `%` for unit bindings: `[style.width.%]`.
|
||||
Identifier { (":" | "-" | "%" | @asciiLetter | @digit | "_" | $[À-\u{EFFFF}])+ }
|
||||
|
||||
jsIdentifier {
|
||||
(@asciiLetter | "_" | "$" | $[À-\u{EFFFF}]) (@asciiLetter | @digit | "_" | "$" | $[À-\u{EFFFF}])*
|
||||
@@ -112,7 +136,7 @@ Interpolation[isolate] {
|
||||
Is { "=" }
|
||||
|
||||
"{{" "}}" "[" "]" "(" ")"
|
||||
"*" "#" "@" ";" ","
|
||||
"*" "#" "@" ";" "," ":"
|
||||
|
||||
"@if" "@else" "@for" "@empty" "@switch" "@case" "@default"
|
||||
"@defer" "@placeholder" "@loading" "@error" "@let"
|
||||
|
||||
@@ -2,7 +2,7 @@ import { describe, expect, it } from "vitest";
|
||||
import { readFileSync } from "node:fs";
|
||||
import { resolve } from "node:path";
|
||||
import { EditorState } from "@codemirror/state";
|
||||
import { ensureSyntaxTree, matchBrackets } from "@codemirror/language";
|
||||
import { ensureSyntaxTree, foldable, matchBrackets } from "@codemirror/language";
|
||||
import { NodeProp, type Tree } from "@lezer/common";
|
||||
import { buildParserFile } from "@lezer/generator";
|
||||
import { highlightTree, tagHighlighter, tags as t } from "@lezer/highlight";
|
||||
@@ -200,12 +200,57 @@ describe("Angular templates", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("highlights key and animation binding names", () => {
|
||||
const doc = `<input (keydown.enter)="save()" (window:resize)="fit()" [@fade]="s" (@fade.done)="end()">`;
|
||||
it("folds a block from its `{` to its `}`", () => {
|
||||
const doc = [
|
||||
"@if (a && b) {",
|
||||
" <p>{{ x }}</p>",
|
||||
"} @else if (c) {",
|
||||
" <ul>",
|
||||
" @for (i of is; track i) {",
|
||||
" <li>{ i }</li>",
|
||||
" }",
|
||||
" </ul>",
|
||||
"} @else {",
|
||||
" none",
|
||||
"}",
|
||||
"@switch (m) {",
|
||||
" @case (1) { one }",
|
||||
" @default {",
|
||||
" other",
|
||||
" }",
|
||||
"}",
|
||||
].join("\n");
|
||||
const state = EditorState.create({ doc, extensions: angular() });
|
||||
stateTree(state);
|
||||
const folds = [];
|
||||
for (let n = 1; n <= state.doc.lines; n++) {
|
||||
const line = state.doc.line(n);
|
||||
const fold = foldable(state, line.from, line.to);
|
||||
if (fold)
|
||||
folds.push([
|
||||
n,
|
||||
state.doc.lineAt(fold.to).number,
|
||||
state.sliceDoc(fold.from - 1, fold.from),
|
||||
state.sliceDoc(fold.to, fold.to + 1),
|
||||
]);
|
||||
}
|
||||
expect(folds).toEqual([
|
||||
[1, 3, "{", "}"],
|
||||
[3, 9, "{", "}"],
|
||||
// The element's own fold.
|
||||
[4, 8, ">", "<"],
|
||||
[5, 7, "{", "}"],
|
||||
[9, 11, "{", "}"],
|
||||
[12, 17, "{", "}"],
|
||||
[14, 16, "{", "}"],
|
||||
]);
|
||||
});
|
||||
|
||||
it("highlights key, unit and animation binding names", () => {
|
||||
const doc = `<input (keydown.enter)="save()" (window:resize)="fit()" [style.width.%]="w" [@fade]="s" (@fade.done)="end()">`;
|
||||
expect(tokens(doc)).toEqual([
|
||||
"input:tag",
|
||||
"keydown:event",
|
||||
"enter:event",
|
||||
"keydown.enter:event",
|
||||
"=:op",
|
||||
'":value',
|
||||
"save:call",
|
||||
@@ -215,15 +260,17 @@ describe("Angular templates", () => {
|
||||
'":value',
|
||||
"fit:call",
|
||||
'":value',
|
||||
"@:attr",
|
||||
"fade:attr",
|
||||
"style.width.%:attr",
|
||||
"=:op",
|
||||
'":value',
|
||||
"w:var",
|
||||
'":value',
|
||||
"@fade:attr",
|
||||
"=:op",
|
||||
'":value',
|
||||
"s:var",
|
||||
'":value',
|
||||
"@:event",
|
||||
"fade:event",
|
||||
"done:event",
|
||||
"@fade.done:event",
|
||||
"=:op",
|
||||
'":value',
|
||||
"end:call",
|
||||
@@ -231,6 +278,168 @@ describe("Angular templates", () => {
|
||||
]);
|
||||
});
|
||||
|
||||
it("highlights structural directives' microsyntax", () => {
|
||||
expect(tokens(`<li *ngFor="let item of items(); index as i; trackBy: byId"></li>`)).toEqual([
|
||||
"li:tag",
|
||||
"*ngFor:keyword",
|
||||
"=:op",
|
||||
'":value',
|
||||
"let:let",
|
||||
"item:def",
|
||||
"of:keyword",
|
||||
"items:call",
|
||||
"index:var",
|
||||
"as:keyword",
|
||||
"i:def",
|
||||
"trackBy:keyword",
|
||||
"byId:var",
|
||||
'":value',
|
||||
"li:tag",
|
||||
]);
|
||||
expect(
|
||||
tokens(
|
||||
`<p *ngIf="user$ | async as user, else loading"></p><b *ngTemplateOutlet="tpl; context: { $implicit: x }"></b>`,
|
||||
),
|
||||
).toEqual([
|
||||
"p:tag",
|
||||
"*ngIf:keyword",
|
||||
"=:op",
|
||||
'":value',
|
||||
"user$:var",
|
||||
"|:op",
|
||||
"async:var",
|
||||
"as:keyword",
|
||||
"user:def",
|
||||
"else:keyword",
|
||||
"loading:var",
|
||||
'":value',
|
||||
"p:tag",
|
||||
"b:tag",
|
||||
"*ngTemplateOutlet:keyword",
|
||||
"=:op",
|
||||
'":value',
|
||||
"tpl:var",
|
||||
"context:keyword",
|
||||
"{:brace",
|
||||
"$implicit:prop",
|
||||
"x:var",
|
||||
"}:brace",
|
||||
'":value',
|
||||
"b:tag",
|
||||
]);
|
||||
expect(tokens(`<i *cdkVirtualFor="let row of rows | slice: 0 : 5; let n = count"></i>`)).toEqual([
|
||||
"i:tag",
|
||||
"*cdkVirtualFor:keyword",
|
||||
"=:op",
|
||||
'":value',
|
||||
"let:let",
|
||||
"row:def",
|
||||
"of:keyword",
|
||||
"rows:var",
|
||||
"|:op",
|
||||
"slice:var",
|
||||
"0:num",
|
||||
"5:num",
|
||||
"let:let",
|
||||
"n:def",
|
||||
"=:op",
|
||||
"count:var",
|
||||
'":value',
|
||||
"i:tag",
|
||||
]);
|
||||
});
|
||||
|
||||
it("ends a directive's expressions at keys without separators", () => {
|
||||
expect(tokens(`<p *ngIf="ok; then yes else no"></p>`)).toEqual([
|
||||
"p:tag",
|
||||
"*ngIf:keyword",
|
||||
"=:op",
|
||||
'":value',
|
||||
"ok:var",
|
||||
"then:keyword",
|
||||
"yes:var",
|
||||
"else:keyword",
|
||||
"no:var",
|
||||
'":value',
|
||||
"p:tag",
|
||||
]);
|
||||
expect(tokens(`<p *ngFor="let a of xs, let i = index"></p>`).slice(4, -2)).toEqual([
|
||||
"let:let",
|
||||
"a:def",
|
||||
"of:keyword",
|
||||
"xs:var",
|
||||
"let:let",
|
||||
"i:def",
|
||||
"=:op",
|
||||
"index:var",
|
||||
]);
|
||||
expect(tokens(`<p *ngFor="let a of xs index as i"></p>`).slice(4, -2)).toEqual([
|
||||
"let:let",
|
||||
"a:def",
|
||||
"of:keyword",
|
||||
"xs:var",
|
||||
"index:var",
|
||||
"as:keyword",
|
||||
"i:def",
|
||||
]);
|
||||
});
|
||||
|
||||
it("parses directive values Angular accepts without errors", () => {
|
||||
const values = [
|
||||
"let a of xs; let i = index; trackBy: byId",
|
||||
"ok; then yes else no",
|
||||
"'a' in obj && x?.y as z; else no",
|
||||
"x, foo",
|
||||
"x; foo let y = z",
|
||||
"tpl; context: { a: [1, 2], b: f(c, d) }",
|
||||
];
|
||||
for (const value of values) {
|
||||
const errors: string[] = [];
|
||||
angularLanguage.parser.parse(`<p *dir="${value}"></p>`).iterate({
|
||||
enter: (node) => void (node.type.isError && errors.push(node.name)),
|
||||
});
|
||||
expect(errors, value).toEqual([]);
|
||||
}
|
||||
});
|
||||
|
||||
it("styles all of a binding's name and value", () => {
|
||||
const doc = `<p *ngIf="a?.b as c" [class.on]="d?.e()" (keydown.shift.enter)="f?.(1)" #ref></p>`;
|
||||
const tree = angularLanguage.parser.parse(doc);
|
||||
const styled = new Uint8Array(doc.length);
|
||||
highlightTree(tree, { style: (tags) => (tags.length ? "styled" : null) }, (from, to) => styled.fill(1, from, to));
|
||||
expect([...doc].filter((ch, i) => !styled[i] && ch != " ").join("")).toBe("");
|
||||
});
|
||||
|
||||
it("parses block keywords as the names they can also be", () => {
|
||||
expect(tokens(`@if (change.after; as after) {} @loading (after 100ms; minimum 1s) {}`)).toEqual([
|
||||
"@if:control",
|
||||
"change:var",
|
||||
".:op",
|
||||
"after:prop",
|
||||
"as:keyword",
|
||||
"after:def",
|
||||
"{:brace",
|
||||
"}:brace",
|
||||
"@loading:keyword",
|
||||
"after:keyword",
|
||||
"100ms:num",
|
||||
"minimum:keyword",
|
||||
"1s:num",
|
||||
"{:brace",
|
||||
"}:brace",
|
||||
]);
|
||||
expect(tokens(`@for (on of track; track on) {}`)).toEqual([
|
||||
"@for:control",
|
||||
"on:def",
|
||||
"of:keyword",
|
||||
"track:var",
|
||||
"track:keyword",
|
||||
"on:var",
|
||||
"{:brace",
|
||||
"}:brace",
|
||||
]);
|
||||
});
|
||||
|
||||
it("keeps HTML's script, style and event handler attribute languages", () => {
|
||||
expect(tokens(`<script>let a = 1;</script><style>p { color: red }</style><b onclick="go(1)">x</b>`)).toEqual([
|
||||
"script:tag",
|
||||
|
||||
@@ -2,17 +2,20 @@
|
||||
* Angular template language: HTML whose text and binding attributes are
|
||||
* Angular syntax (`angular.grammar`), expressions parsed as JavaScript. From
|
||||
* @codemirror/lang-angular 0.1.4 (MIT, see LICENSE); changed by OpenIDEA:
|
||||
* control-flow blocks, `@let`, key and animation binding names, and runs of
|
||||
* text that entities split (`a && b`) parsed as one.
|
||||
* control-flow blocks, `@let`, key and animation binding names, structural
|
||||
* directives' microsyntax, runs of text that entities split (`a && b`)
|
||||
* parsed as one, and folding blocks.
|
||||
*/
|
||||
import { LRLanguage, LanguageSupport } from "@codemirror/language";
|
||||
import { LRLanguage, LanguageSupport, foldService, matchBrackets, syntaxTree } from "@codemirror/language";
|
||||
import { html } from "@codemirror/lang-html";
|
||||
import { javascriptLanguage } from "@codemirror/lang-javascript";
|
||||
import { styleTags, tags as t } from "@lezer/highlight";
|
||||
import { IterMode, parseMixed, type Input, type NestedParse, type SyntaxNode, type SyntaxNodeRef } from "@lezer/common";
|
||||
import { parser } from "./parser";
|
||||
|
||||
const exprParser = javascriptLanguage.parser.configure({ top: "SingleExpression" });
|
||||
// @lezer/javascript styles `.` as a deref operator, but not `?.`.
|
||||
const jsParser = javascriptLanguage.parser.configure({ props: [styleTags({ "?.": t.derefOperator })] });
|
||||
const exprParser = jsParser.configure({ top: "SingleExpression" });
|
||||
|
||||
const baseParser = parser.configure({
|
||||
props: [
|
||||
@@ -23,16 +26,17 @@ const baseParser = parser.configure({
|
||||
"AttributeValue ExpressionAttributeValue StatementAttributeValue": t.attributeValue,
|
||||
Entity: t.character,
|
||||
InvalidEntity: t.invalid,
|
||||
"BoundAttributeName/Identifier BoundAttributeName/@": t.attributeName,
|
||||
"EventName/Identifier EventName/@": t.special(t.attributeName),
|
||||
// A name's own style covers the `.`s between its parts.
|
||||
"BoundAttributeName BoundAttributeName/Identifier BoundAttributeName/@": t.attributeName,
|
||||
"EventName EventName/Identifier EventName/@": t.special(t.attributeName),
|
||||
"ReferenceName/Identifier": t.variableName,
|
||||
"DirectiveName/Identifier": t.keyword,
|
||||
'DirectiveName DirectiveName/Identifier DirectiveName/"*"': t.keyword,
|
||||
"{{ }}": t.special(t.brace),
|
||||
"( )": t.paren,
|
||||
"[ ]": t.bracket,
|
||||
"# '*'": t.punctuation,
|
||||
"# :": t.punctuation,
|
||||
"@if @else @for @empty @switch @case @default if": t.controlKeyword,
|
||||
"@defer @placeholder @loading @error of as track on when prefetch hydrate never minimum after TriggerName":
|
||||
"@defer @placeholder @loading @error of as track on when prefetch hydrate never minimum after TriggerName BindingKey":
|
||||
t.keyword,
|
||||
"@let let": t.definitionKeyword,
|
||||
Brace: t.brace,
|
||||
@@ -45,12 +49,16 @@ const baseParser = parser.configure({
|
||||
});
|
||||
|
||||
const exprMixed = { parser: exprParser },
|
||||
statementMixed = { parser: javascriptLanguage.parser };
|
||||
statementMixed = { parser: jsParser };
|
||||
|
||||
const EXPRESSIONS = new Set(["InterpolationContent", "Expression", "BindingExpression"]);
|
||||
|
||||
const textParser = baseParser.configure({
|
||||
wrap: parseMixed((node) => (node.name == "InterpolationContent" || node.name == "Expression" ? exprMixed : null)),
|
||||
wrap: parseMixed((node) => (EXPRESSIONS.has(node.name) ? exprMixed : null)),
|
||||
});
|
||||
|
||||
const microsyntaxMixed = { parser: textParser.configure({ top: "Microsyntax" }) };
|
||||
|
||||
// To reuse old inner trees, parseMixed takes those that the node at a mount's
|
||||
// start and its ancestors had with the same parser. So each kind of mount has
|
||||
// its own instance: the document's runs would take those of an element at its
|
||||
@@ -58,15 +66,20 @@ const textParser = baseParser.configure({
|
||||
const runsParser = textParser.configure({}),
|
||||
documentRunsParser = textParser.configure({});
|
||||
|
||||
/** The parser of an attribute's interpolation or bound value (`node`). */
|
||||
function attrValueMixed(node: SyntaxNode) {
|
||||
const value = node.parent;
|
||||
if (value?.name != "StatementAttributeValue") return exprMixed;
|
||||
return value.parent?.firstChild?.name == "DirectiveName" ? microsyntaxMixed : statementMixed;
|
||||
}
|
||||
|
||||
const attrParser = baseParser.configure({
|
||||
wrap: parseMixed((node) =>
|
||||
node.name == "InterpolationContent"
|
||||
? exprMixed
|
||||
: node.name != "AttributeInterpolation"
|
||||
? null
|
||||
: node.node.parent?.name == "StatementAttributeValue"
|
||||
? statementMixed
|
||||
: exprMixed,
|
||||
: node.name == "AttributeInterpolation"
|
||||
? attrValueMixed(node.node)
|
||||
: null,
|
||||
),
|
||||
top: "Attribute",
|
||||
});
|
||||
@@ -124,9 +137,27 @@ function mixAngular(node: SyntaxNodeRef, input: Input): NestedParse | null {
|
||||
|
||||
export const angularLanguage = (baseHTML.language as LRLanguage).configure({ wrap: parseMixed(mixAngular) }, "angular");
|
||||
|
||||
/**
|
||||
* The body of the first block opened on a line and closed on a later one, up
|
||||
* to its `}`, which stays visible: `@if (a) {…}`, `} @else {…}`. Its braces
|
||||
* pair like plain text, across elements.
|
||||
*/
|
||||
const blockFolding = foldService.of((state, lineStart, lineEnd) => {
|
||||
const text = state.sliceDoc(lineStart, lineEnd);
|
||||
const tree = syntaxTree(state);
|
||||
for (let i = text.indexOf("{"); i >= 0; i = text.indexOf("{", i + 1)) {
|
||||
const open = tree.resolveInner(lineStart + i, 1);
|
||||
if (open.name != "Brace" || open.parent?.name != "Block") continue;
|
||||
const match = matchBrackets(state, open.from, 1, { brackets: "{}", maxScanDistance: state.doc.length });
|
||||
if (match?.matched && match.end && match.end.from > lineEnd) return { from: open.to, to: match.end.from };
|
||||
}
|
||||
return null;
|
||||
});
|
||||
|
||||
export function angular(): LanguageSupport {
|
||||
return new LanguageSupport(angularLanguage, [
|
||||
baseHTML.support,
|
||||
blockFolding,
|
||||
baseHTML.language.data.of({
|
||||
closeBrackets: { brackets: ["[", "{", '"'] },
|
||||
indentOnInput: /^\s*[}\]]$/,
|
||||
|
||||
@@ -4,35 +4,38 @@ export const
|
||||
Brace = 2,
|
||||
Time = 3,
|
||||
Expression = 4,
|
||||
attributeContentSingle = 80,
|
||||
attributeContentDouble = 81,
|
||||
scriptAttributeContentSingle = 82,
|
||||
scriptAttributeContentDouble = 83,
|
||||
Content = 5,
|
||||
Interpolation = 8,
|
||||
InterpolationContent = 9,
|
||||
Entity = 10,
|
||||
InvalidEntity = 11,
|
||||
Block = 12,
|
||||
as = 18,
|
||||
VariableDefinition = 19,
|
||||
_if = 21,
|
||||
of = 24,
|
||||
track = 25,
|
||||
_let = 26,
|
||||
ForAlias = 27,
|
||||
Is = 28,
|
||||
VariableName = 29,
|
||||
prefetch = 39,
|
||||
hydrate = 40,
|
||||
on = 41,
|
||||
DeferTrigger = 42,
|
||||
TriggerName = 43,
|
||||
when = 44,
|
||||
never = 45,
|
||||
minimum = 49,
|
||||
after = 50,
|
||||
LetDeclaration = 51,
|
||||
Attribute = 53,
|
||||
Identifier = 57,
|
||||
AttributeValue = 69
|
||||
BindingExpression = 5,
|
||||
attributeContentSingle = 85,
|
||||
attributeContentDouble = 86,
|
||||
scriptAttributeContentSingle = 87,
|
||||
scriptAttributeContentDouble = 88,
|
||||
Content = 6,
|
||||
Interpolation = 9,
|
||||
InterpolationContent = 10,
|
||||
Entity = 11,
|
||||
InvalidEntity = 12,
|
||||
Block = 13,
|
||||
as = 19,
|
||||
VariableDefinition = 20,
|
||||
_if = 22,
|
||||
of = 25,
|
||||
track = 26,
|
||||
_let = 27,
|
||||
ForAlias = 28,
|
||||
Is = 29,
|
||||
VariableName = 30,
|
||||
prefetch = 40,
|
||||
hydrate = 41,
|
||||
on = 42,
|
||||
DeferTrigger = 43,
|
||||
TriggerName = 44,
|
||||
when = 45,
|
||||
never = 46,
|
||||
minimum = 50,
|
||||
after = 51,
|
||||
LetDeclaration = 52,
|
||||
Attribute = 54,
|
||||
Identifier = 58,
|
||||
AttributeValue = 70,
|
||||
Microsyntax = 71,
|
||||
BindingKey = 72
|
||||
|
||||
@@ -1,24 +1,24 @@
|
||||
// This file was generated by lezer-generator. You probably shouldn't edit it.
|
||||
import {LRParser} from "@lezer/lr"
|
||||
import {text, brace, time, expression, attrSingle, attrDouble, scriptAttrSingle, scriptAttrDouble} from "./tokens"
|
||||
const spec_jsIdentifier = {__proto__:null,as:36, if:42, of:48, track:50, let:52, prefetch:78, hydrate:80, on:82, when:88, never:90, minimum:98, after:100}
|
||||
const spec_jsIdentifier = {__proto__:null,as:38, if:45, of:51, track:53, let:54, prefetch:81, hydrate:83, on:85, when:91, never:93, minimum:101, after:103}
|
||||
export const parser = LRParser.deserialize({
|
||||
version: 14,
|
||||
states: "4jOVO&mOOO!^Q&jOOO!oO,UO'#CdO!wQ&jO'#ChO!|Q7^O'#ChO#UQ&jO'#ChO#ZQ&jO'#ChO#`Q&lO'#ChO#eQ&lO'#ChO#mQ&lO'#ChO#uQ7[O'#DaOOOR'#Dt'#DtQVO&mOOO#zQMhO'#DdO$VQ&jO'#DiO#}QMhO'#DnO$[Q&jO'#DoOOQO'#Dr'#DrQ$aQ&jOOQ$fQ&jOOQ$kQ&jOOOOO!T,59O,59OO$pO&jO,59OO$uQ&rO'#ClO$zQ&lO,59SOOOR,59S,59SO!wQ&jO,59SO#uQ7[O'#CsO%PQ&rO'#C}O%gQ7[O'#DSO%nQ7[O'#D^OOQO'#Co'#CoO%yQ&jO,59{OOOR-E7r-E7rO&OQ&jO'#EYO&ZQ&jO'#EYO&`Q&jO,5:OO#}QMhO,5:OOOQO,5:T,5:TO&eQ&jO,5:YOOQO,5:Z,5:ZO&jQ&jOOO&jQ&jOOO&rQ&jOOOOO!T1G.j1G.jO&zQ&jO,59WOOOR1G.n1G.nO'SQ&lO1G.nO'XQ7[O,59_O'^Q&jO,59iOOQQ,59n,59nO'cQ&jO,59nO'kQ7[O'#EWO'pQ&rO'#EWOOQO'#EW'#EWO'uQ7[O'#EWOOQQ,59x,59xO(QQ&jO,59xO(YQ&nO'#EXO(_Q&rO1G/gO(dQ&jO'#DzO(iQ&jO,5:tO(iQ&jO,5:tOOQO1G/j1G/jO(tQ&jO1G/jOOQO1G/t1G/tO(yO#tO'#E[O)OO!bO'#E[OOQO'#Dk'#DkQOQ&jOOOOQO'#Dq'#DqO)TO'[O'#DsO)fO&zO'#DsOOQQ1G.r1G.rO)wQ7[O1G.rOOOR7+$Y7+$YO)|Q&rO1G.yOOQQ1G/T1G/TO*RQ7[O'#DxO*]Q&jO1G/YOOQQ1G/Y1G/YOOQO'#DX'#DXO*eQ&jO'#DWO*sQ&jO,5:rOOQO,5:r,5:rO'kQ7[O,5:rO+OQ&rO,5:rO+TQ7[O'#DyO+cQ&jO1G/dOOQQ1G/d1G/dOOQO,5:s,5:sO+kQ&jO7+%ROOQO,5:f,5:fOOQO-E7x-E7xO+pQ&jO1G0`O+{Q&jO7+%UOOOO'#Dl'#DlO,QO&jO,5:vOOOO'#Dm'#DmO,VO&jO,5:vOOOp'#D{'#D{O,[O'[O,5:_OOQO,5:_,5:_OOO`'#D|'#D|O,mO&zO,5:_O#uQ7[O7+$^O-OQ&jO7+$eOOQO,5:d,5:dOOQO-E7v-E7vOOQQ7+$t7+$tO-WQ&vO,59rO'kQ7[O'#DwO-cQ&jO1G0^O-cQ&jO1G0^OOQO1G0^1G0^OOQO,5:e,5:eOOQO-E7w-E7wOOQQ7+%O7+%OOOOR<<Hm<<HmOOQO<<Hp<<HpOOQO1G0b1G0bOOOp-E7y-E7yOOQO1G/y1G/yOOO`-E7z-E7zO-nQ&jO<<GxO-sQ7[O'#DuO.RQ&jO<<HPOOQQ<<HP<<HPOOQO1G/^1G/^O.ZQ&jO1G/^OOQO,5:c,5:cOOQO-E7u-E7uO.`Q&jO7+%xOOQQAN=dAN=dOOQO,5:a,5:aO.kQ&rO'#EVO#uQ7[O'#EVOOQO-E7s-E7sOOQQAN=kAN=kOOQO7+$x7+$xOOQO,5:q,5:qO.pQ&jO'#CwO.uQ&jO,5:qO/QQ7[O,59cO#uQ7[O'#DvO/VQ&jO1G0]OOQO'#Cy'#CyOOQO1G.}1G.}OOQO,5:b,5:bOOQO-E7t-E7t",
|
||||
stateData: "/b~O!wOS~OPZOQZOVQOYZOZZO]ROdSOfTOoUOpUOrVOsVOtVOuWO!OXO!PXO!UYO~O__O!X]O!ZaO!^^O!d`O~OUeOXfO~O_gO~OQiOejO~O_kO~O_lO~OQiO~OQiO_mO~OQiO_nO~O!xoO~O_uO!YsO!ZrO~O!ZvO~O!ZxO~OlyO~OlzO~Ol{O~OU|O~OS}O~OQ!OO~OS!RO~Ow!XOx!XOy!UO|!VO}!WO~O^!SO~P%UO^!YO!R![O!S![O~Ol!]O~O!}!^O![!|X^!|X~O!Z!`O~O![!aO~O^!cO~O#P!dO#Q!eO~O#P!iO#Q!jO~O^!kOa!lO~OQ!mO~Oh!nO~O^!oO~O^!rOa!pO~O!x!sO~OS!vO~Oy!wO|!xO}!vO~O^!{Oa!yO~OR!|O~OS!}O~O!Z#OO~O!}!^O![!|a^!|a~O^#RO~O!u#SO~O!t#UO~OVQOY#WOZ#WO!s#WO#P#YO~OVQOY#ZOZ#ZO!r#ZO#Q#YO~Ob#]O~OS#^O~O^!lXa!lX~P%UO^#aOa!pO~O_#bO^zXazXnzX~On#cO^!zaa!za~OS#fO~O!R![O!S![O^!mXa!mX~O^#iOa!yO~Oa#jO~O!}!^O![!|i^!|i~O![#kO~O#P#lO~O#Q#lO~OVQOY#WOZ#WO!s#WO#P#nO~OVQOY#ZOZ#ZO!r#ZO#Q#nO~O^#sOa#qO~OR#uOS#uO^#tO~On#cO^!zia!zi~O^#yO~Oi#{Oj#|O^!iXa!iX~O^$OOa#qO~O^$PO~On#cO^!zqa!zq~OS$QO~Ol$TO~On$UO^!yaa!ya~O!x$WO~On$UO^!yia!yi~O",
|
||||
goto: "'S#PPPPPPPPP#QPPP#^PPP#bPP#hPPP#uPPP#xP$OPPP$RPPPP$UPPP$X$bPPPP$gPP#^PP$jPPPP$jP$m$p$s$v$vP$y$|%P%S%Y%`%f%p%v%|&W&^PPPPPPPP&d&g&m&sP&|SZO[S#W!i#XT#Z!j#[TZO[QhRR!PjQpYQ!QkQ#p#]T$R#|$URhTQ$S#|R$Y$UR$X$TRhURhWQ!u!UQ#e!wR#v#cV!t!U!w#cRhXRbPR!gyR#T!dR#V!eRcPR!gzRdPR!g{Q[ORq[Q#r#^R#}#rQ$V$SR$Z$VQ#d!uS#w#d#xR#x#eQ!q!TR#`!qQ!z!ZR#h!zQ!_rS#P!_#QR#Q!`Q#X!iR#m#XQ#[!jR#o#[R#z#qQ!TmR#_!pQ!ZnR#g!yQt]Qw_R!buQ!fyR!hz",
|
||||
nodeNames: "⚠ Text Brace Time Expression Content }} {{ Interpolation InterpolationContent Entity InvalidEntity Block @if ) ( BlockParameters ; as VariableDefinition @else if @for BlockParameters of track let ForAlias Is VariableName , @switch @case BlockParameters @default @empty @error @defer BlockParameters prefetch hydrate on DeferTrigger TriggerName when never @placeholder @loading BlockParameters minimum after LetDeclaration @let Attribute BoundAttributeName [ @ Identifier ] ReferenceName # ExpressionAttributeValue AttributeInterpolation AttributeInterpolation EventName DirectiveName * StatementAttributeValue AttributeName AttributeValue",
|
||||
maxTerm: 94,
|
||||
states: "7dOVO&mOOO!^Q&jOOQ!oQ,^OOO#QO7[O'#CeO#YQ&jO'#CiO#_Q,WO'#CiO#gQ&jO'#CiO#lQ&jO'#CiO#qQ&lO'#CiO#vQ&lO'#CiO$OQ&lO'#CiO$WQ,UO'#DbOOOR'#Dx'#DxQVO&mOOO$]QMhO'#DeO$hQ&jO'#DjO$`QMhO'#DoO$mQ&jO'#DpOOQO'#Ds'#DsQ$rQ&jOOQ$wQ&jOOQ$|Q&jOOO%RQ,^O'#CzO%mQ,^O'#EdOOQO'#ER'#ERQ!rQ,UOOQ&UQ,UOOO$WQ,UO'#EdO&gQ,UO'#EdOOO!T,59P,59PO&lO&jO,59PO&qQ&rO'#CmO&vQ&lO,59TOOOR,59T,59TO#YQ&jO,59TO$WQ,UO'#CtO&{Q&rO'#DOO'cQ,UO'#DTO'jQ,UO'#D_OOQO'#Cp'#CpO'uQ&jO,59|OOOR-E7v-E7vO'zQ&jO'#E_O(VQ&jO'#E_O([Q&jO,5:PO$`QMhO,5:POOQO,5:U,5:UO(aQ&jO,5:ZOOQO,5:[,5:[O(fQ&jOOO(fQ&jOOO(nQ&jOOO)XQ,UO,5;OO)`Q&rO,5;OOOQO-E8P-E8PO$WQ,UOOO)eQ,UO,5;OO$WQ,UO,5;OOOO!T1G.k1G.kO)lQ&jO,59XOOOR1G.o1G.oO)tQ&lO1G.oO)yQ,UO,59`O*OQ&jO,59jOOQQ,59o,59oO*TQ&jO,59oO*]Q,UO'#E]O*bQ&rO'#E]OOQO'#E]'#E]O*gQ,UO'#E]OOQQ,59y,59yO*rQ&jO,59yO*zQ&nO'#E^O+PQ&rO1G/hO+UQ&jO'#EOO+ZQ&jO,5:yO+ZQ&jO,5:yOOQO1G/k1G/kO+fQ&jO1G/kOOQO1G/u1G/uO+kO#tO'#EaO+pO!bO'#EaOOQO'#Dl'#DlQOQ&jOOOOQO'#Dr'#DrO+uO'[O'#DtO,WO&zO'#DtO$WQ,UO1G0jO,iQ,UO1G0jQ!rQ,UOOO,}Q,UO1G0jOOQO1G0j1G0jOOQQ1G.s1G.sO-SQ,UO1G.sOOOR7+$Z7+$ZO-XQ&rO1G.zOOQQ1G/U1G/UO-^Q,UO'#D|O-hQ&jO1G/ZOOQQ1G/Z1G/ZOOQO'#DY'#DYO-pQ&jO'#DXO.OQ&jO,5:wOOQO,5:w,5:wO*]Q,UO,5:wO.ZQ&rO,5:wO.`Q,UO'#D}O.nQ&jO1G/eOOQQ1G/e1G/eOOQO,5:x,5:xO.vQ&jO7+%SOOQO,5:j,5:jOOQO-E7|-E7|O.{Q&jO1G0eO/WQ&jO7+%VOOOO'#Dm'#DmO/]O&jO,5:{OOOO'#Dn'#DnO/bO&jO,5:{OOOp'#EP'#EPO/gO'[O,5:`OOQO,5:`,5:`OOO`'#EQ'#EQO/xO&zO,5:`OOQO7+&U7+&UO$WQ,UO7+&UOOQO'#Cz'#CzO$WQ,UO7+$_O0ZQ&jO7+$fOOQO,5:h,5:hOOQO-E7z-E7zOOQQ7+$u7+$uO0cQ&vO,59sO*]Q,UO'#D{O0nQ&jO1G0cO0nQ&jO1G0cOOQO1G0c1G0cOOQO,5:i,5:iOOQO-E7{-E7{OOQQ7+%P7+%POOOR<<Hn<<HnOOQO<<Hq<<HqOOQO1G0g1G0gOOOp-E7}-E7}OOQO1G/z1G/zOOO`-E8O-E8OOOQO<<Ip<<IpO0yQ&jO<<GyO1OQ,UO'#DyO1^Q&jO<<HQOOQQ<<HQ<<HQOOQO1G/_1G/_O1fQ&jO1G/_OOQO,5:g,5:gOOQO-E7y-E7yO1kQ&jO7+%}OOQQAN=eAN=eOOQO,5:e,5:eO1vQ&rO'#E[O$WQ,UO'#E[OOQO-E7w-E7wOOQQAN=lAN=lOOQO7+$y7+$yOOQO,5:v,5:vO1{Q&jO'#CxO2QQ&jO,5:vO,}Q,UO,59dO$WQ,UO'#DzO2]Q&jO1G0bOOQO1G/O1G/OOOQO,5:f,5:fOOQO-E7x-E7x",
|
||||
stateData: "2h~O!|OS~OP[OQ[OWROZ[O[[O^SOeTOgUOpVOqVOsWOtWOuWOvXO!PYO!QYO!VZO~O``O!Y^O![bO!__O!eaO~OTjObhOkkOohO!}fO~OVmOYnO~O`oO~OQqOfrO~O`sO~O`tO~OQqO~OQqO`uO~OQqO`vO~O!}wO~O`}O!Z{O![zO~O![!OO~O![!QO~Om!RO~Om!SO~Om!TO~OT!jXb!jXcnXk!jXo!jX!k!jX!v!jX!}!jX~OT!UO!k!VOb#WXk#WXo#WX!v#WX!}#WX~ObhOc!XOkkOohO!}fO~Oc!ZO~OV![O~OS!]O~OQ!^O~OS!aO~Ox!gOy!gOz!dO}!eO!O!fO~O_!bO~P'QO_!hO!S!jO!T!jO~Om!kO~O#S!lO!]#RX_#RX~O![!nO~O!]!oO~O_!qO~O#U!rO#V!sO~O#U!wO#V!xO~Ob#Wak#Wao#Wa!v#Wa!}#Wa~Oc!yO~P(vOT!zO~Om!|O~P(vO_#OOb#PO~OQ#QO~Oi#RO~O_#SO~O_#VOb#TO~O!}#WO~OS#ZO~Oz#[O}#]O!O#ZO~O_#`Ob#^O~OR#aO~OS#bO~O![#cO~O#S!lO!]#Ra_#Ra~O_#fO~O!z#gO~O!y#iO~OWROZ#kO[#kO!x#kO#U#mO~OWROZ#nO[#nO!w#nO#V#mO~Oc#qOb#Wik#Wio#Wi!v#Wi!}#Wi~O!}#rO~Oc#sO~OS#tO~O_!pXb!pX~P'QO_#wOb#TO~O`#xO_{Xb{Xo{X~Oo#yO_#Pab#Pa~OS#|O~O!S!jO!T!jO_!qXb!qX~O_$POb#^O~Ob$QO~O#S!lO!]#Ri_#Ri~O!]$RO~O#U$SO~O#V$SO~OWROZ#kO[#kO!x#kO#U$UO~OWROZ#nO[#nO!w#nO#V$UO~O_$[Ob$YO~OR$^OS$^O_$]O~Oo#yO_#Pib#Pi~O_$bO~Oj$dOk$eO_!mXb!mX~O_$gOb$YO~O_$hO~Oo#yO_#Pqb#Pq~OS$iO~Om$lO~Oo$mO_#Oab#Oa~Oo$mO_#Oib#Oi~O",
|
||||
goto: "(Y#XPPPPPPPPP#YPPP#fPPP#jPP#pPPP$^PPP$aP$gPPP$sPPPP$vPPP$y%SPPPP%XPP#fPP%[PPPP%[P%_%b%e%h%hP%k%n%qP%tP%z&Q&W&^&h&n&t'O'U'[PPPPPPPP'd'g'm'sP'|PP(SS[O]S#k!w#lT#n!x#oT[O]QpSR!_rQxZQ!YkQ!`sQ!{!XQ!}!ZQ#p!yQ$W#qQ$X#sT$j$e$mRpUQ$k$eR$p$mWlQij!{Q#p!|R$o$lRpVRpXQ#Y!dQ#{#[R$_#yV#X!d#[#yRpYRcPR!u!RR#h!rR#j!sRdPR!u!SRePR!u!TXgQij!{Q]ORy]Q$Z#tR$f$ZQ$n$kR$q$nQ#z#YS$`#z$aR$a#{Q#U!cR#v#UQ#_!iR$O#_Q!mzS#d!m#eR#e!nQ#l!wR$T#lQ#o!xR$V#oUiQj!{R!WiR$c$YQ!cuR#u#TQ!ivR#}#^Q|^Q!P`R!p}Q!t!RR!v!SXhQij!{",
|
||||
nodeNames: "⚠ Text Brace Time Expression BindingExpression Content }} {{ Interpolation InterpolationContent Entity InvalidEntity Block @if ) ( BlockParameters ; as VariableDefinition @else if @for BlockParameters of track let ForAlias Is VariableName , @switch @case BlockParameters @default @empty @error @defer BlockParameters prefetch hydrate on DeferTrigger TriggerName when never @placeholder @loading BlockParameters minimum after LetDeclaration @let Attribute BoundAttributeName [ @ Identifier ] ReferenceName # ExpressionAttributeValue AttributeInterpolation AttributeInterpolation EventName DirectiveName * StatementAttributeValue AttributeName AttributeValue Microsyntax BindingKey :",
|
||||
maxTerm: 100,
|
||||
nodeProps: [
|
||||
["openedBy", 6,"{{",14,"("],
|
||||
["closedBy", 7,"}}",15,")"],
|
||||
["isolate", -4,8,61,67,69,""]
|
||||
["openedBy", 7,"{{",15,"("],
|
||||
["closedBy", 8,"}}",16,")"],
|
||||
["isolate", -4,9,62,68,70,""]
|
||||
],
|
||||
skippedNodes: [0],
|
||||
repeatNodeCount: 9,
|
||||
tokenData: "!'a~R}OX$OXY$yYZ$yZ]$O]^$y^p$Opq$yqr$Ors%vst&^tu&tuv$Ovw(Wwx+jxy,Qyz,hz{-O{|$O|}-f}!O-|!O!P/c!P!Q$O!Q![-|![!]-|!]!^/y!^!_$O!_!`0a!`!b$O!b!c0w!c!}!#Z!}#O!$x#O#P$O#P#Q!%`#Q#R$O#R#S!#Z#S#T$O#T#o!#Z#o#p!%v#p#q$O#q#r!&x#r%W$O%W;'S!#Z;'S;:j!$r;:j;=`$s<%lO!#ZQ$TTXQO#q$O#q#r$d#r;'S$O;'S;=`$s<%lO$OQ$gSO#q$O#r;'S$O;'S;=`$s<%lO$OQ$vP;=`<%l$O_%Q[XQ!w]OX$OXY$yYZ$yZ]$O]^$y^p$Opq$yq#q$O#q#r$d#r;'S$O;'S;=`$s<%lO$OR%}T#PPXQO#q$O#q#r$d#r;'S$O;'S;=`$s<%lO$OR&eT!^PXQO#q$O#q#r$d#r;'S$O;'S;=`$s<%lO$OU&{aXQ!xSOt$Otu&tu!Q$O!QPXXQOp(zpq$Oq!](z!]!^)l!^#q(z#q#r*S#r;'S(z;'S;=`+d<%lO(zR)sTYPXQO#q$O#q#r$d#r;'S$O;'S;=`$s<%lO$OR*VXOp(zpq$Oq!](z!]!^)l!^#q(z#q#r*r#r;'S(z;'S;=`+d<%lO(zP*uUOp*rq!]*r!]!^+X!^;'S*r;'S;=`+^<%lO*rP+^OYPP+aP;=`<%l*rR+gP;=`<%l(zR+qT#QPXQO#q$O#q#r$d#r;'S$O;'S;=`$s<%lO$OZ,XT_XXQO#q$O#q#r$d#r;'S$O;'S;=`$s<%lO$OV,oT^TXQO#q$O#q#r$d#r;'S$O;'S;=`$s<%lO$OR-VT!dPXQO#q$O#q#r$d#r;'S$O;'S;=`$s<%lO$OR-mTnPXQO#q$O#q#r$d#r;'S$O;'S;=`$s<%lO$OZ.Tb!ZXXQO}$O}!O-|!O!Q$O!Q![-|![!]-|!]!c$O!c!}-|!}#R$O#R#S-|#S#T$O#T#o-|#o#q$O#q#r$d#r%W$O%W;'S-|;'S;:j/];:j;=`$s<%lO-|Z/`P;=`<%l-|R/jT!}PXQO#q$O#q#r$d#r;'S$O;'S;=`$s<%lO$OV0QTaTXQO#q$O#q#r$d#r;'S$O;'S;=`$s<%lO$OR0hTXQlPO#q$O#q#r$d#r;'S$O;'S;=`$s<%lO$OZ1Ob!YWXQO#V$O#V#W2W#W#X4`#X#Y9b#Y#Z@T#Z#]$O#]#^Aq#^#`$O#`#aBs#a#d$O#d#eGu#e#g$O#g#hM{#h#q$O#q#r$d#r;'S$O;'S;=`$s<%lO$OR2]VXQO#T$O#T#U2r#U#q$O#q#r$d#r;'S$O;'S;=`$s<%lO$OR2wVXQO#g$O#g#h3^#h#q$O#q#r$d#r;'S$O;'S;=`$s<%lO$OR3cVXQO#X$O#X#Y3x#Y#q$O#q#r$d#r;'S$O;'S;=`$s<%lO$OR4PTpPXQO#q$O#q#r$d#r;'S$O;'S;=`$s<%lO$OR4eVXQO#X$O#X#Y4z#Y#q$O#q#r$d#r;'S$O;'S;=`$s<%lO$OR5PVXQO#Y$O#Y#Z5f#Z#q$O#q#r$d#r;'S$O;'S;=`$s<%lO$OR5kXXQO#T$O#T#U6W#U#X$O#X#Y8`#Y#q$O#q#r$d#r;'S$O;'S;=`$s<%lO$OR6]VXQO#i$O#i#j6r#j#q$O#q#r$d#r;'S$O;'S;=`$s<%lO$OR6wVXQO#`$O#`#a7^#a#q$O#q#r$d#r;'S$O;'S;=`$s<%lO$OR7cVXQO#h$O#h#i7x#i#q$O#q#r$d#r;'S$O;'S;=`$s<%lO$OR8PTrPXQO#q$O#q#r$d#r;'S$O;'S;=`$s<%lO$OR8eVXQO#f$O#f#g8z#g#q$O#q#r$d#r;'S$O;'S;=`$s<%lO$OR9RTuPXQO#q$O#q#r$d#r;'S$O;'S;=`$s<%lO$OR9gYXQO#`$O#`#a:V#a#b;s#b#f$O#f#g={#g#q$O#q#r$d#r;'S$O;'S;=`$s<%lO$OR:[VXQO#g$O#g#h:q#h#q$O#q#r$d#r;'S$O;'S;=`$s<%lO$OR:vVXQO#X$O#X#Y;]#Y#q$O#q#r$d#r;'S$O;'S;=`$s<%lO$OR;dTdPXQO#q$O#q#r$d#r;'S$O;'S;=`$s<%lO$OR;xVXQO#d$O#d#e<_#e#q$O#q#r$d#r;'S$O;'S;=`$s<%lO$OR<dVXQO#h$O#h#i<y#i#q$O#q#r$d#r;'S$O;'S;=`$s<%lO$OR=OVXQO#m$O#m#n=e#n#q$O#q#r$d#r;'S$O;'S;=`$s<%lO$OR=lTsPXQO#q$O#q#r$d#r;'S$O;'S;=`$s<%lO$OR>QVXQO#f$O#f#g>g#g#q$O#q#r$d#r;'S$O;'S;=`$s<%lO$OR>lVXQO#c$O#c#d?R#d#q$O#q#r$d#r;'S$O;'S;=`$s<%lO$OR?WVXQO#f$O#f#g?m#g#q$O#q#r$d#r;'S$O;'S;=`$s<%lO$OR?tTtPXQO#q$O#q#r$d#r;'S$O;'S;=`$s<%lO$OR@YVXQO#c$O#c#d@o#d#q$O#q#r$d#r;'S$O;'S;=`$s<%lO$OR@tVXQO#f$O#f#gAZ#g#q$O#q#r$d#r;'S$O;'S;=`$s<%lO$ORAbTfPXQO#q$O#q#r$d#r;'S$O;'S;=`$s<%lO$ORAvVXQO#Y$O#Y#ZB]#Z#q$O#q#r$d#r;'S$O;'S;=`$s<%lO$ORBdT]PXQO#q$O#q#r$d#r;'S$O;'S;=`$s<%lO$ORBxXXQO#X$O#X#YCe#Y#c$O#c#dDg#d#q$O#q#r$d#r;'S$O;'S;=`$s<%lO$ORCjVXQO#h$O#h#iDP#i#q$O#q#r$d#r;'S$O;'S;=`$s<%lO$ORDWT!UPXQO#q$O#q#r$d#r;'S$O;'S;=`$s<%lO$ORDlVXQO#T$O#T#UER#U#q$O#q#r$d#r;'S$O;'S;=`$s<%lO$OREWVXQO#W$O#W#XEm#X#q$O#q#r$d#r;'S$O;'S;=`$s<%lO$ORErVXQO#]$O#]#^FX#^#q$O#q#r$d#r;'S$O;'S;=`$s<%lO$ORF^VXQO#b$O#b#cFs#c#q$O#q#r$d#r;'S$O;'S;=`$s<%lO$ORFxVXQO#Z$O#Z#[G_#[#q$O#q#r$d#r;'S$O;'S;=`$s<%lO$ORGfT!PPXQO#q$O#q#r$d#r;'S$O;'S;=`$s<%lO$ORGzVXQO#`$O#`#aHa#a#q$O#q#r$d#r;'S$O;'S;=`$s<%lO$ORHfVXQO#T$O#T#UH{#U#q$O#q#r$d#r;'S$O;'S;=`$s<%lO$ORIQVXQO#V$O#V#WIg#W#q$O#q#r$d#r;'S$O;'S;=`$s<%lO$ORIlVXQO#X$O#X#YJR#Y#q$O#q#r$d#r;'S$O;'S;=`$s<%lO$ORJWVXQO#[$O#[#]Jm#]#q$O#q#r$d#r;'S$O;'S;=`$s<%lO$ORJrVXQO#c$O#c#dKX#d#q$O#q#r$d#r;'S$O;'S;=`$s<%lO$ORK^VXQO#`$O#`#aKs#a#q$O#q#r$d#r;'S$O;'S;=`$s<%lO$ORKxVXQO#W$O#W#XL_#X#q$O#q#r$d#r;'S$O;'S;=`$s<%lO$ORLdVXQO#X$O#X#YLy#Y#q$O#q#r$d#r;'S$O;'S;=`$s<%lO$ORMOVXQO#f$O#f#gMe#g#q$O#q#r$d#r;'S$O;'S;=`$s<%lO$ORMlT!OPXQO#q$O#q#r$d#r;'S$O;'S;=`$s<%lO$ORNQVXQO#k$O#k#lNg#l#q$O#q#r$d#r;'S$O;'S;=`$s<%lO$ORNlVXQO#]$O#]#^! R#^#q$O#q#r$d#r;'S$O;'S;=`$s<%lO$OR! WVXQO#h$O#h#i! m#i#q$O#q#r$d#r;'S$O;'S;=`$s<%lO$OR! rVXQO#V$O#V#W!!X#W#q$O#q#r$d#r;'S$O;'S;=`$s<%lO$OR!!^VXQO#[$O#[#]!!s#]#q$O#q#r$d#r;'S$O;'S;=`$s<%lO$OR!!zToPXQO#q$O#q#r$d#r;'S$O;'S;=`$s<%lO$O_!#dd!ZXXQ!xSOt$Otu&tu}$O}!O-|!O!Q$O!Q![!#Z![!]-|!]!c$O!c!}!#Z!}#R$O#R#S!#Z#S#T$O#T#o!#Z#o#q$O#q#r$d#r%W$O%W;'S!#Z;'S;:j!$r;:j;=`$s<%lO!#Z_!$uP;=`<%l!#ZR!%PT!XPXQO#q$O#q#r$d#r;'S$O;'S;=`$s<%lO$OR!%gT![PXQO#q$O#q#r$d#r;'S$O;'S;=`$s<%lO$OR!%{VXQO#o$O#o#p!&b#p#q$O#q#r$d#r;'S$O;'S;=`$s<%lO$OR!&iTVPXQO#q$O#q#r$d#r;'S$O;'S;=`$s<%lO$O~!&{TO#q$O#q#r!'[#r;'S$O;'S;=`$s<%lO$O~!'aOU~",
|
||||
repeatNodeCount: 10,
|
||||
tokenData: "!)R~R}OX$OXY$yYZ$yZ]$O]^$y^p$Opq$yqr$Ors%vst&^tu&tuv(Wvw)swx-Vxy-myz.Tz{.k{|$O|}/R}!O(W!O!P/i!P!Q$O!Q![(W![!]0P!]!^1h!^!_$O!_!`2O!`!b$O!b!c2f!c!}!$x!}#O!&j#O#P$O#P#Q!'Q#Q#R$O#R#S!$x#S#T$O#T#o!$x#o#p!'h#p#q$O#q#r!(j#r%W$O%W;'S!$x;'S;:j!&d;:j;=`$s<%lO!$xS$TTYSO#q$O#q#r$d#r;'S$O;'S;=`$s<%lO$OS$gSO#q$O#r;'S$O;'S;=`$s<%lO$OS$vP;=`<%l$O_%Q[YS!|ZOX$OXY$yYZ$yZ]$O]^$y^p$Opq$yq#q$O#q#r$d#r;'S$O;'S;=`$s<%lO$OT%}T#UPYSO#q$O#q#r$d#r;'S$O;'S;=`$s<%lO$OT&eT!_PYSO#q$O#q#r$d#r;'S$O;'S;=`$s<%lO$OU&{aYS!}QOt$Otu&tu!Q$O!Qm;:j;=`$s<%lO(W])pP;=`<%l(WT)zXYS[POp*gpq$Oq!]*g!]!^$O!^#q*g#q#r+o#r;'S*g;'S;=`-P<%lO*gT*lXYSOp*gpq$Oq!]*g!]!^+X!^#q*g#q#r+o#r;'S*g;'S;=`-P<%lO*gT+`TZPYSO#q$O#q#r$d#r;'S$O;'S;=`$s<%lO$OT+rXOp*gpq$Oq!]*g!]!^+X!^#q*g#q#r,_#r;'S*g;'S;=`-P<%lO*gP,bUOp,_q!],_!]!^,t!^;'S,_;'S;=`,y<%lO,_P,yOZPP,|P;=`<%l,_T-SP;=`<%l*gT-^T#VPYSO#q$O#q#r$d#r;'S$O;'S;=`$s<%lO$O]-tT`XYSO#q$O#q#r$d#r;'S$O;'S;=`$s<%lO$OV.[T_RYSO#q$O#q#r$d#r;'S$O;'S;=`$s<%lO$OT.rT!ePYSO#q$O#q#r$d#r;'S$O;'S;=`$s<%lO$OV/YToRYSO#q$O#q#r$d#r;'S$O;'S;=`$s<%lO$OT/pT#SPYSO#q$O#q#r$d#r;'S$O;'S;=`$s<%lO$O_0Yd!kQm;:j;=`$s<%lO(WV1oTbRYSO#q$O#q#r$d#r;'S$O;'S;=`$s<%lO$OV2VTYSmRO#q$O#q#r$d#r;'S$O;'S;=`$s<%lO$O]2mb!ZWYSO#V$O#V#W3u#W#X5}#X#Y;P#Y#ZAr#Z#]$O#]#^C`#^#`$O#`#aDb#a#d$O#d#eId#e#g$O#g#h! j#h#q$O#q#r$d#r;'S$O;'S;=`$s<%lO$OT3zVYSO#T$O#T#U4a#U#q$O#q#r$d#r;'S$O;'S;=`$s<%lO$OT4fVYSO#g$O#g#h4{#h#q$O#q#r$d#r;'S$O;'S;=`$s<%lO$OT5QVYSO#X$O#X#Y5g#Y#q$O#q#r$d#r;'S$O;'S;=`$s<%lO$OT5nTqPYSO#q$O#q#r$d#r;'S$O;'S;=`$s<%lO$OT6SVYSO#X$O#X#Y6i#Y#q$O#q#r$d#r;'S$O;'S;=`$s<%lO$OT6nVYSO#Y$O#Y#Z7T#Z#q$O#q#r$d#r;'S$O;'S;=`$s<%lO$OT7YXYSO#T$O#T#U7u#U#X$O#X#Y9}#Y#q$O#q#r$d#r;'S$O;'S;=`$s<%lO$OT7zVYSO#i$O#i#j8a#j#q$O#q#r$d#r;'S$O;'S;=`$s<%lO$OT8fVYSO#`$O#`#a8{#a#q$O#q#r$d#r;'S$O;'S;=`$s<%lO$OT9QVYSO#h$O#h#i9g#i#q$O#q#r$d#r;'S$O;'S;=`$s<%lO$OT9nTsPYSO#q$O#q#r$d#r;'S$O;'S;=`$s<%lO$OT:SVYSO#f$O#f#g:i#g#q$O#q#r$d#r;'S$O;'S;=`$s<%lO$OT:pTvPYSO#q$O#q#r$d#r;'S$O;'S;=`$s<%lO$OT;UYYSO#`$O#`#a;t#a#b=b#b#f$O#f#g?j#g#q$O#q#r$d#r;'S$O;'S;=`$s<%lO$OT;yVYSO#g$O#g#h<`#h#q$O#q#r$d#r;'S$O;'S;=`$s<%lO$OT<eVYSO#X$O#X#Y<z#Y#q$O#q#r$d#r;'S$O;'S;=`$s<%lO$OT=RTePYSO#q$O#q#r$d#r;'S$O;'S;=`$s<%lO$OT=gVYSO#d$O#d#e=|#e#q$O#q#r$d#r;'S$O;'S;=`$s<%lO$OT>RVYSO#h$O#h#i>h#i#q$O#q#r$d#r;'S$O;'S;=`$s<%lO$OT>mVYSO#m$O#m#n?S#n#q$O#q#r$d#r;'S$O;'S;=`$s<%lO$OT?ZTtPYSO#q$O#q#r$d#r;'S$O;'S;=`$s<%lO$OT?oVYSO#f$O#f#g@U#g#q$O#q#r$d#r;'S$O;'S;=`$s<%lO$OT@ZVYSO#c$O#c#d@p#d#q$O#q#r$d#r;'S$O;'S;=`$s<%lO$OT@uVYSO#f$O#f#gA[#g#q$O#q#r$d#r;'S$O;'S;=`$s<%lO$OTAcTuPYSO#q$O#q#r$d#r;'S$O;'S;=`$s<%lO$OTAwVYSO#c$O#c#dB^#d#q$O#q#r$d#r;'S$O;'S;=`$s<%lO$OTBcVYSO#f$O#f#gBx#g#q$O#q#r$d#r;'S$O;'S;=`$s<%lO$OTCPTgPYSO#q$O#q#r$d#r;'S$O;'S;=`$s<%lO$OTCeVYSO#Y$O#Y#ZCz#Z#q$O#q#r$d#r;'S$O;'S;=`$s<%lO$OTDRT^PYSO#q$O#q#r$d#r;'S$O;'S;=`$s<%lO$OTDgXYSO#X$O#X#YES#Y#c$O#c#dFU#d#q$O#q#r$d#r;'S$O;'S;=`$s<%lO$OTEXVYSO#h$O#h#iEn#i#q$O#q#r$d#r;'S$O;'S;=`$s<%lO$OTEuT!VPYSO#q$O#q#r$d#r;'S$O;'S;=`$s<%lO$OTFZVYSO#T$O#T#UFp#U#q$O#q#r$d#r;'S$O;'S;=`$s<%lO$OTFuVYSO#W$O#W#XG[#X#q$O#q#r$d#r;'S$O;'S;=`$s<%lO$OTGaVYSO#]$O#]#^Gv#^#q$O#q#r$d#r;'S$O;'S;=`$s<%lO$OTG{VYSO#b$O#b#cHb#c#q$O#q#r$d#r;'S$O;'S;=`$s<%lO$OTHgVYSO#Z$O#Z#[H|#[#q$O#q#r$d#r;'S$O;'S;=`$s<%lO$OTITT!QPYSO#q$O#q#r$d#r;'S$O;'S;=`$s<%lO$OTIiVYSO#`$O#`#aJO#a#q$O#q#r$d#r;'S$O;'S;=`$s<%lO$OTJTVYSO#T$O#T#UJj#U#q$O#q#r$d#r;'S$O;'S;=`$s<%lO$OTJoVYSO#V$O#V#WKU#W#q$O#q#r$d#r;'S$O;'S;=`$s<%lO$OTKZVYSO#X$O#X#YKp#Y#q$O#q#r$d#r;'S$O;'S;=`$s<%lO$OTKuVYSO#[$O#[#]L[#]#q$O#q#r$d#r;'S$O;'S;=`$s<%lO$OTLaVYSO#c$O#c#dLv#d#q$O#q#r$d#r;'S$O;'S;=`$s<%lO$OTL{VYSO#`$O#`#aMb#a#q$O#q#r$d#r;'S$O;'S;=`$s<%lO$OTMgVYSO#W$O#W#XM|#X#q$O#q#r$d#r;'S$O;'S;=`$s<%lO$OTNRVYSO#X$O#X#YNh#Y#q$O#q#r$d#r;'S$O;'S;=`$s<%lO$OTNmVYSO#f$O#f#g! S#g#q$O#q#r$d#r;'S$O;'S;=`$s<%lO$OT! ZT!PPYSO#q$O#q#r$d#r;'S$O;'S;=`$s<%lO$OT! oVYSO#k$O#k#l!!U#l#q$O#q#r$d#r;'S$O;'S;=`$s<%lO$OT!!ZVYSO#]$O#]#^!!p#^#q$O#q#r$d#r;'S$O;'S;=`$s<%lO$OT!!uVYSO#h$O#h#i!#[#i#q$O#q#r$d#r;'S$O;'S;=`$s<%lO$OT!#aVYSO#V$O#V#W!#v#W#q$O#q#r$d#r;'S$O;'S;=`$s<%lO$OT!#{VYSO#[$O#[#]!$b#]#q$O#q#r$d#r;'S$O;'S;=`$s<%lO$OT!$iTpPYSO#q$O#q#r$d#r;'S$O;'S;=`$s<%lO$O_!%ReROV~",
|
||||
tokenizers: [text, brace, time, expression, attrSingle, attrDouble, scriptAttrSingle, scriptAttrDouble, 0, 1, 2, 3],
|
||||
topRules: {"Content":[0,5],"Attribute":[1,53]},
|
||||
specialized: [{term: 86, get: (value: keyof typeof spec_jsIdentifier) => spec_jsIdentifier[value] || -1}],
|
||||
topRules: {"Content":[0,6],"Attribute":[1,54],"Microsyntax":[2,71]},
|
||||
specialized: [{term: 91, get: (value: keyof typeof spec_jsIdentifier) => spec_jsIdentifier[value] || -1}],
|
||||
tokenPrec: 0
|
||||
})
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
/**
|
||||
* External tokens of `angular.grammar`. From @codemirror/lang-angular 0.1.4
|
||||
* (MIT, see LICENSE); text stopping at blocks, and the block tokens, added by
|
||||
* OpenIDEA.
|
||||
* (MIT, see LICENSE); text stopping at blocks, the block tokens and
|
||||
* microsyntax expressions added by OpenIDEA.
|
||||
*/
|
||||
import { ExternalTokenizer, type InputStream } from "@lezer/lr";
|
||||
import {
|
||||
BindingExpression,
|
||||
Brace,
|
||||
Expression,
|
||||
Text,
|
||||
@@ -22,7 +23,9 @@ const NEWLINE = 10,
|
||||
SINGLE_QUOTE = 39,
|
||||
PAREN_L = 40,
|
||||
PAREN_R = 41,
|
||||
COMMA = 44,
|
||||
DOT = 46,
|
||||
COLON = 58,
|
||||
SEMICOLON = 59,
|
||||
AT = 64,
|
||||
BRACKET_L = 91,
|
||||
@@ -33,6 +36,12 @@ const NEWLINE = 10,
|
||||
BRACE_L = 123,
|
||||
BRACE_R = 125;
|
||||
|
||||
/** Characters ending an operand outside an identifier: brackets and quotes. */
|
||||
const CLOSERS = new Set([PAREN_R, BRACKET_R, BRACE_R, SINGLE_QUOTE, DOUBLE_QUOTE, BACKTICK]);
|
||||
|
||||
/** Words that are operators rather than operands, unless after a `.`. */
|
||||
const OPERATORS = new Set(["in", "instanceof", "new", "typeof", "void"]);
|
||||
|
||||
const BLOCKS = new Set([
|
||||
"if",
|
||||
"else",
|
||||
@@ -130,44 +139,75 @@ function blockOnNextLine(input: InputStream): boolean {
|
||||
return input.peek(i) == AT && blockAt(input, i);
|
||||
}
|
||||
|
||||
/** The word at offset `i`: `as` in `index as i`, `let` in `let x`. */
|
||||
function wordAt(input: InputStream, i: number): string {
|
||||
let word = "";
|
||||
for (let ch = input.peek(i); isIdentifierChar(ch); ch = input.peek(++i)) word += String.fromCharCode(ch);
|
||||
return word;
|
||||
}
|
||||
|
||||
/**
|
||||
* A block parameter's or `@let`'s expression: up to a `;` or a closing bracket
|
||||
* outside brackets and strings, without the spaces around it. An unterminated
|
||||
* one (being typed) also ends before a child element or comment and before a
|
||||
* line starting with a block, so the blocks after it keep their highlighting.
|
||||
* A structural directive's (`BindingExpression`) also ends at a `,`, an `as`
|
||||
* alias or a word after an operand, the next key (`cond; then a else b`), and
|
||||
* is not one starting with `let`, `as` or a key's `:`.
|
||||
*/
|
||||
export const expression = new ExternalTokenizer((input) => {
|
||||
if (isSpace(input.next)) return;
|
||||
const start = input.pos;
|
||||
let depth = 0,
|
||||
quote = 0,
|
||||
escaped = false,
|
||||
end = start;
|
||||
for (;;) {
|
||||
const ch = input.next;
|
||||
if (ch < 0 || (ch == NEWLINE && blockOnNextLine(input))) break;
|
||||
if (escaped) {
|
||||
escaped = false;
|
||||
} else if (quote) {
|
||||
// Strings but template literals end with their line, like in JavaScript.
|
||||
if (ch == quote || (ch == NEWLINE && quote != BACKTICK)) quote = 0;
|
||||
else escaped = ch == BACKSLASH;
|
||||
} else if (ch == SINGLE_QUOTE || ch == DOUBLE_QUOTE || ch == BACKTICK) {
|
||||
quote = ch;
|
||||
} else if (ch == PAREN_L || ch == BRACKET_L || ch == BRACE_L) {
|
||||
depth++;
|
||||
} else if (ch == PAREN_R || ch == BRACKET_R || ch == BRACE_R) {
|
||||
if (depth == 0) break;
|
||||
depth--;
|
||||
} else if (ch == SEMICOLON && depth == 0) {
|
||||
break;
|
||||
export const expression = new ExternalTokenizer(
|
||||
(input, stack) => {
|
||||
if (isSpace(input.next)) return;
|
||||
const binding = stack.canShift(BindingExpression);
|
||||
if (binding && (input.next == COLON || /^(let|as)$/.test(wordAt(input, 0)))) return;
|
||||
const start = input.pos;
|
||||
let depth = 0,
|
||||
quote = 0,
|
||||
escaped = false,
|
||||
end = start,
|
||||
prev = -1,
|
||||
// In a word of `OPERATORS`.
|
||||
operator = false,
|
||||
// The last character outside spaces ends an operand: `a`, `)`, `'`.
|
||||
operand = false;
|
||||
for (;;) {
|
||||
const ch = input.next;
|
||||
if (ch < 0 || (ch == NEWLINE && blockOnNextLine(input))) break;
|
||||
if (escaped) {
|
||||
escaped = false;
|
||||
} else if (quote) {
|
||||
// Strings but template literals end with their line, like in JavaScript.
|
||||
if (ch == quote || (ch == NEWLINE && quote != BACKTICK)) quote = 0;
|
||||
else escaped = ch == BACKSLASH;
|
||||
} else if (ch == SINGLE_QUOTE || ch == DOUBLE_QUOTE || ch == BACKTICK) {
|
||||
quote = ch;
|
||||
} else if (ch == PAREN_L || ch == BRACKET_L || ch == BRACE_L) {
|
||||
depth++;
|
||||
} else if (ch == PAREN_R || ch == BRACKET_R || ch == BRACE_R) {
|
||||
if (depth == 0) break;
|
||||
depth--;
|
||||
} else if (depth == 0 && (ch == SEMICOLON || (binding && ch == COMMA))) {
|
||||
break;
|
||||
} else if (binding && isIdentifierStart(ch) && !isIdentifierChar(prev)) {
|
||||
const word = wordAt(input, 0);
|
||||
// After a space, an alias or, after an operand, the next binding's
|
||||
// key: `user$ | async as user`, `items index as i`.
|
||||
if (depth == 0 && end < input.pos && (word == "as" || (operand && !OPERATORS.has(word)))) break;
|
||||
operator = prev != DOT && OPERATORS.has(word);
|
||||
}
|
||||
if (!isSpace(ch)) {
|
||||
end = input.pos + 1;
|
||||
operand = !quote && (CLOSERS.has(ch) || (isIdentifierChar(ch) && !operator));
|
||||
}
|
||||
if (!isIdentifierChar(ch)) operator = false;
|
||||
prev = ch;
|
||||
if (!step(input)) break;
|
||||
}
|
||||
if (!isSpace(ch)) end = input.pos + 1;
|
||||
if (!step(input)) break;
|
||||
}
|
||||
// `end` is a document position: the stream skips the ranges' gaps.
|
||||
if (end > start) input.acceptTokenTo(Expression, end);
|
||||
});
|
||||
// `end` is a document position: the stream skips the ranges' gaps.
|
||||
if (end > start) input.acceptTokenTo(binding ? BindingExpression : Expression, end);
|
||||
},
|
||||
{ contextual: true },
|
||||
);
|
||||
|
||||
function attrContent(quote: number, token: number, script: boolean) {
|
||||
return new ExternalTokenizer((input) => {
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { highlightTree, tagHighlighter, tags as t } from "@lezer/highlight";
|
||||
import { findLanguageDescription, loadSupport } from "./languages";
|
||||
|
||||
describe("language modes", () => {
|
||||
it("styles `?.` like `.` in JavaScript and TypeScript", async () => {
|
||||
const highlighter = tagHighlighter([{ tag: t.derefOperator, class: "deref" }]);
|
||||
for (const [mode, path] of [
|
||||
["JavaScript", "/p/a.js"],
|
||||
["TypeScript", "/p/a.ts"],
|
||||
["JSX", "/p/a.jsx"],
|
||||
["TSX", "/p/a.tsx"],
|
||||
] as const) {
|
||||
const support = await loadSupport(findLanguageDescription(mode, path)!);
|
||||
const doc = "a?.b.c?.();";
|
||||
const derefs: string[] = [];
|
||||
highlightTree(support!.language.parser.parse(doc), highlighter, (from, to) => derefs.push(doc.slice(from, to)));
|
||||
expect(derefs, mode).toEqual(["?.", ".", "?."]);
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -200,8 +200,15 @@ const CSS_TAGS: Parameters<typeof styleTags>[0] = {
|
||||
"ClassName IdName PseudoClassName ColorLiteral CallTag": t.tagName,
|
||||
};
|
||||
|
||||
// @lezer/javascript styles `.` as a deref operator, but not `?.`.
|
||||
const JS_TAGS: Parameters<typeof styleTags>[0] = { "?.": t.derefOperator };
|
||||
|
||||
/** Highlighting the stock grammars lack or colour unlike IntelliJ. */
|
||||
const EXTRA_TAGS: Record<string, Parameters<typeof styleTags>[0]> = {
|
||||
JavaScript: JS_TAGS,
|
||||
TypeScript: JS_TAGS,
|
||||
JSX: JS_TAGS,
|
||||
TSX: JS_TAGS,
|
||||
// @lezer/java tags annotations as plain identifiers.
|
||||
Java: { "MarkerAnnotation/Identifier Annotation/Identifier MarkerAnnotation Annotation": t.annotation },
|
||||
CSS: CSS_TAGS,
|
||||
|
||||
Reference in New Issue
Block a user