@cratis/eslint-plugin-components 4.14.1 → 4.14.3
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 +2 -10
- package/lib/onbeforeexecuteMustReturn.js +56 -32
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -7,7 +7,7 @@ Cratis base config, [`@cratis/eslint-config`](https://www.npmjs.com/package/@cra
|
|
|
7
7
|
| ----------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
8
8
|
| `no-root-barrel-import` | Disallows importing a removed Components 3 component namespace from the Components 4 setup-only root. Use the exact component subpath (`@cratis/components/CommandDialog`, `@cratis/components/DataPage`, `@cratis/components/Toolbar`, …). Package-wide provider/configuration symbols remain allowed at the root; an unambiguous namespace violation is autofixed. |
|
|
9
9
|
| `no-primereact-dialog` | Disallows importing `Dialog` from `primereact/dialog`. Use `CommandDialog` from `@cratis/components/CommandDialog`, or `Dialog` from `@cratis/components/Dialogs` — the wrappers add Arc command binding, overlay/focus fixes, and theming. |
|
|
10
|
-
| `onbeforeexecute-must-return` | Requires
|
|
10
|
+
| `onbeforeexecute-must-return` | Requires `onBeforeExecute` on imported Components command dialogs/steppers or Arc `CommandForm` to return command values. It does not flag Arc `CommandScope`, whose callback is void. |
|
|
11
11
|
| `no-hooks-in-view-model` | Disallows React hooks (including generated Arc proxies' `.use()`) inside a view model class. View models must be plain, hook-free classes that receive injected abstractions. |
|
|
12
12
|
| `no-raw-command-form-marker` | Disallows identifying a CommandForm field or column by a hand-written `displayName` string, in either direction. Use `markAsCommandFormField`/`markAsCommandFormColumn` and `isCommandFormField`/`isCommandFormColumn` from `@cratis/components/CommandForm` — they go through a marker a build transform cannot rewrite. |
|
|
13
13
|
| `no-react-in-kernel` | Repository architecture rule that rejects React, React DOM, React Aria Components, and browser DOM globals in the explicit Components kernel inventory. It is intentionally not part of the consumer `recommended` config. |
|
|
@@ -173,15 +173,7 @@ relies on the fallback.
|
|
|
173
173
|
<CommandDialog onBeforeExecute={values => { return; }} />
|
|
174
174
|
```
|
|
175
175
|
|
|
176
|
-
|
|
177
|
-
and variable (`const onBeforeExecute = …`) forms. It is a lint backstop, not a full
|
|
178
|
-
control-flow analysis: a callback that returns a value on some branches but can still fall
|
|
179
|
-
through is not flagged.
|
|
180
|
-
|
|
181
|
-
The rule matches the `onBeforeExecute` name, not the component. Arc's `CommandScope` also has an
|
|
182
|
-
`onBeforeExecute` prop, but it is typed `(command) => void` and is not a transformer; the rule
|
|
183
|
-
still reports a callback there that returns nothing. Return the command (harmless) or disable the
|
|
184
|
-
rule for that line.
|
|
176
|
+
The rule checks JSX callbacks only when the component resolves through an import from `@cratis/components/CommandDialog` (`CommandDialog`, `StepperCommandDialog`, `CommandStepper`), `@cratis/components/CommandStepper` (`CommandStepper`), or `@cratis/arc.react/commands` (`CommandForm`). Aliases and namespace imports are supported. Unknown components, object properties, and standalone variables are not flagged because their callback contract cannot be inferred. In particular, Arc `CommandScope` has a void-returning `onBeforeExecute` side-effect hook, not a transformer, so it is not flagged. This rule is not a full control-flow analysis: a callback that returns a value on some branches but can still fall through is not flagged.
|
|
185
177
|
|
|
186
178
|
### `no-hooks-in-view-model`
|
|
187
179
|
|
|
@@ -32,39 +32,22 @@ const keyNameOf = (key) => {
|
|
|
32
32
|
return undefined;
|
|
33
33
|
};
|
|
34
34
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
35
|
+
const transformerExports = new Map([
|
|
36
|
+
[
|
|
37
|
+
'@cratis/components/CommandDialog',
|
|
38
|
+
new Set(['CommandDialog', 'StepperCommandDialog', 'CommandStepper']),
|
|
39
|
+
],
|
|
40
|
+
['@cratis/components/CommandStepper', new Set(['CommandStepper'])],
|
|
41
|
+
['@cratis/arc.react/commands', new Set(['CommandForm'])],
|
|
42
|
+
]);
|
|
41
43
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
return true;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
if (
|
|
51
|
-
(parent.type === 'Property' || parent.type === 'PropertyDefinition') &&
|
|
52
|
-
parent.value === node &&
|
|
53
|
-
keyNameOf(parent.key) === 'onBeforeExecute'
|
|
54
|
-
) {
|
|
55
|
-
return true;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
if (
|
|
59
|
-
parent.type === 'VariableDeclarator' &&
|
|
60
|
-
parent.init === node &&
|
|
61
|
-
parent.id?.type === 'Identifier' &&
|
|
62
|
-
parent.id.name === 'onBeforeExecute'
|
|
63
|
-
) {
|
|
64
|
-
return true;
|
|
44
|
+
const importBinding = (scope, name) => {
|
|
45
|
+
for (let current = scope; current; current = current.upper) {
|
|
46
|
+
const variable = current.set.get(name);
|
|
47
|
+
if (variable)
|
|
48
|
+
return variable.defs.find(definition => definition.type === 'ImportBinding')?.node;
|
|
65
49
|
}
|
|
66
|
-
|
|
67
|
-
return false;
|
|
50
|
+
return undefined;
|
|
68
51
|
};
|
|
69
52
|
|
|
70
53
|
// Require an `onBeforeExecute` callback to return the command values on every path. It is a
|
|
@@ -94,8 +77,34 @@ export const onbeforeexecuteMustReturn = {
|
|
|
94
77
|
},
|
|
95
78
|
},
|
|
96
79
|
create(context) {
|
|
80
|
+
const imports = new Map();
|
|
81
|
+
const isTransformer = (node) => {
|
|
82
|
+
const attribute = node.parent?.parent;
|
|
83
|
+
if (
|
|
84
|
+
node.parent?.type !== 'JSXExpressionContainer' ||
|
|
85
|
+
attribute?.type !== 'JSXAttribute' ||
|
|
86
|
+
attribute.name?.name !== 'onBeforeExecute'
|
|
87
|
+
) return false;
|
|
88
|
+
const opening = attribute.parent;
|
|
89
|
+
if (opening?.type !== 'JSXOpeningElement') return false;
|
|
90
|
+
const tag = opening.name;
|
|
91
|
+
const identifier = tag.type === 'JSXIdentifier'
|
|
92
|
+
? tag
|
|
93
|
+
: tag.type === 'JSXMemberExpression' &&
|
|
94
|
+
tag.object.type === 'JSXIdentifier' &&
|
|
95
|
+
tag.property.type === 'JSXIdentifier'
|
|
96
|
+
? tag.object
|
|
97
|
+
: undefined;
|
|
98
|
+
if (!identifier) return false;
|
|
99
|
+
const specifier = importBinding(context.sourceCode.getScope(node), identifier.name);
|
|
100
|
+
const imported = imports.get(specifier);
|
|
101
|
+
if (!imported) return false;
|
|
102
|
+
return tag.type === 'JSXIdentifier'
|
|
103
|
+
? imported.kind === 'named'
|
|
104
|
+
: imported.kind === 'namespace' && imported.exports.has(tag.property.name);
|
|
105
|
+
};
|
|
97
106
|
const check = (node) => {
|
|
98
|
-
if (!
|
|
107
|
+
if (!isTransformer(node)) return;
|
|
99
108
|
// An expression-bodied arrow (`values => values`) always returns a value.
|
|
100
109
|
if (
|
|
101
110
|
node.type === 'ArrowFunctionExpression' &&
|
|
@@ -118,6 +127,21 @@ export const onbeforeexecuteMustReturn = {
|
|
|
118
127
|
};
|
|
119
128
|
|
|
120
129
|
return {
|
|
130
|
+
ImportDeclaration(node) {
|
|
131
|
+
const exports = transformerExports.get(node.source.value);
|
|
132
|
+
if (!exports || node.importKind === 'type') return;
|
|
133
|
+
for (const specifier of node.specifiers) {
|
|
134
|
+
if (specifier.type === 'ImportNamespaceSpecifier') {
|
|
135
|
+
imports.set(specifier, { kind: 'namespace', exports });
|
|
136
|
+
} else if (
|
|
137
|
+
specifier.type === 'ImportSpecifier' &&
|
|
138
|
+
specifier.importKind !== 'type' &&
|
|
139
|
+
exports.has(keyNameOf(specifier.imported))
|
|
140
|
+
) {
|
|
141
|
+
imports.set(specifier, { kind: 'named' });
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
},
|
|
121
145
|
ArrowFunctionExpression: check,
|
|
122
146
|
FunctionExpression: check,
|
|
123
147
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cratis/eslint-plugin-components",
|
|
3
|
-
"version": "4.14.
|
|
3
|
+
"version": "4.14.3",
|
|
4
4
|
"description": "Cratis Components ESLint rules: import from subpaths not the root barrel, use the Cratis dialog wrappers instead of primereact/dialog, require onBeforeExecute callbacks to return their values, keep React hooks out of view models, protect the repository-owned React-free kernel, and identify CommandForm fields by a tamper-resistant marker rather than a raw displayName string. Compose on top of @cratis/eslint-config.",
|
|
5
5
|
"author": "Cratis",
|
|
6
6
|
"license": "MIT",
|