@vaadin/hilla-generator-utils 25.2.0-alpha7 → 25.2.0-alpha9

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vaadin/hilla-generator-utils",
3
- "version": "25.2.0-alpha7",
3
+ "version": "25.2.0-alpha9",
4
4
  "description": "A set of utils for developing Hilla generator plugins",
5
5
  "main": "index.js",
6
6
  "type": "module",
@@ -29,7 +29,8 @@
29
29
  "./createSourceFile.js": "./createSourceFile.js",
30
30
  "./memoize.js": "./memoize.js",
31
31
  "./LoggerFactory.js": "./LoggerFactory.js",
32
- "./PluginError.js": "./PluginError.js"
32
+ "./PluginError.js": "./PluginError.js",
33
+ "./tsc-template.js": "./tsc-template.js"
33
34
  },
34
35
  "repository": {
35
36
  "type": "git",
@@ -0,0 +1,10 @@
1
+ import { type Node, type SourceFile, type TransformerFactory, type VisitResult } from "typescript";
2
+ export type Transformer = (node: Node) => VisitResult<Node | undefined>;
3
+ export declare function createTransformer(transformer: Transformer): TransformerFactory<SourceFile>;
4
+ declare const $templateResult: unique symbol;
5
+ export type TemplateResult = Readonly<{
6
+ brand: typeof $templateResult
7
+ node: Node
8
+ source: SourceFile
9
+ }>;
10
+ export default function ast(parts: TemplateStringsArray, ...fillers: ReadonlyArray<TemplateResult | Node | string | null | undefined>): TemplateResult;
@@ -0,0 +1,72 @@
1
+ import { createSourceFile, forEachChild, isIdentifier, ScriptKind, ScriptTarget, transform, visitEachChild } from "typescript";
2
+ export function createTransformer(transformer) {
3
+ return (context) => (root) => {
4
+ const visitor = (node) => {
5
+ const transformed = transformer(node);
6
+ if (transformed !== node) {
7
+ return transformed;
8
+ }
9
+ return visitEachChild(transformed, visitor, context);
10
+ };
11
+ return visitEachChild(root, visitor, context);
12
+ };
13
+ }
14
+ const $templateResult = Symbol("TemplateResult");
15
+ function isTemplateResult(value) {
16
+ return typeof value === "object" && value !== null && "brand" in value && value.brand === $templateResult;
17
+ }
18
+ const START_MARKER = /\/\*\*\s*@START\s*\*\//iu;
19
+ const END_MARKER = /\/\*\*\s*@END\s*\*\//iu;
20
+ function extractCodePart(str) {
21
+ const startMatch = START_MARKER.exec(str);
22
+ const afterStart = startMatch ? str.slice(startMatch.index + startMatch[0].length) : str;
23
+ const endMatch = END_MARKER.exec(afterStart);
24
+ const codePart = endMatch ? afterStart.slice(0, endMatch.index) : afterStart;
25
+ return codePart.trim();
26
+ }
27
+ function isInjectedNode(node) {
28
+ return node.pos < 0 || node.end < 0;
29
+ }
30
+ function findBestNode(file) {
31
+ const codePart = extractCodePart(file.getText());
32
+ let latest = file;
33
+ const find = (node) => {
34
+ if (!isInjectedNode(node) && node.getText(file) === codePart) {
35
+ latest = node;
36
+ } else if (latest !== file) {
37
+ return latest;
38
+ }
39
+ return forEachChild(node, find);
40
+ };
41
+ return forEachChild(file, find) ?? latest;
42
+ }
43
+ export default function ast(parts, ...fillers) {
44
+ let code = "";
45
+ const transformers = [];
46
+ for (let i = 0; i < parts.length; i++) {
47
+ const filler = fillers[i];
48
+ code += parts[i];
49
+ if (filler != null) {
50
+ if (typeof filler === "string") {
51
+ code += filler;
52
+ } else {
53
+ const id = `$${crypto.randomUUID().replaceAll("-", "_")}`;
54
+ code += id;
55
+ transformers.push(createTransformer((n) => isIdentifier(n) && n.text === id ? isTemplateResult(filler) ? filler.node : filler : n));
56
+ }
57
+ }
58
+ }
59
+ if (code.includes("%{") || code.includes("}%")) {
60
+ code = code.replaceAll("%{", "/** @START */").replaceAll("}%", "/** @END */");
61
+ }
62
+ if (code.indexOf("/** @START */") !== code.lastIndexOf("/** @START */") || code.indexOf("/** @END */") !== code.lastIndexOf("/** @END */")) {
63
+ throw new Error("Only one set of code extractors is allowed: %{ ... }% or /** @START */ ... /** @END */");
64
+ }
65
+ const [source] = transform(createSourceFile("", code, ScriptTarget.Latest, false, ScriptKind.TSX), transformers).transformed;
66
+ return {
67
+ brand: $templateResult,
68
+ node: findBestNode(source),
69
+ source
70
+ };
71
+ }
72
+ //# sourceMappingURL=./tsc-template.js.map
@@ -0,0 +1 @@
1
+ {"mappings":"AAOA,SACE,kBACA,cACA,cAEA,YACA,cAEA,WAEA,kCAEkB;AAIpB,OAAO,SAAS,kBAAkBA,aAA0D;AAC1F,QAAO,CAAC,YAAY,CAAC,SAAS;EAC5B,MAAM,UAAU,CAACC,SAA8C;GAC7D,MAAM,cAAc,YAAY,KAAK;AACrC,OAAI,gBAAgB,MAAM;AACxB,WAAO;GACR;AACD,UAAO,eAAe,aAAa,SAAS,QAAQ;EACrD;AACD,SAAO,eAAe,MAAM,SAAS,QAAQ;CAC9C;AACF;AAED,MAAMC,kBAAiC,OAAO,iBAAiB;AAQ/D,SAAS,iBAAiBC,OAAyC;AACjE,eAAc,UAAU,YAAY,UAAU,QAAQ,WAAW,SAAS,MAAM,UAAU;AAC3F;AAID,MAAM,eAAe;AACrB,MAAM,aAAa;AAEnB,SAAS,gBAAgBC,KAAqB;CAC5C,MAAM,aAAa,aAAa,KAAK,IAAI;CACzC,MAAM,aAAa,aAAa,IAAI,MAAM,WAAW,QAAQ,WAAW,GAAG,OAAO,GAAG;CACrF,MAAM,WAAW,WAAW,KAAK,WAAW;CAC5C,MAAM,WAAW,WAAW,WAAW,MAAM,GAAG,SAAS,MAAM,GAAG;AAClE,QAAO,SAAS,MAAM;AACvB;AAED,SAAS,eAAeH,MAAqB;AAC3C,QAAO,KAAK,MAAM,KAAK,KAAK,MAAM;AACnC;AAED,SAAS,aAAaI,MAAwB;CAC5C,MAAM,WAAW,gBAAgB,KAAK,SAAS,CAAC;CAChD,IAAIC,SAAe;CACnB,MAAM,OAAO,CAACL,SAAiC;AAC7C,OAAK,eAAe,KAAK,IAAI,KAAK,QAAQ,KAAK,KAAK,UAAU;AAC5D,YAAS;EACV,WAAU,WAAW,MAAM;AAC1B,UAAO;EACR;AACD,SAAO,aAAa,MAAM,KAAK;CAChC;AACD,QAAO,aAAa,MAAM,KAAK,IAAI;AACpC;AAED,eAAe,SAAS,IACtBM,OACA,GAAG,SACa;CAChB,IAAI,OAAO;CACX,MAAMC,eAAsD,CAAE;AAE9D,MAAK,IAAI,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK;EACrC,MAAM,SAAS,QAAQ;AACvB,UAAQ,MAAM;AACd,MAAI,UAAU,MAAM;AAClB,cAAW,WAAW,UAAU;AAC9B,YAAQ;GACT,OAAM;IACL,MAAM,MAAM,GAAG,OAAO,YAAY,CAAC,WAAW,KAAK,IAAI,CAAC;AACxD,YAAQ;AACR,iBAAa,KACX,kBAAkB,CAAC,MACjB,aAAa,EAAE,IAAI,EAAE,SAAS,KAAM,iBAAiB,OAAO,GAAG,OAAO,OAAO,SAAU,EACxF,CACF;GACF;EACF;CACF;AAED,KAAI,KAAK,SAAS,KAAK,IAAI,KAAK,SAAS,KAAK,EAAE;AAC9C,SAAO,KAAK,WAAW,MAAM,gBAAgB,CAAC,WAAW,MAAM,cAAc;CAC9E;AAED,KACE,KAAK,QAAQ,gBAAgB,KAAK,KAAK,YAAY,gBAAgB,IACnE,KAAK,QAAQ,cAAc,KAAK,KAAK,YAAY,cAAc,EAC/D;AACA,QAAM,IAAI,MAAM;CACjB;CAED,MAAM,CAAC,OAAO,GAAG,UACf,iBAAiB,IAAI,MAAM,aAAa,QAAQ,OAAO,WAAW,IAAI,EACtE,aACD,CAAC;AACF,QAAO;EAAE,OAAO;EAAiB,MAAM,aAAa,OAAO;EAAE;CAAQ;AACtE","names":["transformer: Transformer","node: Node","$templateResult: unique symbol","value: unknown","str: string","file: SourceFile","latest: Node","parts: TemplateStringsArray","transformers: Array<TransformerFactory<SourceFile>>"],"sources":["/opt/agent/work/649c11185a3798db/packages/ts/generator-utils/src/tsc-template.ts"],"sourcesContent":["/*\n * Adapted from `tsc-template` (https://github.com/ausginer/tsc-template),\n * © 2024 Vlad Rindevich, Apache-2.0 licensed.\n *\n * Inlined here so Hilla packages don't carry an external dependency whose\n * peerDependency on TypeScript lags behind ours.\n */\nimport {\n createSourceFile,\n forEachChild,\n isIdentifier,\n type Node,\n ScriptKind,\n ScriptTarget,\n type SourceFile,\n transform,\n type TransformerFactory,\n visitEachChild,\n type VisitResult,\n} from 'typescript';\n\nexport type Transformer = (node: Node) => VisitResult<Node | undefined>;\n\nexport function createTransformer(transformer: Transformer): TransformerFactory<SourceFile> {\n return (context) => (root) => {\n const visitor = (node: Node): VisitResult<Node | undefined> => {\n const transformed = transformer(node);\n if (transformed !== node) {\n return transformed;\n }\n return visitEachChild(transformed, visitor, context);\n };\n return visitEachChild(root, visitor, context);\n };\n}\n\nconst $templateResult: unique symbol = Symbol('TemplateResult');\n\nexport type TemplateResult = Readonly<{\n brand: typeof $templateResult;\n node: Node;\n source: SourceFile;\n}>;\n\nfunction isTemplateResult(value: unknown): value is TemplateResult {\n return typeof value === 'object' && value !== null && 'brand' in value && value.brand === $templateResult;\n}\n\n// Anchored, fixed-length patterns — no nested or overlapping quantifiers, so\n// matching is linear in the input length (no ReDoS surface).\nconst START_MARKER = /\\/\\*\\*\\s*@START\\s*\\*\\//iu;\nconst END_MARKER = /\\/\\*\\*\\s*@END\\s*\\*\\//iu;\n\nfunction extractCodePart(str: string): string {\n const startMatch = START_MARKER.exec(str);\n const afterStart = startMatch ? str.slice(startMatch.index + startMatch[0].length) : str;\n const endMatch = END_MARKER.exec(afterStart);\n const codePart = endMatch ? afterStart.slice(0, endMatch.index) : afterStart;\n return codePart.trim();\n}\n\nfunction isInjectedNode(node: Node): boolean {\n return node.pos < 0 || node.end < 0;\n}\n\nfunction findBestNode(file: SourceFile): Node {\n const codePart = extractCodePart(file.getText());\n let latest: Node = file;\n const find = (node: Node): Node | undefined => {\n if (!isInjectedNode(node) && node.getText(file) === codePart) {\n latest = node;\n } else if (latest !== file) {\n return latest;\n }\n return forEachChild(node, find);\n };\n return forEachChild(file, find) ?? latest;\n}\n\nexport default function ast(\n parts: TemplateStringsArray,\n ...fillers: ReadonlyArray<TemplateResult | Node | string | null | undefined>\n): TemplateResult {\n let code = '';\n const transformers: Array<TransformerFactory<SourceFile>> = [];\n\n for (let i = 0; i < parts.length; i++) {\n const filler = fillers[i];\n code += parts[i];\n if (filler != null) {\n if (typeof filler === 'string') {\n code += filler;\n } else {\n const id = `$${crypto.randomUUID().replaceAll('-', '_')}`;\n code += id;\n transformers.push(\n createTransformer((n) =>\n isIdentifier(n) && n.text === id ? (isTemplateResult(filler) ? filler.node : filler) : n,\n ),\n );\n }\n }\n }\n\n if (code.includes('%{') || code.includes('}%')) {\n code = code.replaceAll('%{', '/** @START */').replaceAll('}%', '/** @END */');\n }\n\n if (\n code.indexOf('/** @START */') !== code.lastIndexOf('/** @START */') ||\n code.indexOf('/** @END */') !== code.lastIndexOf('/** @END */')\n ) {\n throw new Error('Only one set of code extractors is allowed: %{ ... }% or /** @START */ ... /** @END */');\n }\n\n const [source] = transform(\n createSourceFile('', code, ScriptTarget.Latest, false, ScriptKind.TSX),\n transformers,\n ).transformed;\n return { brand: $templateResult, node: findBestNode(source), source };\n}\n"],"version":3}