@localeguard/react-analyzer 0.1.0 → 0.3.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/src/glob.d.ts +4 -17
- package/dist/src/glob.d.ts.map +1 -1
- package/dist/src/glob.js +6 -128
- package/dist/src/glob.js.map +1 -1
- package/dist/src/index.d.ts +2 -0
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +4 -1
- package/dist/src/index.js.map +1 -1
- package/dist/src/key-references.d.ts +31 -0
- package/dist/src/key-references.d.ts.map +1 -0
- package/dist/src/key-references.js +206 -0
- package/dist/src/key-references.js.map +1 -0
- package/package.json +2 -2
package/dist/src/glob.d.ts
CHANGED
|
@@ -1,20 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
* Supports the subset of glob syntax LocaleGuard configs use:
|
|
5
|
-
* - `**` matches any number of path segments
|
|
6
|
-
* - `*` matches anything except a path separator
|
|
7
|
-
* - `?` matches a single non-separator character
|
|
8
|
-
* - `{a,b}` matches any of the comma-separated alternatives
|
|
9
|
-
*
|
|
10
|
-
* Paths are matched in POSIX form relative to the root directory.
|
|
2
|
+
* Re-exported from `@localeguard/core`, where the shared file-discovery utility
|
|
3
|
+
* now lives (used by both the React and template analyzers).
|
|
11
4
|
*/
|
|
12
|
-
export
|
|
13
|
-
export
|
|
14
|
-
rootDir: string;
|
|
15
|
-
include: string[];
|
|
16
|
-
ignore?: string[];
|
|
17
|
-
}
|
|
18
|
-
/** Return absolute paths of files under `rootDir` matching the patterns. */
|
|
19
|
-
export declare function findFiles(opts: FindFilesOptions): string[];
|
|
5
|
+
export { findFiles, globToRegExp } from "@localeguard/core";
|
|
6
|
+
export type { FindFilesOptions } from "@localeguard/core";
|
|
20
7
|
//# sourceMappingURL=glob.d.ts.map
|
package/dist/src/glob.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"glob.d.ts","sourceRoot":"","sources":["../../src/glob.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"glob.d.ts","sourceRoot":"","sources":["../../src/glob.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAC5D,YAAY,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC"}
|
package/dist/src/glob.js
CHANGED
|
@@ -1,133 +1,11 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
* Supports the subset of glob syntax LocaleGuard configs use:
|
|
6
|
-
* - `**` matches any number of path segments
|
|
7
|
-
* - `*` matches anything except a path separator
|
|
8
|
-
* - `?` matches a single non-separator character
|
|
9
|
-
* - `{a,b}` matches any of the comma-separated alternatives
|
|
10
|
-
*
|
|
11
|
-
* Paths are matched in POSIX form relative to the root directory.
|
|
3
|
+
* Re-exported from `@localeguard/core`, where the shared file-discovery utility
|
|
4
|
+
* now lives (used by both the React and template analyzers).
|
|
12
5
|
*/
|
|
13
|
-
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
14
|
-
if (k2 === undefined) k2 = k;
|
|
15
|
-
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
16
|
-
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
17
|
-
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
18
|
-
}
|
|
19
|
-
Object.defineProperty(o, k2, desc);
|
|
20
|
-
}) : (function(o, m, k, k2) {
|
|
21
|
-
if (k2 === undefined) k2 = k;
|
|
22
|
-
o[k2] = m[k];
|
|
23
|
-
}));
|
|
24
|
-
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
25
|
-
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
26
|
-
}) : function(o, v) {
|
|
27
|
-
o["default"] = v;
|
|
28
|
-
});
|
|
29
|
-
var __importStar = (this && this.__importStar) || (function () {
|
|
30
|
-
var ownKeys = function(o) {
|
|
31
|
-
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
32
|
-
var ar = [];
|
|
33
|
-
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
34
|
-
return ar;
|
|
35
|
-
};
|
|
36
|
-
return ownKeys(o);
|
|
37
|
-
};
|
|
38
|
-
return function (mod) {
|
|
39
|
-
if (mod && mod.__esModule) return mod;
|
|
40
|
-
var result = {};
|
|
41
|
-
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
42
|
-
__setModuleDefault(result, mod);
|
|
43
|
-
return result;
|
|
44
|
-
};
|
|
45
|
-
})();
|
|
46
6
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
47
|
-
exports.globToRegExp =
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
const ALWAYS_SKIP = new Set(["node_modules", ".git", "dist", "build", "coverage"]);
|
|
52
|
-
function escapeLiteral(text) {
|
|
53
|
-
return text.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
54
|
-
}
|
|
55
|
-
function globToRegExp(glob) {
|
|
56
|
-
let re = "";
|
|
57
|
-
let i = 0;
|
|
58
|
-
while (i < glob.length) {
|
|
59
|
-
const c = glob[i];
|
|
60
|
-
if (c === "*") {
|
|
61
|
-
if (glob[i + 1] === "*") {
|
|
62
|
-
if (glob[i + 2] === "/") {
|
|
63
|
-
re += "(?:[^/]+/)*"; // **/ — zero or more segments
|
|
64
|
-
i += 3;
|
|
65
|
-
}
|
|
66
|
-
else {
|
|
67
|
-
re += ".*"; // ** — anything, including separators
|
|
68
|
-
i += 2;
|
|
69
|
-
}
|
|
70
|
-
continue;
|
|
71
|
-
}
|
|
72
|
-
re += "[^/]*";
|
|
73
|
-
i += 1;
|
|
74
|
-
continue;
|
|
75
|
-
}
|
|
76
|
-
if (c === "?") {
|
|
77
|
-
re += "[^/]";
|
|
78
|
-
i += 1;
|
|
79
|
-
continue;
|
|
80
|
-
}
|
|
81
|
-
if (c === "{") {
|
|
82
|
-
const end = glob.indexOf("}", i);
|
|
83
|
-
if (end !== -1) {
|
|
84
|
-
const alts = glob.slice(i + 1, end).split(",").map(escapeLiteral);
|
|
85
|
-
re += `(?:${alts.join("|")})`;
|
|
86
|
-
i = end + 1;
|
|
87
|
-
continue;
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
re += escapeLiteral(c);
|
|
91
|
-
i += 1;
|
|
92
|
-
}
|
|
93
|
-
return new RegExp(`^${re}$`);
|
|
94
|
-
}
|
|
95
|
-
/** Return absolute paths of files under `rootDir` matching the patterns. */
|
|
96
|
-
function findFiles(opts) {
|
|
97
|
-
const includeRe = opts.include.map(globToRegExp);
|
|
98
|
-
const ignoreRe = (opts.ignore ?? []).map(globToRegExp);
|
|
99
|
-
const results = [];
|
|
100
|
-
const walk = (dir) => {
|
|
101
|
-
let entries;
|
|
102
|
-
try {
|
|
103
|
-
entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
104
|
-
}
|
|
105
|
-
catch {
|
|
106
|
-
return;
|
|
107
|
-
}
|
|
108
|
-
for (const entry of entries) {
|
|
109
|
-
const abs = path.join(dir, entry.name);
|
|
110
|
-
const rel = toPosix(path.relative(opts.rootDir, abs));
|
|
111
|
-
if (entry.isDirectory()) {
|
|
112
|
-
if (ALWAYS_SKIP.has(entry.name))
|
|
113
|
-
continue;
|
|
114
|
-
if (ignoreRe.some((re) => re.test(rel)))
|
|
115
|
-
continue;
|
|
116
|
-
walk(abs);
|
|
117
|
-
}
|
|
118
|
-
else if (entry.isFile()) {
|
|
119
|
-
if (ignoreRe.some((re) => re.test(rel)))
|
|
120
|
-
continue;
|
|
121
|
-
if (includeRe.some((re) => re.test(rel)))
|
|
122
|
-
results.push(abs);
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
};
|
|
126
|
-
walk(opts.rootDir);
|
|
127
|
-
results.sort();
|
|
128
|
-
return results;
|
|
129
|
-
}
|
|
130
|
-
function toPosix(p) {
|
|
131
|
-
return p.split(path.sep).join("/");
|
|
132
|
-
}
|
|
7
|
+
exports.globToRegExp = exports.findFiles = void 0;
|
|
8
|
+
var core_1 = require("@localeguard/core");
|
|
9
|
+
Object.defineProperty(exports, "findFiles", { enumerable: true, get: function () { return core_1.findFiles; } });
|
|
10
|
+
Object.defineProperty(exports, "globToRegExp", { enumerable: true, get: function () { return core_1.globToRegExp; } });
|
|
133
11
|
//# sourceMappingURL=glob.js.map
|
package/dist/src/glob.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"glob.js","sourceRoot":"","sources":["../../src/glob.ts"],"names":[],"mappings":";AAAA
|
|
1
|
+
{"version":3,"file":"glob.js","sourceRoot":"","sources":["../../src/glob.ts"],"names":[],"mappings":";AAAA;;;GAGG;;;AAEH,0CAA4D;AAAnD,iGAAA,SAAS,OAAA;AAAE,oGAAA,YAAY,OAAA"}
|
package/dist/src/index.d.ts
CHANGED
|
@@ -5,4 +5,6 @@ export { analyzeProject, analyzeSource, DEFAULT_INCLUDE, DEFAULT_TRANSLATION_COM
|
|
|
5
5
|
export type { AnalyzerConfig, AnalyzeOptions } from "./analyzer";
|
|
6
6
|
export { findFiles, globToRegExp } from "./glob";
|
|
7
7
|
export type { FindFilesOptions } from "./glob";
|
|
8
|
+
export { extractKeyReferences, extractFromSource } from "./key-references";
|
|
9
|
+
export type { KeyReferenceConfig, KeyReferenceResult } from "./key-references";
|
|
8
10
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/src/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EACL,cAAc,EACd,aAAa,EACb,eAAe,EACf,8BAA8B,GAC/B,MAAM,YAAY,CAAC;AACpB,YAAY,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AACjE,OAAO,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,QAAQ,CAAC;AACjD,YAAY,EAAE,gBAAgB,EAAE,MAAM,QAAQ,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EACL,cAAc,EACd,aAAa,EACb,eAAe,EACf,8BAA8B,GAC/B,MAAM,YAAY,CAAC;AACpB,YAAY,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AACjE,OAAO,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,QAAQ,CAAC;AACjD,YAAY,EAAE,gBAAgB,EAAE,MAAM,QAAQ,CAAC;AAC/C,OAAO,EAAE,oBAAoB,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAC3E,YAAY,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC"}
|
package/dist/src/index.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* @localeguard/react-analyzer — public API.
|
|
4
4
|
*/
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.globToRegExp = exports.findFiles = exports.DEFAULT_TRANSLATION_COMPONENTS = exports.DEFAULT_INCLUDE = exports.analyzeSource = exports.analyzeProject = void 0;
|
|
6
|
+
exports.extractFromSource = exports.extractKeyReferences = exports.globToRegExp = exports.findFiles = exports.DEFAULT_TRANSLATION_COMPONENTS = exports.DEFAULT_INCLUDE = exports.analyzeSource = exports.analyzeProject = void 0;
|
|
7
7
|
var analyzer_1 = require("./analyzer");
|
|
8
8
|
Object.defineProperty(exports, "analyzeProject", { enumerable: true, get: function () { return analyzer_1.analyzeProject; } });
|
|
9
9
|
Object.defineProperty(exports, "analyzeSource", { enumerable: true, get: function () { return analyzer_1.analyzeSource; } });
|
|
@@ -12,4 +12,7 @@ Object.defineProperty(exports, "DEFAULT_TRANSLATION_COMPONENTS", { enumerable: t
|
|
|
12
12
|
var glob_1 = require("./glob");
|
|
13
13
|
Object.defineProperty(exports, "findFiles", { enumerable: true, get: function () { return glob_1.findFiles; } });
|
|
14
14
|
Object.defineProperty(exports, "globToRegExp", { enumerable: true, get: function () { return glob_1.globToRegExp; } });
|
|
15
|
+
var key_references_1 = require("./key-references");
|
|
16
|
+
Object.defineProperty(exports, "extractKeyReferences", { enumerable: true, get: function () { return key_references_1.extractKeyReferences; } });
|
|
17
|
+
Object.defineProperty(exports, "extractFromSource", { enumerable: true, get: function () { return key_references_1.extractFromSource; } });
|
|
15
18
|
//# sourceMappingURL=index.js.map
|
package/dist/src/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":";AAAA;;GAEG;;;AAEH,uCAKoB;AAJlB,0GAAA,cAAc,OAAA;AACd,yGAAA,aAAa,OAAA;AACb,2GAAA,eAAe,OAAA;AACf,0HAAA,8BAA8B,OAAA;AAGhC,+BAAiD;AAAxC,iGAAA,SAAS,OAAA;AAAE,oGAAA,YAAY,OAAA"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":";AAAA;;GAEG;;;AAEH,uCAKoB;AAJlB,0GAAA,cAAc,OAAA;AACd,yGAAA,aAAa,OAAA;AACb,2GAAA,eAAe,OAAA;AACf,0HAAA,8BAA8B,OAAA;AAGhC,+BAAiD;AAAxC,iGAAA,SAAS,OAAA;AAAE,oGAAA,YAAY,OAAA;AAEhC,mDAA2E;AAAlE,sHAAA,oBAAoB,OAAA;AAAE,mHAAA,iBAAiB,OAAA"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Extract literal translation-key references from React/TypeScript source so
|
|
3
|
+
* they can be checked against the locale keys (used-but-missing / unused).
|
|
4
|
+
*
|
|
5
|
+
* Recognizes:
|
|
6
|
+
* - `t('key')`, `i18n.t('key')`, `intl.formatMessage({ id: 'key' })`
|
|
7
|
+
* - `<FormattedMessage id="key" />`, `<Trans i18nKey="key" />`
|
|
8
|
+
* - `useTranslations('NS')` / `useTranslation('ns')` to resolve a file-level
|
|
9
|
+
* namespace applied to bare `t('key')` calls.
|
|
10
|
+
*
|
|
11
|
+
* Non-literal keys (`t(name)`, template strings with substitutions) are not
|
|
12
|
+
* resolvable and are reported via `hasDynamicKeys` so the unused-key check can
|
|
13
|
+
* stay conservative.
|
|
14
|
+
*/
|
|
15
|
+
import type { KeyReference } from "@localeguard/core";
|
|
16
|
+
export interface KeyReferenceConfig {
|
|
17
|
+
include?: string[];
|
|
18
|
+
ignore?: string[];
|
|
19
|
+
translationFunctions?: string[];
|
|
20
|
+
translationComponents?: string[];
|
|
21
|
+
}
|
|
22
|
+
export interface AnalyzeOptions {
|
|
23
|
+
rootDir: string;
|
|
24
|
+
}
|
|
25
|
+
export interface KeyReferenceResult {
|
|
26
|
+
references: KeyReference[];
|
|
27
|
+
hasDynamicKeys: boolean;
|
|
28
|
+
}
|
|
29
|
+
export declare function extractKeyReferences(config: KeyReferenceConfig, opts: AnalyzeOptions): KeyReferenceResult;
|
|
30
|
+
export declare function extractFromSource(text: string, fileName: string, functions: Set<string>, components: Set<string>): KeyReferenceResult;
|
|
31
|
+
//# sourceMappingURL=key-references.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"key-references.d.ts","sourceRoot":"","sources":["../../src/key-references.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAQH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAMtD,MAAM,WAAW,kBAAkB;IACjC,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAClB,oBAAoB,CAAC,EAAE,MAAM,EAAE,CAAC;IAChC,qBAAqB,CAAC,EAAE,MAAM,EAAE,CAAC;CAClC;AAED,MAAM,WAAW,cAAc;IAC7B,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,kBAAkB;IACjC,UAAU,EAAE,YAAY,EAAE,CAAC;IAC3B,cAAc,EAAE,OAAO,CAAC;CACzB;AAID,wBAAgB,oBAAoB,CAClC,MAAM,EAAE,kBAAkB,EAC1B,IAAI,EAAE,cAAc,GACnB,kBAAkB,CAwBpB;AAQD,wBAAgB,iBAAiB,CAC/B,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,GAAG,CAAC,MAAM,CAAC,EACtB,UAAU,EAAE,GAAG,CAAC,MAAM,CAAC,GACtB,kBAAkB,CAmDpB"}
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Extract literal translation-key references from React/TypeScript source so
|
|
4
|
+
* they can be checked against the locale keys (used-but-missing / unused).
|
|
5
|
+
*
|
|
6
|
+
* Recognizes:
|
|
7
|
+
* - `t('key')`, `i18n.t('key')`, `intl.formatMessage({ id: 'key' })`
|
|
8
|
+
* - `<FormattedMessage id="key" />`, `<Trans i18nKey="key" />`
|
|
9
|
+
* - `useTranslations('NS')` / `useTranslation('ns')` to resolve a file-level
|
|
10
|
+
* namespace applied to bare `t('key')` calls.
|
|
11
|
+
*
|
|
12
|
+
* Non-literal keys (`t(name)`, template strings with substitutions) are not
|
|
13
|
+
* resolvable and are reported via `hasDynamicKeys` so the unused-key check can
|
|
14
|
+
* stay conservative.
|
|
15
|
+
*/
|
|
16
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
17
|
+
if (k2 === undefined) k2 = k;
|
|
18
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
19
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
20
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
21
|
+
}
|
|
22
|
+
Object.defineProperty(o, k2, desc);
|
|
23
|
+
}) : (function(o, m, k, k2) {
|
|
24
|
+
if (k2 === undefined) k2 = k;
|
|
25
|
+
o[k2] = m[k];
|
|
26
|
+
}));
|
|
27
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
28
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
29
|
+
}) : function(o, v) {
|
|
30
|
+
o["default"] = v;
|
|
31
|
+
});
|
|
32
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
33
|
+
var ownKeys = function(o) {
|
|
34
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
35
|
+
var ar = [];
|
|
36
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
37
|
+
return ar;
|
|
38
|
+
};
|
|
39
|
+
return ownKeys(o);
|
|
40
|
+
};
|
|
41
|
+
return function (mod) {
|
|
42
|
+
if (mod && mod.__esModule) return mod;
|
|
43
|
+
var result = {};
|
|
44
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
45
|
+
__setModuleDefault(result, mod);
|
|
46
|
+
return result;
|
|
47
|
+
};
|
|
48
|
+
})();
|
|
49
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
50
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
51
|
+
};
|
|
52
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
53
|
+
exports.extractKeyReferences = extractKeyReferences;
|
|
54
|
+
exports.extractFromSource = extractFromSource;
|
|
55
|
+
const fs = __importStar(require("node:fs"));
|
|
56
|
+
const path = __importStar(require("node:path"));
|
|
57
|
+
const typescript_1 = __importDefault(require("typescript"));
|
|
58
|
+
const core_1 = require("@localeguard/core");
|
|
59
|
+
const NAMESPACE_SETTERS = new Set(["useTranslations", "getTranslations", "useTranslation"]);
|
|
60
|
+
const DEFAULT_TRANSLATION_FUNCTIONS = ["t", "i18n.t", "formatMessage", "intl.formatMessage"];
|
|
61
|
+
const DEFAULT_TRANSLATION_COMPONENTS = ["FormattedMessage", "Trans"];
|
|
62
|
+
const SOURCE_EXTENSIONS = new Set([".ts", ".tsx", ".js", ".jsx", ".mts", ".cts"]);
|
|
63
|
+
function extractKeyReferences(config, opts) {
|
|
64
|
+
const include = config.include ?? ["src/**/*.{ts,tsx}"];
|
|
65
|
+
const functions = new Set(config.translationFunctions ?? DEFAULT_TRANSLATION_FUNCTIONS);
|
|
66
|
+
const components = new Set(config.translationComponents ?? DEFAULT_TRANSLATION_COMPONENTS);
|
|
67
|
+
const files = (0, core_1.findFiles)({ rootDir: opts.rootDir, include, ignore: config.ignore });
|
|
68
|
+
const references = [];
|
|
69
|
+
let hasDynamicKeys = false;
|
|
70
|
+
for (const absFile of files) {
|
|
71
|
+
if (!SOURCE_EXTENSIONS.has(path.extname(absFile).toLowerCase()))
|
|
72
|
+
continue;
|
|
73
|
+
let text;
|
|
74
|
+
try {
|
|
75
|
+
text = fs.readFileSync(absFile, "utf8");
|
|
76
|
+
}
|
|
77
|
+
catch {
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
const relPath = path.relative(opts.rootDir, absFile) || absFile;
|
|
81
|
+
const result = extractFromSource(text, relPath, functions, components);
|
|
82
|
+
references.push(...result.references);
|
|
83
|
+
hasDynamicKeys = hasDynamicKeys || result.hasDynamicKeys;
|
|
84
|
+
}
|
|
85
|
+
return { references, hasDynamicKeys };
|
|
86
|
+
}
|
|
87
|
+
function extractFromSource(text, fileName, functions, components) {
|
|
88
|
+
const sf = typescript_1.default.createSourceFile(fileName, text, typescript_1.default.ScriptTarget.Latest, true, scriptKindFor(fileName));
|
|
89
|
+
const raw = [];
|
|
90
|
+
let fileNamespace;
|
|
91
|
+
let hasDynamicKeys = false;
|
|
92
|
+
const lineOf = (node) => sf.getLineAndCharacterOfPosition(node.getStart(sf)).line + 1;
|
|
93
|
+
const visit = (node) => {
|
|
94
|
+
if (typescript_1.default.isCallExpression(node)) {
|
|
95
|
+
const callee = calleeName(node.expression, sf);
|
|
96
|
+
if (callee && NAMESPACE_SETTERS.has(callee.name)) {
|
|
97
|
+
const ns = stringArg(node.arguments[0]);
|
|
98
|
+
if (ns && fileNamespace === undefined)
|
|
99
|
+
fileNamespace = ns;
|
|
100
|
+
}
|
|
101
|
+
else if (callee && (functions.has(callee.name) || functions.has(callee.full))) {
|
|
102
|
+
const arg = node.arguments[0];
|
|
103
|
+
if (arg && typescript_1.default.isObjectLiteralExpression(arg)) {
|
|
104
|
+
// formatMessage({ id: '...' })
|
|
105
|
+
const id = objectStringProp(arg, "id");
|
|
106
|
+
if (id !== undefined)
|
|
107
|
+
raw.push({ key: id, line: lineOf(node), useFileNamespace: false });
|
|
108
|
+
else
|
|
109
|
+
hasDynamicKeys = true;
|
|
110
|
+
}
|
|
111
|
+
else {
|
|
112
|
+
const key = stringArg(arg);
|
|
113
|
+
if (key !== undefined)
|
|
114
|
+
raw.push({ key, line: lineOf(node), useFileNamespace: true });
|
|
115
|
+
else if (arg)
|
|
116
|
+
hasDynamicKeys = true;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
else if (typescript_1.default.isJsxAttribute(node)) {
|
|
121
|
+
const attrName = node.name.getText(sf);
|
|
122
|
+
if (attrName === "id" || attrName === "i18nKey") {
|
|
123
|
+
const tag = enclosingJsxTagName(node);
|
|
124
|
+
if (tag && components.has(tag)) {
|
|
125
|
+
const value = jsxAttrString(node);
|
|
126
|
+
if (value !== undefined) {
|
|
127
|
+
raw.push({ key: value, line: lineOf(node), useFileNamespace: attrName === "i18nKey" });
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
typescript_1.default.forEachChild(node, visit);
|
|
133
|
+
};
|
|
134
|
+
visit(sf);
|
|
135
|
+
const references = raw.map((r) => ({
|
|
136
|
+
key: r.key,
|
|
137
|
+
namespace: r.useFileNamespace ? fileNamespace : undefined,
|
|
138
|
+
file: fileName,
|
|
139
|
+
line: r.line,
|
|
140
|
+
}));
|
|
141
|
+
return { references, hasDynamicKeys };
|
|
142
|
+
}
|
|
143
|
+
function calleeName(expr, sf) {
|
|
144
|
+
if (typescript_1.default.isIdentifier(expr))
|
|
145
|
+
return { name: expr.text, full: expr.text };
|
|
146
|
+
if (typescript_1.default.isPropertyAccessExpression(expr)) {
|
|
147
|
+
return { name: expr.name.text, full: expr.getText(sf) };
|
|
148
|
+
}
|
|
149
|
+
return null;
|
|
150
|
+
}
|
|
151
|
+
function stringArg(arg) {
|
|
152
|
+
if (!arg)
|
|
153
|
+
return undefined;
|
|
154
|
+
if (typescript_1.default.isStringLiteral(arg) || typescript_1.default.isNoSubstitutionTemplateLiteral(arg))
|
|
155
|
+
return arg.text;
|
|
156
|
+
return undefined;
|
|
157
|
+
}
|
|
158
|
+
function objectStringProp(obj, name) {
|
|
159
|
+
for (const prop of obj.properties) {
|
|
160
|
+
if (typescript_1.default.isPropertyAssignment(prop) && prop.name.getText() === name) {
|
|
161
|
+
return stringArg(prop.initializer);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
return undefined;
|
|
165
|
+
}
|
|
166
|
+
function jsxAttrString(node) {
|
|
167
|
+
const init = node.initializer;
|
|
168
|
+
if (!init)
|
|
169
|
+
return undefined;
|
|
170
|
+
if (typescript_1.default.isStringLiteral(init))
|
|
171
|
+
return init.text;
|
|
172
|
+
if (typescript_1.default.isJsxExpression(init) && init.expression && typescript_1.default.isStringLiteralLike(init.expression)) {
|
|
173
|
+
return init.expression.text;
|
|
174
|
+
}
|
|
175
|
+
return undefined;
|
|
176
|
+
}
|
|
177
|
+
function enclosingJsxTagName(node) {
|
|
178
|
+
let current = node.parent;
|
|
179
|
+
while (current) {
|
|
180
|
+
if (typescript_1.default.isJsxOpeningElement(current) || typescript_1.default.isJsxSelfClosingElement(current)) {
|
|
181
|
+
const tag = current.tagName;
|
|
182
|
+
if (typescript_1.default.isIdentifier(tag))
|
|
183
|
+
return tag.text;
|
|
184
|
+
if (typescript_1.default.isPropertyAccessExpression(tag))
|
|
185
|
+
return tag.name.text;
|
|
186
|
+
return undefined;
|
|
187
|
+
}
|
|
188
|
+
current = current.parent;
|
|
189
|
+
}
|
|
190
|
+
return undefined;
|
|
191
|
+
}
|
|
192
|
+
function scriptKindFor(fileName) {
|
|
193
|
+
switch (path.extname(fileName)) {
|
|
194
|
+
case ".tsx":
|
|
195
|
+
return typescript_1.default.ScriptKind.TSX;
|
|
196
|
+
case ".jsx":
|
|
197
|
+
return typescript_1.default.ScriptKind.JSX;
|
|
198
|
+
case ".js":
|
|
199
|
+
return typescript_1.default.ScriptKind.JS;
|
|
200
|
+
case ".ts":
|
|
201
|
+
return typescript_1.default.ScriptKind.TS;
|
|
202
|
+
default:
|
|
203
|
+
return typescript_1.default.ScriptKind.TSX;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
//# sourceMappingURL=key-references.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"key-references.js","sourceRoot":"","sources":["../../src/key-references.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@localeguard/react-analyzer",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Static analysis for LocaleGuard: detect hardcoded JSX text and unlocalized accessibility attributes in React/TypeScript source.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"author": "isamrish",
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
"prepublishOnly": "tsc -b"
|
|
26
26
|
},
|
|
27
27
|
"dependencies": {
|
|
28
|
-
"@localeguard/core": "0.
|
|
28
|
+
"@localeguard/core": "0.3.0",
|
|
29
29
|
"typescript": "^5.7.0"
|
|
30
30
|
},
|
|
31
31
|
"publishConfig": {
|