eslint-plugin-flawless 0.1.5 → 0.1.7
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 +11 -10
- package/dist/index.d.mts +45 -8
- package/dist/index.mjs +294 -49
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -176,16 +176,17 @@ pnpm eslint-docs
|
|
|
176
176
|
💭
|
|
177
177
|
Requires [type information](https://typescript-eslint.io/linting/typed-linting).
|
|
178
178
|
|
|
179
|
-
| Name | Description
|
|
180
|
-
| :-------------------------------------------------------------------------------------------- |
|
|
181
|
-
| [jsx-shorthand-boolean](src/rules/jsx-shorthand-boolean/documentation.md) | Disallow shorthand boolean JSX attributes
|
|
182
|
-
| [jsx-shorthand-fragment](src/rules/jsx-shorthand-fragment/documentation.md) |
|
|
183
|
-
| [naming-convention](src/rules/naming-convention/documentation.md) | Enforce naming conventions for everything across a codebase
|
|
184
|
-
| [no-unnecessary-use-callback](src/rules/no-unnecessary-use-callback/documentation.md) | Disallow unnecessary usage of 'useCallback'
|
|
185
|
-
| [no-unnecessary-use-memo](src/rules/no-unnecessary-use-memo/documentation.md) | Disallow unnecessary usage of 'useMemo'
|
|
186
|
-
| [prefer-destructuring-assignment](src/rules/prefer-destructuring-assignment/documentation.md) | Enforce destructuring assignment for component props
|
|
187
|
-
| [
|
|
188
|
-
| [
|
|
179
|
+
| Name | Description | 🔧 | 💭 |
|
|
180
|
+
| :-------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------- | :-- | :-- |
|
|
181
|
+
| [jsx-shorthand-boolean](src/rules/jsx-shorthand-boolean/documentation.md) | Disallow shorthand boolean JSX attributes | 🔧 | |
|
|
182
|
+
| [jsx-shorthand-fragment](src/rules/jsx-shorthand-fragment/documentation.md) | Enforce a consistent fragment form: the shorthand `<>...</>` or a named fragment | 🔧 | |
|
|
183
|
+
| [naming-convention](src/rules/naming-convention/documentation.md) | Enforce naming conventions for everything across a codebase | | 💭 |
|
|
184
|
+
| [no-unnecessary-use-callback](src/rules/no-unnecessary-use-callback/documentation.md) | Disallow unnecessary usage of 'useCallback' | | |
|
|
185
|
+
| [no-unnecessary-use-memo](src/rules/no-unnecessary-use-memo/documentation.md) | Disallow unnecessary usage of 'useMemo' | | |
|
|
186
|
+
| [prefer-destructuring-assignment](src/rules/prefer-destructuring-assignment/documentation.md) | Enforce destructuring assignment for component props | 🔧 | |
|
|
187
|
+
| [purity](src/rules/purity/documentation.md) | Disallow impure calls such as `math.random` or `os.clock` during render | | |
|
|
188
|
+
| [toml-sort-keys](src/rules/toml-sort-keys/documentation.md) | Enforce a configured sort order for TOML keys and tables | 🔧 | |
|
|
189
|
+
| [yaml-block-key-blank-lines](src/rules/yaml-block-key-blank-lines/documentation.md) | Enforce blank lines around top-level YAML block collection keys | 🔧 | |
|
|
189
190
|
|
|
190
191
|
<!-- end auto-generated rules list -->
|
|
191
192
|
|
package/dist/index.d.mts
CHANGED
|
@@ -9,7 +9,23 @@ interface PluginDocumentation {
|
|
|
9
9
|
}
|
|
10
10
|
//#endregion
|
|
11
11
|
//#region src/rules/jsx-shorthand-fragment/rule.d.ts
|
|
12
|
-
|
|
12
|
+
declare const MESSAGE_ID_NAMED = "useNamedFragment";
|
|
13
|
+
declare const MESSAGE_ID_SHORTHAND = "useShorthandFragment";
|
|
14
|
+
type MessageIds$3 = typeof MESSAGE_ID_NAMED | typeof MESSAGE_ID_SHORTHAND;
|
|
15
|
+
interface JsxShorthandFragmentOptions {
|
|
16
|
+
/**
|
|
17
|
+
* The identifier used for the named fragment element in `"element"` mode
|
|
18
|
+
* (e.g. `"Fragment"` or `"React.Fragment"`). Defaults to `"Fragment"`.
|
|
19
|
+
*/
|
|
20
|
+
readonly fragmentName?: string;
|
|
21
|
+
/**
|
|
22
|
+
* Which form to enforce: `"syntax"` (shorthand `<>...</>`, the default) or
|
|
23
|
+
* `"element"` (a named fragment such as `<Fragment>...</Fragment>`).
|
|
24
|
+
*/
|
|
25
|
+
readonly mode?: Mode;
|
|
26
|
+
}
|
|
27
|
+
type Options$3 = [JsxShorthandFragmentOptions?];
|
|
28
|
+
type Mode = "element" | "syntax";
|
|
13
29
|
//#endregion
|
|
14
30
|
//#region src/rules/naming-convention/utils/enums.d.ts
|
|
15
31
|
declare const Selector: {
|
|
@@ -119,7 +135,7 @@ interface NamingSelector {
|
|
|
119
135
|
//#endregion
|
|
120
136
|
//#region src/rules/naming-convention/rule.d.ts
|
|
121
137
|
type MessageIds$2 = "doesNotMatchFormat" | "doesNotMatchFormatTrimmed" | "missingAffix" | "missingUnderscore" | "satisfyCustom" | "unexpectedUnderscore";
|
|
122
|
-
type Options$
|
|
138
|
+
type Options$2 = Array<NamingSelector>;
|
|
123
139
|
//#endregion
|
|
124
140
|
//#region src/rules/no-unnecessary-use-callback/rule.d.ts
|
|
125
141
|
declare const MESSAGE_ID_DEFAULT$1 = "default";
|
|
@@ -131,6 +147,18 @@ declare const MESSAGE_ID_DEFAULT = "default";
|
|
|
131
147
|
declare const MESSAGE_ID_INSIDE_USE_EFFECT = "noUnnecessaryUseMemoInsideUseEffect";
|
|
132
148
|
type MessageIds = typeof MESSAGE_ID_DEFAULT | typeof MESSAGE_ID_INSIDE_USE_EFFECT;
|
|
133
149
|
//#endregion
|
|
150
|
+
//#region src/rules/purity/rule.d.ts
|
|
151
|
+
interface PurityOptions {
|
|
152
|
+
/**
|
|
153
|
+
* Extra dotted call signatures to treat as impure, added to the defaults
|
|
154
|
+
* (e.g. `"Math.random"` when using a Luau `Math` polyfill).
|
|
155
|
+
*/
|
|
156
|
+
readonly additionalFunctions?: ReadonlyArray<string>;
|
|
157
|
+
/** Default signatures to exclude (e.g. `"os.date"`). */
|
|
158
|
+
readonly ignore?: ReadonlyArray<string>;
|
|
159
|
+
}
|
|
160
|
+
type Options$1 = [PurityOptions?];
|
|
161
|
+
//#endregion
|
|
134
162
|
//#region src/rules/toml-sort-keys/rule.d.ts
|
|
135
163
|
type Options = Array<SortSpec>;
|
|
136
164
|
interface SortOrderObject {
|
|
@@ -154,10 +182,10 @@ declare const plugin: {
|
|
|
154
182
|
"jsx-shorthand-boolean": TSESLint.RuleModule<"setAttributeValue", [], PluginDocumentation, TSESLint.RuleListener> & {
|
|
155
183
|
name: string;
|
|
156
184
|
};
|
|
157
|
-
"jsx-shorthand-fragment": TSESLint.RuleModule<
|
|
185
|
+
"jsx-shorthand-fragment": TSESLint.RuleModule<MessageIds$3, Options$3, PluginDocumentation, TSESLint.RuleListener> & {
|
|
158
186
|
name: string;
|
|
159
187
|
};
|
|
160
|
-
"naming-convention": TSESLint.RuleModule<MessageIds$2, Options$
|
|
188
|
+
"naming-convention": TSESLint.RuleModule<MessageIds$2, Options$2, PluginDocumentation, TSESLint.RuleListener> & {
|
|
161
189
|
name: string;
|
|
162
190
|
};
|
|
163
191
|
"no-unnecessary-use-callback": TSESLint.RuleModule<MessageIds$1, [], PluginDocumentation, TSESLint.RuleListener> & {
|
|
@@ -169,6 +197,9 @@ declare const plugin: {
|
|
|
169
197
|
"prefer-destructuring-assignment": TSESLint.RuleModule<"default", [], PluginDocumentation, TSESLint.RuleListener> & {
|
|
170
198
|
name: string;
|
|
171
199
|
};
|
|
200
|
+
purity: TSESLint.RuleModule<"impureCall", Options$1, PluginDocumentation, TSESLint.RuleListener> & {
|
|
201
|
+
name: string;
|
|
202
|
+
};
|
|
172
203
|
"toml-sort-keys": TSESLint.RuleModule<"unsorted", Options, PluginDocumentation, TSESLint.RuleListener> & {
|
|
173
204
|
name: string;
|
|
174
205
|
};
|
|
@@ -192,10 +223,10 @@ declare const _default: {
|
|
|
192
223
|
"jsx-shorthand-boolean": import("${configDir}").RuleModule<"setAttributeValue", [], PluginDocumentation, import("${configDir}").RuleListener> & {
|
|
193
224
|
name: string;
|
|
194
225
|
};
|
|
195
|
-
"jsx-shorthand-fragment": import("${configDir}").RuleModule<
|
|
226
|
+
"jsx-shorthand-fragment": import("${configDir}").RuleModule<MessageIds$3, Options$3, PluginDocumentation, import("${configDir}").RuleListener> & {
|
|
196
227
|
name: string;
|
|
197
228
|
};
|
|
198
|
-
"naming-convention": import("${configDir}").RuleModule<MessageIds$2, Options$
|
|
229
|
+
"naming-convention": import("${configDir}").RuleModule<MessageIds$2, Options$2, PluginDocumentation, import("${configDir}").RuleListener> & {
|
|
199
230
|
name: string;
|
|
200
231
|
};
|
|
201
232
|
"no-unnecessary-use-callback": import("${configDir}").RuleModule<MessageIds$1, [], PluginDocumentation, import("${configDir}").RuleListener> & {
|
|
@@ -207,6 +238,9 @@ declare const _default: {
|
|
|
207
238
|
"prefer-destructuring-assignment": import("${configDir}").RuleModule<"default", [], PluginDocumentation, import("${configDir}").RuleListener> & {
|
|
208
239
|
name: string;
|
|
209
240
|
};
|
|
241
|
+
purity: import("${configDir}").RuleModule<"impureCall", Options$1, PluginDocumentation, import("${configDir}").RuleListener> & {
|
|
242
|
+
name: string;
|
|
243
|
+
};
|
|
210
244
|
"toml-sort-keys": import("${configDir}").RuleModule<"unsorted", Options, PluginDocumentation, import("${configDir}").RuleListener> & {
|
|
211
245
|
name: string;
|
|
212
246
|
};
|
|
@@ -227,10 +261,10 @@ declare const _default: {
|
|
|
227
261
|
"jsx-shorthand-boolean": import("${configDir}").RuleModule<"setAttributeValue", [], PluginDocumentation, import("${configDir}").RuleListener> & {
|
|
228
262
|
name: string;
|
|
229
263
|
};
|
|
230
|
-
"jsx-shorthand-fragment": import("${configDir}").RuleModule<
|
|
264
|
+
"jsx-shorthand-fragment": import("${configDir}").RuleModule<MessageIds$3, Options$3, PluginDocumentation, import("${configDir}").RuleListener> & {
|
|
231
265
|
name: string;
|
|
232
266
|
};
|
|
233
|
-
"naming-convention": import("${configDir}").RuleModule<MessageIds$2, Options$
|
|
267
|
+
"naming-convention": import("${configDir}").RuleModule<MessageIds$2, Options$2, PluginDocumentation, import("${configDir}").RuleListener> & {
|
|
234
268
|
name: string;
|
|
235
269
|
};
|
|
236
270
|
"no-unnecessary-use-callback": import("${configDir}").RuleModule<MessageIds$1, [], PluginDocumentation, import("${configDir}").RuleListener> & {
|
|
@@ -242,6 +276,9 @@ declare const _default: {
|
|
|
242
276
|
"prefer-destructuring-assignment": import("${configDir}").RuleModule<"default", [], PluginDocumentation, import("${configDir}").RuleListener> & {
|
|
243
277
|
name: string;
|
|
244
278
|
};
|
|
279
|
+
purity: import("${configDir}").RuleModule<"impureCall", Options$1, PluginDocumentation, import("${configDir}").RuleListener> & {
|
|
280
|
+
name: string;
|
|
281
|
+
};
|
|
245
282
|
"toml-sort-keys": import("${configDir}").RuleModule<"unsorted", Options, PluginDocumentation, import("${configDir}").RuleListener> & {
|
|
246
283
|
name: string;
|
|
247
284
|
};
|
package/dist/index.mjs
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import { RuleCreator, getParserServices } from "@typescript-eslint/utils/eslint-utils";
|
|
2
|
+
import { ASTUtils, AST_NODE_TYPES, ESLintUtils, TSESLint } from "@typescript-eslint/utils";
|
|
2
3
|
import { DefinitionType, ImplicitLibVariable, PatternVisitor, ScopeType, Visitor } from "@typescript-eslint/scope-manager";
|
|
3
4
|
import { requiresQuoting } from "@typescript-eslint/type-utils";
|
|
4
|
-
import { ASTUtils, AST_NODE_TYPES, ESLintUtils, TSESLint } from "@typescript-eslint/utils";
|
|
5
5
|
import assert from "node:assert";
|
|
6
6
|
import * as core from "@eslint-react/core";
|
|
7
7
|
import { findVariable } from "@typescript-eslint/utils/ast-utils";
|
|
8
8
|
import { getStaticTOMLValue } from "toml-eslint-parser";
|
|
9
9
|
//#region package.json
|
|
10
10
|
var name = "eslint-plugin-flawless";
|
|
11
|
-
var version = "0.1.
|
|
11
|
+
var version = "0.1.7";
|
|
12
12
|
var repository = {
|
|
13
13
|
"url": "git+https://github.com/christopher-buss/eslint-plugin-flawless.git",
|
|
14
14
|
"type": "git"
|
|
@@ -35,10 +35,10 @@ function createEslintRule(rule) {
|
|
|
35
35
|
}
|
|
36
36
|
//#endregion
|
|
37
37
|
//#region src/rules/jsx-shorthand-boolean/rule.ts
|
|
38
|
-
const RULE_NAME$
|
|
38
|
+
const RULE_NAME$8 = "jsx-shorthand-boolean";
|
|
39
39
|
const MESSAGE_ID$3 = "setAttributeValue";
|
|
40
|
-
const messages$
|
|
41
|
-
function create$
|
|
40
|
+
const messages$8 = { [MESSAGE_ID$3]: "Set an explicit value for boolean attribute '{{name}}'." };
|
|
41
|
+
function create$8(context) {
|
|
42
42
|
const { sourceCode } = context;
|
|
43
43
|
return { JSXAttribute(node) {
|
|
44
44
|
if (node.value !== null) return;
|
|
@@ -51,8 +51,8 @@ function create$7(context) {
|
|
|
51
51
|
} };
|
|
52
52
|
}
|
|
53
53
|
const jsxShorthandBoolean = createEslintRule({
|
|
54
|
-
name: RULE_NAME$
|
|
55
|
-
create: create$
|
|
54
|
+
name: RULE_NAME$8,
|
|
55
|
+
create: create$8,
|
|
56
56
|
defaultOptions: [],
|
|
57
57
|
meta: {
|
|
58
58
|
docs: {
|
|
@@ -62,50 +62,112 @@ const jsxShorthandBoolean = createEslintRule({
|
|
|
62
62
|
},
|
|
63
63
|
fixable: "code",
|
|
64
64
|
hasSuggestions: false,
|
|
65
|
-
messages: messages$
|
|
65
|
+
messages: messages$8,
|
|
66
66
|
schema: [],
|
|
67
67
|
type: "suggestion"
|
|
68
68
|
}
|
|
69
69
|
});
|
|
70
70
|
//#endregion
|
|
71
71
|
//#region src/rules/jsx-shorthand-fragment/rule.ts
|
|
72
|
-
const RULE_NAME$
|
|
73
|
-
const
|
|
72
|
+
const RULE_NAME$7 = "jsx-shorthand-fragment";
|
|
73
|
+
const MESSAGE_ID_NAMED = "useNamedFragment";
|
|
74
|
+
const MESSAGE_ID_SHORTHAND = "useShorthandFragment";
|
|
75
|
+
const DEFAULT_MODE = "syntax";
|
|
74
76
|
const DEFAULT_FRAGMENT_NAME = "Fragment";
|
|
75
|
-
const messages$
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
77
|
+
const messages$7 = {
|
|
78
|
+
[MESSAGE_ID_NAMED]: "Use the '{{name}}' component instead of fragment shorthand syntax.",
|
|
79
|
+
[MESSAGE_ID_SHORTHAND]: "Use the fragment shorthand syntax '<>...</>' instead of the '{{name}}' component."
|
|
80
|
+
};
|
|
81
|
+
const schema$2 = [{
|
|
82
|
+
additionalProperties: false,
|
|
83
|
+
properties: {
|
|
84
|
+
fragmentName: {
|
|
85
|
+
description: "The identifier to use for the named fragment element in \"element\" mode.",
|
|
86
|
+
type: "string"
|
|
87
|
+
},
|
|
88
|
+
mode: {
|
|
89
|
+
description: "Which form to enforce: \"syntax\" (shorthand `<>...</>`, default) or \"element\" (a named fragment).",
|
|
90
|
+
enum: ["element", "syntax"],
|
|
91
|
+
type: "string"
|
|
92
|
+
}
|
|
93
|
+
},
|
|
94
|
+
type: "object"
|
|
79
95
|
}];
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
96
|
+
/**
|
|
97
|
+
* Flattens a JSX element name into a dotted string, e.g. `Fragment` or
|
|
98
|
+
* `React.Fragment`. Returns `null` for namespaced names (`<a:b>`) which cannot
|
|
99
|
+
* be a fragment.
|
|
100
|
+
*
|
|
101
|
+
* @param node - The JSX element name node.
|
|
102
|
+
* @returns The dotted name, or `null` when it is not a plain identifier chain.
|
|
103
|
+
*/
|
|
104
|
+
function jsxNameToString(node) {
|
|
105
|
+
if (node.type === AST_NODE_TYPES.JSXIdentifier) return node.name;
|
|
106
|
+
if (node.type === AST_NODE_TYPES.JSXMemberExpression) {
|
|
107
|
+
const object = jsxNameToString(node.object);
|
|
108
|
+
if (object === null) return null;
|
|
109
|
+
return `${object}.${node.property.name}`;
|
|
110
|
+
}
|
|
111
|
+
return null;
|
|
112
|
+
}
|
|
113
|
+
function create$7(context) {
|
|
114
|
+
const options = context.options[0] ?? {};
|
|
115
|
+
const mode = options.mode ?? DEFAULT_MODE;
|
|
116
|
+
const fragmentName = options.fragmentName ?? DEFAULT_FRAGMENT_NAME;
|
|
117
|
+
if (mode === "element") return { JSXFragment(node) {
|
|
83
118
|
const { closingFragment, openingFragment } = node;
|
|
119
|
+
context.report({
|
|
120
|
+
data: { name: fragmentName },
|
|
121
|
+
fix: (fixer) => {
|
|
122
|
+
return [fixer.replaceText(openingFragment, `<${fragmentName}>`), fixer.replaceText(closingFragment, `</${fragmentName}>`)];
|
|
123
|
+
},
|
|
124
|
+
messageId: MESSAGE_ID_NAMED,
|
|
125
|
+
node
|
|
126
|
+
});
|
|
127
|
+
} };
|
|
128
|
+
const namedFragments = /* @__PURE__ */ new Set([
|
|
129
|
+
"Fragment",
|
|
130
|
+
fragmentName,
|
|
131
|
+
"React.Fragment"
|
|
132
|
+
]);
|
|
133
|
+
return { JSXElement(node) {
|
|
134
|
+
const { openingElement } = node;
|
|
135
|
+
const name = jsxNameToString(openingElement.name);
|
|
136
|
+
if (name === null || !namedFragments.has(name)) return;
|
|
137
|
+
if (openingElement.attributes.length > 0) return;
|
|
84
138
|
context.report({
|
|
85
139
|
data: { name },
|
|
86
140
|
fix: (fixer) => {
|
|
87
|
-
|
|
141
|
+
const { closingElement } = node;
|
|
142
|
+
if (closingElement === null) return fixer.replaceText(node, "<></>");
|
|
143
|
+
return [fixer.replaceText(openingElement, "<>"), fixer.replaceText(closingElement, "</>")];
|
|
88
144
|
},
|
|
89
|
-
messageId:
|
|
145
|
+
messageId: MESSAGE_ID_SHORTHAND,
|
|
90
146
|
node
|
|
91
147
|
});
|
|
92
148
|
} };
|
|
93
149
|
}
|
|
94
150
|
const jsxShorthandFragment = createEslintRule({
|
|
95
|
-
name: RULE_NAME$
|
|
96
|
-
create: create$
|
|
97
|
-
defaultOptions: [
|
|
151
|
+
name: RULE_NAME$7,
|
|
152
|
+
create: create$7,
|
|
153
|
+
defaultOptions: [{
|
|
154
|
+
fragmentName: DEFAULT_FRAGMENT_NAME,
|
|
155
|
+
mode: DEFAULT_MODE
|
|
156
|
+
}],
|
|
98
157
|
meta: {
|
|
99
|
-
defaultOptions: [
|
|
158
|
+
defaultOptions: [{
|
|
159
|
+
fragmentName: DEFAULT_FRAGMENT_NAME,
|
|
160
|
+
mode: DEFAULT_MODE
|
|
161
|
+
}],
|
|
100
162
|
docs: {
|
|
101
|
-
description: "
|
|
163
|
+
description: "Enforce a consistent fragment form: the shorthand `<>...</>` or a named fragment",
|
|
102
164
|
recommended: false,
|
|
103
165
|
requiresTypeChecking: false
|
|
104
166
|
},
|
|
105
167
|
fixable: "code",
|
|
106
168
|
hasSuggestions: false,
|
|
107
|
-
messages: messages$
|
|
108
|
-
schema: schema$
|
|
169
|
+
messages: messages$7,
|
|
170
|
+
schema: schema$2,
|
|
109
171
|
type: "suggestion"
|
|
110
172
|
}
|
|
111
173
|
});
|
|
@@ -1269,8 +1331,8 @@ const SCHEMA = {
|
|
|
1269
1331
|
};
|
|
1270
1332
|
//#endregion
|
|
1271
1333
|
//#region src/rules/naming-convention/rule.ts
|
|
1272
|
-
const RULE_NAME$
|
|
1273
|
-
const messages$
|
|
1334
|
+
const RULE_NAME$6 = "naming-convention";
|
|
1335
|
+
const messages$6 = {
|
|
1274
1336
|
doesNotMatchFormat: "{{type}} name `{{name}}` must match one of the following formats: {{formats}}",
|
|
1275
1337
|
doesNotMatchFormatTrimmed: "{{type}} name `{{name}}` trimmed as `{{processedName}}` must match one of the following formats: {{formats}}",
|
|
1276
1338
|
missingAffix: "{{type}} name `{{name}}` must have one of the following {{position}}es: {{affixes}}",
|
|
@@ -1300,7 +1362,7 @@ const camelCaseNamingConfig = [
|
|
|
1300
1362
|
selector: "typeLike"
|
|
1301
1363
|
}
|
|
1302
1364
|
];
|
|
1303
|
-
function create$
|
|
1365
|
+
function create$6(contextWithoutDefaults) {
|
|
1304
1366
|
const context = contextWithoutDefaults.options.length > 0 ? contextWithoutDefaults : Object.setPrototypeOf({ options: camelCaseNamingConfig }, contextWithoutDefaults);
|
|
1305
1367
|
const validators = parseOptions(context);
|
|
1306
1368
|
const compilerOptions = getParserServices(context, true).program?.getCompilerOptions() ?? {};
|
|
@@ -1620,8 +1682,8 @@ function create$5(contextWithoutDefaults) {
|
|
|
1620
1682
|
}));
|
|
1621
1683
|
}
|
|
1622
1684
|
const namingConvention = createEslintRule({
|
|
1623
|
-
name: RULE_NAME$
|
|
1624
|
-
create: create$
|
|
1685
|
+
name: RULE_NAME$6,
|
|
1686
|
+
create: create$6,
|
|
1625
1687
|
defaultOptions: camelCaseNamingConfig,
|
|
1626
1688
|
meta: {
|
|
1627
1689
|
docs: {
|
|
@@ -1631,7 +1693,7 @@ const namingConvention = createEslintRule({
|
|
|
1631
1693
|
},
|
|
1632
1694
|
fixable: void 0,
|
|
1633
1695
|
hasSuggestions: false,
|
|
1634
|
-
messages: messages$
|
|
1696
|
+
messages: messages$6,
|
|
1635
1697
|
schema: SCHEMA,
|
|
1636
1698
|
type: "suggestion"
|
|
1637
1699
|
}
|
|
@@ -1899,15 +1961,15 @@ function resolveFactory(sourceCode, node) {
|
|
|
1899
1961
|
}
|
|
1900
1962
|
//#endregion
|
|
1901
1963
|
//#region src/rules/no-unnecessary-use-callback/rule.ts
|
|
1902
|
-
const RULE_NAME$
|
|
1964
|
+
const RULE_NAME$5 = "no-unnecessary-use-callback";
|
|
1903
1965
|
const MESSAGE_ID_DEFAULT$1 = "default";
|
|
1904
1966
|
const MESSAGE_ID_INSIDE_USE_EFFECT$1 = "noUnnecessaryUseCallbackInsideUseEffect";
|
|
1905
|
-
const messages$
|
|
1967
|
+
const messages$5 = {
|
|
1906
1968
|
[MESSAGE_ID_DEFAULT$1]: "An 'useCallback' with empty deps and no references to the component scope may be unnecessary.",
|
|
1907
1969
|
[MESSAGE_ID_INSIDE_USE_EFFECT$1]: "'{{name}}' is only used inside 1 useEffect, which may be unnecessary. You can move the computation into useEffect directly and merge the dependency arrays."
|
|
1908
1970
|
};
|
|
1909
1971
|
const noUnnecessaryUseCallback = createEslintRule({
|
|
1910
|
-
name: RULE_NAME$
|
|
1972
|
+
name: RULE_NAME$5,
|
|
1911
1973
|
create: createUnnecessaryHookRule({
|
|
1912
1974
|
hook: "useCallback",
|
|
1913
1975
|
messageIds: {
|
|
@@ -1923,22 +1985,22 @@ const noUnnecessaryUseCallback = createEslintRule({
|
|
|
1923
1985
|
requiresTypeChecking: false
|
|
1924
1986
|
},
|
|
1925
1987
|
hasSuggestions: false,
|
|
1926
|
-
messages: messages$
|
|
1988
|
+
messages: messages$5,
|
|
1927
1989
|
schema: [],
|
|
1928
1990
|
type: "suggestion"
|
|
1929
1991
|
}
|
|
1930
1992
|
});
|
|
1931
1993
|
//#endregion
|
|
1932
1994
|
//#region src/rules/no-unnecessary-use-memo/rule.ts
|
|
1933
|
-
const RULE_NAME$
|
|
1995
|
+
const RULE_NAME$4 = "no-unnecessary-use-memo";
|
|
1934
1996
|
const MESSAGE_ID_DEFAULT = "default";
|
|
1935
1997
|
const MESSAGE_ID_INSIDE_USE_EFFECT = "noUnnecessaryUseMemoInsideUseEffect";
|
|
1936
|
-
const messages$
|
|
1998
|
+
const messages$4 = {
|
|
1937
1999
|
[MESSAGE_ID_DEFAULT]: "An 'useMemo' with empty deps and no references to the component scope may be unnecessary.",
|
|
1938
2000
|
[MESSAGE_ID_INSIDE_USE_EFFECT]: "'{{name}}' is only used inside 1 useEffect, which may be unnecessary. You can move the computation into useEffect directly and merge the dependency arrays."
|
|
1939
2001
|
};
|
|
1940
2002
|
const noUnnecessaryUseMemo = createEslintRule({
|
|
1941
|
-
name: RULE_NAME$
|
|
2003
|
+
name: RULE_NAME$4,
|
|
1942
2004
|
create: createUnnecessaryHookRule({
|
|
1943
2005
|
hook: "useMemo",
|
|
1944
2006
|
messageIds: {
|
|
@@ -1954,16 +2016,16 @@ const noUnnecessaryUseMemo = createEslintRule({
|
|
|
1954
2016
|
requiresTypeChecking: false
|
|
1955
2017
|
},
|
|
1956
2018
|
hasSuggestions: false,
|
|
1957
|
-
messages: messages$
|
|
2019
|
+
messages: messages$4,
|
|
1958
2020
|
schema: [],
|
|
1959
2021
|
type: "suggestion"
|
|
1960
2022
|
}
|
|
1961
2023
|
});
|
|
1962
2024
|
//#endregion
|
|
1963
2025
|
//#region src/rules/prefer-destructuring-assignment/rule.ts
|
|
1964
|
-
const RULE_NAME$
|
|
1965
|
-
const MESSAGE_ID$
|
|
1966
|
-
const messages$
|
|
2026
|
+
const RULE_NAME$3 = "prefer-destructuring-assignment";
|
|
2027
|
+
const MESSAGE_ID$2 = "default";
|
|
2028
|
+
const messages$3 = { [MESSAGE_ID$2]: "Use destructuring assignment for component props." };
|
|
1967
2029
|
/**
|
|
1968
2030
|
* Identifiers that cannot be used as a binding name in strict-mode module code.
|
|
1969
2031
|
* A property may be accessed with such a name (`props.default`), but a shorthand
|
|
@@ -2089,7 +2151,7 @@ function reportComponent(context, scope, propsParameter, propertyVariable) {
|
|
|
2089
2151
|
const propertyNames = collectPropertyNames(memberReferences);
|
|
2090
2152
|
if (!(!hasNonMemberReference && propertyNames !== null && !wouldShadowExistingBinding(context.sourceCode, scope, propertyVariable, memberReferences))) {
|
|
2091
2153
|
for (const { member } of memberReferences) context.report({
|
|
2092
|
-
messageId: MESSAGE_ID$
|
|
2154
|
+
messageId: MESSAGE_ID$2,
|
|
2093
2155
|
node: member
|
|
2094
2156
|
});
|
|
2095
2157
|
return;
|
|
@@ -2106,7 +2168,7 @@ function reportComponent(context, scope, propsParameter, propertyVariable) {
|
|
|
2106
2168
|
if (index === 0) return [fixer.replaceTextRange([propsParameter.range[0], nameEnd], pattern), replaceAccess];
|
|
2107
2169
|
return replaceAccess;
|
|
2108
2170
|
},
|
|
2109
|
-
messageId: MESSAGE_ID$
|
|
2171
|
+
messageId: MESSAGE_ID$2,
|
|
2110
2172
|
node: member
|
|
2111
2173
|
});
|
|
2112
2174
|
}
|
|
@@ -2125,7 +2187,7 @@ function reportComponent(context, scope, propsParameter, propertyVariable) {
|
|
|
2125
2187
|
* @param context - The rule context.
|
|
2126
2188
|
* @returns The rule listener.
|
|
2127
2189
|
*/
|
|
2128
|
-
function create$
|
|
2190
|
+
function create$3(context) {
|
|
2129
2191
|
const { api, visitor } = core.getFunctionComponentCollector(context);
|
|
2130
2192
|
return {
|
|
2131
2193
|
...visitor,
|
|
@@ -2143,8 +2205,8 @@ function create$2(context) {
|
|
|
2143
2205
|
};
|
|
2144
2206
|
}
|
|
2145
2207
|
const preferDestructuringAssignment = createEslintRule({
|
|
2146
|
-
name: RULE_NAME$
|
|
2147
|
-
create: create$
|
|
2208
|
+
name: RULE_NAME$3,
|
|
2209
|
+
create: create$3,
|
|
2148
2210
|
defaultOptions: [],
|
|
2149
2211
|
meta: {
|
|
2150
2212
|
docs: {
|
|
@@ -2154,12 +2216,194 @@ const preferDestructuringAssignment = createEslintRule({
|
|
|
2154
2216
|
},
|
|
2155
2217
|
fixable: "code",
|
|
2156
2218
|
hasSuggestions: false,
|
|
2157
|
-
messages: messages$
|
|
2219
|
+
messages: messages$3,
|
|
2158
2220
|
schema: [],
|
|
2159
2221
|
type: "problem"
|
|
2160
2222
|
}
|
|
2161
2223
|
});
|
|
2162
2224
|
//#endregion
|
|
2225
|
+
//#region src/rules/purity/rule.ts
|
|
2226
|
+
const RULE_NAME$2 = "purity";
|
|
2227
|
+
const MESSAGE_ID$1 = "impureCall";
|
|
2228
|
+
/**
|
|
2229
|
+
* Non-deterministic Luau / Roblox calls, written as dotted paths matching how
|
|
2230
|
+
* they appear in roblox-ts source. `new Random()` is normalized to
|
|
2231
|
+
* `"Random.new"` (see the `NewExpression` visitor).
|
|
2232
|
+
*/
|
|
2233
|
+
const DEFAULT_SIGNATURES = [
|
|
2234
|
+
"DateTime.now",
|
|
2235
|
+
"HttpService.GenerateGUID",
|
|
2236
|
+
"Random.new",
|
|
2237
|
+
"Workspace.GetServerTimeNow",
|
|
2238
|
+
"elapsedTime",
|
|
2239
|
+
"math.random",
|
|
2240
|
+
"math.randomseed",
|
|
2241
|
+
"os.clock",
|
|
2242
|
+
"os.date",
|
|
2243
|
+
"os.time",
|
|
2244
|
+
"tick",
|
|
2245
|
+
"time"
|
|
2246
|
+
];
|
|
2247
|
+
/**
|
|
2248
|
+
* Bare Luau globals for which a matching local binding means the reference is
|
|
2249
|
+
* shadowed rather than the ambient global. Service objects are intentionally
|
|
2250
|
+
* excluded because they are impure even when imported from `@rbxts/services`.
|
|
2251
|
+
*/
|
|
2252
|
+
const GUARDED_GLOBALS = /* @__PURE__ */ new Set([
|
|
2253
|
+
"elapsedTime",
|
|
2254
|
+
"math",
|
|
2255
|
+
"os",
|
|
2256
|
+
"tick",
|
|
2257
|
+
"time"
|
|
2258
|
+
]);
|
|
2259
|
+
const messages$2 = { [MESSAGE_ID$1]: "Do not call '{{name}}' during render. Components and hooks must be pure. Move this call into an event handler, effect, or state initializer." };
|
|
2260
|
+
const schema$1 = [{
|
|
2261
|
+
additionalProperties: false,
|
|
2262
|
+
properties: {
|
|
2263
|
+
additionalFunctions: {
|
|
2264
|
+
description: "Extra dotted call signatures to treat as impure (e.g. \"Math.random\").",
|
|
2265
|
+
items: { type: "string" },
|
|
2266
|
+
type: "array",
|
|
2267
|
+
uniqueItems: true
|
|
2268
|
+
},
|
|
2269
|
+
ignore: {
|
|
2270
|
+
description: "Default signatures to exclude (e.g. \"os.date\").",
|
|
2271
|
+
items: { type: "string" },
|
|
2272
|
+
type: "array",
|
|
2273
|
+
uniqueItems: true
|
|
2274
|
+
}
|
|
2275
|
+
},
|
|
2276
|
+
type: "object"
|
|
2277
|
+
}];
|
|
2278
|
+
/**
|
|
2279
|
+
* Strips wrappers that are irrelevant to the callee identity so that
|
|
2280
|
+
* `a.b?.()` and `a.b!()` still match.
|
|
2281
|
+
*
|
|
2282
|
+
* @param node - The node to unwrap.
|
|
2283
|
+
* @returns The unwrapped node.
|
|
2284
|
+
*/
|
|
2285
|
+
function unwrap(node) {
|
|
2286
|
+
let current = node;
|
|
2287
|
+
while (current.type === AST_NODE_TYPES.ChainExpression || current.type === AST_NODE_TYPES.TSNonNullExpression) current = current.expression;
|
|
2288
|
+
return current;
|
|
2289
|
+
}
|
|
2290
|
+
/**
|
|
2291
|
+
* Builds the dotted path for a callee, e.g. `math.random` or `tick`.
|
|
2292
|
+
*
|
|
2293
|
+
* @param callee - The callee expression.
|
|
2294
|
+
* @returns The match, or `null` when the callee cannot be represented as a
|
|
2295
|
+
* simple dotted path (computed access, or an object that is an expression).
|
|
2296
|
+
*/
|
|
2297
|
+
function dottedCalleePath(callee) {
|
|
2298
|
+
let current = unwrap(callee);
|
|
2299
|
+
const parts = [];
|
|
2300
|
+
while (current.type === AST_NODE_TYPES.MemberExpression) {
|
|
2301
|
+
if (current.computed || current.property.type !== AST_NODE_TYPES.Identifier) return null;
|
|
2302
|
+
parts.unshift(current.property.name);
|
|
2303
|
+
current = unwrap(current.object);
|
|
2304
|
+
}
|
|
2305
|
+
if (current.type !== AST_NODE_TYPES.Identifier) return null;
|
|
2306
|
+
parts.unshift(current.name);
|
|
2307
|
+
return {
|
|
2308
|
+
path: parts.join("."),
|
|
2309
|
+
root: current
|
|
2310
|
+
};
|
|
2311
|
+
}
|
|
2312
|
+
/**
|
|
2313
|
+
* Determines whether an identifier resolves to a real user binding (import,
|
|
2314
|
+
* parameter, or local declaration) rather than the ambient Luau global.
|
|
2315
|
+
*
|
|
2316
|
+
* @param sourceCode - Provides scope lookup.
|
|
2317
|
+
* @param node - The identifier node.
|
|
2318
|
+
* @returns `true` when the identifier is a user binding rather than the ambient
|
|
2319
|
+
* global.
|
|
2320
|
+
*/
|
|
2321
|
+
function isUserBinding(sourceCode, node) {
|
|
2322
|
+
let scope = sourceCode.getScope(node);
|
|
2323
|
+
while (scope !== null) {
|
|
2324
|
+
const variable = scope.variables.find((candidate) => candidate.name === node.name);
|
|
2325
|
+
if (variable !== void 0) return variable.defs.length > 0;
|
|
2326
|
+
scope = scope.upper;
|
|
2327
|
+
}
|
|
2328
|
+
return false;
|
|
2329
|
+
}
|
|
2330
|
+
/**
|
|
2331
|
+
* Finds the immediate enclosing function of a node, stopping at the program.
|
|
2332
|
+
*
|
|
2333
|
+
* @param node - The node to search upward from.
|
|
2334
|
+
* @returns The enclosing function, or `null` when the node is at module level.
|
|
2335
|
+
*/
|
|
2336
|
+
function enclosingFunction(node) {
|
|
2337
|
+
let current = node.parent;
|
|
2338
|
+
while (current !== void 0) {
|
|
2339
|
+
if (ASTUtils.isFunction(current)) return current;
|
|
2340
|
+
if (current.type === AST_NODE_TYPES.Program) return null;
|
|
2341
|
+
current = current.parent;
|
|
2342
|
+
}
|
|
2343
|
+
return null;
|
|
2344
|
+
}
|
|
2345
|
+
/**
|
|
2346
|
+
* Determines whether a function executes during render: a component body, a
|
|
2347
|
+
* custom/builtin hook body, or a `useMemo` callback.
|
|
2348
|
+
*
|
|
2349
|
+
* @param reactContext - The context `@eslint-react/core` predicates expect.
|
|
2350
|
+
* @param func - The enclosing function node.
|
|
2351
|
+
* @returns `true` when the function runs during render.
|
|
2352
|
+
*/
|
|
2353
|
+
function isRenderContext(reactContext, func) {
|
|
2354
|
+
if (core.isFunctionComponentDefinition(reactContext, func, core.DEFAULT_COMPONENT_DETECTION_HINT)) return true;
|
|
2355
|
+
if (core.isHookDefinition(func)) return true;
|
|
2356
|
+
const { parent } = func;
|
|
2357
|
+
return parent.type === AST_NODE_TYPES.CallExpression && parent.arguments[0] === func && core.isUseMemoCall(reactContext, parent);
|
|
2358
|
+
}
|
|
2359
|
+
function create$2(context) {
|
|
2360
|
+
const { sourceCode } = context;
|
|
2361
|
+
const reactContext = context;
|
|
2362
|
+
const options = context.options[0] ?? {};
|
|
2363
|
+
const signatures = new Set(DEFAULT_SIGNATURES);
|
|
2364
|
+
for (const signature of options.additionalFunctions ?? []) signatures.add(signature);
|
|
2365
|
+
for (const signature of options.ignore ?? []) signatures.delete(signature);
|
|
2366
|
+
function handle(node, match, path) {
|
|
2367
|
+
if (!signatures.has(path)) return;
|
|
2368
|
+
if (path === "Random.new" && node.arguments.length > 0) return;
|
|
2369
|
+
if (GUARDED_GLOBALS.has(match.root.name) && isUserBinding(sourceCode, match.root)) return;
|
|
2370
|
+
const func = enclosingFunction(node);
|
|
2371
|
+
if (func === null || !isRenderContext(reactContext, func)) return;
|
|
2372
|
+
context.report({
|
|
2373
|
+
data: { name: path },
|
|
2374
|
+
messageId: MESSAGE_ID$1,
|
|
2375
|
+
node
|
|
2376
|
+
});
|
|
2377
|
+
}
|
|
2378
|
+
return {
|
|
2379
|
+
CallExpression(node) {
|
|
2380
|
+
const match = dottedCalleePath(node.callee);
|
|
2381
|
+
if (match !== null) handle(node, match, match.path);
|
|
2382
|
+
},
|
|
2383
|
+
NewExpression(node) {
|
|
2384
|
+
const match = dottedCalleePath(node.callee);
|
|
2385
|
+
if (match !== null) handle(node, match, `${match.path}.new`);
|
|
2386
|
+
}
|
|
2387
|
+
};
|
|
2388
|
+
}
|
|
2389
|
+
const purity = createEslintRule({
|
|
2390
|
+
name: RULE_NAME$2,
|
|
2391
|
+
create: create$2,
|
|
2392
|
+
defaultOptions: [{}],
|
|
2393
|
+
meta: {
|
|
2394
|
+
defaultOptions: [{}],
|
|
2395
|
+
docs: {
|
|
2396
|
+
description: "Disallow impure calls such as `math.random` or `os.clock` during render",
|
|
2397
|
+
recommended: false,
|
|
2398
|
+
requiresTypeChecking: false
|
|
2399
|
+
},
|
|
2400
|
+
hasSuggestions: false,
|
|
2401
|
+
messages: messages$2,
|
|
2402
|
+
schema: schema$1,
|
|
2403
|
+
type: "problem"
|
|
2404
|
+
}
|
|
2405
|
+
});
|
|
2406
|
+
//#endregion
|
|
2163
2407
|
//#region src/rules/toml-sort-keys/rule.ts
|
|
2164
2408
|
const RULE_NAME$1 = "toml-sort-keys";
|
|
2165
2409
|
const MESSAGE_ID = "unsorted";
|
|
@@ -2461,6 +2705,7 @@ const plugin = {
|
|
|
2461
2705
|
"no-unnecessary-use-callback": noUnnecessaryUseCallback,
|
|
2462
2706
|
"no-unnecessary-use-memo": noUnnecessaryUseMemo,
|
|
2463
2707
|
"prefer-destructuring-assignment": preferDestructuringAssignment,
|
|
2708
|
+
"purity": purity,
|
|
2464
2709
|
"toml-sort-keys": tomlSortKeys,
|
|
2465
2710
|
"yaml-block-key-blank-lines": yamlBlockKeyBlankLines
|
|
2466
2711
|
}
|