@better-i18n/cli 0.1.7 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/analyzer/dynamic-matcher.d.ts +53 -0
- package/dist/analyzer/dynamic-matcher.d.ts.map +1 -0
- package/dist/analyzer/dynamic-matcher.js +93 -0
- package/dist/analyzer/dynamic-matcher.js.map +1 -0
- package/dist/analyzer/index.d.ts +13 -4
- package/dist/analyzer/index.d.ts.map +1 -1
- package/dist/analyzer/index.js +165 -22
- package/dist/analyzer/index.js.map +1 -1
- package/dist/analyzer/rules/data-structure.d.ts +13 -0
- package/dist/analyzer/rules/data-structure.d.ts.map +1 -0
- package/dist/analyzer/rules/data-structure.js +264 -0
- package/dist/analyzer/rules/data-structure.js.map +1 -0
- package/dist/analyzer/rules/index.d.ts +10 -7
- package/dist/analyzer/rules/index.d.ts.map +1 -1
- package/dist/analyzer/rules/index.js +10 -7
- package/dist/analyzer/rules/index.js.map +1 -1
- package/dist/analyzer/rules/jsx-text.js +1 -1
- package/dist/analyzer/rules/jsx-text.js.map +1 -1
- package/dist/analyzer/rules/translation-function.d.ts.map +1 -1
- package/dist/analyzer/rules/translation-function.js +94 -6
- package/dist/analyzer/rules/translation-function.js.map +1 -1
- package/dist/analyzer/types.d.ts +28 -3
- package/dist/analyzer/types.d.ts.map +1 -1
- package/dist/commands/check.d.ts +14 -0
- package/dist/commands/check.d.ts.map +1 -0
- package/dist/commands/check.js +70 -0
- package/dist/commands/check.js.map +1 -0
- package/dist/commands/doctor.d.ts +20 -0
- package/dist/commands/doctor.d.ts.map +1 -0
- package/dist/commands/doctor.js +77 -0
- package/dist/commands/doctor.js.map +1 -0
- package/dist/commands/sync.d.ts +1 -0
- package/dist/commands/sync.d.ts.map +1 -1
- package/dist/commands/sync.js +158 -272
- package/dist/commands/sync.js.map +1 -1
- package/dist/context/detector.js +1 -1
- package/dist/context/detector.js.map +1 -1
- package/dist/context/oidc.d.ts +33 -0
- package/dist/context/oidc.d.ts.map +1 -0
- package/dist/context/oidc.js +63 -0
- package/dist/context/oidc.js.map +1 -0
- package/dist/doctor/index.d.ts +65 -0
- package/dist/doctor/index.d.ts.map +1 -0
- package/dist/doctor/index.js +252 -0
- package/dist/doctor/index.js.map +1 -0
- package/dist/doctor/project-discovery.d.ts +37 -0
- package/dist/doctor/project-discovery.d.ts.map +1 -0
- package/dist/doctor/project-discovery.js +140 -0
- package/dist/doctor/project-discovery.js.map +1 -0
- package/dist/doctor/score.d.ts +41 -0
- package/dist/doctor/score.d.ts.map +1 -0
- package/dist/doctor/score.js +107 -0
- package/dist/doctor/score.js.map +1 -0
- package/dist/index.js +35 -0
- package/dist/index.js.map +1 -1
- package/dist/reporters/doctor-eslint.d.ts +17 -0
- package/dist/reporters/doctor-eslint.d.ts.map +1 -0
- package/dist/reporters/doctor-eslint.js +139 -0
- package/dist/reporters/doctor-eslint.js.map +1 -0
- package/dist/reporters/doctor-json.d.ts +12 -0
- package/dist/reporters/doctor-json.d.ts.map +1 -0
- package/dist/reporters/doctor-json.js +21 -0
- package/dist/reporters/doctor-json.js.map +1 -0
- package/dist/reporters/doctor-report.d.ts +23 -0
- package/dist/reporters/doctor-report.d.ts.map +1 -0
- package/dist/reporters/doctor-report.js +62 -0
- package/dist/reporters/doctor-report.js.map +1 -0
- package/dist/reporters/eslint-style.js +4 -4
- package/dist/reporters/eslint-style.js.map +1 -1
- package/dist/rules/categories.d.ts +36 -0
- package/dist/rules/categories.d.ts.map +1 -0
- package/dist/rules/categories.js +80 -0
- package/dist/rules/categories.js.map +1 -0
- package/dist/rules/code/index.d.ts +9 -0
- package/dist/rules/code/index.d.ts.map +1 -0
- package/dist/rules/code/index.js +9 -0
- package/dist/rules/code/index.js.map +1 -0
- package/dist/rules/code/jsx-attribute.d.ts +12 -0
- package/dist/rules/code/jsx-attribute.d.ts.map +1 -0
- package/dist/rules/code/jsx-attribute.js +78 -0
- package/dist/rules/code/jsx-attribute.js.map +1 -0
- package/dist/rules/code/jsx-text.d.ts +12 -0
- package/dist/rules/code/jsx-text.d.ts.map +1 -0
- package/dist/rules/code/jsx-text.js +47 -0
- package/dist/rules/code/jsx-text.js.map +1 -0
- package/dist/rules/code/string-variable.d.ts +13 -0
- package/dist/rules/code/string-variable.d.ts.map +1 -0
- package/dist/rules/code/string-variable.js +185 -0
- package/dist/rules/code/string-variable.js.map +1 -0
- package/dist/rules/code/ternary-locale.d.ts +12 -0
- package/dist/rules/code/ternary-locale.d.ts.map +1 -0
- package/dist/rules/code/ternary-locale.js +53 -0
- package/dist/rules/code/ternary-locale.js.map +1 -0
- package/dist/rules/code/toast-message.d.ts +13 -0
- package/dist/rules/code/toast-message.d.ts.map +1 -0
- package/dist/rules/code/toast-message.js +101 -0
- package/dist/rules/code/toast-message.js.map +1 -0
- package/dist/rules/extraction/data-structure.d.ts +13 -0
- package/dist/rules/extraction/data-structure.d.ts.map +1 -0
- package/dist/rules/extraction/data-structure.js +212 -0
- package/dist/rules/extraction/data-structure.js.map +1 -0
- package/dist/rules/extraction/index.d.ts +6 -0
- package/dist/rules/extraction/index.d.ts.map +1 -0
- package/dist/rules/extraction/index.js +6 -0
- package/dist/rules/extraction/index.js.map +1 -0
- package/dist/rules/extraction/translation-function.d.ts +12 -0
- package/dist/rules/extraction/translation-function.d.ts.map +1 -0
- package/dist/rules/extraction/translation-function.js +153 -0
- package/dist/rules/extraction/translation-function.js.map +1 -0
- package/dist/rules/health/index.d.ts +16 -0
- package/dist/rules/health/index.d.ts.map +1 -0
- package/dist/rules/health/index.js +22 -0
- package/dist/rules/health/index.js.map +1 -0
- package/dist/rules/health/missing-translations.d.ts +13 -0
- package/dist/rules/health/missing-translations.d.ts.map +1 -0
- package/dist/rules/health/missing-translations.js +48 -0
- package/dist/rules/health/missing-translations.js.map +1 -0
- package/dist/rules/health/orphan-keys.d.ts +16 -0
- package/dist/rules/health/orphan-keys.d.ts.map +1 -0
- package/dist/rules/health/orphan-keys.js +50 -0
- package/dist/rules/health/orphan-keys.js.map +1 -0
- package/dist/rules/health/placeholder-mismatch.d.ts +32 -0
- package/dist/rules/health/placeholder-mismatch.d.ts.map +1 -0
- package/dist/rules/health/placeholder-mismatch.js +120 -0
- package/dist/rules/health/placeholder-mismatch.js.map +1 -0
- package/dist/rules/registry.d.ts +84 -0
- package/dist/rules/registry.d.ts.map +1 -0
- package/dist/rules/registry.js +11 -0
- package/dist/rules/registry.js.map +1 -0
- package/dist/utils/cdn-client.d.ts +30 -0
- package/dist/utils/cdn-client.d.ts.map +1 -0
- package/dist/utils/cdn-client.js +59 -0
- package/dist/utils/cdn-client.js.map +1 -0
- package/dist/utils/json-keys.d.ts +60 -0
- package/dist/utils/json-keys.d.ts.map +1 -0
- package/dist/utils/json-keys.js +103 -0
- package/dist/utils/json-keys.js.map +1 -0
- package/dist/utils/key-comparison.d.ts +67 -0
- package/dist/utils/key-comparison.d.ts.map +1 -0
- package/dist/utils/key-comparison.js +299 -0
- package/dist/utils/key-comparison.js.map +1 -0
- package/package.json +2 -2
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rule Metadata — Category and Help Maps
|
|
3
|
+
*
|
|
4
|
+
* Centralized metadata for all i18n Doctor rules.
|
|
5
|
+
* Following react-doctor's pattern: rules produce only `message`,
|
|
6
|
+
* category and help text are external for central management.
|
|
7
|
+
*
|
|
8
|
+
* To add a new rule:
|
|
9
|
+
* 1. Add entry to RULE_CATEGORY_MAP
|
|
10
|
+
* 2. Add entry to RULE_HELP_MAP
|
|
11
|
+
* 3. Implement the rule in src/rules/health/ or src/rules/code/
|
|
12
|
+
*/
|
|
13
|
+
/**
|
|
14
|
+
* Maps rule ID → category.
|
|
15
|
+
*
|
|
16
|
+
* react-doctor uses RULE_CATEGORY_MAP + PLUGIN_CATEGORY_MAP with fallback.
|
|
17
|
+
* We simplify to a single map since all rules are "better-i18n" plugin.
|
|
18
|
+
*/
|
|
19
|
+
export const RULE_CATEGORY_MAP = {
|
|
20
|
+
// ── Health Rules (translation file analysis) ──────────────────────
|
|
21
|
+
"missing-translations": "Coverage",
|
|
22
|
+
"orphan-keys": "Performance",
|
|
23
|
+
"placeholder-mismatch": "Quality",
|
|
24
|
+
"empty-translations": "Quality",
|
|
25
|
+
"key-naming": "Structure",
|
|
26
|
+
"namespace-structure": "Structure",
|
|
27
|
+
"file-format": "Structure",
|
|
28
|
+
// ── Code Rules (AST analysis, from existing analyzer) ─────────────
|
|
29
|
+
"jsx-text": "Code",
|
|
30
|
+
"jsx-attribute": "Code",
|
|
31
|
+
"ternary-locale": "Code",
|
|
32
|
+
"toast-message": "Code",
|
|
33
|
+
"string-variable": "Code",
|
|
34
|
+
};
|
|
35
|
+
/**
|
|
36
|
+
* Maps rule ID → actionable fix suggestion.
|
|
37
|
+
*
|
|
38
|
+
* react-doctor stores these in RULE_HELP_MAP in run-oxlint.ts.
|
|
39
|
+
* We centralize them here alongside categories.
|
|
40
|
+
*/
|
|
41
|
+
export const RULE_HELP_MAP = {
|
|
42
|
+
// ── Health Rules ──────────────────────────────────────────────────
|
|
43
|
+
"missing-translations": "Add translations for missing keys using the AI Drawer or manual entry in the editor",
|
|
44
|
+
"orphan-keys": "Remove unused keys from translation files to reduce bundle size and maintenance cost",
|
|
45
|
+
"placeholder-mismatch": "Ensure {placeholders} in source and target translations match exactly. Missing or extra placeholders cause runtime errors",
|
|
46
|
+
"empty-translations": "Replace empty translation strings with actual translations, or remove the key if unused",
|
|
47
|
+
"key-naming": "Use consistent naming convention within each namespace (e.g., camelCase or snake_case, not mixed)",
|
|
48
|
+
"namespace-structure": "Maintain consistent namespace depth across translation files for predictable key organization",
|
|
49
|
+
"file-format": "Fix malformed JSON or encoding issues in translation files",
|
|
50
|
+
// ── Code Rules ────────────────────────────────────────────────────
|
|
51
|
+
"jsx-text": "Wrap hardcoded JSX text with t() function call for translation support",
|
|
52
|
+
"jsx-attribute": "Use t() for translatable attributes like title, alt, placeholder, aria-label",
|
|
53
|
+
"ternary-locale": "Replace locale-based ternary (locale === 'en' ? 'Hello' : 'Merhaba') with t() function",
|
|
54
|
+
"toast-message": "Wrap toast notification messages with t() for translation support",
|
|
55
|
+
"string-variable": "Extract user-facing string variables into translation keys using t()",
|
|
56
|
+
};
|
|
57
|
+
/**
|
|
58
|
+
* Default severity for code rules.
|
|
59
|
+
*
|
|
60
|
+
* Health rules define defaultSeverity on the rule object itself.
|
|
61
|
+
* Code rules (from the analyzer) have severity embedded in their output,
|
|
62
|
+
* but this map provides fallback/override capability.
|
|
63
|
+
*/
|
|
64
|
+
export const RULE_DEFAULT_SEVERITY = {
|
|
65
|
+
// Health rules (also defined on rule object, this is for reference)
|
|
66
|
+
"missing-translations": "error",
|
|
67
|
+
"orphan-keys": "warning",
|
|
68
|
+
"placeholder-mismatch": "error",
|
|
69
|
+
"empty-translations": "warning",
|
|
70
|
+
"key-naming": "warning",
|
|
71
|
+
"namespace-structure": "info",
|
|
72
|
+
"file-format": "error",
|
|
73
|
+
// Code rules
|
|
74
|
+
"jsx-text": "warning",
|
|
75
|
+
"jsx-attribute": "warning",
|
|
76
|
+
"ternary-locale": "error",
|
|
77
|
+
"toast-message": "warning",
|
|
78
|
+
"string-variable": "warning",
|
|
79
|
+
};
|
|
80
|
+
//# sourceMappingURL=categories.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"categories.js","sourceRoot":"","sources":["../../src/rules/categories.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAIH;;;;;GAKG;AACH,MAAM,CAAC,MAAM,iBAAiB,GAA+C;IAC3E,qEAAqE;IACrE,sBAAsB,EAAE,UAAU;IAClC,aAAa,EAAE,aAAa;IAC5B,sBAAsB,EAAE,SAAS;IACjC,oBAAoB,EAAE,SAAS;IAC/B,YAAY,EAAE,WAAW;IACzB,qBAAqB,EAAE,WAAW;IAClC,aAAa,EAAE,WAAW;IAE1B,qEAAqE;IACrE,UAAU,EAAE,MAAM;IAClB,eAAe,EAAE,MAAM;IACvB,gBAAgB,EAAE,MAAM;IACxB,eAAe,EAAE,MAAM;IACvB,iBAAiB,EAAE,MAAM;CAC1B,CAAC;AAEF;;;;;GAKG;AACH,MAAM,CAAC,MAAM,aAAa,GAA2B;IACnD,qEAAqE;IACrE,sBAAsB,EACpB,qFAAqF;IACvF,aAAa,EACX,sFAAsF;IACxF,sBAAsB,EACpB,2HAA2H;IAC7H,oBAAoB,EAClB,yFAAyF;IAC3F,YAAY,EACV,mGAAmG;IACrG,qBAAqB,EACnB,+FAA+F;IACjG,aAAa,EACX,4DAA4D;IAE9D,qEAAqE;IACrE,UAAU,EACR,wEAAwE;IAC1E,eAAe,EACb,8EAA8E;IAChF,gBAAgB,EACd,wFAAwF;IAC1F,eAAe,EACb,mEAAmE;IACrE,iBAAiB,EACf,sEAAsE;CACzE,CAAC;AAEF;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,qBAAqB,GAA+C;IAC/E,oEAAoE;IACpE,sBAAsB,EAAE,OAAO;IAC/B,aAAa,EAAE,SAAS;IACxB,sBAAsB,EAAE,OAAO;IAC/B,oBAAoB,EAAE,SAAS;IAC/B,YAAY,EAAE,SAAS;IACvB,qBAAqB,EAAE,MAAM;IAC7B,aAAa,EAAE,OAAO;IAEtB,aAAa;IACb,UAAU,EAAE,SAAS;IACrB,eAAe,EAAE,SAAS;IAC1B,gBAAgB,EAAE,OAAO;IACzB,eAAe,EAAE,SAAS;IAC1B,iBAAiB,EAAE,SAAS;CAC7B,CAAC"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Code detection rules — detect hardcoded strings in source code
|
|
3
|
+
*/
|
|
4
|
+
export { checkJsxText } from "./jsx-text.js";
|
|
5
|
+
export { checkJsxAttribute } from "./jsx-attribute.js";
|
|
6
|
+
export { checkTernaryLocale } from "./ternary-locale.js";
|
|
7
|
+
export { checkToastMessage } from "./toast-message.js";
|
|
8
|
+
export { checkStringVariable } from "./string-variable.js";
|
|
9
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/rules/code/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAC7C,OAAO,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AACvD,OAAO,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;AACzD,OAAO,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AACvD,OAAO,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAC"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Code detection rules — detect hardcoded strings in source code
|
|
3
|
+
*/
|
|
4
|
+
export { checkJsxText } from "./jsx-text.js";
|
|
5
|
+
export { checkJsxAttribute } from "./jsx-attribute.js";
|
|
6
|
+
export { checkTernaryLocale } from "./ternary-locale.js";
|
|
7
|
+
export { checkToastMessage } from "./toast-message.js";
|
|
8
|
+
export { checkStringVariable } from "./string-variable.js";
|
|
9
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/rules/code/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAC7C,OAAO,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AACvD,OAAO,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;AACzD,OAAO,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AACvD,OAAO,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAC"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* JSX Attribute detection rule
|
|
3
|
+
*
|
|
4
|
+
* Detects hardcoded strings in title, alt, placeholder, etc.
|
|
5
|
+
*/
|
|
6
|
+
import ts from "typescript";
|
|
7
|
+
import type { Issue, RuleContext } from "../../analyzer/types.js";
|
|
8
|
+
/**
|
|
9
|
+
* Check JSX attribute for hardcoded strings
|
|
10
|
+
*/
|
|
11
|
+
export declare function checkJsxAttribute(node: ts.JsxAttribute, ctx: RuleContext): Issue | null;
|
|
12
|
+
//# sourceMappingURL=jsx-attribute.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"jsx-attribute.d.ts","sourceRoot":"","sources":["../../../src/rules/code/jsx-attribute.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,MAAM,YAAY,CAAC;AAE5B,OAAO,KAAK,EAAE,KAAK,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AA8BlE;;GAEG;AACH,wBAAgB,iBAAiB,CAC/B,IAAI,EAAE,EAAE,CAAC,YAAY,EACrB,GAAG,EAAE,WAAW,GACf,KAAK,GAAG,IAAI,CA6Cd"}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* JSX Attribute detection rule
|
|
3
|
+
*
|
|
4
|
+
* Detects hardcoded strings in title, alt, placeholder, etc.
|
|
5
|
+
*/
|
|
6
|
+
import ts from "typescript";
|
|
7
|
+
import { generateKeyFromContext, truncate } from "../../utils/text.js";
|
|
8
|
+
/**
|
|
9
|
+
* Attributes to check for hardcoded strings
|
|
10
|
+
*/
|
|
11
|
+
const CHECK_ATTRIBUTES = new Set([
|
|
12
|
+
"title",
|
|
13
|
+
"alt",
|
|
14
|
+
"placeholder",
|
|
15
|
+
"aria-label",
|
|
16
|
+
"label",
|
|
17
|
+
]);
|
|
18
|
+
/**
|
|
19
|
+
* Attributes to ignore (never flag these)
|
|
20
|
+
*/
|
|
21
|
+
const IGNORE_ATTRIBUTES = new Set([
|
|
22
|
+
"className",
|
|
23
|
+
"class",
|
|
24
|
+
"id",
|
|
25
|
+
"key",
|
|
26
|
+
"ref",
|
|
27
|
+
"data-testid",
|
|
28
|
+
"href",
|
|
29
|
+
"src",
|
|
30
|
+
"name",
|
|
31
|
+
"type",
|
|
32
|
+
"role",
|
|
33
|
+
]);
|
|
34
|
+
/**
|
|
35
|
+
* Check JSX attribute for hardcoded strings
|
|
36
|
+
*/
|
|
37
|
+
export function checkJsxAttribute(node, ctx) {
|
|
38
|
+
const attrName = node.name.getText();
|
|
39
|
+
// Skip ignored attributes
|
|
40
|
+
if (IGNORE_ATTRIBUTES.has(attrName))
|
|
41
|
+
return null;
|
|
42
|
+
// Only check specific attributes
|
|
43
|
+
if (!CHECK_ATTRIBUTES.has(attrName))
|
|
44
|
+
return null;
|
|
45
|
+
// Get the value
|
|
46
|
+
const value = node.initializer;
|
|
47
|
+
if (!value)
|
|
48
|
+
return null;
|
|
49
|
+
let text = null;
|
|
50
|
+
// String literal: title="Hello"
|
|
51
|
+
if (ts.isStringLiteral(value)) {
|
|
52
|
+
text = value.text;
|
|
53
|
+
}
|
|
54
|
+
// JSX expression with string: title={"Hello"}
|
|
55
|
+
if (ts.isJsxExpression(value) && value.expression) {
|
|
56
|
+
if (ts.isStringLiteral(value.expression)) {
|
|
57
|
+
text = value.expression.text;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
// No text found or too short
|
|
61
|
+
if (!text || text.length <= 2)
|
|
62
|
+
return null;
|
|
63
|
+
// Skip URLs
|
|
64
|
+
if (text.startsWith("http") || text.startsWith("/"))
|
|
65
|
+
return null;
|
|
66
|
+
const pos = ctx.sourceFile.getLineAndCharacterOfPosition(node.getStart());
|
|
67
|
+
return {
|
|
68
|
+
file: ctx.filePath,
|
|
69
|
+
line: pos.line + 1,
|
|
70
|
+
column: pos.character + 1,
|
|
71
|
+
text,
|
|
72
|
+
type: "jsx-attribute",
|
|
73
|
+
severity: "warning",
|
|
74
|
+
message: `Hardcoded ${attrName}: "${truncate(text, 40)}"`,
|
|
75
|
+
suggestedKey: generateKeyFromContext(text, ctx.filePath),
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
//# sourceMappingURL=jsx-attribute.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"jsx-attribute.js","sourceRoot":"","sources":["../../../src/rules/code/jsx-attribute.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,MAAM,YAAY,CAAC;AAC5B,OAAO,EAAE,sBAAsB,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAGvE;;GAEG;AACH,MAAM,gBAAgB,GAAG,IAAI,GAAG,CAAC;IAC/B,OAAO;IACP,KAAK;IACL,aAAa;IACb,YAAY;IACZ,OAAO;CACR,CAAC,CAAC;AAEH;;GAEG;AACH,MAAM,iBAAiB,GAAG,IAAI,GAAG,CAAC;IAChC,WAAW;IACX,OAAO;IACP,IAAI;IACJ,KAAK;IACL,KAAK;IACL,aAAa;IACb,MAAM;IACN,KAAK;IACL,MAAM;IACN,MAAM;IACN,MAAM;CACP,CAAC,CAAC;AAEH;;GAEG;AACH,MAAM,UAAU,iBAAiB,CAC/B,IAAqB,EACrB,GAAgB;IAEhB,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC;IAErC,0BAA0B;IAC1B,IAAI,iBAAiB,CAAC,GAAG,CAAC,QAAQ,CAAC;QAAE,OAAO,IAAI,CAAC;IAEjD,iCAAiC;IACjC,IAAI,CAAC,gBAAgB,CAAC,GAAG,CAAC,QAAQ,CAAC;QAAE,OAAO,IAAI,CAAC;IAEjD,gBAAgB;IAChB,MAAM,KAAK,GAAG,IAAI,CAAC,WAAW,CAAC;IAC/B,IAAI,CAAC,KAAK;QAAE,OAAO,IAAI,CAAC;IAExB,IAAI,IAAI,GAAkB,IAAI,CAAC;IAE/B,gCAAgC;IAChC,IAAI,EAAE,CAAC,eAAe,CAAC,KAAK,CAAC,EAAE,CAAC;QAC9B,IAAI,GAAG,KAAK,CAAC,IAAI,CAAC;IACpB,CAAC;IAED,8CAA8C;IAC9C,IAAI,EAAE,CAAC,eAAe,CAAC,KAAK,CAAC,IAAI,KAAK,CAAC,UAAU,EAAE,CAAC;QAClD,IAAI,EAAE,CAAC,eAAe,CAAC,KAAK,CAAC,UAAU,CAAC,EAAE,CAAC;YACzC,IAAI,GAAG,KAAK,CAAC,UAAU,CAAC,IAAI,CAAC;QAC/B,CAAC;IACH,CAAC;IAED,6BAA6B;IAC7B,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,MAAM,IAAI,CAAC;QAAE,OAAO,IAAI,CAAC;IAE3C,YAAY;IACZ,IAAI,IAAI,CAAC,UAAU,CAAC,MAAM,CAAC,IAAI,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC;QAAE,OAAO,IAAI,CAAC;IAEjE,MAAM,GAAG,GAAG,GAAG,CAAC,UAAU,CAAC,6BAA6B,CAAC,IAAI,CAAC,QAAQ,EAAE,CAAC,CAAC;IAE1E,OAAO;QACL,IAAI,EAAE,GAAG,CAAC,QAAQ;QAClB,IAAI,EAAE,GAAG,CAAC,IAAI,GAAG,CAAC;QAClB,MAAM,EAAE,GAAG,CAAC,SAAS,GAAG,CAAC;QACzB,IAAI;QACJ,IAAI,EAAE,eAAe;QACrB,QAAQ,EAAE,SAAS;QACnB,OAAO,EAAE,aAAa,QAAQ,MAAM,QAAQ,CAAC,IAAI,EAAE,EAAE,CAAC,GAAG;QACzD,YAAY,EAAE,sBAAsB,CAAC,IAAI,EAAE,GAAG,CAAC,QAAQ,CAAC;KACzD,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* JSX Text detection rule
|
|
3
|
+
*
|
|
4
|
+
* Detects hardcoded text content in JSX elements
|
|
5
|
+
*/
|
|
6
|
+
import ts from "typescript";
|
|
7
|
+
import type { Issue, RuleContext } from "../../analyzer/types.js";
|
|
8
|
+
/**
|
|
9
|
+
* Check JSX text node for hardcoded strings
|
|
10
|
+
*/
|
|
11
|
+
export declare function checkJsxText(node: ts.JsxText, ctx: RuleContext): Issue | null;
|
|
12
|
+
//# sourceMappingURL=jsx-text.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"jsx-text.d.ts","sourceRoot":"","sources":["../../../src/rules/code/jsx-text.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,MAAM,YAAY,CAAC;AAE5B,OAAO,KAAK,EAAE,KAAK,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AAiBlE;;GAEG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE,WAAW,GAAG,KAAK,GAAG,IAAI,CAwB7E"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* JSX Text detection rule
|
|
3
|
+
*
|
|
4
|
+
* Detects hardcoded text content in JSX elements
|
|
5
|
+
*/
|
|
6
|
+
import { generateKeyFromContext, truncate } from "../../utils/text.js";
|
|
7
|
+
/**
|
|
8
|
+
* Default patterns to ignore
|
|
9
|
+
*/
|
|
10
|
+
const IGNORE_PATTERNS = [
|
|
11
|
+
/^[\s\n\r\t]+$/, // Whitespace only
|
|
12
|
+
/^[→←↑↓★•·\-–—/\\|,;:.!?()[\]{}]+$/, // Symbols only
|
|
13
|
+
/^\d+[+%KkMm]?$/, // Numbers with optional suffix
|
|
14
|
+
/^[A-Z_]+$/, // SCREAMING_CASE
|
|
15
|
+
/^https?:\/\//, // URLs
|
|
16
|
+
/^\//, // Paths
|
|
17
|
+
/^[a-z-]+$/, // CSS-like (lowercase with hyphens only)
|
|
18
|
+
/^&[a-z]+;$/, // HTML entities like " &
|
|
19
|
+
/^&#\d+;$/, // Numeric HTML entities like '
|
|
20
|
+
];
|
|
21
|
+
/**
|
|
22
|
+
* Check JSX text node for hardcoded strings
|
|
23
|
+
*/
|
|
24
|
+
export function checkJsxText(node, ctx) {
|
|
25
|
+
const text = node.text.trim();
|
|
26
|
+
// Skip empty or very short
|
|
27
|
+
if (!text || text.length <= 2)
|
|
28
|
+
return null;
|
|
29
|
+
// Skip ignored patterns
|
|
30
|
+
for (const pattern of IGNORE_PATTERNS) {
|
|
31
|
+
if (pattern.test(text))
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
// Get position
|
|
35
|
+
const pos = ctx.sourceFile.getLineAndCharacterOfPosition(node.getStart());
|
|
36
|
+
return {
|
|
37
|
+
file: ctx.filePath,
|
|
38
|
+
line: pos.line + 1,
|
|
39
|
+
column: pos.character + 1,
|
|
40
|
+
text,
|
|
41
|
+
type: "jsx-text",
|
|
42
|
+
severity: "warning",
|
|
43
|
+
message: `Hardcoded text: "${truncate(text, 40)}"`,
|
|
44
|
+
suggestedKey: generateKeyFromContext(text, ctx.filePath),
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
//# sourceMappingURL=jsx-text.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"jsx-text.js","sourceRoot":"","sources":["../../../src/rules/code/jsx-text.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,OAAO,EAAE,sBAAsB,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAGvE;;GAEG;AACH,MAAM,eAAe,GAAG;IACtB,eAAe,EAAE,kBAAkB;IACnC,mCAAmC,EAAE,eAAe;IACpD,gBAAgB,EAAE,+BAA+B;IACjD,WAAW,EAAE,iBAAiB;IAC9B,cAAc,EAAE,OAAO;IACvB,KAAK,EAAE,QAAQ;IACf,WAAW,EAAE,yCAAyC;IACtD,YAAY,EAAE,yCAAyC;IACvD,UAAU,EAAE,mCAAmC;CAChD,CAAC;AAEF;;GAEG;AACH,MAAM,UAAU,YAAY,CAAC,IAAgB,EAAE,GAAgB;IAC7D,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC;IAE9B,2BAA2B;IAC3B,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,MAAM,IAAI,CAAC;QAAE,OAAO,IAAI,CAAC;IAE3C,wBAAwB;IACxB,KAAK,MAAM,OAAO,IAAI,eAAe,EAAE,CAAC;QACtC,IAAI,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC;YAAE,OAAO,IAAI,CAAC;IACtC,CAAC;IAED,eAAe;IACf,MAAM,GAAG,GAAG,GAAG,CAAC,UAAU,CAAC,6BAA6B,CAAC,IAAI,CAAC,QAAQ,EAAE,CAAC,CAAC;IAE1E,OAAO;QACL,IAAI,EAAE,GAAG,CAAC,QAAQ;QAClB,IAAI,EAAE,GAAG,CAAC,IAAI,GAAG,CAAC;QAClB,MAAM,EAAE,GAAG,CAAC,SAAS,GAAG,CAAC;QACzB,IAAI;QACJ,IAAI,EAAE,UAAU;QAChB,QAAQ,EAAE,SAAS;QACnB,OAAO,EAAE,oBAAoB,QAAQ,CAAC,IAAI,EAAE,EAAE,CAAC,GAAG;QAClD,YAAY,EAAE,sBAAsB,CAAC,IAAI,EAAE,GAAG,CAAC,QAAQ,CAAC;KACzD,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* String variable detection rule
|
|
3
|
+
*
|
|
4
|
+
* Detects hardcoded user-facing strings assigned to variables
|
|
5
|
+
* Uses strict filtering to avoid false positives
|
|
6
|
+
*/
|
|
7
|
+
import ts from "typescript";
|
|
8
|
+
import type { Issue, RuleContext } from "../../analyzer/types.js";
|
|
9
|
+
/**
|
|
10
|
+
* Check for string variable assignments with hardcoded user-facing text
|
|
11
|
+
*/
|
|
12
|
+
export declare function checkStringVariable(node: ts.VariableDeclaration, ctx: RuleContext): Issue | null;
|
|
13
|
+
//# sourceMappingURL=string-variable.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"string-variable.d.ts","sourceRoot":"","sources":["../../../src/rules/code/string-variable.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,MAAM,YAAY,CAAC;AAE5B,OAAO,KAAK,EAAE,KAAK,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AA0ElE;;GAEG;AACH,wBAAgB,mBAAmB,CACjC,IAAI,EAAE,EAAE,CAAC,mBAAmB,EAC5B,GAAG,EAAE,WAAW,GACf,KAAK,GAAG,IAAI,CA6Cd"}
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* String variable detection rule
|
|
3
|
+
*
|
|
4
|
+
* Detects hardcoded user-facing strings assigned to variables
|
|
5
|
+
* Uses strict filtering to avoid false positives
|
|
6
|
+
*/
|
|
7
|
+
import ts from "typescript";
|
|
8
|
+
import { generateKeyFromContext, truncate } from "../../utils/text.js";
|
|
9
|
+
/**
|
|
10
|
+
* Patterns to IGNORE (not user-facing text)
|
|
11
|
+
*/
|
|
12
|
+
const IGNORE_PATTERNS = [
|
|
13
|
+
/^[A-Z_][A-Z0-9_]*$/, // SCREAMING_CASE constants
|
|
14
|
+
/^[a-z][a-z0-9]*$/, // Single lowercase word (identifiers)
|
|
15
|
+
/^[a-z-]+$/, // CSS-like (lowercase with hyphens)
|
|
16
|
+
/^[a-z_]+$/, // snake_case identifiers
|
|
17
|
+
/^https?:\/\//, // URLs
|
|
18
|
+
/^\/[\w/.-]+$/, // Paths
|
|
19
|
+
/^\d+(\.\d+)?(px|rem|em|%|vh|vw|ms|s)?$/, // Numbers with units
|
|
20
|
+
/^#[0-9a-fA-F]{3,8}$/, // Hex colors
|
|
21
|
+
/^rgba?\(/, // RGB colors
|
|
22
|
+
/^hsla?\(/, // HSL colors
|
|
23
|
+
/^[\w-]+:[\w-]+$/, // CSS-like property:value
|
|
24
|
+
/^\s*$/, // Whitespace only
|
|
25
|
+
/^[{}[\]()]+$/, // Brackets only
|
|
26
|
+
];
|
|
27
|
+
/**
|
|
28
|
+
* Variable names that typically hold non-translatable values
|
|
29
|
+
*/
|
|
30
|
+
const IGNORE_VARIABLE_NAMES = new Set([
|
|
31
|
+
"id",
|
|
32
|
+
"key",
|
|
33
|
+
"type",
|
|
34
|
+
"name",
|
|
35
|
+
"className",
|
|
36
|
+
"class",
|
|
37
|
+
"style",
|
|
38
|
+
"styles",
|
|
39
|
+
"href",
|
|
40
|
+
"src",
|
|
41
|
+
"url",
|
|
42
|
+
"path",
|
|
43
|
+
"route",
|
|
44
|
+
"endpoint",
|
|
45
|
+
"query",
|
|
46
|
+
"params",
|
|
47
|
+
"config",
|
|
48
|
+
"options",
|
|
49
|
+
"settings",
|
|
50
|
+
"env",
|
|
51
|
+
"mode",
|
|
52
|
+
"format",
|
|
53
|
+
"variant",
|
|
54
|
+
"size",
|
|
55
|
+
"color",
|
|
56
|
+
"status",
|
|
57
|
+
"state",
|
|
58
|
+
"icon",
|
|
59
|
+
"target",
|
|
60
|
+
"rel",
|
|
61
|
+
"method",
|
|
62
|
+
"headers",
|
|
63
|
+
"contentType",
|
|
64
|
+
"mimeType",
|
|
65
|
+
"encoding",
|
|
66
|
+
"charset",
|
|
67
|
+
"locale",
|
|
68
|
+
"lang",
|
|
69
|
+
"language",
|
|
70
|
+
"namespace",
|
|
71
|
+
"ns",
|
|
72
|
+
"prefix",
|
|
73
|
+
"suffix",
|
|
74
|
+
"extension",
|
|
75
|
+
"ext",
|
|
76
|
+
"version",
|
|
77
|
+
"v",
|
|
78
|
+
]);
|
|
79
|
+
/**
|
|
80
|
+
* Check for string variable assignments with hardcoded user-facing text
|
|
81
|
+
*/
|
|
82
|
+
export function checkStringVariable(node, ctx) {
|
|
83
|
+
// Must have an initializer
|
|
84
|
+
if (!node.initializer)
|
|
85
|
+
return null;
|
|
86
|
+
// Must be a string literal
|
|
87
|
+
if (!ts.isStringLiteral(node.initializer))
|
|
88
|
+
return null;
|
|
89
|
+
const text = node.initializer.text;
|
|
90
|
+
// Skip if it doesn't look like user-facing text
|
|
91
|
+
if (!isUserFacingText(text))
|
|
92
|
+
return null;
|
|
93
|
+
// Skip if variable name suggests non-translatable content
|
|
94
|
+
if (ts.isIdentifier(node.name)) {
|
|
95
|
+
const varName = node.name.text.toLowerCase();
|
|
96
|
+
if (IGNORE_VARIABLE_NAMES.has(varName))
|
|
97
|
+
return null;
|
|
98
|
+
// Skip if variable name ends with common technical suffixes
|
|
99
|
+
if (varName.endsWith("id") ||
|
|
100
|
+
varName.endsWith("key") ||
|
|
101
|
+
varName.endsWith("class") ||
|
|
102
|
+
varName.endsWith("style") ||
|
|
103
|
+
varName.endsWith("url") ||
|
|
104
|
+
varName.endsWith("path") ||
|
|
105
|
+
varName.endsWith("type")) {
|
|
106
|
+
return null;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
const pos = ctx.sourceFile.getLineAndCharacterOfPosition(node.initializer.getStart());
|
|
110
|
+
return {
|
|
111
|
+
file: ctx.filePath,
|
|
112
|
+
line: pos.line + 1,
|
|
113
|
+
column: pos.character + 1,
|
|
114
|
+
text,
|
|
115
|
+
type: "string-variable",
|
|
116
|
+
severity: "warning",
|
|
117
|
+
message: `Hardcoded string: "${truncate(text, 40)}"`,
|
|
118
|
+
suggestedKey: generateKeyFromContext(text, ctx.filePath),
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Determine if a string looks like user-facing text
|
|
123
|
+
* - Must be at least 5 characters
|
|
124
|
+
* - Must start with a capital letter OR contain a space
|
|
125
|
+
* - Must not match any ignore patterns
|
|
126
|
+
*/
|
|
127
|
+
function isUserFacingText(text) {
|
|
128
|
+
// Too short
|
|
129
|
+
if (text.length < 5)
|
|
130
|
+
return false;
|
|
131
|
+
// Check ignore patterns
|
|
132
|
+
for (const pattern of IGNORE_PATTERNS) {
|
|
133
|
+
if (pattern.test(text))
|
|
134
|
+
return false;
|
|
135
|
+
}
|
|
136
|
+
// Must either:
|
|
137
|
+
// 1. Start with capital letter and have length >= 5, OR
|
|
138
|
+
// 2. Contain at least one space (indicates a phrase)
|
|
139
|
+
const startsWithCapital = /^[A-Z]/.test(text);
|
|
140
|
+
const hasSpace = /\s/.test(text);
|
|
141
|
+
if (!startsWithCapital && !hasSpace)
|
|
142
|
+
return false;
|
|
143
|
+
// Additional check: if no space, must look like a proper word/title
|
|
144
|
+
if (!hasSpace) {
|
|
145
|
+
const technicalWords = [
|
|
146
|
+
"Primary",
|
|
147
|
+
"Secondary",
|
|
148
|
+
"Default",
|
|
149
|
+
"None",
|
|
150
|
+
"True",
|
|
151
|
+
"False",
|
|
152
|
+
"Null",
|
|
153
|
+
"Undefined",
|
|
154
|
+
"Material",
|
|
155
|
+
"Outlined",
|
|
156
|
+
"Contained",
|
|
157
|
+
"Text",
|
|
158
|
+
"Small",
|
|
159
|
+
"Medium",
|
|
160
|
+
"Large",
|
|
161
|
+
"Left",
|
|
162
|
+
"Right",
|
|
163
|
+
"Center",
|
|
164
|
+
"Top",
|
|
165
|
+
"Bottom",
|
|
166
|
+
"Start",
|
|
167
|
+
"End",
|
|
168
|
+
"Vertical",
|
|
169
|
+
"Horizontal",
|
|
170
|
+
"Enabled",
|
|
171
|
+
"Disabled",
|
|
172
|
+
"Active",
|
|
173
|
+
"Inactive",
|
|
174
|
+
"Loading",
|
|
175
|
+
"Success",
|
|
176
|
+
"Error",
|
|
177
|
+
"Warning",
|
|
178
|
+
"Info",
|
|
179
|
+
];
|
|
180
|
+
if (technicalWords.includes(text))
|
|
181
|
+
return false;
|
|
182
|
+
}
|
|
183
|
+
return true;
|
|
184
|
+
}
|
|
185
|
+
//# sourceMappingURL=string-variable.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"string-variable.js","sourceRoot":"","sources":["../../../src/rules/code/string-variable.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,MAAM,YAAY,CAAC;AAC5B,OAAO,EAAE,sBAAsB,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAGvE;;GAEG;AACH,MAAM,eAAe,GAAG;IACtB,oBAAoB,EAAE,2BAA2B;IACjD,kBAAkB,EAAE,sCAAsC;IAC1D,WAAW,EAAE,oCAAoC;IACjD,WAAW,EAAE,yBAAyB;IACtC,cAAc,EAAE,OAAO;IACvB,cAAc,EAAE,QAAQ;IACxB,wCAAwC,EAAE,qBAAqB;IAC/D,qBAAqB,EAAE,aAAa;IACpC,UAAU,EAAE,aAAa;IACzB,UAAU,EAAE,aAAa;IACzB,iBAAiB,EAAE,0BAA0B;IAC7C,OAAO,EAAE,kBAAkB;IAC3B,cAAc,EAAE,gBAAgB;CACjC,CAAC;AAEF;;GAEG;AACH,MAAM,qBAAqB,GAAG,IAAI,GAAG,CAAC;IACpC,IAAI;IACJ,KAAK;IACL,MAAM;IACN,MAAM;IACN,WAAW;IACX,OAAO;IACP,OAAO;IACP,QAAQ;IACR,MAAM;IACN,KAAK;IACL,KAAK;IACL,MAAM;IACN,OAAO;IACP,UAAU;IACV,OAAO;IACP,QAAQ;IACR,QAAQ;IACR,SAAS;IACT,UAAU;IACV,KAAK;IACL,MAAM;IACN,QAAQ;IACR,SAAS;IACT,MAAM;IACN,OAAO;IACP,QAAQ;IACR,OAAO;IACP,MAAM;IACN,QAAQ;IACR,KAAK;IACL,QAAQ;IACR,SAAS;IACT,aAAa;IACb,UAAU;IACV,UAAU;IACV,SAAS;IACT,QAAQ;IACR,MAAM;IACN,UAAU;IACV,WAAW;IACX,IAAI;IACJ,QAAQ;IACR,QAAQ;IACR,WAAW;IACX,KAAK;IACL,SAAS;IACT,GAAG;CACJ,CAAC,CAAC;AAEH;;GAEG;AACH,MAAM,UAAU,mBAAmB,CACjC,IAA4B,EAC5B,GAAgB;IAEhB,2BAA2B;IAC3B,IAAI,CAAC,IAAI,CAAC,WAAW;QAAE,OAAO,IAAI,CAAC;IAEnC,2BAA2B;IAC3B,IAAI,CAAC,EAAE,CAAC,eAAe,CAAC,IAAI,CAAC,WAAW,CAAC;QAAE,OAAO,IAAI,CAAC;IAEvD,MAAM,IAAI,GAAG,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC;IAEnC,gDAAgD;IAChD,IAAI,CAAC,gBAAgB,CAAC,IAAI,CAAC;QAAE,OAAO,IAAI,CAAC;IAEzC,0DAA0D;IAC1D,IAAI,EAAE,CAAC,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;QAC/B,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC;QAC7C,IAAI,qBAAqB,CAAC,GAAG,CAAC,OAAO,CAAC;YAAE,OAAO,IAAI,CAAC;QAEpD,4DAA4D;QAC5D,IACE,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAC;YACtB,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAC;YACvB,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAC;YACzB,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAC;YACzB,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAC;YACvB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAC;YACxB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAC,EACxB,CAAC;YACD,OAAO,IAAI,CAAC;QACd,CAAC;IACH,CAAC;IAED,MAAM,GAAG,GAAG,GAAG,CAAC,UAAU,CAAC,6BAA6B,CACtD,IAAI,CAAC,WAAW,CAAC,QAAQ,EAAE,CAC5B,CAAC;IAEF,OAAO;QACL,IAAI,EAAE,GAAG,CAAC,QAAQ;QAClB,IAAI,EAAE,GAAG,CAAC,IAAI,GAAG,CAAC;QAClB,MAAM,EAAE,GAAG,CAAC,SAAS,GAAG,CAAC;QACzB,IAAI;QACJ,IAAI,EAAE,iBAAiB;QACvB,QAAQ,EAAE,SAAS;QACnB,OAAO,EAAE,sBAAsB,QAAQ,CAAC,IAAI,EAAE,EAAE,CAAC,GAAG;QACpD,YAAY,EAAE,sBAAsB,CAAC,IAAI,EAAE,GAAG,CAAC,QAAQ,CAAC;KACzD,CAAC;AACJ,CAAC;AAED;;;;;GAKG;AACH,SAAS,gBAAgB,CAAC,IAAY;IACpC,YAAY;IACZ,IAAI,IAAI,CAAC,MAAM,GAAG,CAAC;QAAE,OAAO,KAAK,CAAC;IAElC,wBAAwB;IACxB,KAAK,MAAM,OAAO,IAAI,eAAe,EAAE,CAAC;QACtC,IAAI,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC;YAAE,OAAO,KAAK,CAAC;IACvC,CAAC;IAED,eAAe;IACf,wDAAwD;IACxD,qDAAqD;IACrD,MAAM,iBAAiB,GAAG,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC9C,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAEjC,IAAI,CAAC,iBAAiB,IAAI,CAAC,QAAQ;QAAE,OAAO,KAAK,CAAC;IAElD,oEAAoE;IACpE,IAAI,CAAC,QAAQ,EAAE,CAAC;QACd,MAAM,cAAc,GAAG;YACrB,SAAS;YACT,WAAW;YACX,SAAS;YACT,MAAM;YACN,MAAM;YACN,OAAO;YACP,MAAM;YACN,WAAW;YACX,UAAU;YACV,UAAU;YACV,WAAW;YACX,MAAM;YACN,OAAO;YACP,QAAQ;YACR,OAAO;YACP,MAAM;YACN,OAAO;YACP,QAAQ;YACR,KAAK;YACL,QAAQ;YACR,OAAO;YACP,KAAK;YACL,UAAU;YACV,YAAY;YACZ,SAAS;YACT,UAAU;YACV,QAAQ;YACR,UAAU;YACV,SAAS;YACT,SAAS;YACT,OAAO;YACP,SAAS;YACT,MAAM;SACP,CAAC;QACF,IAAI,cAAc,CAAC,QAAQ,CAAC,IAAI,CAAC;YAAE,OAAO,KAAK,CAAC;IAClD,CAAC;IAED,OAAO,IAAI,CAAC;AACd,CAAC"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ternary locale detection rule
|
|
3
|
+
*
|
|
4
|
+
* Detects anti-pattern: locale === 'en' ? 'Hello' : 'Merhaba'
|
|
5
|
+
*/
|
|
6
|
+
import ts from "typescript";
|
|
7
|
+
import type { Issue, RuleContext } from "../../analyzer/types.js";
|
|
8
|
+
/**
|
|
9
|
+
* Check conditional expression for locale-based ternary
|
|
10
|
+
*/
|
|
11
|
+
export declare function checkTernaryLocale(node: ts.ConditionalExpression, ctx: RuleContext): Issue | null;
|
|
12
|
+
//# sourceMappingURL=ternary-locale.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ternary-locale.d.ts","sourceRoot":"","sources":["../../../src/rules/code/ternary-locale.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,MAAM,YAAY,CAAC;AAE5B,OAAO,KAAK,EAAE,KAAK,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AAElE;;GAEG;AACH,wBAAgB,kBAAkB,CAChC,IAAI,EAAE,EAAE,CAAC,qBAAqB,EAC9B,GAAG,EAAE,WAAW,GACf,KAAK,GAAG,IAAI,CAgDd"}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ternary locale detection rule
|
|
3
|
+
*
|
|
4
|
+
* Detects anti-pattern: locale === 'en' ? 'Hello' : 'Merhaba'
|
|
5
|
+
*/
|
|
6
|
+
import ts from "typescript";
|
|
7
|
+
import { generateKeyFromContext, truncate } from "../../utils/text.js";
|
|
8
|
+
/**
|
|
9
|
+
* Check conditional expression for locale-based ternary
|
|
10
|
+
*/
|
|
11
|
+
export function checkTernaryLocale(node, ctx) {
|
|
12
|
+
const condition = node.condition;
|
|
13
|
+
// Check for binary expression
|
|
14
|
+
if (!ts.isBinaryExpression(condition))
|
|
15
|
+
return null;
|
|
16
|
+
// Check if comparing with locale
|
|
17
|
+
const left = condition.left;
|
|
18
|
+
let isLocaleComparison = false;
|
|
19
|
+
// Direct identifier: locale === 'en'
|
|
20
|
+
if (ts.isIdentifier(left) && left.text === "locale") {
|
|
21
|
+
isLocaleComparison = true;
|
|
22
|
+
}
|
|
23
|
+
// Property access: i18n.locale === 'en' or router.locale === 'en'
|
|
24
|
+
if (ts.isPropertyAccessExpression(left) && left.name.text === "locale") {
|
|
25
|
+
isLocaleComparison = true;
|
|
26
|
+
}
|
|
27
|
+
if (!isLocaleComparison)
|
|
28
|
+
return null;
|
|
29
|
+
// Check if both branches have string literals
|
|
30
|
+
const hasStringTrue = ts.isStringLiteral(node.whenTrue);
|
|
31
|
+
const hasStringFalse = ts.isStringLiteral(node.whenFalse);
|
|
32
|
+
if (!hasStringTrue && !hasStringFalse)
|
|
33
|
+
return null;
|
|
34
|
+
// Get text for message
|
|
35
|
+
const text = hasStringTrue
|
|
36
|
+
? node.whenTrue.text
|
|
37
|
+
: node.whenFalse.text;
|
|
38
|
+
// Ignore empty strings (common in URL construction: locale === 'en' ? '' : locale + '/')
|
|
39
|
+
if (text.trim() === "")
|
|
40
|
+
return null;
|
|
41
|
+
const pos = ctx.sourceFile.getLineAndCharacterOfPosition(node.getStart());
|
|
42
|
+
return {
|
|
43
|
+
file: ctx.filePath,
|
|
44
|
+
line: pos.line + 1,
|
|
45
|
+
column: pos.character + 1,
|
|
46
|
+
text,
|
|
47
|
+
type: "ternary-locale",
|
|
48
|
+
severity: "error", // This is an anti-pattern, so error
|
|
49
|
+
message: `Locale ternary pattern detected: "${truncate(text, 30)}"`,
|
|
50
|
+
suggestedKey: generateKeyFromContext(text, ctx.filePath),
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
//# sourceMappingURL=ternary-locale.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ternary-locale.js","sourceRoot":"","sources":["../../../src/rules/code/ternary-locale.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,MAAM,YAAY,CAAC;AAC5B,OAAO,EAAE,sBAAsB,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAGvE;;GAEG;AACH,MAAM,UAAU,kBAAkB,CAChC,IAA8B,EAC9B,GAAgB;IAEhB,MAAM,SAAS,GAAG,IAAI,CAAC,SAAS,CAAC;IAEjC,8BAA8B;IAC9B,IAAI,CAAC,EAAE,CAAC,kBAAkB,CAAC,SAAS,CAAC;QAAE,OAAO,IAAI,CAAC;IAEnD,iCAAiC;IACjC,MAAM,IAAI,GAAG,SAAS,CAAC,IAAI,CAAC;IAC5B,IAAI,kBAAkB,GAAG,KAAK,CAAC;IAE/B,qCAAqC;IACrC,IAAI,EAAE,CAAC,YAAY,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;QACpD,kBAAkB,GAAG,IAAI,CAAC;IAC5B,CAAC;IAED,kEAAkE;IAClE,IAAI,EAAE,CAAC,0BAA0B,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,IAAI,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;QACvE,kBAAkB,GAAG,IAAI,CAAC;IAC5B,CAAC;IAED,IAAI,CAAC,kBAAkB;QAAE,OAAO,IAAI,CAAC;IAErC,8CAA8C;IAC9C,MAAM,aAAa,GAAG,EAAE,CAAC,eAAe,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IACxD,MAAM,cAAc,GAAG,EAAE,CAAC,eAAe,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;IAE1D,IAAI,CAAC,aAAa,IAAI,CAAC,cAAc;QAAE,OAAO,IAAI,CAAC;IAEnD,uBAAuB;IACvB,MAAM,IAAI,GAAG,aAAa;QACxB,CAAC,CAAE,IAAI,CAAC,QAA6B,CAAC,IAAI;QAC1C,CAAC,CAAE,IAAI,CAAC,SAA8B,CAAC,IAAI,CAAC;IAE9C,yFAAyF;IACzF,IAAI,IAAI,CAAC,IAAI,EAAE,KAAK,EAAE;QAAE,OAAO,IAAI,CAAC;IAEpC,MAAM,GAAG,GAAG,GAAG,CAAC,UAAU,CAAC,6BAA6B,CAAC,IAAI,CAAC,QAAQ,EAAE,CAAC,CAAC;IAE1E,OAAO;QACL,IAAI,EAAE,GAAG,CAAC,QAAQ;QAClB,IAAI,EAAE,GAAG,CAAC,IAAI,GAAG,CAAC;QAClB,MAAM,EAAE,GAAG,CAAC,SAAS,GAAG,CAAC;QACzB,IAAI;QACJ,IAAI,EAAE,gBAAgB;QACtB,QAAQ,EAAE,OAAO,EAAE,oCAAoC;QACvD,OAAO,EAAE,qCAAqC,QAAQ,CAAC,IAAI,EAAE,EAAE,CAAC,GAAG;QACnE,YAAY,EAAE,sBAAsB,CAAC,IAAI,EAAE,GAAG,CAAC,QAAQ,CAAC;KACzD,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Toast message detection rule
|
|
3
|
+
*
|
|
4
|
+
* Detects hardcoded strings in toast function calls
|
|
5
|
+
* Works with: react-hot-toast, sonner, react-toastify
|
|
6
|
+
*/
|
|
7
|
+
import ts from "typescript";
|
|
8
|
+
import type { Issue, RuleContext } from "../../analyzer/types.js";
|
|
9
|
+
/**
|
|
10
|
+
* Check for toast function calls with hardcoded strings
|
|
11
|
+
*/
|
|
12
|
+
export declare function checkToastMessage(node: ts.CallExpression, ctx: RuleContext): Issue | null;
|
|
13
|
+
//# sourceMappingURL=toast-message.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"toast-message.d.ts","sourceRoot":"","sources":["../../../src/rules/code/toast-message.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,MAAM,YAAY,CAAC;AAE5B,OAAO,KAAK,EAAE,KAAK,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AA2BlE;;GAEG;AACH,wBAAgB,iBAAiB,CAC/B,IAAI,EAAE,EAAE,CAAC,cAAc,EACvB,GAAG,EAAE,WAAW,GACf,KAAK,GAAG,IAAI,CAoBd"}
|