i18next-cli 1.56.8 → 1.56.10
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 +7 -7
- package/dist/cjs/cli.js +1 -1
- package/dist/cjs/extractor/core/ast-visitors.js +34 -0
- package/dist/cjs/extractor/parsers/scope-manager.js +218 -0
- package/dist/cjs/linter.js +1 -0
- package/dist/esm/cli.js +1 -1
- package/dist/esm/extractor/core/ast-visitors.js +34 -0
- package/dist/esm/extractor/parsers/scope-manager.js +218 -0
- package/dist/esm/linter.js +1 -0
- package/package.json +1 -1
- package/types/extractor/core/ast-visitors.d.ts.map +1 -1
- package/types/extractor/parsers/scope-manager.d.ts +62 -1
- package/types/extractor/parsers/scope-manager.d.ts.map +1 -1
package/README.md
CHANGED
|
@@ -44,7 +44,7 @@ A unified, high-performance i18next CLI toolchain, powered by SWC.
|
|
|
44
44
|
- **Translation Status**: Get a high-level overview or a detailed, key-by-key report of your project's translation completeness.
|
|
45
45
|
- **Plugin System**: Extensible architecture for custom extraction patterns and file types (e.g., HTML, Handlebars).
|
|
46
46
|
- **Legacy Migration**: Automatic migration from `i18next-parser` configurations.
|
|
47
|
-
- **Cloud Integration**: Seamless integration with the [Locize](https://locize.com) translation management platform.
|
|
47
|
+
- **Cloud Integration**: Seamless integration with the [Locize](https://www.locize.com?utm_source=i18next_cli_readme&utm_medium=github&utm_campaign=readme) translation management platform.
|
|
48
48
|
|
|
49
49
|
## Installation
|
|
50
50
|
|
|
@@ -253,7 +253,7 @@ npx i18next-cli lint
|
|
|
253
253
|
|
|
254
254
|
### `instrument`
|
|
255
255
|
|
|
256
|
-
Scans your source code for hardcoded user-facing strings and instruments them with i18next translation calls. This is useful for adding i18next instrumentation to an existing codebase that wasn't built with internationalization in mind. You can see this in action in [this video](https://youtu.be/aWZnZXwGg34) or in [this blog post](https://www.locize.com/blog/i18next-cli-instrument).
|
|
256
|
+
Scans your source code for hardcoded user-facing strings and instruments them with i18next translation calls. This is useful for adding i18next instrumentation to an existing codebase that wasn't built with internationalization in mind. You can see this in action in [this video](https://youtu.be/aWZnZXwGg34) or in [this blog post](https://www.locize.com/blog/i18next-cli-instrument?utm_source=i18next_cli_readme&utm_medium=github&utm_campaign=readme).
|
|
257
257
|
|
|
258
258
|
> **⚠️ First-Step Tool:** The `instrument` command uses heuristic-based detection and is designed as a **first pass** to identify and suggest transformation candidates. It will **not catch 100% of cases**, and you should expect both false positives and false negatives. Always review the suggested transformations carefully before committing them to your codebase. Think of it as an intelligent code assistant, not an automated compiler.
|
|
259
259
|
|
|
@@ -1507,21 +1507,21 @@ This programmatic API gives you the same power as the CLI but with full control
|
|
|
1507
1507
|
<h3 align="center">Gold Sponsors</h3>
|
|
1508
1508
|
|
|
1509
1509
|
<p align="center">
|
|
1510
|
-
<a href="https://www.locize.com
|
|
1510
|
+
<a href="https://www.locize.com/?utm_source=i18next_cli_readme&utm_medium=github&utm_campaign=readme" target="_blank">
|
|
1511
1511
|
<img src="https://raw.githubusercontent.com/i18next/i18next/master/assets/locize_sponsor_240.gif" width="240px">
|
|
1512
1512
|
</a>
|
|
1513
1513
|
</p>
|
|
1514
1514
|
|
|
1515
1515
|
---
|
|
1516
1516
|
|
|
1517
|
-
**From the creators of i18next: localization as a service - [Locize](https://www.locize.com)**
|
|
1517
|
+
**From the creators of i18next: localization as a service - [Locize](https://www.locize.com?utm_source=i18next_cli_readme&utm_medium=github&utm_campaign=readme)**
|
|
1518
1518
|
|
|
1519
|
-
A translation management system built around the i18next ecosystem - [Locize](https://locize.com).
|
|
1519
|
+
A translation management system built around the i18next ecosystem - [Locize](https://www.locize.com?utm_source=i18next_cli_readme&utm_medium=github&utm_campaign=readme).
|
|
1520
1520
|
|
|
1521
|
-
**Now with a [Free plan](https://locize.com/pricing) for small projects!** Perfect for hobbyists or getting started.
|
|
1521
|
+
**Now with a [Free plan](https://www.locize.com/pricing?utm_source=i18next_cli_readme&utm_medium=github&utm_campaign=readme) for small projects!** Perfect for hobbyists or getting started.
|
|
1522
1522
|
|
|
1523
1523
|

|
|
1524
1524
|
|
|
1525
|
-
With using [Locize](https://www.locize.com/?utm_source=i18next_cli_readme&utm_medium=github) you directly support the future of i18next.
|
|
1525
|
+
With using [Locize](https://www.locize.com/?utm_source=i18next_cli_readme&utm_medium=github&utm_campaign=readme) you directly support the future of i18next.
|
|
1526
1526
|
|
|
1527
1527
|
---
|
package/dist/cjs/cli.js
CHANGED
|
@@ -32,7 +32,7 @@ const program = new commander.Command();
|
|
|
32
32
|
program
|
|
33
33
|
.name('i18next-cli')
|
|
34
34
|
.description('A unified, high-performance i18next CLI.')
|
|
35
|
-
.version('1.56.
|
|
35
|
+
.version('1.56.10'); // This string is replaced with the actual version at build time by rollup
|
|
36
36
|
// new: global config override option
|
|
37
37
|
program.option('-c, --config <path>', 'Path to i18next-cli config file (overrides detection)');
|
|
38
38
|
program
|
|
@@ -165,7 +165,37 @@ class ASTVisitors {
|
|
|
165
165
|
if (!node)
|
|
166
166
|
return;
|
|
167
167
|
let isNewScope = false;
|
|
168
|
+
let isNewClassScope = false;
|
|
168
169
|
let paramTemporaries;
|
|
170
|
+
// ENTER CLASS SCOPE for class declarations / expressions and pre-register
|
|
171
|
+
// class field initializers so that later `this.<field>` references inside
|
|
172
|
+
// method bodies can inherit the namespace/keyPrefix from the field.
|
|
173
|
+
if (node.type === 'ClassDeclaration' ||
|
|
174
|
+
node.type === 'ClassExpression' ||
|
|
175
|
+
node.type === 'ClassDecl' ||
|
|
176
|
+
node.type === 'ClassExpr') {
|
|
177
|
+
this.scopeManager.enterClassScope();
|
|
178
|
+
isNewClassScope = true;
|
|
179
|
+
const classBody = Array.isArray(node.body) ? node.body : (node.body?.body ?? []);
|
|
180
|
+
for (const member of classBody) {
|
|
181
|
+
if (!member || typeof member !== 'object')
|
|
182
|
+
continue;
|
|
183
|
+
if (member.type === 'ClassProperty' ||
|
|
184
|
+
member.type === 'PrivateProperty' ||
|
|
185
|
+
member.type === 'PropertyDefinition' ||
|
|
186
|
+
member.type === 'ClassPrivateProperty') {
|
|
187
|
+
let fieldName;
|
|
188
|
+
const key = member.key;
|
|
189
|
+
if (key?.type === 'Identifier')
|
|
190
|
+
fieldName = key.value ?? key.name;
|
|
191
|
+
else if (key?.type === 'PrivateName')
|
|
192
|
+
fieldName = '#' + (key.value ?? key.name ?? key.id?.value ?? key.id?.name);
|
|
193
|
+
if (!fieldName)
|
|
194
|
+
continue;
|
|
195
|
+
this.scopeManager.registerClassField(fieldName, member.value);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
169
199
|
// ENTER SCOPE for functions
|
|
170
200
|
// Accept many SWC/TS AST variants for function-like nodes (declarations, expressions, arrow functions)
|
|
171
201
|
if (node.type === 'Function' ||
|
|
@@ -540,6 +570,10 @@ class ASTVisitors {
|
|
|
540
570
|
}
|
|
541
571
|
this.scopeManager.exitScope();
|
|
542
572
|
}
|
|
573
|
+
// LEAVE CLASS SCOPE for classes
|
|
574
|
+
if (isNewClassScope) {
|
|
575
|
+
this.scopeManager.exitClassScope();
|
|
576
|
+
}
|
|
543
577
|
}
|
|
544
578
|
/**
|
|
545
579
|
* If `node` is a call like `ARRAY.map(param => ...)` where ARRAY is a known
|
|
@@ -6,6 +6,11 @@ class ScopeManager {
|
|
|
6
6
|
scopeStack = [];
|
|
7
7
|
config;
|
|
8
8
|
scope = new Map();
|
|
9
|
+
// Stack of class scopes; each entry maps a class field name (e.g. `myField`
|
|
10
|
+
// or `#privateField`) to the ScopeInfo derived from its initializer. Used to
|
|
11
|
+
// resolve `const { t } = this.field` and `const { t } = this.field()` patterns
|
|
12
|
+
// inside class methods.
|
|
13
|
+
thisFieldStack = [];
|
|
9
14
|
// Track simple local constants with string literal values to resolve identifier args
|
|
10
15
|
simpleConstants = new Map();
|
|
11
16
|
// Track simple local constant objects with string literal property values
|
|
@@ -63,6 +68,49 @@ class ScopeManager {
|
|
|
63
68
|
this.scope = new Map();
|
|
64
69
|
this.simpleConstants.clear();
|
|
65
70
|
this.simpleConstantObjects.clear();
|
|
71
|
+
this.thisFieldStack = [];
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Pushes a new class scope used for tracking class field → ScopeInfo
|
|
75
|
+
* mappings. Should be called when entering a ClassDeclaration or
|
|
76
|
+
* ClassExpression.
|
|
77
|
+
*/
|
|
78
|
+
enterClassScope() {
|
|
79
|
+
this.thisFieldStack.push(new Map());
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Pops the current class scope. Should be called when leaving a
|
|
83
|
+
* ClassDeclaration or ClassExpression.
|
|
84
|
+
*/
|
|
85
|
+
exitClassScope() {
|
|
86
|
+
this.thisFieldStack.pop();
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Records a class field's ScopeInfo so that later `this.<field>` or
|
|
90
|
+
* `this.<field>()` accesses inside the class can inherit the namespace
|
|
91
|
+
* and keyPrefix from its initializer.
|
|
92
|
+
*
|
|
93
|
+
* @param fieldName - Field name (private fields are prefixed with `#`)
|
|
94
|
+
* @param info - Scope information derived from the field's initializer
|
|
95
|
+
*/
|
|
96
|
+
setThisField(fieldName, info) {
|
|
97
|
+
if (this.thisFieldStack.length > 0) {
|
|
98
|
+
this.thisFieldStack[this.thisFieldStack.length - 1].set(fieldName, info);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Looks up a class field's ScopeInfo from the innermost class scope outward.
|
|
103
|
+
*
|
|
104
|
+
* @param fieldName - Field name (private fields are prefixed with `#`)
|
|
105
|
+
* @returns Scope information if found, undefined otherwise
|
|
106
|
+
*/
|
|
107
|
+
getThisField(fieldName) {
|
|
108
|
+
for (let i = this.thisFieldStack.length - 1; i >= 0; i--) {
|
|
109
|
+
if (this.thisFieldStack[i].has(fieldName)) {
|
|
110
|
+
return this.thisFieldStack[i].get(fieldName);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
return undefined;
|
|
66
114
|
}
|
|
67
115
|
/**
|
|
68
116
|
* Enters a new variable scope by pushing a new scope map onto the stack.
|
|
@@ -219,6 +267,17 @@ class ScopeManager {
|
|
|
219
267
|
}
|
|
220
268
|
// continue processing; still may be a useTranslation/getFixedT call below
|
|
221
269
|
}
|
|
270
|
+
// Handle: const { t } = this.#field OR const { t } = this.#field()
|
|
271
|
+
// Resolve the source `this.<field>` to its previously-registered ScopeInfo
|
|
272
|
+
// and propagate it onto the destructured variables.
|
|
273
|
+
const thisFieldName = ScopeManager.extractThisFieldName(init);
|
|
274
|
+
if (thisFieldName !== undefined) {
|
|
275
|
+
const sourceScope = this.getThisField(thisFieldName);
|
|
276
|
+
if (sourceScope) {
|
|
277
|
+
this.attachScopeToDestructuredVars(node, sourceScope);
|
|
278
|
+
return;
|
|
279
|
+
}
|
|
280
|
+
}
|
|
222
281
|
// Determine the actual call expression, looking inside AwaitExpressions.
|
|
223
282
|
const callExpr = init.type === 'AwaitExpression' && init.argument.type === 'CallExpression'
|
|
224
283
|
? init.argument
|
|
@@ -545,6 +604,165 @@ class ScopeManager {
|
|
|
545
604
|
this.setVarInScope(targetVarName, { defaultNs: finalNs, keyPrefix: finalKeyPrefix });
|
|
546
605
|
}
|
|
547
606
|
}
|
|
607
|
+
/**
|
|
608
|
+
* Returns the field name being referenced when an expression takes the form
|
|
609
|
+
* `this.<field>` or `this.<field>()`. Strips the call wrapper if present and
|
|
610
|
+
* prefixes private fields with `#`.
|
|
611
|
+
*
|
|
612
|
+
* Returns undefined if the expression is not such an access.
|
|
613
|
+
*/
|
|
614
|
+
static extractThisFieldName(init) {
|
|
615
|
+
let memberExpr;
|
|
616
|
+
if (init.type === 'MemberExpression') {
|
|
617
|
+
memberExpr = init;
|
|
618
|
+
}
|
|
619
|
+
else if (init.type === 'CallExpression' && init.callee.type === 'MemberExpression') {
|
|
620
|
+
memberExpr = init.callee;
|
|
621
|
+
}
|
|
622
|
+
if (!memberExpr)
|
|
623
|
+
return undefined;
|
|
624
|
+
if (memberExpr.object.type !== 'ThisExpression')
|
|
625
|
+
return undefined;
|
|
626
|
+
const prop = memberExpr.property;
|
|
627
|
+
if (prop?.type === 'Identifier')
|
|
628
|
+
return prop.value;
|
|
629
|
+
if (prop?.type === 'PrivateName')
|
|
630
|
+
return '#' + prop.value;
|
|
631
|
+
return undefined;
|
|
632
|
+
}
|
|
633
|
+
/**
|
|
634
|
+
* Attaches the given ScopeInfo to all destructured variables of a variable
|
|
635
|
+
* declarator (`const { t } = ...`, `const [t] = ...`, or `const t = ...`).
|
|
636
|
+
*/
|
|
637
|
+
attachScopeToDestructuredVars(node, info) {
|
|
638
|
+
if (node.id.type === 'ObjectPattern') {
|
|
639
|
+
for (const prop of node.id.properties) {
|
|
640
|
+
if (prop.type === 'AssignmentPatternProperty' && prop.key.type === 'Identifier') {
|
|
641
|
+
this.setVarInScope(prop.key.value, info);
|
|
642
|
+
}
|
|
643
|
+
if (prop.type === 'KeyValuePatternProperty' && prop.value.type === 'Identifier') {
|
|
644
|
+
this.setVarInScope(prop.value.value, info);
|
|
645
|
+
// Also store under the pattern key name so legacy comment-scope
|
|
646
|
+
// lookups by the original property name continue to work.
|
|
647
|
+
if (prop.key.type === 'Identifier' && (prop.key.value === 't' || prop.key.value === 'getFixedT')) {
|
|
648
|
+
this.scope.set(prop.value.value, { defaultNs: info.defaultNs, keyPrefix: info.keyPrefix });
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
else if (node.id.type === 'Identifier') {
|
|
654
|
+
this.setVarInScope(node.id.value, info);
|
|
655
|
+
}
|
|
656
|
+
else if (node.id.type === 'ArrayPattern') {
|
|
657
|
+
const firstElement = node.id.elements[0];
|
|
658
|
+
if (firstElement?.type === 'Identifier') {
|
|
659
|
+
this.setVarInScope(firstElement.value, info);
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
/**
|
|
664
|
+
* Registers a class field's initializer with the current class scope so that
|
|
665
|
+
* later `const { t } = this.<field>` or `const { t } = this.<field>()` calls
|
|
666
|
+
* inside the class body inherit the namespace and keyPrefix from the field
|
|
667
|
+
* initializer.
|
|
668
|
+
*
|
|
669
|
+
* Recognizes the same useTranslation-like patterns supported on regular
|
|
670
|
+
* variable declarations (configured via `useTranslationNames`).
|
|
671
|
+
*
|
|
672
|
+
* @param fieldName - Field name (private fields are prefixed with `#`)
|
|
673
|
+
* @param value - The field initializer expression
|
|
674
|
+
*/
|
|
675
|
+
registerClassField(fieldName, value) {
|
|
676
|
+
if (!value)
|
|
677
|
+
return;
|
|
678
|
+
if (this.thisFieldStack.length === 0)
|
|
679
|
+
return;
|
|
680
|
+
const callExpr = value.type === 'AwaitExpression' && value.argument.type === 'CallExpression'
|
|
681
|
+
? value.argument
|
|
682
|
+
: value.type === 'CallExpression'
|
|
683
|
+
? value
|
|
684
|
+
: null;
|
|
685
|
+
if (!callExpr)
|
|
686
|
+
return;
|
|
687
|
+
const callee = callExpr.callee;
|
|
688
|
+
if (callee.type !== 'Identifier')
|
|
689
|
+
return;
|
|
690
|
+
const hookConfig = this.getUseTranslationConfig(callee.value);
|
|
691
|
+
if (!hookConfig)
|
|
692
|
+
return;
|
|
693
|
+
const info = this.computeScopeInfoFromHookCall(callExpr, hookConfig);
|
|
694
|
+
if (info.defaultNs || info.keyPrefix) {
|
|
695
|
+
this.setThisField(fieldName, info);
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
/**
|
|
699
|
+
* Computes ScopeInfo (defaultNs, keyPrefix) for a useTranslation-style call
|
|
700
|
+
* given a hook configuration that describes argument positions. Mirrors the
|
|
701
|
+
* extraction performed by handleUseTranslationDeclarator without attaching
|
|
702
|
+
* the result to any variable.
|
|
703
|
+
*/
|
|
704
|
+
computeScopeInfoFromHookCall(callExpr, hookConfig) {
|
|
705
|
+
const nsArgIndex = hookConfig.nsArg ?? 0;
|
|
706
|
+
const kpArgIndex = hookConfig.keyPrefixArg ?? 1;
|
|
707
|
+
let defaultNs;
|
|
708
|
+
let keyPrefix;
|
|
709
|
+
const first = callExpr.arguments?.[0]?.expression;
|
|
710
|
+
const second = callExpr.arguments?.[1]?.expression;
|
|
711
|
+
const third = callExpr.arguments?.[2]?.expression;
|
|
712
|
+
const looksLikeLanguage = (s) => /^[a-z]{2,3}([-_][A-Za-z0-9-]+)?$/i.test(s);
|
|
713
|
+
const isBuiltInLngNsForm = hookConfig.name === 'useTranslation' &&
|
|
714
|
+
first?.type === 'StringLiteral' &&
|
|
715
|
+
second?.type === 'StringLiteral' &&
|
|
716
|
+
looksLikeLanguage(first.value);
|
|
717
|
+
let kpArg;
|
|
718
|
+
if (isBuiltInLngNsForm) {
|
|
719
|
+
defaultNs = second.value;
|
|
720
|
+
kpArg = third;
|
|
721
|
+
}
|
|
722
|
+
else {
|
|
723
|
+
if (nsArgIndex !== -1) {
|
|
724
|
+
const nsNode = callExpr.arguments?.[nsArgIndex]?.expression;
|
|
725
|
+
if (nsNode?.type === 'StringLiteral') {
|
|
726
|
+
defaultNs = nsNode.value;
|
|
727
|
+
}
|
|
728
|
+
else if (nsNode?.type === 'Identifier') {
|
|
729
|
+
defaultNs = this.resolveSimpleStringIdentifier(nsNode.value);
|
|
730
|
+
}
|
|
731
|
+
else if (nsNode?.type === 'MemberExpression') {
|
|
732
|
+
defaultNs = this.resolveSimpleMemberExpression(nsNode);
|
|
733
|
+
}
|
|
734
|
+
else if (nsNode?.type === 'ArrayExpression') {
|
|
735
|
+
const firstEl = nsNode.elements[0]?.expression;
|
|
736
|
+
if (firstEl?.type === 'StringLiteral') {
|
|
737
|
+
defaultNs = firstEl.value;
|
|
738
|
+
}
|
|
739
|
+
else if (firstEl?.type === 'Identifier') {
|
|
740
|
+
defaultNs = this.resolveSimpleStringIdentifier(firstEl.value);
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
}
|
|
744
|
+
kpArg = kpArgIndex === -1 ? undefined : callExpr.arguments?.[kpArgIndex]?.expression;
|
|
745
|
+
}
|
|
746
|
+
if (kpArg?.type === 'ObjectExpression') {
|
|
747
|
+
const kp = astUtils.getObjectPropValue(kpArg, 'keyPrefix', this.resolveSimpleStringIdentifier.bind(this));
|
|
748
|
+
if (typeof kp === 'string') {
|
|
749
|
+
keyPrefix = kp;
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
else if (kpArg?.type === 'StringLiteral') {
|
|
753
|
+
keyPrefix = kpArg.value;
|
|
754
|
+
}
|
|
755
|
+
else if (kpArg?.type === 'Identifier') {
|
|
756
|
+
keyPrefix = this.resolveSimpleStringIdentifier(kpArg.value);
|
|
757
|
+
}
|
|
758
|
+
else if (kpArg?.type === 'TemplateLiteral') {
|
|
759
|
+
const tpl = kpArg;
|
|
760
|
+
if ((tpl.expressions || []).length === 0) {
|
|
761
|
+
keyPrefix = tpl.quasis?.[0]?.cooked ?? undefined;
|
|
762
|
+
}
|
|
763
|
+
}
|
|
764
|
+
return { defaultNs, keyPrefix };
|
|
765
|
+
}
|
|
548
766
|
}
|
|
549
767
|
|
|
550
768
|
exports.ScopeManager = ScopeManager;
|
package/dist/cjs/linter.js
CHANGED
package/dist/esm/cli.js
CHANGED
|
@@ -30,7 +30,7 @@ const program = new Command();
|
|
|
30
30
|
program
|
|
31
31
|
.name('i18next-cli')
|
|
32
32
|
.description('A unified, high-performance i18next CLI.')
|
|
33
|
-
.version('1.56.
|
|
33
|
+
.version('1.56.10'); // This string is replaced with the actual version at build time by rollup
|
|
34
34
|
// new: global config override option
|
|
35
35
|
program.option('-c, --config <path>', 'Path to i18next-cli config file (overrides detection)');
|
|
36
36
|
program
|
|
@@ -163,7 +163,37 @@ class ASTVisitors {
|
|
|
163
163
|
if (!node)
|
|
164
164
|
return;
|
|
165
165
|
let isNewScope = false;
|
|
166
|
+
let isNewClassScope = false;
|
|
166
167
|
let paramTemporaries;
|
|
168
|
+
// ENTER CLASS SCOPE for class declarations / expressions and pre-register
|
|
169
|
+
// class field initializers so that later `this.<field>` references inside
|
|
170
|
+
// method bodies can inherit the namespace/keyPrefix from the field.
|
|
171
|
+
if (node.type === 'ClassDeclaration' ||
|
|
172
|
+
node.type === 'ClassExpression' ||
|
|
173
|
+
node.type === 'ClassDecl' ||
|
|
174
|
+
node.type === 'ClassExpr') {
|
|
175
|
+
this.scopeManager.enterClassScope();
|
|
176
|
+
isNewClassScope = true;
|
|
177
|
+
const classBody = Array.isArray(node.body) ? node.body : (node.body?.body ?? []);
|
|
178
|
+
for (const member of classBody) {
|
|
179
|
+
if (!member || typeof member !== 'object')
|
|
180
|
+
continue;
|
|
181
|
+
if (member.type === 'ClassProperty' ||
|
|
182
|
+
member.type === 'PrivateProperty' ||
|
|
183
|
+
member.type === 'PropertyDefinition' ||
|
|
184
|
+
member.type === 'ClassPrivateProperty') {
|
|
185
|
+
let fieldName;
|
|
186
|
+
const key = member.key;
|
|
187
|
+
if (key?.type === 'Identifier')
|
|
188
|
+
fieldName = key.value ?? key.name;
|
|
189
|
+
else if (key?.type === 'PrivateName')
|
|
190
|
+
fieldName = '#' + (key.value ?? key.name ?? key.id?.value ?? key.id?.name);
|
|
191
|
+
if (!fieldName)
|
|
192
|
+
continue;
|
|
193
|
+
this.scopeManager.registerClassField(fieldName, member.value);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
}
|
|
167
197
|
// ENTER SCOPE for functions
|
|
168
198
|
// Accept many SWC/TS AST variants for function-like nodes (declarations, expressions, arrow functions)
|
|
169
199
|
if (node.type === 'Function' ||
|
|
@@ -538,6 +568,10 @@ class ASTVisitors {
|
|
|
538
568
|
}
|
|
539
569
|
this.scopeManager.exitScope();
|
|
540
570
|
}
|
|
571
|
+
// LEAVE CLASS SCOPE for classes
|
|
572
|
+
if (isNewClassScope) {
|
|
573
|
+
this.scopeManager.exitClassScope();
|
|
574
|
+
}
|
|
541
575
|
}
|
|
542
576
|
/**
|
|
543
577
|
* If `node` is a call like `ARRAY.map(param => ...)` where ARRAY is a known
|
|
@@ -4,6 +4,11 @@ class ScopeManager {
|
|
|
4
4
|
scopeStack = [];
|
|
5
5
|
config;
|
|
6
6
|
scope = new Map();
|
|
7
|
+
// Stack of class scopes; each entry maps a class field name (e.g. `myField`
|
|
8
|
+
// or `#privateField`) to the ScopeInfo derived from its initializer. Used to
|
|
9
|
+
// resolve `const { t } = this.field` and `const { t } = this.field()` patterns
|
|
10
|
+
// inside class methods.
|
|
11
|
+
thisFieldStack = [];
|
|
7
12
|
// Track simple local constants with string literal values to resolve identifier args
|
|
8
13
|
simpleConstants = new Map();
|
|
9
14
|
// Track simple local constant objects with string literal property values
|
|
@@ -61,6 +66,49 @@ class ScopeManager {
|
|
|
61
66
|
this.scope = new Map();
|
|
62
67
|
this.simpleConstants.clear();
|
|
63
68
|
this.simpleConstantObjects.clear();
|
|
69
|
+
this.thisFieldStack = [];
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Pushes a new class scope used for tracking class field → ScopeInfo
|
|
73
|
+
* mappings. Should be called when entering a ClassDeclaration or
|
|
74
|
+
* ClassExpression.
|
|
75
|
+
*/
|
|
76
|
+
enterClassScope() {
|
|
77
|
+
this.thisFieldStack.push(new Map());
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Pops the current class scope. Should be called when leaving a
|
|
81
|
+
* ClassDeclaration or ClassExpression.
|
|
82
|
+
*/
|
|
83
|
+
exitClassScope() {
|
|
84
|
+
this.thisFieldStack.pop();
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Records a class field's ScopeInfo so that later `this.<field>` or
|
|
88
|
+
* `this.<field>()` accesses inside the class can inherit the namespace
|
|
89
|
+
* and keyPrefix from its initializer.
|
|
90
|
+
*
|
|
91
|
+
* @param fieldName - Field name (private fields are prefixed with `#`)
|
|
92
|
+
* @param info - Scope information derived from the field's initializer
|
|
93
|
+
*/
|
|
94
|
+
setThisField(fieldName, info) {
|
|
95
|
+
if (this.thisFieldStack.length > 0) {
|
|
96
|
+
this.thisFieldStack[this.thisFieldStack.length - 1].set(fieldName, info);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Looks up a class field's ScopeInfo from the innermost class scope outward.
|
|
101
|
+
*
|
|
102
|
+
* @param fieldName - Field name (private fields are prefixed with `#`)
|
|
103
|
+
* @returns Scope information if found, undefined otherwise
|
|
104
|
+
*/
|
|
105
|
+
getThisField(fieldName) {
|
|
106
|
+
for (let i = this.thisFieldStack.length - 1; i >= 0; i--) {
|
|
107
|
+
if (this.thisFieldStack[i].has(fieldName)) {
|
|
108
|
+
return this.thisFieldStack[i].get(fieldName);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
return undefined;
|
|
64
112
|
}
|
|
65
113
|
/**
|
|
66
114
|
* Enters a new variable scope by pushing a new scope map onto the stack.
|
|
@@ -217,6 +265,17 @@ class ScopeManager {
|
|
|
217
265
|
}
|
|
218
266
|
// continue processing; still may be a useTranslation/getFixedT call below
|
|
219
267
|
}
|
|
268
|
+
// Handle: const { t } = this.#field OR const { t } = this.#field()
|
|
269
|
+
// Resolve the source `this.<field>` to its previously-registered ScopeInfo
|
|
270
|
+
// and propagate it onto the destructured variables.
|
|
271
|
+
const thisFieldName = ScopeManager.extractThisFieldName(init);
|
|
272
|
+
if (thisFieldName !== undefined) {
|
|
273
|
+
const sourceScope = this.getThisField(thisFieldName);
|
|
274
|
+
if (sourceScope) {
|
|
275
|
+
this.attachScopeToDestructuredVars(node, sourceScope);
|
|
276
|
+
return;
|
|
277
|
+
}
|
|
278
|
+
}
|
|
220
279
|
// Determine the actual call expression, looking inside AwaitExpressions.
|
|
221
280
|
const callExpr = init.type === 'AwaitExpression' && init.argument.type === 'CallExpression'
|
|
222
281
|
? init.argument
|
|
@@ -543,6 +602,165 @@ class ScopeManager {
|
|
|
543
602
|
this.setVarInScope(targetVarName, { defaultNs: finalNs, keyPrefix: finalKeyPrefix });
|
|
544
603
|
}
|
|
545
604
|
}
|
|
605
|
+
/**
|
|
606
|
+
* Returns the field name being referenced when an expression takes the form
|
|
607
|
+
* `this.<field>` or `this.<field>()`. Strips the call wrapper if present and
|
|
608
|
+
* prefixes private fields with `#`.
|
|
609
|
+
*
|
|
610
|
+
* Returns undefined if the expression is not such an access.
|
|
611
|
+
*/
|
|
612
|
+
static extractThisFieldName(init) {
|
|
613
|
+
let memberExpr;
|
|
614
|
+
if (init.type === 'MemberExpression') {
|
|
615
|
+
memberExpr = init;
|
|
616
|
+
}
|
|
617
|
+
else if (init.type === 'CallExpression' && init.callee.type === 'MemberExpression') {
|
|
618
|
+
memberExpr = init.callee;
|
|
619
|
+
}
|
|
620
|
+
if (!memberExpr)
|
|
621
|
+
return undefined;
|
|
622
|
+
if (memberExpr.object.type !== 'ThisExpression')
|
|
623
|
+
return undefined;
|
|
624
|
+
const prop = memberExpr.property;
|
|
625
|
+
if (prop?.type === 'Identifier')
|
|
626
|
+
return prop.value;
|
|
627
|
+
if (prop?.type === 'PrivateName')
|
|
628
|
+
return '#' + prop.value;
|
|
629
|
+
return undefined;
|
|
630
|
+
}
|
|
631
|
+
/**
|
|
632
|
+
* Attaches the given ScopeInfo to all destructured variables of a variable
|
|
633
|
+
* declarator (`const { t } = ...`, `const [t] = ...`, or `const t = ...`).
|
|
634
|
+
*/
|
|
635
|
+
attachScopeToDestructuredVars(node, info) {
|
|
636
|
+
if (node.id.type === 'ObjectPattern') {
|
|
637
|
+
for (const prop of node.id.properties) {
|
|
638
|
+
if (prop.type === 'AssignmentPatternProperty' && prop.key.type === 'Identifier') {
|
|
639
|
+
this.setVarInScope(prop.key.value, info);
|
|
640
|
+
}
|
|
641
|
+
if (prop.type === 'KeyValuePatternProperty' && prop.value.type === 'Identifier') {
|
|
642
|
+
this.setVarInScope(prop.value.value, info);
|
|
643
|
+
// Also store under the pattern key name so legacy comment-scope
|
|
644
|
+
// lookups by the original property name continue to work.
|
|
645
|
+
if (prop.key.type === 'Identifier' && (prop.key.value === 't' || prop.key.value === 'getFixedT')) {
|
|
646
|
+
this.scope.set(prop.value.value, { defaultNs: info.defaultNs, keyPrefix: info.keyPrefix });
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
else if (node.id.type === 'Identifier') {
|
|
652
|
+
this.setVarInScope(node.id.value, info);
|
|
653
|
+
}
|
|
654
|
+
else if (node.id.type === 'ArrayPattern') {
|
|
655
|
+
const firstElement = node.id.elements[0];
|
|
656
|
+
if (firstElement?.type === 'Identifier') {
|
|
657
|
+
this.setVarInScope(firstElement.value, info);
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
/**
|
|
662
|
+
* Registers a class field's initializer with the current class scope so that
|
|
663
|
+
* later `const { t } = this.<field>` or `const { t } = this.<field>()` calls
|
|
664
|
+
* inside the class body inherit the namespace and keyPrefix from the field
|
|
665
|
+
* initializer.
|
|
666
|
+
*
|
|
667
|
+
* Recognizes the same useTranslation-like patterns supported on regular
|
|
668
|
+
* variable declarations (configured via `useTranslationNames`).
|
|
669
|
+
*
|
|
670
|
+
* @param fieldName - Field name (private fields are prefixed with `#`)
|
|
671
|
+
* @param value - The field initializer expression
|
|
672
|
+
*/
|
|
673
|
+
registerClassField(fieldName, value) {
|
|
674
|
+
if (!value)
|
|
675
|
+
return;
|
|
676
|
+
if (this.thisFieldStack.length === 0)
|
|
677
|
+
return;
|
|
678
|
+
const callExpr = value.type === 'AwaitExpression' && value.argument.type === 'CallExpression'
|
|
679
|
+
? value.argument
|
|
680
|
+
: value.type === 'CallExpression'
|
|
681
|
+
? value
|
|
682
|
+
: null;
|
|
683
|
+
if (!callExpr)
|
|
684
|
+
return;
|
|
685
|
+
const callee = callExpr.callee;
|
|
686
|
+
if (callee.type !== 'Identifier')
|
|
687
|
+
return;
|
|
688
|
+
const hookConfig = this.getUseTranslationConfig(callee.value);
|
|
689
|
+
if (!hookConfig)
|
|
690
|
+
return;
|
|
691
|
+
const info = this.computeScopeInfoFromHookCall(callExpr, hookConfig);
|
|
692
|
+
if (info.defaultNs || info.keyPrefix) {
|
|
693
|
+
this.setThisField(fieldName, info);
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
/**
|
|
697
|
+
* Computes ScopeInfo (defaultNs, keyPrefix) for a useTranslation-style call
|
|
698
|
+
* given a hook configuration that describes argument positions. Mirrors the
|
|
699
|
+
* extraction performed by handleUseTranslationDeclarator without attaching
|
|
700
|
+
* the result to any variable.
|
|
701
|
+
*/
|
|
702
|
+
computeScopeInfoFromHookCall(callExpr, hookConfig) {
|
|
703
|
+
const nsArgIndex = hookConfig.nsArg ?? 0;
|
|
704
|
+
const kpArgIndex = hookConfig.keyPrefixArg ?? 1;
|
|
705
|
+
let defaultNs;
|
|
706
|
+
let keyPrefix;
|
|
707
|
+
const first = callExpr.arguments?.[0]?.expression;
|
|
708
|
+
const second = callExpr.arguments?.[1]?.expression;
|
|
709
|
+
const third = callExpr.arguments?.[2]?.expression;
|
|
710
|
+
const looksLikeLanguage = (s) => /^[a-z]{2,3}([-_][A-Za-z0-9-]+)?$/i.test(s);
|
|
711
|
+
const isBuiltInLngNsForm = hookConfig.name === 'useTranslation' &&
|
|
712
|
+
first?.type === 'StringLiteral' &&
|
|
713
|
+
second?.type === 'StringLiteral' &&
|
|
714
|
+
looksLikeLanguage(first.value);
|
|
715
|
+
let kpArg;
|
|
716
|
+
if (isBuiltInLngNsForm) {
|
|
717
|
+
defaultNs = second.value;
|
|
718
|
+
kpArg = third;
|
|
719
|
+
}
|
|
720
|
+
else {
|
|
721
|
+
if (nsArgIndex !== -1) {
|
|
722
|
+
const nsNode = callExpr.arguments?.[nsArgIndex]?.expression;
|
|
723
|
+
if (nsNode?.type === 'StringLiteral') {
|
|
724
|
+
defaultNs = nsNode.value;
|
|
725
|
+
}
|
|
726
|
+
else if (nsNode?.type === 'Identifier') {
|
|
727
|
+
defaultNs = this.resolveSimpleStringIdentifier(nsNode.value);
|
|
728
|
+
}
|
|
729
|
+
else if (nsNode?.type === 'MemberExpression') {
|
|
730
|
+
defaultNs = this.resolveSimpleMemberExpression(nsNode);
|
|
731
|
+
}
|
|
732
|
+
else if (nsNode?.type === 'ArrayExpression') {
|
|
733
|
+
const firstEl = nsNode.elements[0]?.expression;
|
|
734
|
+
if (firstEl?.type === 'StringLiteral') {
|
|
735
|
+
defaultNs = firstEl.value;
|
|
736
|
+
}
|
|
737
|
+
else if (firstEl?.type === 'Identifier') {
|
|
738
|
+
defaultNs = this.resolveSimpleStringIdentifier(firstEl.value);
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
kpArg = kpArgIndex === -1 ? undefined : callExpr.arguments?.[kpArgIndex]?.expression;
|
|
743
|
+
}
|
|
744
|
+
if (kpArg?.type === 'ObjectExpression') {
|
|
745
|
+
const kp = getObjectPropValue(kpArg, 'keyPrefix', this.resolveSimpleStringIdentifier.bind(this));
|
|
746
|
+
if (typeof kp === 'string') {
|
|
747
|
+
keyPrefix = kp;
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
else if (kpArg?.type === 'StringLiteral') {
|
|
751
|
+
keyPrefix = kpArg.value;
|
|
752
|
+
}
|
|
753
|
+
else if (kpArg?.type === 'Identifier') {
|
|
754
|
+
keyPrefix = this.resolveSimpleStringIdentifier(kpArg.value);
|
|
755
|
+
}
|
|
756
|
+
else if (kpArg?.type === 'TemplateLiteral') {
|
|
757
|
+
const tpl = kpArg;
|
|
758
|
+
if ((tpl.expressions || []).length === 0) {
|
|
759
|
+
keyPrefix = tpl.quasis?.[0]?.cooked ?? undefined;
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
return { defaultNs, keyPrefix };
|
|
763
|
+
}
|
|
546
764
|
}
|
|
547
765
|
|
|
548
766
|
export { ScopeManager };
|
package/dist/esm/linter.js
CHANGED
package/package.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ast-visitors.d.ts","sourceRoot":"","sources":["../../../src/extractor/core/ast-visitors.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAQ,MAAM,WAAW,CAAA;AAC7C,OAAO,KAAK,EAAE,aAAa,EAAE,oBAAoB,EAAE,MAAM,EAAE,eAAe,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAA;AAC7G,OAAO,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAA;AAC1D,OAAO,EAAE,kBAAkB,EAAE,MAAM,mCAAmC,CAAA;AAItE;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,qBAAa,WAAW;IACtB,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAe;IAC7C,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAuC;IAC9D,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAQ;IAC/B,OAAO,CAAC,KAAK,CAAiB;IAE9B,IAAW,UAAU,gBAEpB;IAED,SAAgB,YAAY,EAAE,YAAY,CAAA;IAC1C,OAAO,CAAC,QAAQ,CAAC,kBAAkB,CAAoB;IACvD,OAAO,CAAC,QAAQ,CAAC,qBAAqB,CAAuB;IAC7D,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAY;IACvC,OAAO,CAAC,WAAW,CAAa;IAChC,OAAO,CAAC,WAAW,CAAa;IAEhC;;;;;;OAMG;gBAED,MAAM,EAAE,IAAI,CAAC,oBAAoB,EAAE,SAAS,CAAC,EAC7C,aAAa,EAAE,aAAa,EAC5B,MAAM,EAAE,MAAM,EACd,KAAK,CAAC,EAAE,eAAe,EACvB,kBAAkB,CAAC,EAAE,kBAAkB;IAiCzC;;;;;;;;;;;;;OAaG;IACI,mBAAmB,CAAE,IAAI,EAAE,MAAM,GAAG,IAAI;IAK/C;;;OAGG;IACH,OAAO,CAAC,iBAAiB;IA8CzB;;;;;OAKG;IACI,KAAK,CAAE,IAAI,EAAE,MAAM,GAAG,IAAI;IAUjC;;;;;;;;;;;;OAYG;IACH,OAAO,CAAC,IAAI;
|
|
1
|
+
{"version":3,"file":"ast-visitors.d.ts","sourceRoot":"","sources":["../../../src/extractor/core/ast-visitors.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAQ,MAAM,WAAW,CAAA;AAC7C,OAAO,KAAK,EAAE,aAAa,EAAE,oBAAoB,EAAE,MAAM,EAAE,eAAe,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAA;AAC7G,OAAO,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAA;AAC1D,OAAO,EAAE,kBAAkB,EAAE,MAAM,mCAAmC,CAAA;AAItE;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,qBAAa,WAAW;IACtB,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAe;IAC7C,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAuC;IAC9D,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAQ;IAC/B,OAAO,CAAC,KAAK,CAAiB;IAE9B,IAAW,UAAU,gBAEpB;IAED,SAAgB,YAAY,EAAE,YAAY,CAAA;IAC1C,OAAO,CAAC,QAAQ,CAAC,kBAAkB,CAAoB;IACvD,OAAO,CAAC,QAAQ,CAAC,qBAAqB,CAAuB;IAC7D,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAY;IACvC,OAAO,CAAC,WAAW,CAAa;IAChC,OAAO,CAAC,WAAW,CAAa;IAEhC;;;;;;OAMG;gBAED,MAAM,EAAE,IAAI,CAAC,oBAAoB,EAAE,SAAS,CAAC,EAC7C,aAAa,EAAE,aAAa,EAC5B,MAAM,EAAE,MAAM,EACd,KAAK,CAAC,EAAE,eAAe,EACvB,kBAAkB,CAAC,EAAE,kBAAkB;IAiCzC;;;;;;;;;;;;;OAaG;IACI,mBAAmB,CAAE,IAAI,EAAE,MAAM,GAAG,IAAI;IAK/C;;;OAGG;IACH,OAAO,CAAC,iBAAiB;IA8CzB;;;;;OAKG;IACI,KAAK,CAAE,IAAI,EAAE,MAAM,GAAG,IAAI;IAUjC;;;;;;;;;;;;OAYG;IACH,OAAO,CAAC,IAAI;IAyZZ;;;;;;;;OAQG;IACH,OAAO,CAAC,gCAAgC;IAqDxC;;;OAGG;IACH,OAAO,CAAC,oBAAoB;IAqB5B;;;;;;;;OAQG;IACI,eAAe,CAAE,IAAI,EAAE,MAAM,GAAG,SAAS,GAAG,SAAS;IAI5D;;OAEG;IACI,cAAc,CAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI;IAKxD;;;;;;OAMG;IACI,cAAc,IAAK,MAAM;IAIhC;;OAEG;IACI,cAAc,IAAK,MAAM;CAGjC"}
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import type { VariableDeclarator } from '@swc/core';
|
|
1
|
+
import type { Expression, VariableDeclarator } from '@swc/core';
|
|
2
2
|
import type { ScopeInfo, I18nextToolkitConfig } from '../../types.js';
|
|
3
3
|
export declare class ScopeManager {
|
|
4
4
|
private scopeStack;
|
|
5
5
|
private config;
|
|
6
6
|
private scope;
|
|
7
|
+
private thisFieldStack;
|
|
7
8
|
private simpleConstants;
|
|
8
9
|
private simpleConstantObjects;
|
|
9
10
|
private sharedConstants;
|
|
@@ -27,6 +28,33 @@ export declare class ScopeManager {
|
|
|
27
28
|
* leak between files.
|
|
28
29
|
*/
|
|
29
30
|
reset(): void;
|
|
31
|
+
/**
|
|
32
|
+
* Pushes a new class scope used for tracking class field → ScopeInfo
|
|
33
|
+
* mappings. Should be called when entering a ClassDeclaration or
|
|
34
|
+
* ClassExpression.
|
|
35
|
+
*/
|
|
36
|
+
enterClassScope(): void;
|
|
37
|
+
/**
|
|
38
|
+
* Pops the current class scope. Should be called when leaving a
|
|
39
|
+
* ClassDeclaration or ClassExpression.
|
|
40
|
+
*/
|
|
41
|
+
exitClassScope(): void;
|
|
42
|
+
/**
|
|
43
|
+
* Records a class field's ScopeInfo so that later `this.<field>` or
|
|
44
|
+
* `this.<field>()` accesses inside the class can inherit the namespace
|
|
45
|
+
* and keyPrefix from its initializer.
|
|
46
|
+
*
|
|
47
|
+
* @param fieldName - Field name (private fields are prefixed with `#`)
|
|
48
|
+
* @param info - Scope information derived from the field's initializer
|
|
49
|
+
*/
|
|
50
|
+
setThisField(fieldName: string, info: ScopeInfo): void;
|
|
51
|
+
/**
|
|
52
|
+
* Looks up a class field's ScopeInfo from the innermost class scope outward.
|
|
53
|
+
*
|
|
54
|
+
* @param fieldName - Field name (private fields are prefixed with `#`)
|
|
55
|
+
* @returns Scope information if found, undefined otherwise
|
|
56
|
+
*/
|
|
57
|
+
getThisField(fieldName: string): ScopeInfo | undefined;
|
|
30
58
|
/**
|
|
31
59
|
* Enters a new variable scope by pushing a new scope map onto the stack.
|
|
32
60
|
* Used when entering functions to isolate variable declarations.
|
|
@@ -131,5 +159,38 @@ export declare class ScopeManager {
|
|
|
131
159
|
* resulting scope to the newly declared variable.
|
|
132
160
|
*/
|
|
133
161
|
private handleGetFixedTFromVariableDeclarator;
|
|
162
|
+
/**
|
|
163
|
+
* Returns the field name being referenced when an expression takes the form
|
|
164
|
+
* `this.<field>` or `this.<field>()`. Strips the call wrapper if present and
|
|
165
|
+
* prefixes private fields with `#`.
|
|
166
|
+
*
|
|
167
|
+
* Returns undefined if the expression is not such an access.
|
|
168
|
+
*/
|
|
169
|
+
private static extractThisFieldName;
|
|
170
|
+
/**
|
|
171
|
+
* Attaches the given ScopeInfo to all destructured variables of a variable
|
|
172
|
+
* declarator (`const { t } = ...`, `const [t] = ...`, or `const t = ...`).
|
|
173
|
+
*/
|
|
174
|
+
private attachScopeToDestructuredVars;
|
|
175
|
+
/**
|
|
176
|
+
* Registers a class field's initializer with the current class scope so that
|
|
177
|
+
* later `const { t } = this.<field>` or `const { t } = this.<field>()` calls
|
|
178
|
+
* inside the class body inherit the namespace and keyPrefix from the field
|
|
179
|
+
* initializer.
|
|
180
|
+
*
|
|
181
|
+
* Recognizes the same useTranslation-like patterns supported on regular
|
|
182
|
+
* variable declarations (configured via `useTranslationNames`).
|
|
183
|
+
*
|
|
184
|
+
* @param fieldName - Field name (private fields are prefixed with `#`)
|
|
185
|
+
* @param value - The field initializer expression
|
|
186
|
+
*/
|
|
187
|
+
registerClassField(fieldName: string, value: Expression | null | undefined): void;
|
|
188
|
+
/**
|
|
189
|
+
* Computes ScopeInfo (defaultNs, keyPrefix) for a useTranslation-style call
|
|
190
|
+
* given a hook configuration that describes argument positions. Mirrors the
|
|
191
|
+
* extraction performed by handleUseTranslationDeclarator without attaching
|
|
192
|
+
* the result to any variable.
|
|
193
|
+
*/
|
|
194
|
+
private computeScopeInfoFromHookCall;
|
|
134
195
|
}
|
|
135
196
|
//# sourceMappingURL=scope-manager.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"scope-manager.d.ts","sourceRoot":"","sources":["../../../src/extractor/parsers/scope-manager.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"scope-manager.d.ts","sourceRoot":"","sources":["../../../src/extractor/parsers/scope-manager.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,UAAU,EAEV,kBAAkB,EAMnB,MAAM,WAAW,CAAA;AAClB,OAAO,KAAK,EAAE,SAAS,EAA4B,oBAAoB,EAAE,MAAM,gBAAgB,CAAA;AAG/F,qBAAa,YAAY;IACvB,OAAO,CAAC,UAAU,CAAoC;IACtD,OAAO,CAAC,MAAM,CAAuC;IACrD,OAAO,CAAC,KAAK,CAAqE;IAMlF,OAAO,CAAC,cAAc,CAAoC;IAG1D,OAAO,CAAC,eAAe,CAAiC;IAGxD,OAAO,CAAC,qBAAqB,CAAiD;IAI9E,OAAO,CAAC,eAAe,CAAiC;IACxD,OAAO,CAAC,qBAAqB,CAAiD;gBAEjE,MAAM,EAAE,IAAI,CAAC,oBAAoB,EAAE,SAAS,CAAC;IAI1D;;;OAGG;IACH,OAAO,CAAC,MAAM,CAAC,kBAAkB;IAcjC;;;OAGG;IACH,OAAO,CAAC,MAAM,CAAC,+BAA+B;IAgB9C;;;;;;OAMG;IACI,KAAK,IAAK,IAAI;IAQrB;;;;OAIG;IACH,eAAe,IAAK,IAAI;IAIxB;;;OAGG;IACH,cAAc,IAAK,IAAI;IAIvB;;;;;;;OAOG;IACH,YAAY,CAAE,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,GAAG,IAAI;IAMvD;;;;;OAKG;IACH,YAAY,CAAE,SAAS,EAAE,MAAM,GAAG,SAAS,GAAG,SAAS;IASvD;;;OAGG;IACH,UAAU,IAAK,IAAI;IAInB;;;OAGG;IACH,SAAS,IAAK,IAAI;IAIlB;;;;;;OAMG;IACH,aAAa,CAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,GAAG,IAAI;IAUnD;;;;;;OAMG;IACH,eAAe,CAAE,IAAI,EAAE,MAAM,GAAG,SAAS,GAAG,SAAS;IAkBrD,OAAO,CAAC,uBAAuB;IAoB/B;;OAEG;IACI,6BAA6B,CAAE,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS;IAIvE;;;OAGG;IACH,OAAO,CAAC,6BAA6B;IAqBrC;;;;;;;;;;OAUG;IACH,wBAAwB,CAAE,IAAI,EAAE,kBAAkB,GAAG,IAAI;IAsGzD;;;;;;;;OAQG;IACH,OAAO,CAAC,+BAA+B;IA0GvC;;;;;;;;;;;;;OAaG;IACH,OAAO,CAAC,8BAA8B;IAmFtC;;;;;;;;;;OAUG;IACH,OAAO,CAAC,yBAAyB;IAiBjC;;;;;;OAMG;IACH,OAAO,CAAC,gBAAgB;IAexB;;;;;;;;;OASG;IACH,OAAO,CAAC,qCAAqC;IAqB7C;;;;;;OAMG;IACH,OAAO,CAAC,MAAM,CAAC,oBAAoB;IAgBnC;;;OAGG;IACH,OAAO,CAAC,6BAA6B;IAyBrC;;;;;;;;;;;OAWG;IACH,kBAAkB,CAAE,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,UAAU,GAAG,IAAI,GAAG,SAAS,GAAG,IAAI;IAwBlF;;;;;OAKG;IACH,OAAO,CAAC,4BAA4B;CA+DrC"}
|