@trackunit/eslint-plugin-trackunit 0.6.114 → 0.6.115
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/CHANGELOG.md +7 -0
- package/README.md +1 -1
- package/package.json +2 -2
- package/src/lib/config/fragments/react-rules.js +1 -1
- package/src/lib/rules/component-name-matches-filename/component-name-matches-filename.d.ts +4 -0
- package/src/lib/rules/component-name-matches-filename/component-name-matches-filename.js +29 -1
package/CHANGELOG.md
CHANGED
package/README.md
CHANGED
|
@@ -65,7 +65,7 @@ All custom rules are enabled automatically through the presets under the `@track
|
|
|
65
65
|
| `@trackunit/require-component-prop-contracts` | — | — | Requires public component props to satisfy configured prop/interface contracts. |
|
|
66
66
|
| `@trackunit/require-list-item-virtualization-props` | — | — | Requires `VirtualizationListItemProps` on list items inside `<List>`. |
|
|
67
67
|
| `@trackunit/one-component-per-file` | error | — | Enforces at most one exported React component per file. `exportedOnly: false` restricts to one total. |
|
|
68
|
-
| `@trackunit/component-name-matches-filename` |
|
|
68
|
+
| `@trackunit/component-name-matches-filename` | error | — | Enforces that exported React component names match the filename (e.g. `Foo` in `Foo.tsx`). |
|
|
69
69
|
|
|
70
70
|
### Testing
|
|
71
71
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@trackunit/eslint-plugin-trackunit",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.115",
|
|
4
4
|
"license": "SEE LICENSE IN LICENSE.txt",
|
|
5
5
|
"repository": "https://github.com/Trackunit/manager",
|
|
6
6
|
"engines": {
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
},
|
|
9
9
|
"dependencies": {
|
|
10
10
|
"@nx/eslint-plugin": "23.1.0",
|
|
11
|
-
"@trackunit/css-classname-utils": "0.0.
|
|
11
|
+
"@trackunit/css-classname-utils": "0.0.18",
|
|
12
12
|
"@typescript-eslint/eslint-plugin": "8.58.1",
|
|
13
13
|
"@typescript-eslint/utils": "8.58.1",
|
|
14
14
|
"eslint-config-prettier": "^10.1.8",
|
|
@@ -120,7 +120,7 @@ exports.reactCustomRules = {
|
|
|
120
120
|
"@trackunit/prefer-field-components": "error",
|
|
121
121
|
"@trackunit/one-component-per-file": "error",
|
|
122
122
|
"@trackunit/component-name-matches-filename": [
|
|
123
|
-
"
|
|
123
|
+
"error",
|
|
124
124
|
{
|
|
125
125
|
allowedMismatches: [
|
|
126
126
|
// i18next convention: every library's translation.tsx exports a `Trans` wrapper
|
|
@@ -6,6 +6,10 @@
|
|
|
6
6
|
*
|
|
7
7
|
* Anonymous default exports are skipped (no name to compare).
|
|
8
8
|
* `index.*` files are skipped (they are entry-point re-export files, not component files).
|
|
9
|
+
* Files with a secondary extension (`Foo.variants.tsx`, `Foo.demo.tsx`) are skipped — their
|
|
10
|
+
* basename is not a valid identifier, so no export name could ever match it.
|
|
11
|
+
* Modules that neither render JSX nor import React are skipped — they hold no components,
|
|
12
|
+
* so a PascalCase export there is a plain function (an Nx generator, an Express handler, …).
|
|
9
13
|
*
|
|
10
14
|
* When combined with `one-component-per-file`, these two rules together fully enforce
|
|
11
15
|
* the "one named component per file" convention.
|
|
@@ -7,6 +7,10 @@
|
|
|
7
7
|
*
|
|
8
8
|
* Anonymous default exports are skipped (no name to compare).
|
|
9
9
|
* `index.*` files are skipped (they are entry-point re-export files, not component files).
|
|
10
|
+
* Files with a secondary extension (`Foo.variants.tsx`, `Foo.demo.tsx`) are skipped — their
|
|
11
|
+
* basename is not a valid identifier, so no export name could ever match it.
|
|
12
|
+
* Modules that neither render JSX nor import React are skipped — they hold no components,
|
|
13
|
+
* so a PascalCase export there is a plain function (an Nx generator, an Express handler, …).
|
|
10
14
|
*
|
|
11
15
|
* When combined with `one-component-per-file`, these two rules together fully enforce
|
|
12
16
|
* the "one named component per file" convention.
|
|
@@ -27,13 +31,16 @@ const minimatch_1 = require("minimatch");
|
|
|
27
31
|
const path_1 = tslib_1.__importDefault(require("path"));
|
|
28
32
|
const component_utils_1 = require("../../utils/component-utils");
|
|
29
33
|
const createRule = utils_1.ESLintUtils.RuleCreator(name => `https://github.com/trackunit/manager/blob/main/libs/eslint/plugin-trackunit/src/lib/rules/${name}/${name}.ts`);
|
|
34
|
+
/** `react`, `react-dom`, `react/jsx-runtime`, `react-dom/client`, … */
|
|
35
|
+
const REACT_MODULE_PATTERN = /^react(-dom)?(\/.+)?$/;
|
|
30
36
|
exports.componentNameMatchesFilename = createRule({
|
|
31
37
|
name: "component-name-matches-filename",
|
|
32
38
|
meta: {
|
|
33
39
|
type: "suggestion",
|
|
34
40
|
docs: {
|
|
35
41
|
description: "Enforce that exported React component names match the filename they live in. " +
|
|
36
|
-
"index.* files
|
|
42
|
+
"index.* files, files with a secondary extension, modules without React, and " +
|
|
43
|
+
"anonymous default exports are exempt.",
|
|
37
44
|
},
|
|
38
45
|
messages: {
|
|
39
46
|
componentNameMismatch: "Component '{{name}}' should match the filename. " + "Rename it to '{{expected}}' or move it to its own file.",
|
|
@@ -74,6 +81,11 @@ exports.componentNameMatchesFilename = createRule({
|
|
|
74
81
|
const basename = path_1.default.basename(filename, path_1.default.extname(filename));
|
|
75
82
|
if (basename === "index" || basename === "<input>")
|
|
76
83
|
return {};
|
|
84
|
+
// Role files keep their secondary extension in the basename (`Foo.variants`, `Foo.demo`),
|
|
85
|
+
// which no identifier can match. The primary segment names the subject the file relates
|
|
86
|
+
// to, not the symbol it exports, so there is nothing to enforce here.
|
|
87
|
+
if (basename.includes("."))
|
|
88
|
+
return {};
|
|
77
89
|
// Normalize kebab-case and snake_case filenames to PascalCase.
|
|
78
90
|
// e.g. `current-weather-status` → `CurrentWeatherStatus`, `app` → `App`.
|
|
79
91
|
const expectedName = basename
|
|
@@ -81,12 +93,28 @@ exports.componentNameMatchesFilename = createRule({
|
|
|
81
93
|
.map(segment => segment.charAt(0).toUpperCase() + segment.slice(1))
|
|
82
94
|
.join("");
|
|
83
95
|
const tracker = (0, component_utils_1.createComponentFileTracker)();
|
|
96
|
+
// A PascalCase export only means "component" in a module that actually uses React.
|
|
97
|
+
// Elsewhere it is a plain function that happens to be capitalized.
|
|
98
|
+
let usesReact = false;
|
|
84
99
|
return {
|
|
100
|
+
ImportDeclaration(node) {
|
|
101
|
+
if (REACT_MODULE_PATTERN.test(node.source.value)) {
|
|
102
|
+
usesReact = true;
|
|
103
|
+
}
|
|
104
|
+
},
|
|
105
|
+
JSXElement() {
|
|
106
|
+
usesReact = true;
|
|
107
|
+
},
|
|
108
|
+
JSXFragment() {
|
|
109
|
+
usesReact = true;
|
|
110
|
+
},
|
|
85
111
|
FunctionDeclaration: tracker.onFunctionDeclaration,
|
|
86
112
|
VariableDeclarator: tracker.onVariableDeclarator,
|
|
87
113
|
ExportNamedDeclaration: tracker.onExportNamedDeclaration,
|
|
88
114
|
ExportDefaultDeclaration: tracker.onExportDefaultDeclaration,
|
|
89
115
|
"Program:exit"() {
|
|
116
|
+
if (!usesReact)
|
|
117
|
+
return;
|
|
90
118
|
for (const { name, node } of tracker.getExportedComponents()) {
|
|
91
119
|
if (name === null)
|
|
92
120
|
continue; // anonymous default exports are exempt
|