feat(highlight): highlight Terraform and HCL files

The grammar is vendored rather than added as a dependency because it only
ships inside an editor extension, and vendoring keeps the highlight
package limited to Lezer primitives without changing the lockfile.

Block labels map to the string role: the upstream labelName tag has no
Paseo syntax role and would render labels uncoloured.
This commit is contained in:
Mohamed Boudra
2026-11-08 13:05:22 +01:00
parent 27872a8e86
commit b03695d787
11 changed files with 661 additions and 1 deletions
@@ -46,6 +46,7 @@ const LANGUAGE_ALIASES: Record<string, string> = {
"objective-c": "m",
markdown: "md",
elixir: "ex",
terraform: "tf",
};
function fenceLanguageToExtension(info: string | null | undefined): string | null {
+2 -1
View File
@@ -5,7 +5,8 @@
"files": [
"dist",
"!dist/**/*.map",
"src/astro/LICENSE"
"src/astro/LICENSE",
"src/hcl/LICENSE"
],
"type": "module",
"main": "./dist/index.js",
@@ -3,6 +3,7 @@ import { setImmediate } from "node:timers/promises";
import { highlightCodeWithBudget, HIGHLIGHT_MAX_SOURCE_BYTES } from "../budgeted-highlighter.js";
import type { HighlightToken } from "../types.js";
import { highlightCode, highlightCodeInSteps, highlightLine } from "../highlighter.js";
import { getParserForFile } from "../parsers.js";
describe("highlightCode", () => {
it("highlights JavaScript code with correct token styles", () => {
@@ -344,6 +345,121 @@ const title = "Hello";
});
});
describe("Terraform", () => {
function styleOf(line: HighlightToken[], text: string) {
return line.find((token) => token.text === text)?.style;
}
it("highlights blocks, labels, attributes, and values", () => {
const code = [
"# The web server",
'resource "aws_instance" "web" {',
" count = 2",
" enabled = true",
' tags = merge(var.labels, { Name = "web" })',
"}",
].join("\n");
const result = highlightCode(code, "main.tf");
expect(result[0][0]).toEqual({ text: "# The web server", style: "comment" });
expect(styleOf(result[1], "resource")).toBe("type");
expect(styleOf(result[1], '"aws_instance"')).toBe("string");
expect(styleOf(result[1], '"web"')).toBe("string");
expect(styleOf(result[2], "count")).toBe("definition");
expect(styleOf(result[2], "2")).toBe("number");
expect(styleOf(result[3], "true")).toBe("literal");
expect(styleOf(result[4], "merge")).toBe("function");
expect(styleOf(result[4], "var")).toBe("variable");
expect(styleOf(result[4], "labels")).toBe("property");
});
it("highlights expressions interpolated inside strings", () => {
const result = highlightCode('name = "app-${var.env}-web"', "main.tf");
expect(result[0].map((token) => [token.text, token.style])).toEqual([
["name", "definition"],
[" ", null],
["=", "operator"],
[" ", null],
['"app-', "string"],
["${", "punctuation"],
["var", "variable"],
[".", "operator"],
["env", "property"],
["}", "punctuation"],
['-web"', "string"],
]);
});
it("highlights heredoc bodies as strings and resumes after the terminator", () => {
const code = ["policy = <<-EOT", ' { "Version": "2012" }', " EOT", "retries = 3"].join("\n");
const result = highlightCode(code, "main.tf");
expect(styleOf(result[0], "EOT")).toBe("keyword");
expect(result[1].map((token) => token.style)).toEqual(["string"]);
expect(styleOf(result[3], "retries")).toBe("definition");
expect(styleOf(result[3], "3")).toBe("number");
});
it("parses a real configuration without error nodes", () => {
const code = [
"terraform {",
' required_version = ">= 1.5"',
' backend "s3" {}',
"}",
"",
'variable "subnets" {',
" type = map(object({ cidr = string }))",
" default = {}",
"}",
"",
"locals {",
" names = [for key, subnet in var.subnets : upper(key) if subnet.cidr != null]",
' first = length(local.names) > 0 ? local.names[0] : "none"',
" arns = aws_instance.web[*].arn",
" /* block comment */",
" path = provider::time::rfc3339_parse(timestamp()) // trailing",
"}",
"",
'data "aws_iam_policy_document" "assume" {',
' dynamic "statement" {',
" for_each = var.subnets",
" content {",
' sid = "${statement.key}-%{ if statement.value.cidr != "" }set%{ else }unset%{ endif }"',
" }",
" }",
"}",
"",
'output "script" {',
" value = <<EOT",
"#!/bin/sh",
"echo ${local.first}",
"EOT",
"}",
].join("\n");
const parser = getParserForFile("main.tf");
if (!parser) throw new Error("Expected a parser for .tf files");
const errors: number[] = [];
parser.parse(code).iterate({
enter(node) {
if (node.type.isError) errors.push(node.from);
},
});
expect(errors.map((from) => code.slice(from, from + 30))).toEqual([]);
});
it("uses the same grammar for tfvars and hcl files", () => {
expect(highlightCode('region = "eu-west-1"', "prod.tfvars")).toEqual(
highlightCode('region = "eu-west-1"', "terragrunt.hcl"),
);
expect(styleOf(highlightLine('region = "eu-west-1"', "prod.tfvars"), "region")).toBe(
"definition",
);
});
});
describe("highlightCodeInSteps", () => {
it("splits a large file into many bounded steps with the same result", () => {
const code = Array.from(
@@ -22,6 +22,9 @@ describe("isLanguageSupported", () => {
expect(isLanguageSupported("test.dart")).toBe(true);
expect(isLanguageSupported("test.cs")).toBe(true);
expect(isLanguageSupported("test.nix")).toBe(true);
expect(isLanguageSupported("main.tf")).toBe(true);
expect(isLanguageSupported("prod.tfvars")).toBe(true);
expect(isLanguageSupported("terragrunt.hcl")).toBe(true);
expect(isLanguageSupported("test.ex")).toBe(true);
expect(isLanguageSupported("Counter.svelte")).toBe(true);
expect(isLanguageSupported("Page.astro")).toBe(true);
@@ -65,6 +68,9 @@ describe("getSupportedExtensions", () => {
expect(extensions).toContain("dart");
expect(extensions).toContain("cs");
expect(extensions).toContain("nix");
expect(extensions).toContain("tf");
expect(extensions).toContain("tfvars");
expect(extensions).toContain("hcl");
expect(extensions).toContain("json");
expect(extensions).toContain("svelte");
expect(extensions).toContain("astro");
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (C) 2021 by Marijn Haverbeke <marijnh@gmail.com> and others
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
+41
View File
@@ -0,0 +1,41 @@
import { styleTags, tags as t } from "@lezer/highlight";
export const hclHighlighting = styleTags({
"for in if else endfor endif": t.controlKeyword,
"Attribute/Identifier": t.definition(t.propertyName),
"ObjectKey/Expression/VariableExpr/Identifier": t.definition(t.propertyName),
"ObjectKey/Expression/LiteralValue/StringLit!": t.definition(t.propertyName),
"ForIntro/Identifier TemplateForStart/Identifier": t.definition(t.variableName),
"BlockType!": t.typeName,
// Paseo themes have no role for labelName; labels render as the strings they are.
"BlockLabel!": t.string,
"FunctionNamespace/Identifier": t.namespace,
"FunctionName/Identifier": t.function(t.variableName),
"VariableExpr/Identifier": t.variableName,
"GetAttr/Identifier": t.propertyName,
Identifier: t.name,
"QuotedTemplateStart QuotedTemplateEnd TemplateLiteral": t.string,
NumericLit: t.number,
"true false": t.bool,
null: t.null,
LineComment: t.lineComment,
BlockComment: t.blockComment,
"( )": t.paren,
"{ }": t.brace,
"[ ]": t.squareBracket,
ArithOp: t.arithmeticOperator,
LogicOp: t.logicOperator,
CompareOp: t.compareOperator,
ControlOp: t.controlOperator,
"ControlOp/Colon": t.controlOperator,
AssignOp: t.definitionOperator,
DerefOp: t.derefOperator,
HeredocIdentifier: t.special(t.controlKeyword),
HeredocStart: t.operator,
"TemplateInterpolationStart TemplateInterpolationEnd": t.special(t.brace),
"TemplateDirectiveStart TemplateDirectiveEnd": t.special(t.brace),
StripMarker: t.special(t.brace),
"AttrSplat FullSplat LegacyIndex": t.derefOperator,
"Arrow Ellipsis": t.punctuation,
"ObjectElem/Colon ForIntro/Colon Comma NamespaceSeparator": t.separator,
});
+61
View File
@@ -0,0 +1,61 @@
import {
delimitedIndent,
foldInside,
foldNodeProp,
indentNodeProp,
LRLanguage,
} from "@codemirror/language";
import type { SyntaxNode } from "@lezer/common";
import { parser } from "./parser.js";
export const hclLanguage = LRLanguage.define({
name: "HCL",
parser: parser.configure({
props: [
indentNodeProp.add({
Block: delimitedIndent({ closing: "}", align: false }),
}),
foldNodeProp.add({
"Object Tuple TemplateFor TemplateIf": foldInside,
BlockComment: (tree: SyntaxNode) => {
return { from: tree.from + 2, to: tree.to - 2 };
},
HeredocTemplate(tree: SyntaxNode) {
const start = tree.getChild("HeredocIdentifier");
if (!start) {
return null;
}
return {
from: start.to,
to: tree.to,
};
},
FunctionCall(tree: SyntaxNode) {
const start = tree.getChild("(");
const end = tree.getChild(")");
if (!start || !end) {
return null;
}
return {
from: start.from + 1,
to: end.to - 1,
};
},
Block(tree: SyntaxNode) {
const start = tree.getChild("{");
const end = tree.getChild("}");
if (!start || !end) {
return null;
}
return {
from: start.from + 1,
to: end.to - 1,
};
},
}),
],
}),
languageData: {
commentTokens: { line: "#", block: { open: "/*", close: "*/" } },
},
});
+64
View File
@@ -0,0 +1,64 @@
// Generated by lezer-generator, taken from codemirror-lang-hcl@0.2.0 (MIT, see LICENSE).
// Vendored for the same reason as packages/highlight/src/nix: only Lezer primitives load here.
import { LRParser } from "@lezer/lr";
import { hclHighlighting } from "./highlight.js";
import {
scanQuotedTemplateStart,
scanQuotedTemplateEnd,
scanTemplateInterpolationStart,
scanTemplateInterpolationEnd,
scanTemplateDirectiveStart,
scanTemplateDirectiveEnd,
scanHeredocIdentifier,
scanTemplateLiteralChunk,
trackTemplate,
} from "./tokens.js";
const spec_Identifier = new Map<string, number>([
["true", 41],
["false", 43],
["null", 47],
["for", 66],
["in", 70],
["endfor", 74],
["if", 80],
["else", 84],
["endif", 88],
]);
export const parser = LRParser.deserialize({
version: 14,
states:
"GxQ]Q&jOOPeO&jOOOmQ&kO'#DcO!kQ&kO'#E_O!yQ&kO'#E^OOQO'#Ea'#EaO#UQ&jO'#ChQOQ&jOOPOOO'#Cd'#CdPOOO)C@`)C@`OOQy'#Co'#CoOOQy'#Cr'#CrO#aQ)vO'#CwOOQy'#Cm'#CmOOQ!b'#D['#D[O#oQ'|O'#DZOOQy'#Cv'#CvO$rQ&kO'#D`O$yQ&jO'#DeO%UQ&jO'#DdO&XQ&kO'#EqO&cQ&kO'#DcOOQy,59},59}O&jQ&jO,59}OOQy'#D]'#D]O(YQ'dO'#DgOOQO'#Ee'#EeO)PQ&jO'#DiO)UQ&jO'#DiOOQy'#Dp'#DpOOQP'#Dx'#DxOOQP'#Dy'#DyO%^Q&kO'#DwO*nQ'dO'#ClOOQy'#Dv'#DvOOQy'#El'#ElO%^Q&kO'#ElOOQy'#Cl'#ClO%^Q&kO,59TO#jQ)`O'#CtOOQP'#E`'#E`OOQP'#Eg'#EgO+eQ&kO,5:xO+pQ&jO,5:xOOQO-E8_-E8_O+xQ+YO'#CuO,ZQ)vO,59`O,iQ&sO'#CxO,sQ)tO'#C{O-OQ)tO'#DSOOQ%n'#Cz'#CzOOQ%n'#Eb'#EbO-ZQ)vO,59cOOQy,59c,59cO-iQ&jO'#DTO-tQ+WO,59uO.SQ&jO'#EpOOQy,59z,59zO._Q&jO,59zO%^Q&kO,5:]O.dQ&jO'#DrOOQP'#E['#E[O%^Q&kO,5:uO.iQ&kO,5:OOOQP'#Ed'#EdO/dQ&kO,5;]O/kQ&kO,5;]O/uQ&kO,5:`OOQy1G/i1G/iOOQO'#Dk'#DkOOQO,5:U,5:UO0pQ&jO'#DlOOQO-E8c-E8cO0xQ&jO,5:TO0}Q&kO,5:TOOQy,5:c,5:cOOQP'#D{'#D{OOQP'#D|'#D|OOQP'#D}'#D}OOQP'#EO'#EOOOQP'#EP'#EPOOQP'#EQ'#EQO%^Q&kO,5:fO%^Q&kO,5:fO%^Q&kO,5:fO%^Q&kO,5:fO%^Q&kO,5:fO%^Q&kO,5:fO%^Q&kO'#ESOOQy'#ER'#ERO1UQ&jO'#EUOOQy'#EX'#EXOOQy'#EY'#EYOOQy'#EW'#EWOOQy,5;W,5;WO1ZQ&jO,5;WO1cQ&jO1G.oO1qQ&lO,59`OOQP-E8e-E8eO1vQ&jO1G0dOOQO1G0d1G0dO1yQ&jO1G0dO+xQ+YO'#EmOOQ%n,59a,59aOOQy1G.z1G.zOOQ%n,59d,59dO2OQ&sO,59dO2YQ&rO,59dOOQ%n,59g,59gO,sQ)tO,59gO2eQ&jO'#DQOOQ%n,59n,59nO2sQ)tO,59nO-OQ)tO,59nO3OQ&jO'#DVOOQ%n-E8`-E8`OOQy1G.}1G.}O3aQ&jO,59hO3fQ&jO,59oO%^Q&kO,59oO3nQ+WO1G/aOOQy1G/a1G/aO3|Q&kO,5;[O4TQ&jO,5;[OOQy1G/f1G/fO4]Q&jO1G/wO4hQ&jO,5:^O4pQ&jO1G0aO4xQ&kO1G/jOOQP,5;O,5;OO5|Q&kO1G0wOOQP-E8b-E8bO7eQ,UO'#DgO7oQ,UO1G/zO9RQ,UO'#ClP9]Q&jO'#DjO9bQ&kO1G/oO9iQ&jO'#DnOOQy1G/o1G/oO9wQ&jO1G/oOOQy1G0Q1G0QO;|Q'dO1G0QO<WQ'dO1G0QO>bQ'dO1G0QO>lQ'dO1G0QO@^Q'dO1G0QOAQQ&jO,5:nOOQy,5:p,5:pPOQy'#Ef'#EfPOQy,5:s,5:sPOQy,5:t,5:tOOQy1G0r1G0rOOQO7+&O7+&OOAYQ&jO7+&OOOQ%n,5;X,5;XOOQ%n1G/O1G/OOA_Q&rO1G/OOAdQ&rO1G/OOOQ%n1G/R1G/ROAoQ&jO,59lOAzQ'[O,59lOOQ%n1G/Y1G/YO2sQ)tO1G/YOBSQ&jO'#DXO2sQ)tO1G/YOBbQ&jO,59qOBpQ'[O,59qOBxQ'[O,59sOCQQ&jO1G/SOCYQ&jO1G/SO%^Q&kO1G/ZOC_Q'[O1G/ZOOQy7+${7+${OCjQ&jO,5:}OC{Q&kO1G0vOOQO-E8a-E8aOOQy7+%c7+%cODSQ&jO7+%cO%^Q&kO'#DsO%^Q&kO1G/xODXQ&jO1G/xOOQP'#E]'#E]O%^Q&kO7+%{P%^Q&kO'#EdO%^Q&kO7+%fOOQy7+%Z7+%ZOD^Q&jO7+%ZODcQ&kO,5:YODjQ&jO,5:YOOQO,5:Y,5:YOOQy1G0Y1G0YPOQy-E8d-E8dOOQO<<Ij<<IjOOQ%n7+$j7+$jODuQ&rO7+$jODzQ'[O1G/WOOQ%n1G/W1G/WOESQ'[O1G/WOOQ%n7+$t7+$tOEXQ&jO,59sO2sQ)tO7+$tOEdQ'[O1G/]OElQ'[O1G/_OOQ$Y1G/]1G/]OEtQ'[O1G/]OOQ%n1G/_1G/_OEyQ'[O1G/_O%^Q&kO7+$nOFOQ&jO7+$nOFTQ&jO7+$nOF]Q'[O7+$uOOQ$Y7+$u7+$uOFhQ'[O7+$uP%^Q&kO'#EcOOQy<<H}<<H}OFmQ&jO,5:_OFxQ&jO7+%dOGQQ&jO7+%dOOQy<<Ig<<IgOGVQ&jO<<IQOHrQ,UO1G0QOJWQ,UO1G0QOKlQ,UO1G0QOMQQ,UO1G0QOMXQ,UO1G0QOOQy<<Hu<<HuOM`Q&kO1G/tOOQO1G/t1G/tOOQ%n<<HU<<HUOOQ%n7+$r7+$rOMgQ'[O7+$rOOQ%n<<H`<<H`OOQ$Y7+$w7+$wOMlQ'[O7+$wOOQ%n7+$y7+$yOMqQ'[O7+$yOMvQ'[O<<HYONRQ&jO<<HYO%^Q&kO<<HYONWQ&jO<<HYOOQ$Y<<Ha<<HaON]Q'[O<<HaOOQP<<IO<<IOO%^Q&kO<<IOOOQyAN>lAN>lONbQ&jOAN>lONgQ&jOAN>lOOQ%n<<H^<<H^OOQ$Y<<Hc<<HcOOQ%n<<He<<HeOOQ$YAN=tAN=tONoQ'[OAN=tO%^Q&kOAN=tONtQ'[OAN=tO! PQ&jOAN=tOOQ$YAN={AN={O! UQ&jOAN>jOOQyG24WG24WO! ^Q&jOG24WOOQ$YG23`G23`O! cQ'[OG23`O! nQ'[OG23`O%^Q&kOG23`OOQPG24UG24UOOQyLD)rLD)rOOQ$YLD(zLD(zO! sQ'[OLD(zO! xQ'[OLD(zOOQ$Y!$'Lf!$'LfO!!TQ'[O!$'LfOOQ$Y!)9BQ!)9BQO!!YQ&kO'#DgO.iQ&kO'#DwO/uQ&kO'#DwO!!|Q&kO'#ClO.iQ&kO,5:fO/uQ&kO,5:fO.iQ&kO,5:fO/uQ&kO,5:fO.iQ&kO,5:fO/uQ&kO,5:fO.iQ&kO,5:fO/uQ&kO,5:fO.iQ&kO,5:fO/uQ&kO,5:fO.iQ&kO,5:fO/uQ&kO,5:fO!#sQ&kO1G0QO!$pQ&kO1G0QO!%mQ&kO1G0QO!(mQ&kO1G0QO!(tQ&kO1G0QO.iQ&kO7+%{O/uQ&kO7+%{O4pQ&jO1G0aO4pQ&jO1G0aO%^Q&kO,5:uO%^Q&kO,5:u",
stateData:
"!)T~O#_OSXPQYPQ~O^RO!UQO~OXWOYWO~OP[O^iOb]OdYOeYOgZO!RaO!TfO!UeO!atO#b^O#c^O#gnO#hoO~O_vOP#RX^#RX!U#RX~OPwO^xO!U{O~O^RO#[[X!T[X~OQ!VOR!POT!WO#]}O~OV!XO~OP[O^iOb]OdYOeYOgZOq!^O!RaO!UeO!atO#b^O#c^O#gnO#hoO~O!Q!ZO~P#tO#w!_O_!XX!Y!XX~O_!aO!Y!aO~OP[O^iOb]OdYOeYOgZO!RaO!UeO!atO#b^O#c^O#gnO#hoO~Or!cO!T#eX~P%^Oq!^O~PmO!T!fO~O#f!gO!R!ZX!w!ZX!y!ZX#g!ZX#i!ZX#j!ZX#k!ZX#l!ZX#m!ZX#n!ZX#o!ZX#p!ZX#q!ZX#r!ZX#s!ZX#t!ZX#u!ZX#v!ZX#w!ZXr!ZX^!ZX!T!ZX~O_!ZX!Y!ZX!a!`X!Q!ZX![!ZX#[!ZXS!ZXm!ZXx!ZX!c!ZXU!ZX~P&oO^!iO~O!a!lO~O!w!{O!y!|O#g!oO#i!nO#j!nO#k!nO#l!oO#m!pO#n!pO#o!pO#p!pO#q!qO#r!qO#s!rO#t!sO#u!}O#v#OO#w`Xr`X^`X!T`X~O!R!zO_`X!Y`X!Q`X![`X#[`XS`Xm`Xx`X!c`XU`X~P)ZOPwO^xO!U#VO~O^RO!T#WO~O#]#YOQ#aPR#aPT#aPV#aP~OQ#[OR#UXT#UX#]#UX~OS#]Om#^O~P%^OR!POT#bO#]}O~OR!POT#fO#]}O~OQ#hOR!POT!WO#]}O~Om#jOq#iOx#kO~OR!POT!WOV#mO#]}O~Or#nO#w!_O!Q#dX~O!Q#pO~O^#rO~OP[O^&yOb]OdYOeYOgZO!RaO!UeO!atO#b^O#c^O#gnO#hoO~O!T#ea~P%^Or#vO!T#ea~P%^OP[O^#xOb]OdYOeYOgZO!RaO!UeO!atO#b^O#c^O#gnO#hoO~O#f!gO!a!`X~O!a#|O~O![$OO~P%^O^$XO~O![$]O#w!_O~O#w!_O^]i#[]i!T]i~OQ#[O~O^RO!T$^O~OS$aOm$bO~P%^OS$aOm$bO#w!_O~Om$eOq#iOu$fOx#kO~OR!POT$iO#]}O~Om$kOq#iOx#kOz$lO|$mO~O^$nO~Oq$oOx$pO~OR!POT!WOV$rO#]}O~O!Q#da~P%^Or$tO!Q#da~Ox$xO!Q$vO#w!_O~Or$zOs$yO~O!Y${O#w!_O~O#w!_OP!Wi^!Wib!Wid!Wie!Wig!Wir!Wi!R!Wi!T!Wi!U!Wi!a!Wi#b!Wi#c!Wi#g!Wi#h!Wi~O!T#ei~P%^O#f!gO!R!ZX!w!ZX!y!ZX#g!ZX#i!ZX#j!ZX#k!ZX#l!ZX#m!ZX#n!ZX#o!ZX#p!ZX#q!ZX#r!ZX#s!ZX#t!ZX#u!ZX#v!ZX#w!ZX~O!a!`X!i!ZX~P6TO!i%OO#w!_O~O!w!{O!y!|O#g!oO#i!nO#j!nO#k!nO#l!oO#m!pO#n!pO#o!pO#p!pO#q!qO#r!qO#s!rO#t!sO#u!}O#v#OO#w`X~O!R!zO!i`X~P7wO#f!gO~O![%PO~P%^Or%RO!c%TO#w!_O![!bX~O![%PO~O#i!nO#j!nO#k!nO_!ni!R!ni!Y!ni!w!ni!y!ni#m!ni#n!ni#o!ni#p!ni#q!ni#r!ni#s!ni#t!ni#u!ni#v!ni#w!nir!ni!Q!ni![!ni^!ni#[!niS!nim!nix!ni!c!niU!ni!T!ni~O#g!ni#l!ni~P9|O#g!oO#l!oO~P9|O#g!oO#i!nO#j!nO#k!nO#l!oO#m!pO#n!pO#o!pO#p!pO_!ni!R!ni!Y!ni!w!ni!y!ni#s!ni#t!ni#u!ni#v!ni#w!nir!ni!Q!ni![!ni^!ni#[!niS!nim!nix!ni!c!niU!ni!T!ni~O#q!ni#r!ni~P<bO#q!qO#r!qO~P<bO#g!oO#i!nO#j!nO#k!nO#l!oO#m!pO#n!pO#o!pO#p!pO#q!qO#r!qO#s!rO!R!ni!w!ni!y!ni#t!ni#u!ni#v!ni#w!nir!ni^!ni!T!ni~O_!ni!Y!ni!Q!ni![!ni#[!niS!nim!nix!ni!c!niU!ni~P>vO!Q%UO#w!_O~O!T%WO~OS%XO~OS%XOm%YO#w!_O~Oq$oOu%ZOx$pO~OU%[Om%]O~Om%_Oq#iOx#kO|$mO~Oq$oOx$pOz%aO|%bO~OU%cOm%dO~OU%eOm%fO~Or%hOs%gO~O^%iO~OU%kOm%lO#w!_O~O#w!_Or#Va!Q#Va![#Va!c#Va~O!Q#di~P%^O!Q%nO~O^%qO~O![%yO~O![!ba~P%^Or%zO!c%{O![!ba~OS%|O~OU%}Om&OO~OU%}O~Oq$oOx$pO|%bO~OU&QOm&RO~OU&SOm&TO~OU&QO~OU&SO~O^&VO~Or&XOs&WO~OU&YOm&ZO#w!_O~OU&YO~O#w!_O!Q!ga!T!ga~O!Y&[O#w!_O~Os&]O~Ox$xO!T&^O!c&`O#w!_O~O#i!nO#j!nO#k!nO!R!ni!w!ni!y!ni#g!ni#l!ni#m!ni#n!ni#o!ni#p!ni#q!ni#r!ni#s!ni#t!ni#u!ni#v!ni#w!ni~O!i!ni~PGeO#g!oO#i!nO#j!nO#k!nO#l!oO!R!ni!w!ni!y!ni#m!ni#n!ni#o!ni#p!ni#q!ni#r!ni#s!ni#t!ni#u!ni#v!ni#w!ni~O!i!ni~PHyO#g!oO#i!nO#j!nO#k!nO#l!oO#m!pO#n!pO#o!pO#p!pO!R!ni!w!ni!y!ni#q!ni#r!ni#s!ni#t!ni#u!ni#v!ni#w!ni~O!i!ni~PJ_O#g!oO#i!nO#j!nO#k!nO#l!oO#m!pO#n!pO#o!pO#p!pO#q!qO#r!qO!R!ni!i!ni!w!ni!y!ni#t!ni#u!ni#v!ni#w!ni~O#s!ni~PKsO#s!rO~PKsO![!bi~P%^OU&aO~OU&bO~OU&cO~OU&dOm&eO#w!_O~Os&fO~O^&hO~OU&iO~O!T&kO~Ox$xO!T&kO~OU&mO~OU&mOm&oO#w!_O~Os&pO~O!Y&qO#w!_O~O!T&rO~OU&sOm&tO#w!_O~OU&sO~OU&vO~OU&vOm&wO#w!_O~OU&xO~OP!ZXb!ZXd!ZXe!ZXg!ZX!U!ZX!a!ZX#b!ZX#c!ZX#h!ZX~P&oOP`Xb`Xd`Xe`Xg`X!R`X!U`X!a`X#b`X#c`X#h`X~P)ZOP!ni^!nib!nid!nie!nig!nir!ni!T!ni!U!ni!a!ni#b!ni#c!ni#h!ni~PGeOP!ni^!nib!nid!nie!nig!nir!ni!T!ni!U!ni!a!ni#b!ni#c!ni#h!ni~PHyOP!ni^!nib!nid!nie!nig!nir!ni!T!ni!U!ni!a!ni#b!ni#c!ni#h!ni~PJ_O#g!oO#i!nO#j!nO#k!nO#l!oO#m!pO#n!pO#o!pO#p!pO#q!qO#r!qOP!ni^!nib!nid!nie!nig!nir!ni!R!ni!T!ni!U!ni!a!ni!w!ni!y!ni#b!ni#c!ni#h!ni#t!ni#u!ni#v!ni#w!ni~O#s!ni~P!&jO#s!rO~P!&jOb!w!y^X#j^~",
goto: "CW#fPPPPPPPP#gPPP#j#sPP#y%xP'YPP'YP(j*O%x*c+sP+s,R,aPPP,oP,R,uP-TP-ZP*c-i%xPP.yPP0Z1n1{P%xP%x2U3g3nP5RP%x5X6i6o5XP%x6x8Y8Y6x9n:W:m;P;`;l;u;zP;uP;u<O<O<T=O=o#s=x>O>S>[>v?Q?WP@kPPPP@qByPPCPCSRXPQVOQ#X{R$_#VXTOU{#V^bQde!c!d#v$}Q!YaQ#RtQ#SvQ#_!PQ#q!]Q#s!`Q#t!aQ#y!eS#}!l#|Q$W!zQ$c#^Q$q#kY$s#n$t%R%m%zQ%j$pQ%o$xQ%p$yU%r$|'`'aQ%s%OQ&U%gQ&g&WQ&j&]Q&n&fQ&u&pQ'b'dR'c'e#msQadeptv!P!]!`!a!c!d!e!l!t!u!v!w!x!y!z#^#k#n#v#|$p$t$x$y$|$}%O%R%g%m%z&W&]&f&p&z&{&}'O'P'Q'R'S'T'U'V'W'X'Y'`'a'd'e#m]Qadeptv!P!]!`!a!c!d!e!l!t!u!v!w!x!y!z#^#k#n#v#|$p$t$x$y$|$}%O%R%g%m%z&W&]&f&p&z&{&}'O'P'Q'R'S'T'U'V'W'X'Y'`'a'd'e#l]Qadeptv!P!]!`!a!c!d!e!l!t!u!v!w!x!y!z#^#k#n#v#|$p$t$x$y$|$}%O%R%g%m%z&W&]&f&p&z&{&}'O'P'Q'R'S'T'U'V'W'X'Y'`'a'd'eTxSzQ!O[f!T!Q!R!U!X#a#d#e#l$h$j%`R#Tw#m`Qadeptv!P!]!`!a!c!d!e!l!t!u!v!w!x!y!z#^#k#n#v#|$p$t$x$y$|$}%O%R%g%m%z&W&]&f&p&z&{&}'O'P'Q'R'S'T'U'V'W'X'Y'`'a'd'ei!T[!Q!R!U!X#a#d#e#l$h$j%`i!S[!Q!R!U!X#a#d#e#l$h$j%`i!Q[!Q!R!U!X#a#d#e#l$h$j%`Q#`!QR$d#ai!R[!Q!R!U!X#a#d#e#l$h$j%`Q#d!RR$j#eQ#c!RS$g#d#eS%^$h$jR&P%`#m_Qadeptv!P!]!`!a!c!d!e!l!t!u!v!w!x!y!z#^#k#n#v#|$p$t$x$y$|$}%O%R%g%m%z&W&]&f&p&z&{&}'O'P'Q'R'S'T'U'V'W'X'Y'`'a'd'e#mhQadeptv!P!]!`!a!c!d!e!l!t!u!v!w!x!y!z#^#k#n#v#|$p$t$x$y$|$}%O%R%g%m%z&W&]&f&p&z&{&}'O'P'Q'R'S'T'U'V'W'X'Y'`'a'd'eQVO#mhQadeptv!P!]!`!a!c!d!e!l!t!u!v!w!x!y!z#^#k#n#v#|$p$t$x$y$|$}%O%R%g%m%z&W&]&f&p&z&{&}'O'P'Q'R'S'T'U'V'W'X'Y'`'a'd'eSdQeS!bd!dV#u!c#v$}_cQde!c!d#v$}#ojQadekptv!P!]!`!a!c!d!e!l!t!u!v!w!x!y!z#^#k#n#v#|$p$t$x$y$|$}%O%R%g%m%z&W&]&f&p&z&{&}'O'P'Q'R'S'T'U'V'W'X'Y'`'a'd'eZ!hi!i#x#{&y#llQadeptv!P!]!`!a!c!d!e!l!t!u!v!w!x!y!z#^#k#n#v#|$p$t$x$y$|$}%O%R%g%m%z&W&]&f&p&z&{&}'O'P'Q'R'S'T'U'V'W'X'Y'`'a'd'eR!kkQ$P!lR%Q#|#mmQadeptv!P!]!`!a!c!d!e!l!t!u!v!w!x!y!z#^#k#n#v#|$p$t$x$y$|$}%O%R%g%m%z&W&]&f&p&z&{&}'O'P'Q'R'S'T'U'V'W'X'Y'`'a'd'eQ!]aR!eeQ$w#qQ&_%sR&l&`#mrQadeptv!P!]!`!a!c!d!e!l!t!u!v!w!x!y!z#^#k#n#v#|$p$t$x$y$|$}%O%R%g%m%z&W&]&f&p&z&{&}'O'P'Q'R'S'T'U'V'W'X'Y'`'a'd'e!vpQadeptv!P!]!`!c!d!l!t!u!v!w!x!y!z#^#k#n#v#|$p$t$x$y$|$}%O%R%g%m%z&W&]&f&p'd'eb&z!a&z&}'P'R'T'V'X'`c&{!e&{'O'Q'S'U'W'Y'a[!tq$R$S$T$U$V[&}&|'Z'[']'^'_]'O#z%t%u%v%w%xY!uq$S$T$U$VY'P&|'[']'^'_Z'Q#z%u%v%w%xW!vq$T$U$VW'R&|']'^'_X'S#z%v%w%xU!wq$U$VU'T&|'^'_V'U#z%w%xS!xq$VS'V&|'_T'W#z%xQ!yqQ'X&|R'Y#zV#Qq#z&|T!{q#zV#Pq#z&|!quQadetv!P!]!`!a!c!d!e!l!z#^#k#n#v#|$p$t$x$y$|$}%O%R%g%m%z&W&]&f&p'`'a'd'e!O!`b!Y#R#S#_#q#s#}$W$c$q$s%j%o%p%s&U&g&j&n&u'b'cQ'd#tR'e#yQ$|#sQ'`'bR'a'cXSOU{#VTySzUUO{#VR|UQ!U[Q#a!QQ#e!R[#g!U#a#e#l$h%`Q#l!XQ$h#dR%`$jQ#o!YS$u#o%SR%S#}Q!ddR#w!d#lkQadeptv!P!]!`!a!c!d!e!l!t!u!v!w!x!y!z#^#k#n#v#|$p$t$x$y$|$}%O%R%g%m%z&W&]&f&p&z&{&}'O'P'Q'R'S'T'U'V'W'X'Y'`'a'd'eR!jkQzSR#Uz!hqQadetv!P!]!`!c!d!l!z#^#k#n#v#|$p$t$x$y$|$}%O%R%g%m%z&W&]&f&p'd'eU!mp&z&{S#z!e'aU$Q!t&}'OQ$R!uQ$S!vQ$T!wQ$U!xQ$V!yQ%t'QQ%u'SQ%v'UQ%w'WQ%x'YS&|!a'`Q'Z'PQ'['RQ']'TQ'^'VR'_'XQ#Z}R$`#YR![aTgQe",
nodeNames:
"⚠ QuotedTemplateStart QuotedTemplateEnd TemplateInterpolationStart TemplateInterpolationEnd TemplateDirectiveStart TemplateDirectiveEnd HeredocIdentifier Comment LineComment BlockComment ConfigFile Body Attribute Identifier AssignOp Expression LiteralValue NumericLit BoolLit true false NullLit null StringLit TemplateLiteral TemplateExpr QuotedTemplate TemplateInterpolation StripMarker TemplateDirective TemplateFor TemplateForStart for Comma in TemplateForEnd endfor TemplateIf TemplateIfIntro if TemplateElseIntro else TemplateIfEnd endif HeredocTemplate HeredocStart CollectionValue ] [ Tuple } { Object ObjectElem ObjectKey Colon VariableExpr ) FunctionCall FunctionNamespace NamespaceSeparator FunctionName ( FunctionArguments Ellipsis ForExpr ForTupleExpr ForIntro ForCond ForObjectExpr Arrow Operation UnaryOperation ArithOp LogicOp BinaryOperation ArithOp ArithOp CompareOp CompareOp LogicOp LogicOp Index NewIndex LegacyIndex GetAttr DerefOp Splat AttrSplat FullSplat Conditional ControlOp ControlOp Block BlockType BlockLabel",
maxTerm: 131,
context: trackTemplate,
nodeProps: [
["openedBy", 48, "[", 51, "{"],
["closedBy", 49, "]", 52, "}"],
],
skippedNodes: [0, 8, 9, 10],
repeatNodeCount: 7,
tokenData:
"-p~RvX^#ipq#iqr$^st$kuv%Svw%Xxy%dyz%iz{%n{|%s|}%x}!O%}!O!P&S!P!Q&z!Q!R(^!R![)l![!]*c!^!_*p!_!`+_!`!a+t!a!b,R!c!},W!}#O,l#P#Q-P#R#S,W#T#o,W#o#p-U#p#q-Z#q#r-f#r#s-k#y#z#i$f$g#i#BY#BZ#i$IS$I_#i$I|$JO#i$JT$JU#i$KV$KW#i&FU&FV#i~#nY#_~X^#ipq#i#y#z#i$f$g#i#BY#BZ#i$IS$I_#i$I|$JO#i$JT$JU#i$KV$KW#i&FU&FV#i~$cP#h~!_!`$f~$kO#r~~$pSX~OY$kZ;'S$k;'S;=`$|<%lO$k~%PP;=`<%l$k~%XO#k~~%[Pvw%_~%dO#s~~%iO!a~~%nO![~~%sO#i~~%xO#l~~%}Or~~&SO#g~~&XR!y~z{&b!O!P&g!Q![&r~&gO#u~~&jP!O!P&m~&rO!c~~&wP!w~!Q![&r~'PQ#j~z{'V!P!Q$k~'YTOz'Vz{'i{;'S'V;'S;=`(W<%lO'V~'lVOz'Vz{'i{!P'V!P!Q(R!Q;'S'V;'S;=`(W<%lO'V~(WOY~~(ZP;=`<%l'V~(cRb~!O!P(l!Q![)l#l#m)w~(oP!Q![(r~(wRb~!Q![(r!g!h)Q#X#Y)Q~)TR{|)^}!O)^!Q![)d~)aP!Q![)d~)iPb~!Q![)d~)qQb~!O!P(l!Q![)l~)zR!Q![*T!c!i*T#T#Z*T~*YRb~!Q![*T!c!i*T#T#Z*T~*hP!Y~![!]*k~*pO#f~~*uQ#o~!^!_*{!_!`+Y~+QP#b~}!O+T~+YO#c~~+_O#p~~+dQ_P!_!`+j!`!a+o~+oO#q~Q+tO!iQ~+yP#m~!_!`+|~,RO#n~~,WO#w~~,]T^~}!O,W!Q![,W!c!},W#R#S,W#T#o,W~,qP!R~z{,t~,wP#P#Q,z~-PO#v~~-UO!Q~~-ZO!U~~-^P#p#q-a~-fO#t~~-kO!T~~-pOm~",
tokenizers: [
scanQuotedTemplateStart,
scanQuotedTemplateEnd,
scanTemplateInterpolationStart,
scanTemplateInterpolationEnd,
scanTemplateDirectiveStart,
scanTemplateDirectiveEnd,
scanHeredocIdentifier,
scanTemplateLiteralChunk,
0,
1,
],
topRules: { ConfigFile: [0, 11] },
dynamicPrecedences: { "19": 1, "22": 1 },
specialized: [{ term: 14, get: (value: string) => spec_Identifier.get(value) ?? -1 }],
propSources: [hclHighlighting],
tokenPrec: 2481,
});
+10
View File
@@ -0,0 +1,10 @@
// Generated by lezer-generator, taken from codemirror-lang-hcl@0.2.0 (MIT, see LICENSE).
// Vendored for the same reason as packages/highlight/src/nix: only Lezer primitives load here.
export const QuotedTemplateStart = 1;
export const QuotedTemplateEnd = 2;
export const TemplateInterpolationStart = 3;
export const TemplateInterpolationEnd = 4;
export const TemplateDirectiveStart = 5;
export const TemplateDirectiveEnd = 6;
export const HeredocIdentifier = 7;
export const templateLiteralChunk = 105;
+334
View File
@@ -0,0 +1,334 @@
// Taken from codemirror-lang-hcl@0.2.0 (MIT, see LICENSE).
// Vendored for the same reason as packages/highlight/src/nix: only Lezer primitives load here.
import { ContextTracker, ExternalTokenizer, type InputStream } from "@lezer/lr";
import {
QuotedTemplateStart,
QuotedTemplateEnd,
TemplateInterpolationStart,
TemplateInterpolationEnd,
TemplateDirectiveStart,
TemplateDirectiveEnd,
HeredocIdentifier,
templateLiteralChunk,
} from "./terms.js";
const ContextType = {
QUOTED_TEMPLATE: 1,
TEMPLATE_INTERPOLATION: 2,
TEMPLATE_DIRECTIVE: 4,
HEREDOC_TEMPLATE: 8,
};
const hashStr = function (str: string) {
var hash = 0,
i,
chr;
if (str.length === 0) return hash;
for (i = 0; i < str.length; i++) {
chr = str.charCodeAt(i);
hash = (hash << 5) - hash + chr;
hash |= 0; // Convert to 32bit integer
}
return hash;
};
class Context {
readonly parent: Context | null;
readonly type: number;
readonly heredoc_identifier: string;
readonly hash: number;
constructor(parent: Context | null, type: number, heredoc_identifier: string) {
this.parent = parent;
this.type = type;
this.heredoc_identifier = heredoc_identifier;
this.hash =
(parent ? (parent.hash + parent.hash) << 8 : 0) +
(type << 4) +
(this.heredoc_identifier === "" ? 0 : hashStr(this.heredoc_identifier));
}
}
function readTokenText(input: InputStream, end: number): string {
let text = "";
for (let offset = 0; offset < end - input.pos; offset++) {
text += String.fromCharCode(input.peek(offset));
}
return text;
}
const topCtx = new Context(null, 0, "");
export const trackTemplate = new ContextTracker({
start: topCtx,
shift(context, term, stack, input) {
if (term === QuotedTemplateStart) {
return new Context(context, ContextType.QUOTED_TEMPLATE, "");
}
if (term === TemplateInterpolationStart) {
return new Context(context, ContextType.TEMPLATE_INTERPOLATION, "");
}
if (term === TemplateDirectiveStart) {
return new Context(context, ContextType.TEMPLATE_DIRECTIVE, "");
}
if (term === HeredocIdentifier && context.type !== ContextType.HEREDOC_TEMPLATE) {
const ctx = new Context(
context,
ContextType.HEREDOC_TEMPLATE,
readTokenText(input, stack.pos),
);
return ctx;
}
if (term === HeredocIdentifier && context.type === ContextType.HEREDOC_TEMPLATE) {
return context.parent ?? context;
}
switch (term) {
case QuotedTemplateEnd:
case TemplateInterpolationEnd:
case TemplateDirectiveEnd:
return context.parent ?? context;
}
return context;
},
hash(context) {
return context.hash;
},
});
const HEX_DIGIT = /[0-9a-fA-F]/;
function scanEscapeSequence(input: InputStream): void {
input.advance();
const kind = String.fromCharCode(input.next);
if (kind === "u" || kind === "U") {
const digits = kind === "u" ? 4 : 8;
for (let i = 0; i < digits; i++) {
input.advance();
if (!HEX_DIGIT.test(String.fromCharCode(input.next))) return;
}
} else if (!'"nrt\\'.includes(kind)) {
return;
}
input.advance();
input.acceptToken(templateLiteralChunk);
}
function scanHeredocBody(input: InputStream, expected: string): void {
let has_leading_whitespace_with_newline = false;
let has_leading_whitespace = false;
let next = String.fromCharCode(input.next);
while (/\s/.test(next)) {
has_leading_whitespace = true;
if (next === "\n") {
has_leading_whitespace_with_newline = true;
}
input.advance();
next = String.fromCharCode(input.next);
}
if (has_leading_whitespace) {
input.acceptToken(templateLiteralChunk);
}
if (!has_leading_whitespace_with_newline) {
return;
}
let advance = 0;
for (let i = 0; i < expected.length; i++) {
if (String.fromCharCode(input.next) === expected[i]) {
input.advance();
advance++;
} else {
if (advance) {
input.acceptToken(templateLiteralChunk);
}
return;
}
}
// check if the identifier is on a line of its own
let i = 0;
while (true) {
const ch = input.peek(i);
if (ch == -1 || String.fromCharCode(ch) === "\n") {
return input.acceptToken(HeredocIdentifier);
}
if (!/\s/.test(String.fromCharCode(ch))) {
return input.acceptToken(templateLiteralChunk);
}
i++;
}
}
export const scanQuotedTemplateStart = new ExternalTokenizer(
(input, stack) => {
if (
stack.canShift(QuotedTemplateStart) &&
!(stack.context && stack.context.type === ContextType.QUOTED_TEMPLATE) &&
String.fromCharCode(input.next) === '"'
) {
input.advance();
return input.acceptToken(QuotedTemplateStart);
}
},
{ contextual: true, fallback: true },
);
export const scanQuotedTemplateEnd = new ExternalTokenizer(
(input, stack) => {
if (
stack.canShift(QuotedTemplateEnd) &&
stack.context &&
stack.context.type === ContextType.QUOTED_TEMPLATE &&
String.fromCharCode(input.next) === '"'
) {
input.advance();
return input.acceptToken(QuotedTemplateEnd);
}
},
{ contextual: true, fallback: true },
);
export const scanTemplateLiteralChunk = new ExternalTokenizer(
(input, stack) => {
if (
stack.canShift(templateLiteralChunk) &&
stack.context &&
stack.context.type === ContextType.QUOTED_TEMPLATE
) {
if (String.fromCharCode(input.next) === "\\") {
return scanEscapeSequence(input);
}
}
if (
stack.canShift(templateLiteralChunk) &&
stack.context &&
(stack.context.type === ContextType.QUOTED_TEMPLATE ||
stack.context.type === ContextType.HEREDOC_TEMPLATE) &&
input.next !== -1
) {
input.advance();
return input.acceptToken(templateLiteralChunk);
}
},
{ contextual: true, fallback: true },
);
export const scanTemplateInterpolationStart = new ExternalTokenizer(
(input, stack) => {
if (
stack.canShift(TemplateInterpolationStart) &&
stack.canShift(templateLiteralChunk) &&
!(stack.context && stack.context.type === ContextType.TEMPLATE_INTERPOLATION) &&
String.fromCharCode(input.next) === "$"
) {
input.advance();
if (String.fromCharCode(input.next) === "{") {
input.advance();
return input.acceptToken(TemplateInterpolationStart);
}
// escape sequence
if (String.fromCharCode(input.next) === "$") {
input.advance();
if (String.fromCharCode(input.next) === "{") {
// $${
input.advance();
return input.acceptToken(templateLiteralChunk);
}
}
return input.acceptToken(templateLiteralChunk);
}
},
{ contextual: true, fallback: true },
);
export const scanTemplateInterpolationEnd = new ExternalTokenizer(
(input, stack) => {
if (
stack.canShift(TemplateInterpolationEnd) &&
stack.context &&
stack.context.type === ContextType.TEMPLATE_INTERPOLATION &&
String.fromCharCode(input.next) === "}"
) {
input.advance();
return input.acceptToken(TemplateInterpolationEnd);
}
},
{ contextual: true, fallback: true },
);
export const scanTemplateDirectiveStart = new ExternalTokenizer(
(input, stack) => {
if (
stack.canShift(TemplateDirectiveStart) &&
stack.canShift(templateLiteralChunk) &&
!(stack.context && stack.context.type === ContextType.TEMPLATE_DIRECTIVE) &&
String.fromCharCode(input.next) === "%"
) {
input.advance();
if (String.fromCharCode(input.next) === "{") {
input.advance();
return input.acceptToken(TemplateDirectiveStart);
}
// escape sequence
if (String.fromCharCode(input.next) === "%") {
input.advance();
if (String.fromCharCode(input.next) === "{") {
// $${
input.advance();
return input.acceptToken(templateLiteralChunk);
}
}
return input.acceptToken(templateLiteralChunk);
}
},
{ contextual: true, fallback: true },
);
export const scanTemplateDirectiveEnd = new ExternalTokenizer(
(input, stack) => {
if (
stack.canShift(TemplateDirectiveEnd) &&
stack.context &&
stack.context.type === ContextType.TEMPLATE_DIRECTIVE &&
String.fromCharCode(input.next) === "}"
) {
input.advance();
return input.acceptToken(TemplateDirectiveEnd);
}
},
{ contextual: true, fallback: true },
);
export const scanHeredocIdentifier = new ExternalTokenizer(
(input, stack) => {
if (
stack.canShift(HeredocIdentifier) &&
!(stack.context && stack.context.type === ContextType.HEREDOC_TEMPLATE) &&
input.next !== -1
) {
let next = String.fromCharCode(input.next);
while (/[0-9a-zA-Z]/.test(next) || next === "_" || next === "-") {
input.advance();
next = String.fromCharCode(input.next);
}
return input.acceptToken(HeredocIdentifier);
}
if (
stack.canShift(HeredocIdentifier) &&
stack.canShift(templateLiteralChunk) &&
stack.context &&
stack.context.type === ContextType.HEREDOC_TEMPLATE &&
input.next != -1
) {
return scanHeredocBody(input, stack.context.heredoc_identifier);
}
},
{ contextual: true, fallback: true },
);
+5
View File
@@ -20,6 +20,7 @@ import { csharpLanguage } from "./csharp/language.js";
import { astroParser } from "./astro/parser.js";
import { vueParser } from "./vue/parser.js";
import { nixLanguage } from "./nix/language.js";
import { hclLanguage } from "./hcl/language.js";
import { parser as svelteBaseParser } from "./svelte/parser.js";
import { configureNesting, defaultNesting } from "./svelte/nesting.js";
@@ -82,6 +83,10 @@ const languagesByExtension: Record<string, Language> = {
cs: csharpLanguage,
// Nix
nix: nixLanguage,
// Terraform / HCL
tf: hclLanguage,
tfvars: hclLanguage,
hcl: hclLanguage,
// Elixir
ex: language(elixirParser),
exs: language(elixirParser),