@generaltranslation/gt-next-lint 0.0.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/README.md +244 -0
- package/dist/configs/recommended.d.ts +15 -0
- package/dist/configs/recommended.d.ts.map +1 -0
- package/dist/configs/recommended.js +18 -0
- package/dist/configs/recommended.js.map +1 -0
- package/dist/index.d.ts +31 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +24 -0
- package/dist/index.js.map +1 -0
- package/dist/rules/no-unwrapped-dynamic-content.d.ts +12 -0
- package/dist/rules/no-unwrapped-dynamic-content.d.ts.map +1 -0
- package/dist/rules/no-unwrapped-dynamic-content.js +177 -0
- package/dist/rules/no-unwrapped-dynamic-content.js.map +1 -0
- package/package.json +66 -0
package/README.md
ADDED
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
# @generaltranslation/gt-next-lint
|
|
2
|
+
|
|
3
|
+
ESLint plugin for General Translation Next.js integration. Provides automatic linting for GT-Next translation components to catch common mistakes and ensure proper usage.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **Automatic Detection**: Finds dynamic content (`{expressions}`) inside `<T>` components
|
|
8
|
+
- **Precise Error Reporting**: Shows exact file locations and line numbers
|
|
9
|
+
- **Framework Agnostic**: Works with Next.js, React, and other JSX frameworks
|
|
10
|
+
- **Zero Configuration**: Works out of the box with sensible defaults
|
|
11
|
+
- **TypeScript Support**: Full TypeScript support with type definitions
|
|
12
|
+
|
|
13
|
+
## Installation
|
|
14
|
+
|
|
15
|
+
### Automatic Setup (Recommended)
|
|
16
|
+
|
|
17
|
+
If you're already using `gt-next`, the ESLint plugin will be automatically configured when you install it:
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
npm install --save-dev @generaltranslation/gt-next-lint
|
|
21
|
+
# or
|
|
22
|
+
yarn add --dev @generaltranslation/gt-next-lint
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
The plugin works automatically with your existing `withGTConfig` setup. No additional configuration needed!
|
|
26
|
+
|
|
27
|
+
### Manual Setup
|
|
28
|
+
|
|
29
|
+
If you want to configure it manually, add the plugin to your ESLint configuration:
|
|
30
|
+
|
|
31
|
+
```javascript
|
|
32
|
+
// eslint.config.mjs
|
|
33
|
+
import gtNext from '@generaltranslation/gt-next-lint';
|
|
34
|
+
|
|
35
|
+
export default [
|
|
36
|
+
{
|
|
37
|
+
files: ['**/*.{js,jsx,ts,tsx}'],
|
|
38
|
+
plugins: {
|
|
39
|
+
'gt-next': gtNext,
|
|
40
|
+
},
|
|
41
|
+
rules: {
|
|
42
|
+
'gt-next/no-unwrapped-dynamic-content': 'warn',
|
|
43
|
+
},
|
|
44
|
+
languageOptions: {
|
|
45
|
+
ecmaVersion: 2020,
|
|
46
|
+
sourceType: 'module',
|
|
47
|
+
parserOptions: {
|
|
48
|
+
ecmaFeatures: {
|
|
49
|
+
jsx: true,
|
|
50
|
+
},
|
|
51
|
+
},
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
];
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
### withGTConfig Integration
|
|
58
|
+
|
|
59
|
+
You can customize the ESLint integration in your Next.js config:
|
|
60
|
+
|
|
61
|
+
```javascript
|
|
62
|
+
// next.config.mjs
|
|
63
|
+
import { withGTConfig } from 'gt-next/config';
|
|
64
|
+
|
|
65
|
+
export default withGTConfig(nextConfig, {
|
|
66
|
+
projectId: 'your-project-id',
|
|
67
|
+
// ESLint options
|
|
68
|
+
eslint: true, // Enable ESLint integration (default: true)
|
|
69
|
+
eslintSeverity: 'error', // 'error' or 'warn' (default: 'warn')
|
|
70
|
+
overwriteESLintConfig: false, // Overwrite existing eslint.config.mjs (default: false)
|
|
71
|
+
});
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### Using Recommended Configuration
|
|
75
|
+
|
|
76
|
+
For the easiest setup, use the recommended configuration:
|
|
77
|
+
|
|
78
|
+
```javascript
|
|
79
|
+
// eslint.config.mjs
|
|
80
|
+
import gtNext from '@generaltranslation/gt-next-lint';
|
|
81
|
+
|
|
82
|
+
export default [
|
|
83
|
+
gtNext.configs.recommended,
|
|
84
|
+
];
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
### Legacy ESLint Configuration (.eslintrc)
|
|
88
|
+
|
|
89
|
+
```json
|
|
90
|
+
{
|
|
91
|
+
"plugins": ["gt-next"],
|
|
92
|
+
"rules": {
|
|
93
|
+
"gt-next/no-unwrapped-dynamic-content": "warn"
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
## Rules
|
|
99
|
+
|
|
100
|
+
### `no-unwrapped-dynamic-content`
|
|
101
|
+
|
|
102
|
+
Detects unwrapped dynamic content in GT-Next translation components.
|
|
103
|
+
|
|
104
|
+
#### ❌ Incorrect
|
|
105
|
+
|
|
106
|
+
```jsx
|
|
107
|
+
import { T } from 'gt-next';
|
|
108
|
+
|
|
109
|
+
// Error: Dynamic content should be wrapped
|
|
110
|
+
<T>Hello {userName}!</T>
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
#### ✅ Correct
|
|
114
|
+
|
|
115
|
+
```jsx
|
|
116
|
+
import { T, Var } from 'gt-next';
|
|
117
|
+
|
|
118
|
+
// Correct: Dynamic content is properly wrapped
|
|
119
|
+
<T>Hello <Var>{userName}</Var>!</T>
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
#### Supported Patterns
|
|
123
|
+
|
|
124
|
+
The rule understands various import and usage patterns:
|
|
125
|
+
|
|
126
|
+
**Named Imports**
|
|
127
|
+
```jsx
|
|
128
|
+
import { T, Var, DateTime, Num, Currency } from 'gt-next';
|
|
129
|
+
|
|
130
|
+
<T>
|
|
131
|
+
Hello <Var>{name}</Var>!
|
|
132
|
+
Today is <DateTime>{date}</DateTime>.
|
|
133
|
+
Price: <Currency>{price}</Currency>
|
|
134
|
+
Count: <Num>{count}</Num>
|
|
135
|
+
</T>
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
**Namespace Imports**
|
|
139
|
+
```jsx
|
|
140
|
+
import * as GT from 'gt-next';
|
|
141
|
+
|
|
142
|
+
<GT.T>Hello <GT.Var>{userName}</GT.Var>!</GT.T>
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
**Variable Assignments**
|
|
146
|
+
```jsx
|
|
147
|
+
import { T, Var } from 'gt-next';
|
|
148
|
+
|
|
149
|
+
const MyT = T;
|
|
150
|
+
const MyVar = Var;
|
|
151
|
+
|
|
152
|
+
<MyT>Hello <MyVar>{userName}</MyVar>!</MyT>
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
**Different Import Sources**
|
|
156
|
+
- `gt-next`
|
|
157
|
+
- `gt-next/client`
|
|
158
|
+
- `gt-next/server`
|
|
159
|
+
|
|
160
|
+
#### Rule Options
|
|
161
|
+
|
|
162
|
+
```javascript
|
|
163
|
+
{
|
|
164
|
+
"gt-next/no-unwrapped-dynamic-content": ["warn", {
|
|
165
|
+
"severity": "warn" // "warn" or "error"
|
|
166
|
+
}]
|
|
167
|
+
}
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
## Integration with Next.js
|
|
171
|
+
|
|
172
|
+
This ESLint plugin works perfectly alongside the GT-Next package:
|
|
173
|
+
|
|
174
|
+
1. **Install GT-Next**:
|
|
175
|
+
```bash
|
|
176
|
+
npm install gt-next
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
2. **Configure Next.js**:
|
|
180
|
+
```javascript
|
|
181
|
+
// next.config.mjs
|
|
182
|
+
import { withGTConfig } from 'gt-next/config';
|
|
183
|
+
|
|
184
|
+
export default withGTConfig(nextConfig, {
|
|
185
|
+
projectId: 'your-project-id',
|
|
186
|
+
locales: ['en', 'es', 'fr'],
|
|
187
|
+
});
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
3. **Add ESLint Plugin**:
|
|
191
|
+
```javascript
|
|
192
|
+
// eslint.config.mjs
|
|
193
|
+
import gtNext from '@generaltranslation/gt-next-lint';
|
|
194
|
+
|
|
195
|
+
export default [gtNext.configs.recommended];
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
## When Linting Runs
|
|
199
|
+
|
|
200
|
+
- **During Development**: Shows warnings in your IDE (VS Code, WebStorm, etc.)
|
|
201
|
+
- **During Build**: Runs as part of `npm run build` and `next build`
|
|
202
|
+
- **In CI/CD**: Catches issues in your continuous integration pipeline
|
|
203
|
+
|
|
204
|
+
## Comparison with SWC Plugin
|
|
205
|
+
|
|
206
|
+
This ESLint plugin provides better developer experience compared to the SWC plugin:
|
|
207
|
+
|
|
208
|
+
| Feature | ESLint Plugin | SWC Plugin |
|
|
209
|
+
|---------|---------------|------------|
|
|
210
|
+
| **Error Location** | ✅ Exact file:line | ❌ Console logs only |
|
|
211
|
+
| **IDE Integration** | ✅ Real-time warnings | ❌ Build-time only |
|
|
212
|
+
| **Customizable Rules** | ✅ Full ESLint config | ❌ Limited options |
|
|
213
|
+
| **Performance** | ✅ Fast linting | ✅ Fast compilation |
|
|
214
|
+
|
|
215
|
+
## Monorepo Support
|
|
216
|
+
|
|
217
|
+
The plugin works seamlessly in monorepo setups:
|
|
218
|
+
|
|
219
|
+
```javascript
|
|
220
|
+
// packages/app/eslint.config.mjs
|
|
221
|
+
import gtNext from '@generaltranslation/gt-next-lint';
|
|
222
|
+
|
|
223
|
+
export default [
|
|
224
|
+
{
|
|
225
|
+
...gtNext.configs.recommended,
|
|
226
|
+
files: ['**/*.{js,jsx,ts,tsx}'],
|
|
227
|
+
// Will detect GT-Next usage across your entire monorepo
|
|
228
|
+
},
|
|
229
|
+
];
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
## Contributing
|
|
233
|
+
|
|
234
|
+
Issues and pull requests are welcome! Please see our [Contributing Guide](../../CONTRIBUTING.md).
|
|
235
|
+
|
|
236
|
+
## License
|
|
237
|
+
|
|
238
|
+
FSL-1.1-ALv2 - see [LICENSE](../../LICENSE) for details.
|
|
239
|
+
|
|
240
|
+
## Support
|
|
241
|
+
|
|
242
|
+
- 📖 [Documentation](https://generaltranslation.com/docs)
|
|
243
|
+
- 🐛 [Issues](https://github.com/generaltranslation/gt/issues)
|
|
244
|
+
- 💬 [Discussions](https://github.com/generaltranslation/gt/discussions)
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Recommended ESLint configuration for GT-Next
|
|
3
|
+
*/
|
|
4
|
+
export declare const recommended: {
|
|
5
|
+
plugins: string[];
|
|
6
|
+
rules: {
|
|
7
|
+
'gt-next/no-unwrapped-dynamic-content': string;
|
|
8
|
+
};
|
|
9
|
+
settings: {
|
|
10
|
+
react: {
|
|
11
|
+
version: string;
|
|
12
|
+
};
|
|
13
|
+
};
|
|
14
|
+
};
|
|
15
|
+
//# sourceMappingURL=recommended.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"recommended.d.ts","sourceRoot":"","sources":["../../src/configs/recommended.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,eAAO,MAAM,WAAW;;;;;;;;;;CAUvB,CAAC"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Recommended ESLint configuration for GT-Next
|
|
4
|
+
*/
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.recommended = void 0;
|
|
7
|
+
exports.recommended = {
|
|
8
|
+
plugins: ['gt-next'],
|
|
9
|
+
rules: {
|
|
10
|
+
'gt-next/no-unwrapped-dynamic-content': 'warn',
|
|
11
|
+
},
|
|
12
|
+
settings: {
|
|
13
|
+
react: {
|
|
14
|
+
version: 'detect',
|
|
15
|
+
},
|
|
16
|
+
},
|
|
17
|
+
};
|
|
18
|
+
//# sourceMappingURL=recommended.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"recommended.js","sourceRoot":"","sources":["../../src/configs/recommended.ts"],"names":[],"mappings":";AAAA;;GAEG;;;AAEU,QAAA,WAAW,GAAG;IACzB,OAAO,EAAE,CAAC,SAAS,CAAC;IACpB,KAAK,EAAE;QACL,sCAAsC,EAAE,MAAM;KAC/C;IACD,QAAQ,EAAE;QACR,KAAK,EAAE;YACL,OAAO,EAAE,QAAQ;SAClB;KACF;CACF,CAAC"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GT-Next ESLint Plugin
|
|
3
|
+
*
|
|
4
|
+
* Provides ESLint rules for General Translation Next.js integration.
|
|
5
|
+
* This plugin detects unwrapped dynamic content in translation components
|
|
6
|
+
* and provides better error reporting with file locations and line numbers.
|
|
7
|
+
*/
|
|
8
|
+
declare const plugin: {
|
|
9
|
+
meta: {
|
|
10
|
+
name: string;
|
|
11
|
+
version: string;
|
|
12
|
+
};
|
|
13
|
+
rules: {
|
|
14
|
+
'no-unwrapped-dynamic-content': import("eslint").Rule.RuleModule;
|
|
15
|
+
};
|
|
16
|
+
configs: {
|
|
17
|
+
recommended: {
|
|
18
|
+
plugins: string[];
|
|
19
|
+
rules: {
|
|
20
|
+
'gt-next/no-unwrapped-dynamic-content': string;
|
|
21
|
+
};
|
|
22
|
+
settings: {
|
|
23
|
+
react: {
|
|
24
|
+
version: string;
|
|
25
|
+
};
|
|
26
|
+
};
|
|
27
|
+
};
|
|
28
|
+
};
|
|
29
|
+
};
|
|
30
|
+
export = plugin;
|
|
31
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAKH,QAAA,MAAM,MAAM;;;;;;;;;;;;;;;;;;;;;CAWX,CAAC;AAEF,SAAS,MAAM,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* GT-Next ESLint Plugin
|
|
4
|
+
*
|
|
5
|
+
* Provides ESLint rules for General Translation Next.js integration.
|
|
6
|
+
* This plugin detects unwrapped dynamic content in translation components
|
|
7
|
+
* and provides better error reporting with file locations and line numbers.
|
|
8
|
+
*/
|
|
9
|
+
const no_unwrapped_dynamic_content_1 = require("./rules/no-unwrapped-dynamic-content");
|
|
10
|
+
const recommended_1 = require("./configs/recommended");
|
|
11
|
+
const plugin = {
|
|
12
|
+
meta: {
|
|
13
|
+
name: '@generaltranslation/gt-next-lint',
|
|
14
|
+
version: '1.0.0',
|
|
15
|
+
},
|
|
16
|
+
rules: {
|
|
17
|
+
'no-unwrapped-dynamic-content': no_unwrapped_dynamic_content_1.noUnwrappedDynamicContent,
|
|
18
|
+
},
|
|
19
|
+
configs: {
|
|
20
|
+
recommended: recommended_1.recommended,
|
|
21
|
+
},
|
|
22
|
+
};
|
|
23
|
+
module.exports = plugin;
|
|
24
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAAA;;;;;;GAMG;AAEH,uFAAiF;AACjF,uDAAoD;AAEpD,MAAM,MAAM,GAAG;IACb,IAAI,EAAE;QACJ,IAAI,EAAE,kCAAkC;QACxC,OAAO,EAAE,OAAO;KACjB;IACD,KAAK,EAAE;QACL,8BAA8B,EAAE,wDAAyB;KAC1D;IACD,OAAO,EAAE;QACP,WAAW,EAAX,yBAAW;KACZ;CACF,CAAC;AAEF,iBAAS,MAAM,CAAC"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ESLint rule: no-unwrapped-dynamic-content
|
|
3
|
+
*
|
|
4
|
+
* Detects unwrapped dynamic content in GT-Next translation components.
|
|
5
|
+
* Equivalent to the SWC plugin functionality but with proper ESLint error reporting.
|
|
6
|
+
*
|
|
7
|
+
* This rule checks for JSX expressions ({dynamic content}) inside <T> components
|
|
8
|
+
* that are not wrapped in variable components (<Var>, <DateTime>, <Num>, <Currency>).
|
|
9
|
+
*/
|
|
10
|
+
import type { Rule } from 'eslint';
|
|
11
|
+
export declare const noUnwrappedDynamicContent: Rule.RuleModule;
|
|
12
|
+
//# sourceMappingURL=no-unwrapped-dynamic-content.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"no-unwrapped-dynamic-content.d.ts","sourceRoot":"","sources":["../../src/rules/no-unwrapped-dynamic-content.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC;AAkBnC,eAAO,MAAM,yBAAyB,EAAE,IAAI,CAAC,UAgL5C,CAAC"}
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* ESLint rule: no-unwrapped-dynamic-content
|
|
4
|
+
*
|
|
5
|
+
* Detects unwrapped dynamic content in GT-Next translation components.
|
|
6
|
+
* Equivalent to the SWC plugin functionality but with proper ESLint error reporting.
|
|
7
|
+
*
|
|
8
|
+
* This rule checks for JSX expressions ({dynamic content}) inside <T> components
|
|
9
|
+
* that are not wrapped in variable components (<Var>, <DateTime>, <Num>, <Currency>).
|
|
10
|
+
*/
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.noUnwrappedDynamicContent = void 0;
|
|
13
|
+
const GT_MODULES = ['gt-next', 'gt-next/client', 'gt-next/server'];
|
|
14
|
+
const TRANSLATION_COMPONENTS = ['T'];
|
|
15
|
+
const VARIABLE_COMPONENTS = ['Var', 'DateTime', 'Num', 'Currency'];
|
|
16
|
+
function isGTModule(source) {
|
|
17
|
+
return GT_MODULES.includes(source);
|
|
18
|
+
}
|
|
19
|
+
function isTranslationComponent(name) {
|
|
20
|
+
return TRANSLATION_COMPONENTS.includes(name);
|
|
21
|
+
}
|
|
22
|
+
function isVariableComponent(name) {
|
|
23
|
+
return VARIABLE_COMPONENTS.includes(name);
|
|
24
|
+
}
|
|
25
|
+
exports.noUnwrappedDynamicContent = {
|
|
26
|
+
meta: {
|
|
27
|
+
type: 'problem',
|
|
28
|
+
docs: {
|
|
29
|
+
description: 'Detect unwrapped dynamic content in GT-Next translation components',
|
|
30
|
+
category: 'Best Practices',
|
|
31
|
+
recommended: true,
|
|
32
|
+
url: 'https://github.com/generaltranslation/gt/tree/main/packages/next-lint#no-unwrapped-dynamic-content',
|
|
33
|
+
},
|
|
34
|
+
fixable: undefined,
|
|
35
|
+
schema: [
|
|
36
|
+
{
|
|
37
|
+
type: 'object',
|
|
38
|
+
properties: {
|
|
39
|
+
severity: {
|
|
40
|
+
type: 'string',
|
|
41
|
+
enum: ['error', 'warn'],
|
|
42
|
+
default: 'warn',
|
|
43
|
+
},
|
|
44
|
+
},
|
|
45
|
+
additionalProperties: false,
|
|
46
|
+
},
|
|
47
|
+
],
|
|
48
|
+
messages: {
|
|
49
|
+
unwrappedDynamicContent: 'Dynamic content in <T> component should be wrapped in a variable component (<Var>, <DateTime>, <Num>, or <Currency>)',
|
|
50
|
+
},
|
|
51
|
+
},
|
|
52
|
+
create(context) {
|
|
53
|
+
const state = {
|
|
54
|
+
translationStack: 0,
|
|
55
|
+
variableStack: 0,
|
|
56
|
+
imports: {
|
|
57
|
+
translationComponents: new Set(),
|
|
58
|
+
variableComponents: new Set(),
|
|
59
|
+
namespaceImports: new Set(),
|
|
60
|
+
assignedTranslationComponents: new Set(),
|
|
61
|
+
assignedVariableComponents: new Set(),
|
|
62
|
+
},
|
|
63
|
+
};
|
|
64
|
+
function getElementName(node) {
|
|
65
|
+
const name = node?.openingElement?.name;
|
|
66
|
+
if (!name)
|
|
67
|
+
return null;
|
|
68
|
+
if (name.type === 'JSXIdentifier') {
|
|
69
|
+
return name.name;
|
|
70
|
+
}
|
|
71
|
+
if (name.type === 'JSXMemberExpression') {
|
|
72
|
+
const obj = name.object;
|
|
73
|
+
const prop = name.property;
|
|
74
|
+
if (obj?.type === 'JSXIdentifier' && prop?.type === 'JSXIdentifier') {
|
|
75
|
+
return `${obj.name}.${prop.name}`;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
80
|
+
function isNamespaceTranslationComponent(elementName) {
|
|
81
|
+
const parts = elementName.split('.');
|
|
82
|
+
if (parts.length === 2) {
|
|
83
|
+
const [namespace, component] = parts;
|
|
84
|
+
return (state.imports.namespaceImports.has(namespace) &&
|
|
85
|
+
isTranslationComponent(component));
|
|
86
|
+
}
|
|
87
|
+
return false;
|
|
88
|
+
}
|
|
89
|
+
function isNamespaceVariableComponent(elementName) {
|
|
90
|
+
const parts = elementName.split('.');
|
|
91
|
+
if (parts.length === 2) {
|
|
92
|
+
const [namespace, component] = parts;
|
|
93
|
+
return (state.imports.namespaceImports.has(namespace) &&
|
|
94
|
+
isVariableComponent(component));
|
|
95
|
+
}
|
|
96
|
+
return false;
|
|
97
|
+
}
|
|
98
|
+
return {
|
|
99
|
+
// Track imports from GT-Next modules
|
|
100
|
+
ImportDeclaration(node) {
|
|
101
|
+
if (node.source?.type === 'Literal' &&
|
|
102
|
+
typeof node.source.value === 'string') {
|
|
103
|
+
const source = node.source.value;
|
|
104
|
+
if (isGTModule(source)) {
|
|
105
|
+
for (const specifier of node.specifiers || []) {
|
|
106
|
+
if (specifier.type === 'ImportSpecifier') {
|
|
107
|
+
const importedName = specifier.imported?.name || '';
|
|
108
|
+
const localName = specifier.local?.name || '';
|
|
109
|
+
if (isTranslationComponent(importedName)) {
|
|
110
|
+
state.imports.translationComponents.add(localName);
|
|
111
|
+
}
|
|
112
|
+
else if (isVariableComponent(importedName)) {
|
|
113
|
+
state.imports.variableComponents.add(localName);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
else if (specifier.type === 'ImportNamespaceSpecifier') {
|
|
117
|
+
const localName = specifier.local?.name || '';
|
|
118
|
+
state.imports.namespaceImports.add(localName);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
},
|
|
124
|
+
// Track variable assignments from GT components
|
|
125
|
+
VariableDeclarator(node) {
|
|
126
|
+
if (node.id?.type === 'Identifier' &&
|
|
127
|
+
node.init?.type === 'Identifier') {
|
|
128
|
+
const varName = node.id.name;
|
|
129
|
+
const assignedFrom = node.init.name;
|
|
130
|
+
if (state.imports.translationComponents.has(assignedFrom)) {
|
|
131
|
+
state.imports.assignedTranslationComponents.add(varName);
|
|
132
|
+
}
|
|
133
|
+
else if (state.imports.variableComponents.has(assignedFrom)) {
|
|
134
|
+
state.imports.assignedVariableComponents.add(varName);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
},
|
|
138
|
+
// Detect unwrapped dynamic content
|
|
139
|
+
JSXExpressionContainer(node) {
|
|
140
|
+
// Check if this expression is inside a translation component but not inside a variable component
|
|
141
|
+
let inTranslationComponent = false;
|
|
142
|
+
let inVariableComponent = false;
|
|
143
|
+
// Walk up the AST to find parent JSX elements
|
|
144
|
+
let currentNode = node.parent;
|
|
145
|
+
while (currentNode) {
|
|
146
|
+
if (currentNode.type === 'JSXElement') {
|
|
147
|
+
const elementName = getElementName(currentNode);
|
|
148
|
+
if (elementName) {
|
|
149
|
+
// Check if this is a variable component
|
|
150
|
+
if (state.imports.variableComponents.has(elementName) ||
|
|
151
|
+
state.imports.assignedVariableComponents.has(elementName) ||
|
|
152
|
+
isNamespaceVariableComponent(elementName)) {
|
|
153
|
+
inVariableComponent = true;
|
|
154
|
+
break; // If we find a variable component, we don't need to check further
|
|
155
|
+
}
|
|
156
|
+
// Check if this is a translation component
|
|
157
|
+
else if (state.imports.translationComponents.has(elementName) ||
|
|
158
|
+
state.imports.assignedTranslationComponents.has(elementName) ||
|
|
159
|
+
isNamespaceTranslationComponent(elementName)) {
|
|
160
|
+
inTranslationComponent = true;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
currentNode = currentNode.parent;
|
|
165
|
+
}
|
|
166
|
+
// Report if we're inside a translation component but not inside a variable component
|
|
167
|
+
if (inTranslationComponent && !inVariableComponent) {
|
|
168
|
+
context.report({
|
|
169
|
+
node,
|
|
170
|
+
messageId: 'unwrappedDynamicContent',
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
},
|
|
174
|
+
};
|
|
175
|
+
},
|
|
176
|
+
};
|
|
177
|
+
//# sourceMappingURL=no-unwrapped-dynamic-content.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"no-unwrapped-dynamic-content.js","sourceRoot":"","sources":["../../src/rules/no-unwrapped-dynamic-content.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@generaltranslation/gt-next-lint",
|
|
3
|
+
"version": "0.0.0",
|
|
4
|
+
"description": "ESLint plugin for General Translation Next.js integration",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"files": [
|
|
8
|
+
"dist"
|
|
9
|
+
],
|
|
10
|
+
"keywords": [
|
|
11
|
+
"eslint",
|
|
12
|
+
"eslintplugin",
|
|
13
|
+
"eslint-plugin",
|
|
14
|
+
"gt-next",
|
|
15
|
+
"translation",
|
|
16
|
+
"internationalization",
|
|
17
|
+
"i18n",
|
|
18
|
+
"jsx",
|
|
19
|
+
"react",
|
|
20
|
+
"nextjs"
|
|
21
|
+
],
|
|
22
|
+
"author": "General Translation, Inc.",
|
|
23
|
+
"license": "FSL-1.1-ALv2",
|
|
24
|
+
"repository": {
|
|
25
|
+
"type": "git",
|
|
26
|
+
"url": "git+https://github.com/generaltranslation/gt.git",
|
|
27
|
+
"directory": "packages/next-lint"
|
|
28
|
+
},
|
|
29
|
+
"bugs": {
|
|
30
|
+
"url": "https://github.com/generaltranslation/gt/issues"
|
|
31
|
+
},
|
|
32
|
+
"homepage": "https://generaltranslation.com/",
|
|
33
|
+
"engines": {
|
|
34
|
+
"node": ">=18.0.0"
|
|
35
|
+
},
|
|
36
|
+
"scripts": {
|
|
37
|
+
"patch": "npm version patch",
|
|
38
|
+
"transpile": "tsc",
|
|
39
|
+
"build": "npm run transpile",
|
|
40
|
+
"build:clean": "rm -rf dist && npm run build",
|
|
41
|
+
"build:release": "npm run build:clean",
|
|
42
|
+
"dev": "tsc --watch",
|
|
43
|
+
"release": "npm run build:clean && npm publish",
|
|
44
|
+
"release:alpha": "npm run build:clean && npm publish --tag alpha",
|
|
45
|
+
"release:beta": "npm run build:clean && npm publish --tag beta",
|
|
46
|
+
"release:latest": "npm run build:clean && npm publish --tag latest",
|
|
47
|
+
"lint": "eslint \"src/**/*.{js,ts,tsx}\" \"**/__tests__/**/*.{js,ts,tsx}\"",
|
|
48
|
+
"lint:fix": "eslint \"src/**/*.{js,ts,tsx}\" \"**/__tests__/**/*.{js,ts,tsx}\" --fix",
|
|
49
|
+
"test": "vitest run",
|
|
50
|
+
"test:watch": "vitest",
|
|
51
|
+
"prepublishOnly": "npm run build:clean"
|
|
52
|
+
},
|
|
53
|
+
"peerDependencies": {
|
|
54
|
+
"eslint": "^8.0.0 || ^9.0.0",
|
|
55
|
+
"gt-next": "^6.0.0"
|
|
56
|
+
},
|
|
57
|
+
"devDependencies": {
|
|
58
|
+
"@types/eslint": "^8.56.0",
|
|
59
|
+
"@types/node": "^20.0.0",
|
|
60
|
+
"@typescript-eslint/eslint-plugin": "^8.0.0",
|
|
61
|
+
"@typescript-eslint/parser": "^8.0.0",
|
|
62
|
+
"eslint": "^9.0.0",
|
|
63
|
+
"typescript": "^5.0.0",
|
|
64
|
+
"vitest": "^2.0.0"
|
|
65
|
+
}
|
|
66
|
+
}
|