eslint-plugin-intlayer 9.3.1
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/README.md +310 -0
- package/dist/cjs/index.cjs +105 -0
- package/dist/cjs/index.cjs.map +1 -0
- package/dist/cjs/rules/enforceAdapterImport.cjs +81 -0
- package/dist/cjs/rules/enforceAdapterImport.cjs.map +1 -0
- package/dist/cjs/rules/noDynamicFieldAccess.cjs +130 -0
- package/dist/cjs/rules/noDynamicFieldAccess.cjs.map +1 -0
- package/dist/cjs/rules/noRawText.cjs +176 -0
- package/dist/cjs/rules/noRawText.cjs.map +1 -0
- package/dist/cjs/rules/staticDictionaryKey.cjs +89 -0
- package/dist/cjs/rules/staticDictionaryKey.cjs.map +1 -0
- package/dist/cjs/utils/ast.cjs +151 -0
- package/dist/cjs/utils/ast.cjs.map +1 -0
- package/dist/cjs/utils/callerMatcher.cjs +130 -0
- package/dist/cjs/utils/callerMatcher.cjs.map +1 -0
- package/dist/cjs/utils/createRule.cjs +26 -0
- package/dist/cjs/utils/createRule.cjs.map +1 -0
- package/dist/cjs/utils/templates.cjs +148 -0
- package/dist/cjs/utils/templates.cjs.map +1 -0
- package/dist/esm/index.mjs +101 -0
- package/dist/esm/index.mjs.map +1 -0
- package/dist/esm/rules/enforceAdapterImport.mjs +80 -0
- package/dist/esm/rules/enforceAdapterImport.mjs.map +1 -0
- package/dist/esm/rules/noDynamicFieldAccess.mjs +129 -0
- package/dist/esm/rules/noDynamicFieldAccess.mjs.map +1 -0
- package/dist/esm/rules/noRawText.mjs +175 -0
- package/dist/esm/rules/noRawText.mjs.map +1 -0
- package/dist/esm/rules/staticDictionaryKey.mjs +88 -0
- package/dist/esm/rules/staticDictionaryKey.mjs.map +1 -0
- package/dist/esm/utils/ast.mjs +143 -0
- package/dist/esm/utils/ast.mjs.map +1 -0
- package/dist/esm/utils/callerMatcher.mjs +126 -0
- package/dist/esm/utils/callerMatcher.mjs.map +1 -0
- package/dist/esm/utils/createRule.mjs +25 -0
- package/dist/esm/utils/createRule.mjs.map +1 -0
- package/dist/esm/utils/templates.mjs +145 -0
- package/dist/esm/utils/templates.mjs.map +1 -0
- package/dist/types/index.d.ts +67 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/rules/enforceAdapterImport.d.ts +6 -0
- package/dist/types/rules/enforceAdapterImport.d.ts.map +1 -0
- package/dist/types/rules/noDynamicFieldAccess.d.ts +7 -0
- package/dist/types/rules/noDynamicFieldAccess.d.ts.map +1 -0
- package/dist/types/rules/noRawText.d.ts +25 -0
- package/dist/types/rules/noRawText.d.ts.map +1 -0
- package/dist/types/rules/staticDictionaryKey.d.ts +7 -0
- package/dist/types/rules/staticDictionaryKey.d.ts.map +1 -0
- package/dist/types/utils/ast.d.ts +105 -0
- package/dist/types/utils/ast.d.ts.map +1 -0
- package/dist/types/utils/callerMatcher.d.ts +58 -0
- package/dist/types/utils/callerMatcher.d.ts.map +1 -0
- package/dist/types/utils/createRule.d.ts +23 -0
- package/dist/types/utils/createRule.d.ts.map +1 -0
- package/dist/types/utils/templates.d.ts +71 -0
- package/dist/types/utils/templates.d.ts.map +1 -0
- package/package.json +105 -0
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
//#region src/utils/templates.ts
|
|
2
|
+
/**
|
|
3
|
+
* Element node types whose text content is never user-facing copy, regardless
|
|
4
|
+
* of the `ignoreElements` option (which matches on tag name, and these carry
|
|
5
|
+
* none in some parsers).
|
|
6
|
+
*/
|
|
7
|
+
const NON_CONTENT_ELEMENT_TYPES = /* @__PURE__ */ new Set(["SvelteScriptElement", "SvelteStyleElement"]);
|
|
8
|
+
/** Element node types, per parser, that carry a tag name. */
|
|
9
|
+
const ELEMENT_TYPES = /* @__PURE__ */ new Set([
|
|
10
|
+
"VElement",
|
|
11
|
+
"SvelteElement",
|
|
12
|
+
"Element",
|
|
13
|
+
"JSXElement",
|
|
14
|
+
"JSXOpeningElement"
|
|
15
|
+
]);
|
|
16
|
+
/**
|
|
17
|
+
* Read the tag name off an element node, normalising the three shapes parsers
|
|
18
|
+
* use: a plain string (Vue, Angular), a `SvelteName` node (Svelte), and a JSX
|
|
19
|
+
* name node.
|
|
20
|
+
*
|
|
21
|
+
* @param element - An element node.
|
|
22
|
+
*/
|
|
23
|
+
const readElementName = (element) => {
|
|
24
|
+
const name = element["name"];
|
|
25
|
+
if (typeof name === "string") return name;
|
|
26
|
+
if (name && typeof name === "object") {
|
|
27
|
+
const nameNode = name;
|
|
28
|
+
if (typeof nameNode["name"] === "string") return nameNode["name"];
|
|
29
|
+
}
|
|
30
|
+
const openingElement = element["openingElement"];
|
|
31
|
+
if (openingElement) return readElementName(openingElement);
|
|
32
|
+
return null;
|
|
33
|
+
};
|
|
34
|
+
/**
|
|
35
|
+
* Walk up to the nearest enclosing element and return its tag name, or the
|
|
36
|
+
* sentinel `false` when the text sits inside a `<script>`/`<style>` block that
|
|
37
|
+
* should never be reported.
|
|
38
|
+
*
|
|
39
|
+
* @param node - The text or attribute node to start from.
|
|
40
|
+
*/
|
|
41
|
+
const resolveEnclosingElement = (node) => {
|
|
42
|
+
let current = node?.parent;
|
|
43
|
+
while (current) {
|
|
44
|
+
if (NON_CONTENT_ELEMENT_TYPES.has(current.type)) return false;
|
|
45
|
+
if (ELEMENT_TYPES.has(current.type)) return readElementName(current);
|
|
46
|
+
current = current.parent;
|
|
47
|
+
}
|
|
48
|
+
return null;
|
|
49
|
+
};
|
|
50
|
+
/**
|
|
51
|
+
* Report a finding unless it sits inside a non-content element.
|
|
52
|
+
*
|
|
53
|
+
* @param report - The rule's reporter.
|
|
54
|
+
* @param node - Node to report on.
|
|
55
|
+
* @param text - The literal text.
|
|
56
|
+
* @param attributeName - Attribute name, or `null` for element text.
|
|
57
|
+
*/
|
|
58
|
+
const reportWithElement = (report, node, text, attributeName) => {
|
|
59
|
+
const elementName = resolveEnclosingElement(node);
|
|
60
|
+
if (elementName === false) return;
|
|
61
|
+
report({
|
|
62
|
+
node,
|
|
63
|
+
text,
|
|
64
|
+
attributeName,
|
|
65
|
+
elementName
|
|
66
|
+
});
|
|
67
|
+
};
|
|
68
|
+
/**
|
|
69
|
+
* Visitors covering every non-JSX template language Intlayer supports.
|
|
70
|
+
*
|
|
71
|
+
* JSX (React, Preact, Solid, Astro, Lit templates compiled through JSX) is
|
|
72
|
+
* handled by the rule itself, since `JSXText`/`JSXAttribute` also need the
|
|
73
|
+
* non-template code paths.
|
|
74
|
+
*
|
|
75
|
+
* @param report - Called for each literal text node found.
|
|
76
|
+
*/
|
|
77
|
+
const createTemplateVisitor = (report) => ({
|
|
78
|
+
VText: (node) => {
|
|
79
|
+
const astNode = node;
|
|
80
|
+
reportWithElement(report, astNode, astNode["value"], null);
|
|
81
|
+
},
|
|
82
|
+
VAttribute: (node) => {
|
|
83
|
+
const astNode = node;
|
|
84
|
+
if (astNode["directive"] === true) return;
|
|
85
|
+
const key = astNode["key"];
|
|
86
|
+
const value = astNode["value"];
|
|
87
|
+
const attributeName = key?.["name"];
|
|
88
|
+
const text = value?.["value"];
|
|
89
|
+
if (typeof attributeName !== "string" || typeof text !== "string") return;
|
|
90
|
+
reportWithElement(report, astNode, text, attributeName);
|
|
91
|
+
},
|
|
92
|
+
SvelteText: (node) => {
|
|
93
|
+
const astNode = node;
|
|
94
|
+
reportWithElement(report, astNode, astNode["value"], null);
|
|
95
|
+
},
|
|
96
|
+
SvelteAttribute: (node) => {
|
|
97
|
+
const astNode = node;
|
|
98
|
+
const attributeName = astNode["key"]?.["name"];
|
|
99
|
+
if (typeof attributeName !== "string") return;
|
|
100
|
+
const parts = astNode["value"];
|
|
101
|
+
if (parts?.length !== 1) return;
|
|
102
|
+
const part = parts[0];
|
|
103
|
+
if (part?.type !== "SvelteLiteral") return;
|
|
104
|
+
const text = part["value"];
|
|
105
|
+
if (typeof text !== "string") return;
|
|
106
|
+
reportWithElement(report, astNode, text, attributeName);
|
|
107
|
+
},
|
|
108
|
+
Text: (node) => {
|
|
109
|
+
const astNode = node;
|
|
110
|
+
const text = astNode["value"];
|
|
111
|
+
if (typeof text !== "string") return;
|
|
112
|
+
reportWithElement(report, astNode, text, null);
|
|
113
|
+
},
|
|
114
|
+
TextAttribute: (node) => {
|
|
115
|
+
const astNode = node;
|
|
116
|
+
const attributeName = astNode["name"];
|
|
117
|
+
const text = astNode["value"];
|
|
118
|
+
if (typeof attributeName !== "string" || typeof text !== "string") return;
|
|
119
|
+
reportWithElement(report, astNode, text, attributeName);
|
|
120
|
+
}
|
|
121
|
+
});
|
|
122
|
+
/**
|
|
123
|
+
* Combine a script visitor and a template visitor into the single object the
|
|
124
|
+
* active parser expects.
|
|
125
|
+
*
|
|
126
|
+
* Vue keeps its template in a side AST reachable only through
|
|
127
|
+
* `defineTemplateBodyVisitor`; every other parser puts markup in the main AST,
|
|
128
|
+
* where a merged visitor object is enough.
|
|
129
|
+
*
|
|
130
|
+
* @param parserServices - `context.sourceCode.parserServices`.
|
|
131
|
+
* @param scriptVisitor - Handlers for ordinary script nodes.
|
|
132
|
+
* @param templateVisitor - Handlers for markup nodes.
|
|
133
|
+
*/
|
|
134
|
+
const combineVisitors = (parserServices, scriptVisitor, templateVisitor) => {
|
|
135
|
+
const services = parserServices;
|
|
136
|
+
if (typeof services?.defineTemplateBodyVisitor === "function") return services.defineTemplateBodyVisitor(templateVisitor, scriptVisitor);
|
|
137
|
+
return {
|
|
138
|
+
...scriptVisitor,
|
|
139
|
+
...templateVisitor
|
|
140
|
+
};
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
//#endregion
|
|
144
|
+
export { combineVisitors, createTemplateVisitor, resolveEnclosingElement };
|
|
145
|
+
//# sourceMappingURL=templates.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"templates.mjs","names":[],"sources":["../../../src/utils/templates.ts"],"sourcesContent":["import type { AstNode, AstNodeWithParent } from './ast';\n\n/**\n * Template-language support for text-coverage rules.\n *\n * Intlayer ships integrations for React, Vue, Svelte, Angular, Astro, Solid,\n * Preact and Lit, and each ecosystem's ESLint parser models markup differently:\n *\n * | Framework | Parser | Text node | Attribute node | Lives in |\n * | --------- | ------------------------------- | ----------- | --------------- | --------------- |\n * | React/JSX | espree, typescript-eslint | `JSXText` | `JSXAttribute` | main AST |\n * | Astro | astro-eslint-parser | `JSXText` | `JSXAttribute` | main AST |\n * | Svelte | svelte-eslint-parser | `SvelteText`| `SvelteAttribute`| main AST |\n * | Angular | @angular-eslint/template-parser | `Text` | `TextAttribute` | main AST |\n * | Vue | vue-eslint-parser | `VText` | `VAttribute` | `templateBody` |\n *\n * Vue is the only one that hides its template behind\n * `parserServices.defineTemplateBodyVisitor`; every other parser exposes markup\n * in the AST a normal visitor already walks. Node type names do not collide\n * across parsers, so one merged visitor is safe to hand to any of them — a\n * Svelte file simply never emits a `VText`.\n */\n\n/** One piece of literal text found in markup. */\nexport type TemplateTextFinding = {\n /** Node to report on. */\n node: AstNode;\n /** The literal text. */\n text: string;\n /** Attribute name when the text is an attribute value, else `null`. */\n attributeName: string | null;\n /** Tag name of the enclosing element, when resolvable. */\n elementName: string | null;\n};\n\n/** Callback invoked for every literal text node the visitors find. */\nexport type TemplateTextReporter = (finding: TemplateTextFinding) => void;\n\n/** A visitor object as returned from a rule's `create`. */\nexport type TemplateVisitor = Record<string, (node: never) => void>;\n\n/**\n * Element node types whose text content is never user-facing copy, regardless\n * of the `ignoreElements` option (which matches on tag name, and these carry\n * none in some parsers).\n */\nconst NON_CONTENT_ELEMENT_TYPES = new Set([\n 'SvelteScriptElement',\n 'SvelteStyleElement',\n]);\n\n/** Element node types, per parser, that carry a tag name. */\nconst ELEMENT_TYPES = new Set([\n 'VElement',\n 'SvelteElement',\n 'Element',\n 'JSXElement',\n 'JSXOpeningElement',\n]);\n\n/**\n * Read the tag name off an element node, normalising the three shapes parsers\n * use: a plain string (Vue, Angular), a `SvelteName` node (Svelte), and a JSX\n * name node.\n *\n * @param element - An element node.\n */\nconst readElementName = (element: AstNode): string | null => {\n const name = element['name'];\n\n if (typeof name === 'string') return name;\n\n if (name && typeof name === 'object') {\n const nameNode = name as AstNode;\n\n if (typeof nameNode['name'] === 'string') return nameNode['name'];\n }\n\n // JSXElement holds its name on the opening element.\n const openingElement = element['openingElement'] as AstNode | undefined;\n\n if (openingElement) return readElementName(openingElement);\n\n return null;\n};\n\n/**\n * Walk up to the nearest enclosing element and return its tag name, or the\n * sentinel `false` when the text sits inside a `<script>`/`<style>` block that\n * should never be reported.\n *\n * @param node - The text or attribute node to start from.\n */\nexport const resolveEnclosingElement = (\n node: AstNodeWithParent | undefined\n): string | null | false => {\n let current = node?.parent;\n\n while (current) {\n if (NON_CONTENT_ELEMENT_TYPES.has(current.type)) return false;\n\n if (ELEMENT_TYPES.has(current.type)) return readElementName(current);\n\n current = current.parent;\n }\n\n return null;\n};\n\n/**\n * Report a finding unless it sits inside a non-content element.\n *\n * @param report - The rule's reporter.\n * @param node - Node to report on.\n * @param text - The literal text.\n * @param attributeName - Attribute name, or `null` for element text.\n */\nconst reportWithElement = (\n report: TemplateTextReporter,\n node: AstNodeWithParent,\n text: string,\n attributeName: string | null\n): void => {\n const elementName = resolveEnclosingElement(node);\n\n if (elementName === false) return;\n\n report({ node, text, attributeName, elementName });\n};\n\n/**\n * Visitors covering every non-JSX template language Intlayer supports.\n *\n * JSX (React, Preact, Solid, Astro, Lit templates compiled through JSX) is\n * handled by the rule itself, since `JSXText`/`JSXAttribute` also need the\n * non-template code paths.\n *\n * @param report - Called for each literal text node found.\n */\nexport const createTemplateVisitor = (\n report: TemplateTextReporter\n): TemplateVisitor => ({\n // ── Vue ────────────────────────────────────────────────────────────────\n VText: (node: never) => {\n const astNode = node as AstNodeWithParent;\n\n reportWithElement(report, astNode, astNode['value'] as string, null);\n },\n\n VAttribute: (node: never) => {\n const astNode = node as AstNodeWithParent;\n\n // Directives (`:title`, `v-bind:title`) hold an expression, not literal text.\n if (astNode['directive'] === true) return;\n\n const key = astNode['key'] as AstNode | undefined;\n const value = astNode['value'] as AstNode | undefined;\n const attributeName = key?.['name'];\n const text = value?.['value'];\n\n if (typeof attributeName !== 'string' || typeof text !== 'string') return;\n\n reportWithElement(report, astNode, text, attributeName);\n },\n\n // ── Svelte ─────────────────────────────────────────────────────────────\n SvelteText: (node: never) => {\n const astNode = node as AstNodeWithParent;\n\n reportWithElement(report, astNode, astNode['value'] as string, null);\n },\n\n SvelteAttribute: (node: never) => {\n const astNode = node as AstNodeWithParent;\n const key = astNode['key'] as AstNode | undefined;\n const attributeName = key?.['name'];\n\n if (typeof attributeName !== 'string') return;\n\n // The value is a list of parts; a single literal part means a static value,\n // anything else interpolates and is not raw copy.\n const parts = astNode['value'] as AstNode[] | undefined;\n\n if (parts?.length !== 1) return;\n\n const part = parts[0];\n\n if (part?.type !== 'SvelteLiteral') return;\n\n const text = part['value'];\n\n if (typeof text !== 'string') return;\n\n reportWithElement(report, astNode, text, attributeName);\n },\n\n // ── Angular ────────────────────────────────────────────────────────────\n Text: (node: never) => {\n const astNode = node as AstNodeWithParent;\n const text = astNode['value'];\n\n if (typeof text !== 'string') return;\n\n reportWithElement(report, astNode, text, null);\n },\n\n TextAttribute: (node: never) => {\n const astNode = node as AstNodeWithParent;\n const attributeName = astNode['name'];\n const text = astNode['value'];\n\n if (typeof attributeName !== 'string' || typeof text !== 'string') return;\n\n reportWithElement(report, astNode, text, attributeName);\n },\n});\n\n/** Parser services that expose a Vue template body. */\ntype VueParserServices = {\n defineTemplateBodyVisitor?: (\n templateVisitor: TemplateVisitor,\n scriptVisitor: TemplateVisitor\n ) => TemplateVisitor;\n};\n\n/**\n * Combine a script visitor and a template visitor into the single object the\n * active parser expects.\n *\n * Vue keeps its template in a side AST reachable only through\n * `defineTemplateBodyVisitor`; every other parser puts markup in the main AST,\n * where a merged visitor object is enough.\n *\n * @param parserServices - `context.sourceCode.parserServices`.\n * @param scriptVisitor - Handlers for ordinary script nodes.\n * @param templateVisitor - Handlers for markup nodes.\n */\nexport const combineVisitors = (\n parserServices: unknown,\n scriptVisitor: TemplateVisitor,\n templateVisitor: TemplateVisitor\n): TemplateVisitor => {\n const services = parserServices as VueParserServices | undefined;\n\n if (typeof services?.defineTemplateBodyVisitor === 'function') {\n return services.defineTemplateBodyVisitor(templateVisitor, scriptVisitor);\n }\n\n return { ...scriptVisitor, ...templateVisitor };\n};\n"],"mappings":";;;;;;AA8CA,MAAM,4CAA4B,IAAI,IAAI,CACxC,uBACA,oBACF,CAAC;;AAGD,MAAM,gCAAgB,IAAI,IAAI;CAC5B;CACA;CACA;CACA;CACA;AACF,CAAC;;;;;;;;AASD,MAAM,mBAAmB,YAAoC;CAC3D,MAAM,OAAO,QAAQ;CAErB,IAAI,OAAO,SAAS,UAAU,OAAO;CAErC,IAAI,QAAQ,OAAO,SAAS,UAAU;EACpC,MAAM,WAAW;EAEjB,IAAI,OAAO,SAAS,YAAY,UAAU,OAAO,SAAS;CAC5D;CAGA,MAAM,iBAAiB,QAAQ;CAE/B,IAAI,gBAAgB,OAAO,gBAAgB,cAAc;CAEzD,OAAO;AACT;;;;;;;;AASA,MAAa,2BACX,SAC0B;CAC1B,IAAI,UAAU,MAAM;CAEpB,OAAO,SAAS;EACd,IAAI,0BAA0B,IAAI,QAAQ,IAAI,GAAG,OAAO;EAExD,IAAI,cAAc,IAAI,QAAQ,IAAI,GAAG,OAAO,gBAAgB,OAAO;EAEnE,UAAU,QAAQ;CACpB;CAEA,OAAO;AACT;;;;;;;;;AAUA,MAAM,qBACJ,QACA,MACA,MACA,kBACS;CACT,MAAM,cAAc,wBAAwB,IAAI;CAEhD,IAAI,gBAAgB,OAAO;CAE3B,OAAO;EAAE;EAAM;EAAM;EAAe;CAAY,CAAC;AACnD;;;;;;;;;;AAWA,MAAa,yBACX,YACqB;CAErB,QAAQ,SAAgB;EACtB,MAAM,UAAU;EAEhB,kBAAkB,QAAQ,SAAS,QAAQ,UAAoB,IAAI;CACrE;CAEA,aAAa,SAAgB;EAC3B,MAAM,UAAU;EAGhB,IAAI,QAAQ,iBAAiB,MAAM;EAEnC,MAAM,MAAM,QAAQ;EACpB,MAAM,QAAQ,QAAQ;EACtB,MAAM,gBAAgB,MAAM;EAC5B,MAAM,OAAO,QAAQ;EAErB,IAAI,OAAO,kBAAkB,YAAY,OAAO,SAAS,UAAU;EAEnE,kBAAkB,QAAQ,SAAS,MAAM,aAAa;CACxD;CAGA,aAAa,SAAgB;EAC3B,MAAM,UAAU;EAEhB,kBAAkB,QAAQ,SAAS,QAAQ,UAAoB,IAAI;CACrE;CAEA,kBAAkB,SAAgB;EAChC,MAAM,UAAU;EAEhB,MAAM,gBADM,QAAQ,MACK,GAAG;EAE5B,IAAI,OAAO,kBAAkB,UAAU;EAIvC,MAAM,QAAQ,QAAQ;EAEtB,IAAI,OAAO,WAAW,GAAG;EAEzB,MAAM,OAAO,MAAM;EAEnB,IAAI,MAAM,SAAS,iBAAiB;EAEpC,MAAM,OAAO,KAAK;EAElB,IAAI,OAAO,SAAS,UAAU;EAE9B,kBAAkB,QAAQ,SAAS,MAAM,aAAa;CACxD;CAGA,OAAO,SAAgB;EACrB,MAAM,UAAU;EAChB,MAAM,OAAO,QAAQ;EAErB,IAAI,OAAO,SAAS,UAAU;EAE9B,kBAAkB,QAAQ,SAAS,MAAM,IAAI;CAC/C;CAEA,gBAAgB,SAAgB;EAC9B,MAAM,UAAU;EAChB,MAAM,gBAAgB,QAAQ;EAC9B,MAAM,OAAO,QAAQ;EAErB,IAAI,OAAO,kBAAkB,YAAY,OAAO,SAAS,UAAU;EAEnE,kBAAkB,QAAQ,SAAS,MAAM,aAAa;CACxD;AACF;;;;;;;;;;;;;AAsBA,MAAa,mBACX,gBACA,eACA,oBACoB;CACpB,MAAM,WAAW;CAEjB,IAAI,OAAO,UAAU,8BAA8B,YACjD,OAAO,SAAS,0BAA0B,iBAAiB,aAAa;CAG1E,OAAO;EAAE,GAAG;EAAe,GAAG;CAAgB;AAChD"}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { IntlayerRule } from "./utils/createRule.js";
|
|
2
|
+
import { NoRawTextOptions } from "./rules/noRawText.js";
|
|
3
|
+
import "./index.js";
|
|
4
|
+
import { ESLint, Linter } from "eslint";
|
|
5
|
+
//#region src/index.d.ts
|
|
6
|
+
/**
|
|
7
|
+
* Every rule this plugin ships, keyed by the name used in configuration.
|
|
8
|
+
*
|
|
9
|
+
* The rules split into two families:
|
|
10
|
+
* - **Coverage** (`no-raw-text`) — user-facing copy that never reached a
|
|
11
|
+
* dictionary. Nothing else in the toolchain reports it.
|
|
12
|
+
* - **Compiler contract** (`static-dictionary-key`, `no-dynamic-field-access`)
|
|
13
|
+
* — call shapes that type-check and run, but that the Babel/SWC passes cannot
|
|
14
|
+
* analyse, so they silently lose the optimize pass or read a purged field.
|
|
15
|
+
*
|
|
16
|
+
* Deliberately absent: unknown dictionary keys, unknown field paths and missing
|
|
17
|
+
* required locales. The generated `__DictionaryRegistry` module augmentation
|
|
18
|
+
* already makes all three compile errors, and `@intlayer/lsp` reports the first
|
|
19
|
+
* as an editor diagnostic — a third report would only add noise.
|
|
20
|
+
*/
|
|
21
|
+
declare const rules: {
|
|
22
|
+
'no-raw-text': IntlayerRule<[NoRawTextOptions?], "rawText" | "rawAttribute" | "rawStringLiteral">;
|
|
23
|
+
'static-dictionary-key': IntlayerRule<[], "dynamicKey" | "dynamicTemplateKey" | "dynamicJsxKey">;
|
|
24
|
+
'no-dynamic-field-access': IntlayerRule<[], "dynamicField" | "dynamicMessageId">;
|
|
25
|
+
'enforce-adapter-import': IntlayerRule<[], "useAdapterImport">;
|
|
26
|
+
};
|
|
27
|
+
/** Rule names this plugin exposes. */
|
|
28
|
+
type IntlayerRuleName = keyof typeof rules;
|
|
29
|
+
declare const meta: {
|
|
30
|
+
name: string;
|
|
31
|
+
version: string;
|
|
32
|
+
};
|
|
33
|
+
/** The flat configs this plugin ships. */
|
|
34
|
+
type IntlayerConfigName = 'recommended' | 'strict' | 'contract-only';
|
|
35
|
+
/**
|
|
36
|
+
* Flat configs, for `eslint.config.js`:
|
|
37
|
+
*
|
|
38
|
+
* ```js
|
|
39
|
+
* import intlayer from 'eslint-plugin-intlayer';
|
|
40
|
+
*
|
|
41
|
+
* export default [intlayer.configs.recommended];
|
|
42
|
+
* ```
|
|
43
|
+
*
|
|
44
|
+
* - `recommended` — the compiler-contract rules as errors, plus `no-raw-text`
|
|
45
|
+
* as a warning so an existing codebase is not blocked on day one.
|
|
46
|
+
* - `strict` — everything as an error, including string literals outside JSX.
|
|
47
|
+
* - `contract-only` — just the rules that protect the build output; use this
|
|
48
|
+
* when translation coverage is tracked by `intlayer test` instead.
|
|
49
|
+
*
|
|
50
|
+
* `enforce-adapter-import` is opt-in: importing from the original package is
|
|
51
|
+
* perfectly valid once the bundler alias is set up, so a project that has done
|
|
52
|
+
* that should not be nagged. Only `strict` turns it on.
|
|
53
|
+
*/
|
|
54
|
+
declare const configs: Record<IntlayerConfigName, Linter.Config>;
|
|
55
|
+
/** The plugin object, as consumed by `eslint.config.js`. */
|
|
56
|
+
type IntlayerPlugin = ESLint.Plugin & {
|
|
57
|
+
meta: {
|
|
58
|
+
name: string;
|
|
59
|
+
version: string;
|
|
60
|
+
};
|
|
61
|
+
rules: typeof rules;
|
|
62
|
+
configs: Record<IntlayerConfigName, Linter.Config>;
|
|
63
|
+
};
|
|
64
|
+
declare const intlayerPlugin: IntlayerPlugin;
|
|
65
|
+
//#endregion
|
|
66
|
+
export { IntlayerConfigName, IntlayerPlugin, IntlayerRuleName, type NoRawTextOptions, configs, intlayerPlugin as default, meta, rules };
|
|
67
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","names":[],"sources":["../../src/index.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;cAwBa;+BAAA;;;;;;KAQD,gCAAgC;cAEtC;EAAQ;EAAc;;;KAQhB;;;;;;;;;;;;;;;;;;;;cAyCC,SAAS,OAAO,oBAAoB,OAAO;;KAiC5C,iBAAiB,OAAO;EAClC;IAAQ;IAAc;;EACtB,cAAc;EACd,SAAS,OAAO,oBAAoB,OAAO;;cAGvC,gBAAgB"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { IntlayerRule } from "../utils/createRule.js";
|
|
2
|
+
//#region src/rules/enforceAdapterImport.d.ts
|
|
3
|
+
declare const enforceAdapterImport: IntlayerRule<[], "useAdapterImport">;
|
|
4
|
+
//#endregion
|
|
5
|
+
export { enforceAdapterImport };
|
|
6
|
+
//# sourceMappingURL=enforceAdapterImport.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"enforceAdapterImport.d.ts","names":[],"sources":["../../../src/rules/enforceAdapterImport.ts"],"mappings":";;cAuDa,sBAAA"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { IntlayerRule } from "../utils/createRule.js";
|
|
2
|
+
//#region src/rules/noDynamicFieldAccess.d.ts
|
|
3
|
+
type MessageIds = 'dynamicField' | 'dynamicMessageId';
|
|
4
|
+
declare const noDynamicFieldAccess: IntlayerRule<[], MessageIds>;
|
|
5
|
+
//#endregion
|
|
6
|
+
export { noDynamicFieldAccess };
|
|
7
|
+
//# sourceMappingURL=noDynamicFieldAccess.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"noDynamicFieldAccess.d.ts","names":[],"sources":["../../../src/rules/noDynamicFieldAccess.ts"],"mappings":";;KAcK;cAiFQ,sBAAoB,iBAAA"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { IntlayerRule } from "../utils/createRule.js";
|
|
2
|
+
//#region src/rules/noRawText.d.ts
|
|
3
|
+
type NoRawTextOptions = {
|
|
4
|
+
/**
|
|
5
|
+
* JSX attributes whose string value is user-facing copy.
|
|
6
|
+
* Defaults to the compiler's own list, so the linter flags exactly what
|
|
7
|
+
* `intlayer extract` would rewrite.
|
|
8
|
+
*/
|
|
9
|
+
attributes?: string[];
|
|
10
|
+
/** JSX elements whose children are never translatable. */
|
|
11
|
+
ignoreElements?: string[];
|
|
12
|
+
/** Regular-expression sources for text that should never be reported. */
|
|
13
|
+
ignorePatterns?: string[];
|
|
14
|
+
/**
|
|
15
|
+
* Also report string literals outside JSX (function arguments, object values,
|
|
16
|
+
* …). Off by default: outside JSX the false-positive rate is much higher, and
|
|
17
|
+
* the compiler's own extractor is the better first pass.
|
|
18
|
+
*/
|
|
19
|
+
includeStringLiterals?: boolean;
|
|
20
|
+
};
|
|
21
|
+
type MessageIds = 'rawText' | 'rawAttribute' | 'rawStringLiteral';
|
|
22
|
+
declare const noRawText: IntlayerRule<[NoRawTextOptions?], MessageIds>;
|
|
23
|
+
//#endregion
|
|
24
|
+
export { NoRawTextOptions, noRawText };
|
|
25
|
+
//# sourceMappingURL=noRawText.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"noRawText.d.ts","names":[],"sources":["../../../src/rules/noRawText.ts"],"mappings":";;KAwBY;;;;;;EAMV;;EAEA;;EAEA;;;;;;EAMA;;KAGG;cAsEQ,WAAS,cAAA,oBAAA"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { IntlayerRule } from "../utils/createRule.js";
|
|
2
|
+
//#region src/rules/staticDictionaryKey.d.ts
|
|
3
|
+
type MessageIds = 'dynamicKey' | 'dynamicTemplateKey' | 'dynamicJsxKey';
|
|
4
|
+
declare const staticDictionaryKey: IntlayerRule<[], MessageIds>;
|
|
5
|
+
//#endregion
|
|
6
|
+
export { staticDictionaryKey };
|
|
7
|
+
//# sourceMappingURL=staticDictionaryKey.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"staticDictionaryKey.d.ts","names":[],"sources":["../../../src/rules/staticDictionaryKey.ts"],"mappings":";;KAkBK;cAsBQ,qBAAmB,iBAAA"}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { Node } from "/Users/aymericpineau/Documents/intlayer_/node_modules/.bun/@types+estree@1.0.9/node_modules/@types/estree/index.js";
|
|
2
|
+
//#region src/utils/ast.d.ts
|
|
3
|
+
/**
|
|
4
|
+
* Minimal structural models for the nodes this plugin inspects that are not
|
|
5
|
+
* part of the ESTree spec — JSX (all parsers) and the TypeScript-only wrapper
|
|
6
|
+
* expressions emitted by `@typescript-eslint/parser`.
|
|
7
|
+
*
|
|
8
|
+
* They are declared here rather than imported from `@typescript-eslint/types`
|
|
9
|
+
* so the plugin stays parser-agnostic: the same rules run under `espree`,
|
|
10
|
+
* `@typescript-eslint/parser`, `vue-eslint-parser` and oxlint's JS plugin
|
|
11
|
+
* bridge, none of which agree on a type package.
|
|
12
|
+
*/
|
|
13
|
+
/** Any AST node, whatever the parser. Narrowed through the guards below. */
|
|
14
|
+
type AstNode = {
|
|
15
|
+
type: string;
|
|
16
|
+
[key: string]: unknown;
|
|
17
|
+
};
|
|
18
|
+
/** An AST node as ESLint hands it to a visitor — always linked to its parent. */
|
|
19
|
+
type AstNodeWithParent = AstNode & {
|
|
20
|
+
parent?: AstNodeWithParent;
|
|
21
|
+
};
|
|
22
|
+
/** `<div>Some text</div>` — the raw text between two JSX tags. */
|
|
23
|
+
type JsxTextNode = AstNode & {
|
|
24
|
+
type: 'JSXText';
|
|
25
|
+
value: string;
|
|
26
|
+
};
|
|
27
|
+
/** `title="Some text"` on a JSX element. */
|
|
28
|
+
type JsxAttributeNode = AstNode & {
|
|
29
|
+
type: 'JSXAttribute';
|
|
30
|
+
name: AstNode;
|
|
31
|
+
value: AstNode | null;
|
|
32
|
+
};
|
|
33
|
+
/** The opening tag of a JSX element, holding its name and attributes. */
|
|
34
|
+
type JsxOpeningElementNode = AstNode & {
|
|
35
|
+
type: 'JSXOpeningElement';
|
|
36
|
+
name: AstNode;
|
|
37
|
+
attributes: AstNode[];
|
|
38
|
+
};
|
|
39
|
+
/**
|
|
40
|
+
* Strip TypeScript-only and parenthesised wrappers to reach the expression that
|
|
41
|
+
* actually produces the value.
|
|
42
|
+
*
|
|
43
|
+
* @param node - Expression to unwrap; `null`/`undefined` passes straight through.
|
|
44
|
+
*/
|
|
45
|
+
declare const unwrapExpression: <T extends AstNode>(node: T | null | undefined) => T | null;
|
|
46
|
+
/**
|
|
47
|
+
* Read the compile-time string value of an expression, or `null` when the value
|
|
48
|
+
* can only be known at runtime.
|
|
49
|
+
*
|
|
50
|
+
* Deliberately does **not** resolve identifiers, even ones bound to a string
|
|
51
|
+
* constant: the Babel/SWC passes read the argument node itself, so a value that
|
|
52
|
+
* needs constant folding is a value the compiler will not see.
|
|
53
|
+
*
|
|
54
|
+
* @param node - Expression to evaluate.
|
|
55
|
+
*/
|
|
56
|
+
declare const getStaticStringValue: (node: AstNode | null | undefined) => string | null;
|
|
57
|
+
/**
|
|
58
|
+
* Name of the function being called, plus whether it was reached as a method.
|
|
59
|
+
*
|
|
60
|
+
* `matchAsMethod` descriptors (`i18n.getFixedT(…)`, `intl.formatMessage(…)`)
|
|
61
|
+
* only match the member form, so the two cases must stay distinguishable.
|
|
62
|
+
*
|
|
63
|
+
* @param callee - The `callee` of a CallExpression.
|
|
64
|
+
*/
|
|
65
|
+
declare const getCalleeName: (callee: AstNode | null | undefined) => {
|
|
66
|
+
name: string;
|
|
67
|
+
isMethod: boolean;
|
|
68
|
+
} | null;
|
|
69
|
+
/**
|
|
70
|
+
* Read a string-valued property from an object-literal argument, e.g. the
|
|
71
|
+
* `namespace` of `getTranslations({ namespace: 'home' })`.
|
|
72
|
+
*
|
|
73
|
+
* Returns the property's value node even when it is not static, so callers can
|
|
74
|
+
* tell "absent" (`null`) from "present but dynamic" (a node, no string).
|
|
75
|
+
*
|
|
76
|
+
* @param node - The argument expected to be an object expression.
|
|
77
|
+
* @param propertyName - Property to look up.
|
|
78
|
+
*/
|
|
79
|
+
declare const getObjectPropertyValueNode: (node: AstNode | null | undefined, propertyName: string) => AstNode | null;
|
|
80
|
+
/**
|
|
81
|
+
* Name of a JSX attribute, or `null` for spread attributes and namespaced
|
|
82
|
+
* names the plugin does not target.
|
|
83
|
+
*
|
|
84
|
+
* @param attribute - A member of a JSX opening element's `attributes`.
|
|
85
|
+
*/
|
|
86
|
+
declare const getJsxAttributeName: (attribute: AstNode | null | undefined) => string | null;
|
|
87
|
+
/**
|
|
88
|
+
* Value node of a JSX attribute, unwrapping `{'…'}` expression containers so
|
|
89
|
+
* `title="Text"` and `title={'Text'}` are treated alike.
|
|
90
|
+
*
|
|
91
|
+
* @param attribute - A JSX attribute node.
|
|
92
|
+
*/
|
|
93
|
+
declare const getJsxAttributeValueNode: (attribute: AstNode | null | undefined) => AstNode | null;
|
|
94
|
+
/**
|
|
95
|
+
* Tag name of the JSX element an attribute or child belongs to, e.g. `'code'`
|
|
96
|
+
* for `<code>`. Member and namespaced element names are joined with `.`.
|
|
97
|
+
*
|
|
98
|
+
* @param openingElement - A JSXOpeningElement node.
|
|
99
|
+
*/
|
|
100
|
+
declare const getJsxElementName: (openingElement: AstNode | null | undefined) => string | null;
|
|
101
|
+
/** Narrow an ESLint visitor node to the loose {@link AstNode} shape. */
|
|
102
|
+
declare const toAstNode: (node: Node | AstNode) => AstNodeWithParent;
|
|
103
|
+
//#endregion
|
|
104
|
+
export { AstNode, AstNodeWithParent, JsxAttributeNode, JsxOpeningElementNode, JsxTextNode, getCalleeName, getJsxAttributeName, getJsxAttributeValueNode, getJsxElementName, getObjectPropertyValueNode, getStaticStringValue, toAstNode, unwrapExpression };
|
|
105
|
+
//# sourceMappingURL=ast.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ast.d.ts","names":[],"sources":["../../../src/utils/ast.ts"],"mappings":";;;;;;;;;;;;;KAcY;EACV;GACC;;;KAIS,oBAAoB;EAC9B,SAAS;;;KAIC,cAAc;EACxB;EACA;;;KAIU,mBAAmB;EAC7B;EACA,MAAM;EACN,OAAO;;;KAIG,wBAAwB;EAClC;EACA,MAAM;EACN,YAAY;;;;;;;;cAsBD,mBAAoB,UAAU,SACzC,MAAM,yBACL;;;;;;;;;;;cAwBU,uBACX,MAAM;;;;;;;;;cAoCK,gBACX,QAAQ;EACL;EAAc;;;;;;;;;;;;cA8BN,6BACX,MAAM,4BACN,yBACC;;;;;;;cAkCU,sBACX,WAAW;;;;;;;cAeA,2BACX,WAAW,+BACV;;;;;;;cAgBU,oBACX,gBAAgB;;cAqBL,YAAa,MAAM,OAAa,YAAU"}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { AstNode } from "./ast.js";
|
|
2
|
+
import { CallerDescriptor, CallerValueSource } from "@intlayer/config/callers";
|
|
3
|
+
//#region src/utils/callerMatcher.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* Where the dictionary key of a matched call site lives, and whether it is
|
|
6
|
+
* statically knowable.
|
|
7
|
+
*/
|
|
8
|
+
type NamespaceResolution = {
|
|
9
|
+
/** The node holding the key, or `null` when the call omits it entirely. */
|
|
10
|
+
node: AstNode | null;
|
|
11
|
+
/** The compile-time key, or `null` when it can only be known at runtime. */
|
|
12
|
+
value: string | null;
|
|
13
|
+
/** The registry entry that described where to look. */
|
|
14
|
+
source: CallerValueSource;
|
|
15
|
+
};
|
|
16
|
+
/** A call expression recognised as an Intlayer content read. */
|
|
17
|
+
type CallerMatch = {
|
|
18
|
+
descriptor: CallerDescriptor;
|
|
19
|
+
/** Resolved namespace, or `null` for callers that take no namespace here. */
|
|
20
|
+
namespace: NamespaceResolution | null;
|
|
21
|
+
};
|
|
22
|
+
/**
|
|
23
|
+
* Module specifiers imported by a file, as they are written in source.
|
|
24
|
+
*
|
|
25
|
+
* `isCallerActive` gates generic caller names (lingui's `t` / `_`) on the file
|
|
26
|
+
* actually importing the library, so this must be collected before matching.
|
|
27
|
+
*
|
|
28
|
+
* @param program - The Program node of the analysed file.
|
|
29
|
+
*/
|
|
30
|
+
declare const collectImportSources: (program: AstNode) => Set<string>;
|
|
31
|
+
/**
|
|
32
|
+
* The registry entries that may match inside a file, given what it imports.
|
|
33
|
+
*
|
|
34
|
+
* @param importSources - Specifiers collected by {@link collectImportSources}.
|
|
35
|
+
*/
|
|
36
|
+
declare const getActiveDescriptors: (importSources: ReadonlySet<string>) => CallerDescriptor[];
|
|
37
|
+
/**
|
|
38
|
+
* Match a call expression against the shared caller registry.
|
|
39
|
+
*
|
|
40
|
+
* Tries every `namespaceSources` entry in declaration order and keeps the first
|
|
41
|
+
* that yields a node or a value — mirroring how `@intlayer/babel` picks an
|
|
42
|
+
* overload, so the linter and the compiler agree on what a call site means.
|
|
43
|
+
*
|
|
44
|
+
* @param callNode - A CallExpression (or TaggedTemplateExpression) node.
|
|
45
|
+
* @param activeDescriptors - Result of {@link getActiveDescriptors}.
|
|
46
|
+
*/
|
|
47
|
+
declare const matchCaller: (callNode: AstNode, activeDescriptors: CallerDescriptor[]) => CallerMatch | null;
|
|
48
|
+
/**
|
|
49
|
+
* Match a JSX element against the registry's JSX-shaped callers,
|
|
50
|
+
* e.g. `<FormattedMessage id="home.title" />` or `<Trans ns="home" …/>`.
|
|
51
|
+
*
|
|
52
|
+
* @param elementName - Tag name of the element.
|
|
53
|
+
* @param activeDescriptors - Result of {@link getActiveDescriptors}.
|
|
54
|
+
*/
|
|
55
|
+
declare const matchJsxCaller: (elementName: string | null, activeDescriptors: CallerDescriptor[]) => CallerDescriptor | null;
|
|
56
|
+
//#endregion
|
|
57
|
+
export { CallerMatch, NamespaceResolution, collectImportSources, getActiveDescriptors, matchCaller, matchJsxCaller };
|
|
58
|
+
//# sourceMappingURL=callerMatcher.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"callerMatcher.d.ts","names":[],"sources":["../../../src/utils/callerMatcher.ts"],"mappings":";;;;;;;KAkBY;;EAEV,MAAM;;EAEN;;EAEA,QAAQ;;;KAIE;EACV,YAAY;;EAEZ,WAAW;;;;;;;;;;cAWA,uBAAwB,SAAS,YAAU;;;;;;cA2B3C,uBACX,eAAe,wBACd;;;;;;;;;;;cAkFU,cACX,UAAU,SACV,mBAAmB,uBAClB;;;;;;;;cAuDU,iBACX,4BACA,mBAAmB,uBAClB"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { JSRuleDefinition } from "eslint";
|
|
2
|
+
//#region src/utils/createRule.d.ts
|
|
3
|
+
/**
|
|
4
|
+
* A rule of this plugin, with its option tuple and message ids pinned so
|
|
5
|
+
* `context.options` and `context.report({ messageId })` are checked rather than
|
|
6
|
+
* inferred as `any[]` / `string`.
|
|
7
|
+
*/
|
|
8
|
+
type IntlayerRule<Options extends readonly unknown[] = [], MessageIds extends string = string> = JSRuleDefinition<{
|
|
9
|
+
RuleOptions: [...Options];
|
|
10
|
+
MessageIds: MessageIds;
|
|
11
|
+
}>;
|
|
12
|
+
/**
|
|
13
|
+
* Build a rule with the plugin's shared metadata conventions applied: a
|
|
14
|
+
* documentation anchor derived from the rule name, and `meta.schema` defaulting
|
|
15
|
+
* to "no options" so an unknown option is rejected instead of ignored.
|
|
16
|
+
*
|
|
17
|
+
* @param name - The rule name as it appears in config, e.g. `'no-raw-text'`.
|
|
18
|
+
* @param rule - The rule definition.
|
|
19
|
+
*/
|
|
20
|
+
declare const createRule: <Options extends readonly unknown[] = [], MessageIds extends string = string>(name: string, rule: IntlayerRule<Options, MessageIds>) => IntlayerRule<Options, MessageIds>;
|
|
21
|
+
//#endregion
|
|
22
|
+
export { IntlayerRule, createRule };
|
|
23
|
+
//# sourceMappingURL=createRule.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"createRule.d.ts","names":[],"sources":["../../../src/utils/createRule.ts"],"mappings":";;;;;;;KAUY,aACV,yCACA,sCACE;EACF,iBAAiB;EACjB,YAAY;;;;;;;;;;cAWD,aACX,yCACA,oCAEA,cACA,MAAM,aAAa,SAAS,gBAC3B,aAAa,SAAS"}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { AstNode, AstNodeWithParent } from "./ast.js";
|
|
2
|
+
//#region src/utils/templates.d.ts
|
|
3
|
+
/**
|
|
4
|
+
* Template-language support for text-coverage rules.
|
|
5
|
+
*
|
|
6
|
+
* Intlayer ships integrations for React, Vue, Svelte, Angular, Astro, Solid,
|
|
7
|
+
* Preact and Lit, and each ecosystem's ESLint parser models markup differently:
|
|
8
|
+
*
|
|
9
|
+
* | Framework | Parser | Text node | Attribute node | Lives in |
|
|
10
|
+
* | --------- | ------------------------------- | ----------- | --------------- | --------------- |
|
|
11
|
+
* | React/JSX | espree, typescript-eslint | `JSXText` | `JSXAttribute` | main AST |
|
|
12
|
+
* | Astro | astro-eslint-parser | `JSXText` | `JSXAttribute` | main AST |
|
|
13
|
+
* | Svelte | svelte-eslint-parser | `SvelteText`| `SvelteAttribute`| main AST |
|
|
14
|
+
* | Angular | @angular-eslint/template-parser | `Text` | `TextAttribute` | main AST |
|
|
15
|
+
* | Vue | vue-eslint-parser | `VText` | `VAttribute` | `templateBody` |
|
|
16
|
+
*
|
|
17
|
+
* Vue is the only one that hides its template behind
|
|
18
|
+
* `parserServices.defineTemplateBodyVisitor`; every other parser exposes markup
|
|
19
|
+
* in the AST a normal visitor already walks. Node type names do not collide
|
|
20
|
+
* across parsers, so one merged visitor is safe to hand to any of them — a
|
|
21
|
+
* Svelte file simply never emits a `VText`.
|
|
22
|
+
*/
|
|
23
|
+
/** One piece of literal text found in markup. */
|
|
24
|
+
type TemplateTextFinding = {
|
|
25
|
+
/** Node to report on. */
|
|
26
|
+
node: AstNode;
|
|
27
|
+
/** The literal text. */
|
|
28
|
+
text: string;
|
|
29
|
+
/** Attribute name when the text is an attribute value, else `null`. */
|
|
30
|
+
attributeName: string | null;
|
|
31
|
+
/** Tag name of the enclosing element, when resolvable. */
|
|
32
|
+
elementName: string | null;
|
|
33
|
+
};
|
|
34
|
+
/** Callback invoked for every literal text node the visitors find. */
|
|
35
|
+
type TemplateTextReporter = (finding: TemplateTextFinding) => void;
|
|
36
|
+
/** A visitor object as returned from a rule's `create`. */
|
|
37
|
+
type TemplateVisitor = Record<string, (node: never) => void>;
|
|
38
|
+
/**
|
|
39
|
+
* Walk up to the nearest enclosing element and return its tag name, or the
|
|
40
|
+
* sentinel `false` when the text sits inside a `<script>`/`<style>` block that
|
|
41
|
+
* should never be reported.
|
|
42
|
+
*
|
|
43
|
+
* @param node - The text or attribute node to start from.
|
|
44
|
+
*/
|
|
45
|
+
declare const resolveEnclosingElement: (node: AstNodeWithParent | undefined) => string | null | false;
|
|
46
|
+
/**
|
|
47
|
+
* Visitors covering every non-JSX template language Intlayer supports.
|
|
48
|
+
*
|
|
49
|
+
* JSX (React, Preact, Solid, Astro, Lit templates compiled through JSX) is
|
|
50
|
+
* handled by the rule itself, since `JSXText`/`JSXAttribute` also need the
|
|
51
|
+
* non-template code paths.
|
|
52
|
+
*
|
|
53
|
+
* @param report - Called for each literal text node found.
|
|
54
|
+
*/
|
|
55
|
+
declare const createTemplateVisitor: (report: TemplateTextReporter) => TemplateVisitor;
|
|
56
|
+
/**
|
|
57
|
+
* Combine a script visitor and a template visitor into the single object the
|
|
58
|
+
* active parser expects.
|
|
59
|
+
*
|
|
60
|
+
* Vue keeps its template in a side AST reachable only through
|
|
61
|
+
* `defineTemplateBodyVisitor`; every other parser puts markup in the main AST,
|
|
62
|
+
* where a merged visitor object is enough.
|
|
63
|
+
*
|
|
64
|
+
* @param parserServices - `context.sourceCode.parserServices`.
|
|
65
|
+
* @param scriptVisitor - Handlers for ordinary script nodes.
|
|
66
|
+
* @param templateVisitor - Handlers for markup nodes.
|
|
67
|
+
*/
|
|
68
|
+
declare const combineVisitors: (parserServices: unknown, scriptVisitor: TemplateVisitor, templateVisitor: TemplateVisitor) => TemplateVisitor;
|
|
69
|
+
//#endregion
|
|
70
|
+
export { TemplateTextFinding, TemplateTextReporter, TemplateVisitor, combineVisitors, createTemplateVisitor, resolveEnclosingElement };
|
|
71
|
+
//# sourceMappingURL=templates.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"templates.d.ts","names":[],"sources":["../../../src/utils/templates.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;KAwBY;;EAEV,MAAM;;EAEN;;EAEA;;EAEA;;;KAIU,wBAAwB,SAAS;;KAGjC,kBAAkB,gBAAgB;;;;;;;;cAsDjC,0BACX,MAAM;;;;;;;;;;cA6CK,wBACX,QAAQ,yBACP;;;;;;;;;;;;;cAgGU,kBACX,yBACA,eAAe,iBACf,iBAAiB,oBAChB"}
|