eslint-plugin-flawless 0.1.5 → 0.1.6
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 +29 -8
- package/dist/index.mjs +225 -42
- 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) | Disallow the shorthand fragment syntax in favour of 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
|
-
| [
|
|
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) | Disallow the shorthand fragment syntax in favour of 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,7 @@ interface PluginDocumentation {
|
|
|
9
9
|
}
|
|
10
10
|
//#endregion
|
|
11
11
|
//#region src/rules/jsx-shorthand-fragment/rule.d.ts
|
|
12
|
-
type Options$
|
|
12
|
+
type Options$3 = [fragmentName?: string];
|
|
13
13
|
//#endregion
|
|
14
14
|
//#region src/rules/naming-convention/utils/enums.d.ts
|
|
15
15
|
declare const Selector: {
|
|
@@ -119,7 +119,7 @@ interface NamingSelector {
|
|
|
119
119
|
//#endregion
|
|
120
120
|
//#region src/rules/naming-convention/rule.d.ts
|
|
121
121
|
type MessageIds$2 = "doesNotMatchFormat" | "doesNotMatchFormatTrimmed" | "missingAffix" | "missingUnderscore" | "satisfyCustom" | "unexpectedUnderscore";
|
|
122
|
-
type Options$
|
|
122
|
+
type Options$2 = Array<NamingSelector>;
|
|
123
123
|
//#endregion
|
|
124
124
|
//#region src/rules/no-unnecessary-use-callback/rule.d.ts
|
|
125
125
|
declare const MESSAGE_ID_DEFAULT$1 = "default";
|
|
@@ -131,6 +131,18 @@ declare const MESSAGE_ID_DEFAULT = "default";
|
|
|
131
131
|
declare const MESSAGE_ID_INSIDE_USE_EFFECT = "noUnnecessaryUseMemoInsideUseEffect";
|
|
132
132
|
type MessageIds = typeof MESSAGE_ID_DEFAULT | typeof MESSAGE_ID_INSIDE_USE_EFFECT;
|
|
133
133
|
//#endregion
|
|
134
|
+
//#region src/rules/purity/rule.d.ts
|
|
135
|
+
interface PurityOptions {
|
|
136
|
+
/**
|
|
137
|
+
* Extra dotted call signatures to treat as impure, added to the defaults
|
|
138
|
+
* (e.g. `"Math.random"` when using a Luau `Math` polyfill).
|
|
139
|
+
*/
|
|
140
|
+
readonly additionalFunctions?: ReadonlyArray<string>;
|
|
141
|
+
/** Default signatures to exclude (e.g. `"os.date"`). */
|
|
142
|
+
readonly ignore?: ReadonlyArray<string>;
|
|
143
|
+
}
|
|
144
|
+
type Options$1 = [PurityOptions?];
|
|
145
|
+
//#endregion
|
|
134
146
|
//#region src/rules/toml-sort-keys/rule.d.ts
|
|
135
147
|
type Options = Array<SortSpec>;
|
|
136
148
|
interface SortOrderObject {
|
|
@@ -154,10 +166,10 @@ declare const plugin: {
|
|
|
154
166
|
"jsx-shorthand-boolean": TSESLint.RuleModule<"setAttributeValue", [], PluginDocumentation, TSESLint.RuleListener> & {
|
|
155
167
|
name: string;
|
|
156
168
|
};
|
|
157
|
-
"jsx-shorthand-fragment": TSESLint.RuleModule<"useNamedFragment", Options$
|
|
169
|
+
"jsx-shorthand-fragment": TSESLint.RuleModule<"useNamedFragment", Options$3, PluginDocumentation, TSESLint.RuleListener> & {
|
|
158
170
|
name: string;
|
|
159
171
|
};
|
|
160
|
-
"naming-convention": TSESLint.RuleModule<MessageIds$2, Options$
|
|
172
|
+
"naming-convention": TSESLint.RuleModule<MessageIds$2, Options$2, PluginDocumentation, TSESLint.RuleListener> & {
|
|
161
173
|
name: string;
|
|
162
174
|
};
|
|
163
175
|
"no-unnecessary-use-callback": TSESLint.RuleModule<MessageIds$1, [], PluginDocumentation, TSESLint.RuleListener> & {
|
|
@@ -169,6 +181,9 @@ declare const plugin: {
|
|
|
169
181
|
"prefer-destructuring-assignment": TSESLint.RuleModule<"default", [], PluginDocumentation, TSESLint.RuleListener> & {
|
|
170
182
|
name: string;
|
|
171
183
|
};
|
|
184
|
+
purity: TSESLint.RuleModule<"impureCall", Options$1, PluginDocumentation, TSESLint.RuleListener> & {
|
|
185
|
+
name: string;
|
|
186
|
+
};
|
|
172
187
|
"toml-sort-keys": TSESLint.RuleModule<"unsorted", Options, PluginDocumentation, TSESLint.RuleListener> & {
|
|
173
188
|
name: string;
|
|
174
189
|
};
|
|
@@ -192,10 +207,10 @@ declare const _default: {
|
|
|
192
207
|
"jsx-shorthand-boolean": import("${configDir}").RuleModule<"setAttributeValue", [], PluginDocumentation, import("${configDir}").RuleListener> & {
|
|
193
208
|
name: string;
|
|
194
209
|
};
|
|
195
|
-
"jsx-shorthand-fragment": import("${configDir}").RuleModule<"useNamedFragment", Options$
|
|
210
|
+
"jsx-shorthand-fragment": import("${configDir}").RuleModule<"useNamedFragment", Options$3, PluginDocumentation, import("${configDir}").RuleListener> & {
|
|
196
211
|
name: string;
|
|
197
212
|
};
|
|
198
|
-
"naming-convention": import("${configDir}").RuleModule<MessageIds$2, Options$
|
|
213
|
+
"naming-convention": import("${configDir}").RuleModule<MessageIds$2, Options$2, PluginDocumentation, import("${configDir}").RuleListener> & {
|
|
199
214
|
name: string;
|
|
200
215
|
};
|
|
201
216
|
"no-unnecessary-use-callback": import("${configDir}").RuleModule<MessageIds$1, [], PluginDocumentation, import("${configDir}").RuleListener> & {
|
|
@@ -207,6 +222,9 @@ declare const _default: {
|
|
|
207
222
|
"prefer-destructuring-assignment": import("${configDir}").RuleModule<"default", [], PluginDocumentation, import("${configDir}").RuleListener> & {
|
|
208
223
|
name: string;
|
|
209
224
|
};
|
|
225
|
+
purity: import("${configDir}").RuleModule<"impureCall", Options$1, PluginDocumentation, import("${configDir}").RuleListener> & {
|
|
226
|
+
name: string;
|
|
227
|
+
};
|
|
210
228
|
"toml-sort-keys": import("${configDir}").RuleModule<"unsorted", Options, PluginDocumentation, import("${configDir}").RuleListener> & {
|
|
211
229
|
name: string;
|
|
212
230
|
};
|
|
@@ -227,10 +245,10 @@ declare const _default: {
|
|
|
227
245
|
"jsx-shorthand-boolean": import("${configDir}").RuleModule<"setAttributeValue", [], PluginDocumentation, import("${configDir}").RuleListener> & {
|
|
228
246
|
name: string;
|
|
229
247
|
};
|
|
230
|
-
"jsx-shorthand-fragment": import("${configDir}").RuleModule<"useNamedFragment", Options$
|
|
248
|
+
"jsx-shorthand-fragment": import("${configDir}").RuleModule<"useNamedFragment", Options$3, PluginDocumentation, import("${configDir}").RuleListener> & {
|
|
231
249
|
name: string;
|
|
232
250
|
};
|
|
233
|
-
"naming-convention": import("${configDir}").RuleModule<MessageIds$2, Options$
|
|
251
|
+
"naming-convention": import("${configDir}").RuleModule<MessageIds$2, Options$2, PluginDocumentation, import("${configDir}").RuleListener> & {
|
|
234
252
|
name: string;
|
|
235
253
|
};
|
|
236
254
|
"no-unnecessary-use-callback": import("${configDir}").RuleModule<MessageIds$1, [], PluginDocumentation, import("${configDir}").RuleListener> & {
|
|
@@ -242,6 +260,9 @@ declare const _default: {
|
|
|
242
260
|
"prefer-destructuring-assignment": import("${configDir}").RuleModule<"default", [], PluginDocumentation, import("${configDir}").RuleListener> & {
|
|
243
261
|
name: string;
|
|
244
262
|
};
|
|
263
|
+
purity: import("${configDir}").RuleModule<"impureCall", Options$1, PluginDocumentation, import("${configDir}").RuleListener> & {
|
|
264
|
+
name: string;
|
|
265
|
+
};
|
|
245
266
|
"toml-sort-keys": import("${configDir}").RuleModule<"unsorted", Options, PluginDocumentation, import("${configDir}").RuleListener> & {
|
|
246
267
|
name: string;
|
|
247
268
|
};
|
package/dist/index.mjs
CHANGED
|
@@ -8,7 +8,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.6";
|
|
12
12
|
var repository = {
|
|
13
13
|
"url": "git+https://github.com/christopher-buss/eslint-plugin-flawless.git",
|
|
14
14
|
"type": "git"
|
|
@@ -35,24 +35,24 @@ function createEslintRule(rule) {
|
|
|
35
35
|
}
|
|
36
36
|
//#endregion
|
|
37
37
|
//#region src/rules/jsx-shorthand-boolean/rule.ts
|
|
38
|
-
const RULE_NAME$
|
|
39
|
-
const MESSAGE_ID$
|
|
40
|
-
const messages$
|
|
41
|
-
function create$
|
|
38
|
+
const RULE_NAME$8 = "jsx-shorthand-boolean";
|
|
39
|
+
const MESSAGE_ID$4 = "setAttributeValue";
|
|
40
|
+
const messages$8 = { [MESSAGE_ID$4]: "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;
|
|
45
45
|
context.report({
|
|
46
46
|
data: { name: sourceCode.getText(node.name) },
|
|
47
47
|
fix: (fixer) => fixer.insertTextAfter(node.name, "={true}"),
|
|
48
|
-
messageId: MESSAGE_ID$
|
|
48
|
+
messageId: MESSAGE_ID$4,
|
|
49
49
|
node
|
|
50
50
|
});
|
|
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,22 +62,22 @@ 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 MESSAGE_ID$
|
|
72
|
+
const RULE_NAME$7 = "jsx-shorthand-fragment";
|
|
73
|
+
const MESSAGE_ID$3 = "useNamedFragment";
|
|
74
74
|
const DEFAULT_FRAGMENT_NAME = "Fragment";
|
|
75
|
-
const messages$
|
|
76
|
-
const schema$
|
|
75
|
+
const messages$7 = { [MESSAGE_ID$3]: "Use the '{{name}}' component instead of fragment shorthand syntax." };
|
|
76
|
+
const schema$2 = [{
|
|
77
77
|
description: "The identifier to use for the named fragment element.",
|
|
78
78
|
type: "string"
|
|
79
79
|
}];
|
|
80
|
-
function create$
|
|
80
|
+
function create$7(context) {
|
|
81
81
|
const name = context.options[0] ?? DEFAULT_FRAGMENT_NAME;
|
|
82
82
|
return { JSXFragment(node) {
|
|
83
83
|
const { closingFragment, openingFragment } = node;
|
|
@@ -86,14 +86,14 @@ function create$6(context) {
|
|
|
86
86
|
fix: (fixer) => {
|
|
87
87
|
return [fixer.replaceText(openingFragment, `<${name}>`), fixer.replaceText(closingFragment, `</${name}>`)];
|
|
88
88
|
},
|
|
89
|
-
messageId: MESSAGE_ID$
|
|
89
|
+
messageId: MESSAGE_ID$3,
|
|
90
90
|
node
|
|
91
91
|
});
|
|
92
92
|
} };
|
|
93
93
|
}
|
|
94
94
|
const jsxShorthandFragment = createEslintRule({
|
|
95
|
-
name: RULE_NAME$
|
|
96
|
-
create: create$
|
|
95
|
+
name: RULE_NAME$7,
|
|
96
|
+
create: create$7,
|
|
97
97
|
defaultOptions: [DEFAULT_FRAGMENT_NAME],
|
|
98
98
|
meta: {
|
|
99
99
|
defaultOptions: [DEFAULT_FRAGMENT_NAME],
|
|
@@ -104,8 +104,8 @@ const jsxShorthandFragment = createEslintRule({
|
|
|
104
104
|
},
|
|
105
105
|
fixable: "code",
|
|
106
106
|
hasSuggestions: false,
|
|
107
|
-
messages: messages$
|
|
108
|
-
schema: schema$
|
|
107
|
+
messages: messages$7,
|
|
108
|
+
schema: schema$2,
|
|
109
109
|
type: "suggestion"
|
|
110
110
|
}
|
|
111
111
|
});
|
|
@@ -1269,8 +1269,8 @@ const SCHEMA = {
|
|
|
1269
1269
|
};
|
|
1270
1270
|
//#endregion
|
|
1271
1271
|
//#region src/rules/naming-convention/rule.ts
|
|
1272
|
-
const RULE_NAME$
|
|
1273
|
-
const messages$
|
|
1272
|
+
const RULE_NAME$6 = "naming-convention";
|
|
1273
|
+
const messages$6 = {
|
|
1274
1274
|
doesNotMatchFormat: "{{type}} name `{{name}}` must match one of the following formats: {{formats}}",
|
|
1275
1275
|
doesNotMatchFormatTrimmed: "{{type}} name `{{name}}` trimmed as `{{processedName}}` must match one of the following formats: {{formats}}",
|
|
1276
1276
|
missingAffix: "{{type}} name `{{name}}` must have one of the following {{position}}es: {{affixes}}",
|
|
@@ -1300,7 +1300,7 @@ const camelCaseNamingConfig = [
|
|
|
1300
1300
|
selector: "typeLike"
|
|
1301
1301
|
}
|
|
1302
1302
|
];
|
|
1303
|
-
function create$
|
|
1303
|
+
function create$6(contextWithoutDefaults) {
|
|
1304
1304
|
const context = contextWithoutDefaults.options.length > 0 ? contextWithoutDefaults : Object.setPrototypeOf({ options: camelCaseNamingConfig }, contextWithoutDefaults);
|
|
1305
1305
|
const validators = parseOptions(context);
|
|
1306
1306
|
const compilerOptions = getParserServices(context, true).program?.getCompilerOptions() ?? {};
|
|
@@ -1620,8 +1620,8 @@ function create$5(contextWithoutDefaults) {
|
|
|
1620
1620
|
}));
|
|
1621
1621
|
}
|
|
1622
1622
|
const namingConvention = createEslintRule({
|
|
1623
|
-
name: RULE_NAME$
|
|
1624
|
-
create: create$
|
|
1623
|
+
name: RULE_NAME$6,
|
|
1624
|
+
create: create$6,
|
|
1625
1625
|
defaultOptions: camelCaseNamingConfig,
|
|
1626
1626
|
meta: {
|
|
1627
1627
|
docs: {
|
|
@@ -1631,7 +1631,7 @@ const namingConvention = createEslintRule({
|
|
|
1631
1631
|
},
|
|
1632
1632
|
fixable: void 0,
|
|
1633
1633
|
hasSuggestions: false,
|
|
1634
|
-
messages: messages$
|
|
1634
|
+
messages: messages$6,
|
|
1635
1635
|
schema: SCHEMA,
|
|
1636
1636
|
type: "suggestion"
|
|
1637
1637
|
}
|
|
@@ -1899,15 +1899,15 @@ function resolveFactory(sourceCode, node) {
|
|
|
1899
1899
|
}
|
|
1900
1900
|
//#endregion
|
|
1901
1901
|
//#region src/rules/no-unnecessary-use-callback/rule.ts
|
|
1902
|
-
const RULE_NAME$
|
|
1902
|
+
const RULE_NAME$5 = "no-unnecessary-use-callback";
|
|
1903
1903
|
const MESSAGE_ID_DEFAULT$1 = "default";
|
|
1904
1904
|
const MESSAGE_ID_INSIDE_USE_EFFECT$1 = "noUnnecessaryUseCallbackInsideUseEffect";
|
|
1905
|
-
const messages$
|
|
1905
|
+
const messages$5 = {
|
|
1906
1906
|
[MESSAGE_ID_DEFAULT$1]: "An 'useCallback' with empty deps and no references to the component scope may be unnecessary.",
|
|
1907
1907
|
[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
1908
|
};
|
|
1909
1909
|
const noUnnecessaryUseCallback = createEslintRule({
|
|
1910
|
-
name: RULE_NAME$
|
|
1910
|
+
name: RULE_NAME$5,
|
|
1911
1911
|
create: createUnnecessaryHookRule({
|
|
1912
1912
|
hook: "useCallback",
|
|
1913
1913
|
messageIds: {
|
|
@@ -1923,22 +1923,22 @@ const noUnnecessaryUseCallback = createEslintRule({
|
|
|
1923
1923
|
requiresTypeChecking: false
|
|
1924
1924
|
},
|
|
1925
1925
|
hasSuggestions: false,
|
|
1926
|
-
messages: messages$
|
|
1926
|
+
messages: messages$5,
|
|
1927
1927
|
schema: [],
|
|
1928
1928
|
type: "suggestion"
|
|
1929
1929
|
}
|
|
1930
1930
|
});
|
|
1931
1931
|
//#endregion
|
|
1932
1932
|
//#region src/rules/no-unnecessary-use-memo/rule.ts
|
|
1933
|
-
const RULE_NAME$
|
|
1933
|
+
const RULE_NAME$4 = "no-unnecessary-use-memo";
|
|
1934
1934
|
const MESSAGE_ID_DEFAULT = "default";
|
|
1935
1935
|
const MESSAGE_ID_INSIDE_USE_EFFECT = "noUnnecessaryUseMemoInsideUseEffect";
|
|
1936
|
-
const messages$
|
|
1936
|
+
const messages$4 = {
|
|
1937
1937
|
[MESSAGE_ID_DEFAULT]: "An 'useMemo' with empty deps and no references to the component scope may be unnecessary.",
|
|
1938
1938
|
[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
1939
|
};
|
|
1940
1940
|
const noUnnecessaryUseMemo = createEslintRule({
|
|
1941
|
-
name: RULE_NAME$
|
|
1941
|
+
name: RULE_NAME$4,
|
|
1942
1942
|
create: createUnnecessaryHookRule({
|
|
1943
1943
|
hook: "useMemo",
|
|
1944
1944
|
messageIds: {
|
|
@@ -1954,16 +1954,16 @@ const noUnnecessaryUseMemo = createEslintRule({
|
|
|
1954
1954
|
requiresTypeChecking: false
|
|
1955
1955
|
},
|
|
1956
1956
|
hasSuggestions: false,
|
|
1957
|
-
messages: messages$
|
|
1957
|
+
messages: messages$4,
|
|
1958
1958
|
schema: [],
|
|
1959
1959
|
type: "suggestion"
|
|
1960
1960
|
}
|
|
1961
1961
|
});
|
|
1962
1962
|
//#endregion
|
|
1963
1963
|
//#region src/rules/prefer-destructuring-assignment/rule.ts
|
|
1964
|
-
const RULE_NAME$
|
|
1965
|
-
const MESSAGE_ID$
|
|
1966
|
-
const messages$
|
|
1964
|
+
const RULE_NAME$3 = "prefer-destructuring-assignment";
|
|
1965
|
+
const MESSAGE_ID$2 = "default";
|
|
1966
|
+
const messages$3 = { [MESSAGE_ID$2]: "Use destructuring assignment for component props." };
|
|
1967
1967
|
/**
|
|
1968
1968
|
* Identifiers that cannot be used as a binding name in strict-mode module code.
|
|
1969
1969
|
* A property may be accessed with such a name (`props.default`), but a shorthand
|
|
@@ -2089,7 +2089,7 @@ function reportComponent(context, scope, propsParameter, propertyVariable) {
|
|
|
2089
2089
|
const propertyNames = collectPropertyNames(memberReferences);
|
|
2090
2090
|
if (!(!hasNonMemberReference && propertyNames !== null && !wouldShadowExistingBinding(context.sourceCode, scope, propertyVariable, memberReferences))) {
|
|
2091
2091
|
for (const { member } of memberReferences) context.report({
|
|
2092
|
-
messageId: MESSAGE_ID$
|
|
2092
|
+
messageId: MESSAGE_ID$2,
|
|
2093
2093
|
node: member
|
|
2094
2094
|
});
|
|
2095
2095
|
return;
|
|
@@ -2106,7 +2106,7 @@ function reportComponent(context, scope, propsParameter, propertyVariable) {
|
|
|
2106
2106
|
if (index === 0) return [fixer.replaceTextRange([propsParameter.range[0], nameEnd], pattern), replaceAccess];
|
|
2107
2107
|
return replaceAccess;
|
|
2108
2108
|
},
|
|
2109
|
-
messageId: MESSAGE_ID$
|
|
2109
|
+
messageId: MESSAGE_ID$2,
|
|
2110
2110
|
node: member
|
|
2111
2111
|
});
|
|
2112
2112
|
}
|
|
@@ -2125,7 +2125,7 @@ function reportComponent(context, scope, propsParameter, propertyVariable) {
|
|
|
2125
2125
|
* @param context - The rule context.
|
|
2126
2126
|
* @returns The rule listener.
|
|
2127
2127
|
*/
|
|
2128
|
-
function create$
|
|
2128
|
+
function create$3(context) {
|
|
2129
2129
|
const { api, visitor } = core.getFunctionComponentCollector(context);
|
|
2130
2130
|
return {
|
|
2131
2131
|
...visitor,
|
|
@@ -2143,8 +2143,8 @@ function create$2(context) {
|
|
|
2143
2143
|
};
|
|
2144
2144
|
}
|
|
2145
2145
|
const preferDestructuringAssignment = createEslintRule({
|
|
2146
|
-
name: RULE_NAME$
|
|
2147
|
-
create: create$
|
|
2146
|
+
name: RULE_NAME$3,
|
|
2147
|
+
create: create$3,
|
|
2148
2148
|
defaultOptions: [],
|
|
2149
2149
|
meta: {
|
|
2150
2150
|
docs: {
|
|
@@ -2154,12 +2154,194 @@ const preferDestructuringAssignment = createEslintRule({
|
|
|
2154
2154
|
},
|
|
2155
2155
|
fixable: "code",
|
|
2156
2156
|
hasSuggestions: false,
|
|
2157
|
-
messages: messages$
|
|
2157
|
+
messages: messages$3,
|
|
2158
2158
|
schema: [],
|
|
2159
2159
|
type: "problem"
|
|
2160
2160
|
}
|
|
2161
2161
|
});
|
|
2162
2162
|
//#endregion
|
|
2163
|
+
//#region src/rules/purity/rule.ts
|
|
2164
|
+
const RULE_NAME$2 = "purity";
|
|
2165
|
+
const MESSAGE_ID$1 = "impureCall";
|
|
2166
|
+
/**
|
|
2167
|
+
* Non-deterministic Luau / Roblox calls, written as dotted paths matching how
|
|
2168
|
+
* they appear in roblox-ts source. `new Random()` is normalized to
|
|
2169
|
+
* `"Random.new"` (see the `NewExpression` visitor).
|
|
2170
|
+
*/
|
|
2171
|
+
const DEFAULT_SIGNATURES = [
|
|
2172
|
+
"DateTime.now",
|
|
2173
|
+
"HttpService.GenerateGUID",
|
|
2174
|
+
"Random.new",
|
|
2175
|
+
"Workspace.GetServerTimeNow",
|
|
2176
|
+
"elapsedTime",
|
|
2177
|
+
"math.random",
|
|
2178
|
+
"math.randomseed",
|
|
2179
|
+
"os.clock",
|
|
2180
|
+
"os.date",
|
|
2181
|
+
"os.time",
|
|
2182
|
+
"tick",
|
|
2183
|
+
"time"
|
|
2184
|
+
];
|
|
2185
|
+
/**
|
|
2186
|
+
* Bare Luau globals for which a matching local binding means the reference is
|
|
2187
|
+
* shadowed rather than the ambient global. Service objects are intentionally
|
|
2188
|
+
* excluded because they are impure even when imported from `@rbxts/services`.
|
|
2189
|
+
*/
|
|
2190
|
+
const GUARDED_GLOBALS = /* @__PURE__ */ new Set([
|
|
2191
|
+
"elapsedTime",
|
|
2192
|
+
"math",
|
|
2193
|
+
"os",
|
|
2194
|
+
"tick",
|
|
2195
|
+
"time"
|
|
2196
|
+
]);
|
|
2197
|
+
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." };
|
|
2198
|
+
const schema$1 = [{
|
|
2199
|
+
additionalProperties: false,
|
|
2200
|
+
properties: {
|
|
2201
|
+
additionalFunctions: {
|
|
2202
|
+
description: "Extra dotted call signatures to treat as impure (e.g. \"Math.random\").",
|
|
2203
|
+
items: { type: "string" },
|
|
2204
|
+
type: "array",
|
|
2205
|
+
uniqueItems: true
|
|
2206
|
+
},
|
|
2207
|
+
ignore: {
|
|
2208
|
+
description: "Default signatures to exclude (e.g. \"os.date\").",
|
|
2209
|
+
items: { type: "string" },
|
|
2210
|
+
type: "array",
|
|
2211
|
+
uniqueItems: true
|
|
2212
|
+
}
|
|
2213
|
+
},
|
|
2214
|
+
type: "object"
|
|
2215
|
+
}];
|
|
2216
|
+
/**
|
|
2217
|
+
* Strips wrappers that are irrelevant to the callee identity so that
|
|
2218
|
+
* `a.b?.()` and `a.b!()` still match.
|
|
2219
|
+
*
|
|
2220
|
+
* @param node - The node to unwrap.
|
|
2221
|
+
* @returns The unwrapped node.
|
|
2222
|
+
*/
|
|
2223
|
+
function unwrap(node) {
|
|
2224
|
+
let current = node;
|
|
2225
|
+
while (current.type === AST_NODE_TYPES.ChainExpression || current.type === AST_NODE_TYPES.TSNonNullExpression) current = current.expression;
|
|
2226
|
+
return current;
|
|
2227
|
+
}
|
|
2228
|
+
/**
|
|
2229
|
+
* Builds the dotted path for a callee, e.g. `math.random` or `tick`.
|
|
2230
|
+
*
|
|
2231
|
+
* @param callee - The callee expression.
|
|
2232
|
+
* @returns The match, or `null` when the callee cannot be represented as a
|
|
2233
|
+
* simple dotted path (computed access, or an object that is an expression).
|
|
2234
|
+
*/
|
|
2235
|
+
function dottedCalleePath(callee) {
|
|
2236
|
+
let current = unwrap(callee);
|
|
2237
|
+
const parts = [];
|
|
2238
|
+
while (current.type === AST_NODE_TYPES.MemberExpression) {
|
|
2239
|
+
if (current.computed || current.property.type !== AST_NODE_TYPES.Identifier) return null;
|
|
2240
|
+
parts.unshift(current.property.name);
|
|
2241
|
+
current = unwrap(current.object);
|
|
2242
|
+
}
|
|
2243
|
+
if (current.type !== AST_NODE_TYPES.Identifier) return null;
|
|
2244
|
+
parts.unshift(current.name);
|
|
2245
|
+
return {
|
|
2246
|
+
path: parts.join("."),
|
|
2247
|
+
root: current
|
|
2248
|
+
};
|
|
2249
|
+
}
|
|
2250
|
+
/**
|
|
2251
|
+
* Determines whether an identifier resolves to a real user binding (import,
|
|
2252
|
+
* parameter, or local declaration) rather than the ambient Luau global.
|
|
2253
|
+
*
|
|
2254
|
+
* @param sourceCode - Provides scope lookup.
|
|
2255
|
+
* @param node - The identifier node.
|
|
2256
|
+
* @returns `true` when the identifier is a user binding rather than the ambient
|
|
2257
|
+
* global.
|
|
2258
|
+
*/
|
|
2259
|
+
function isUserBinding(sourceCode, node) {
|
|
2260
|
+
let scope = sourceCode.getScope(node);
|
|
2261
|
+
while (scope !== null) {
|
|
2262
|
+
const variable = scope.variables.find((candidate) => candidate.name === node.name);
|
|
2263
|
+
if (variable !== void 0) return variable.defs.length > 0;
|
|
2264
|
+
scope = scope.upper;
|
|
2265
|
+
}
|
|
2266
|
+
return false;
|
|
2267
|
+
}
|
|
2268
|
+
/**
|
|
2269
|
+
* Finds the immediate enclosing function of a node, stopping at the program.
|
|
2270
|
+
*
|
|
2271
|
+
* @param node - The node to search upward from.
|
|
2272
|
+
* @returns The enclosing function, or `null` when the node is at module level.
|
|
2273
|
+
*/
|
|
2274
|
+
function enclosingFunction(node) {
|
|
2275
|
+
let current = node.parent;
|
|
2276
|
+
while (current !== void 0) {
|
|
2277
|
+
if (ASTUtils.isFunction(current)) return current;
|
|
2278
|
+
if (current.type === AST_NODE_TYPES.Program) return null;
|
|
2279
|
+
current = current.parent;
|
|
2280
|
+
}
|
|
2281
|
+
return null;
|
|
2282
|
+
}
|
|
2283
|
+
/**
|
|
2284
|
+
* Determines whether a function executes during render: a component body, a
|
|
2285
|
+
* custom/builtin hook body, or a `useMemo` callback.
|
|
2286
|
+
*
|
|
2287
|
+
* @param reactContext - The context `@eslint-react/core` predicates expect.
|
|
2288
|
+
* @param func - The enclosing function node.
|
|
2289
|
+
* @returns `true` when the function runs during render.
|
|
2290
|
+
*/
|
|
2291
|
+
function isRenderContext(reactContext, func) {
|
|
2292
|
+
if (core.isFunctionComponentDefinition(reactContext, func, core.DEFAULT_COMPONENT_DETECTION_HINT)) return true;
|
|
2293
|
+
if (core.isHookDefinition(func)) return true;
|
|
2294
|
+
const { parent } = func;
|
|
2295
|
+
return parent.type === AST_NODE_TYPES.CallExpression && parent.arguments[0] === func && core.isUseMemoCall(reactContext, parent);
|
|
2296
|
+
}
|
|
2297
|
+
function create$2(context) {
|
|
2298
|
+
const { sourceCode } = context;
|
|
2299
|
+
const reactContext = context;
|
|
2300
|
+
const options = context.options[0] ?? {};
|
|
2301
|
+
const signatures = new Set(DEFAULT_SIGNATURES);
|
|
2302
|
+
for (const signature of options.additionalFunctions ?? []) signatures.add(signature);
|
|
2303
|
+
for (const signature of options.ignore ?? []) signatures.delete(signature);
|
|
2304
|
+
function handle(node, match, path) {
|
|
2305
|
+
if (!signatures.has(path)) return;
|
|
2306
|
+
if (path === "Random.new" && node.arguments.length > 0) return;
|
|
2307
|
+
if (GUARDED_GLOBALS.has(match.root.name) && isUserBinding(sourceCode, match.root)) return;
|
|
2308
|
+
const func = enclosingFunction(node);
|
|
2309
|
+
if (func === null || !isRenderContext(reactContext, func)) return;
|
|
2310
|
+
context.report({
|
|
2311
|
+
data: { name: path },
|
|
2312
|
+
messageId: MESSAGE_ID$1,
|
|
2313
|
+
node
|
|
2314
|
+
});
|
|
2315
|
+
}
|
|
2316
|
+
return {
|
|
2317
|
+
CallExpression(node) {
|
|
2318
|
+
const match = dottedCalleePath(node.callee);
|
|
2319
|
+
if (match !== null) handle(node, match, match.path);
|
|
2320
|
+
},
|
|
2321
|
+
NewExpression(node) {
|
|
2322
|
+
const match = dottedCalleePath(node.callee);
|
|
2323
|
+
if (match !== null) handle(node, match, `${match.path}.new`);
|
|
2324
|
+
}
|
|
2325
|
+
};
|
|
2326
|
+
}
|
|
2327
|
+
const purity = createEslintRule({
|
|
2328
|
+
name: RULE_NAME$2,
|
|
2329
|
+
create: create$2,
|
|
2330
|
+
defaultOptions: [{}],
|
|
2331
|
+
meta: {
|
|
2332
|
+
defaultOptions: [{}],
|
|
2333
|
+
docs: {
|
|
2334
|
+
description: "Disallow impure calls such as `math.random` or `os.clock` during render",
|
|
2335
|
+
recommended: false,
|
|
2336
|
+
requiresTypeChecking: false
|
|
2337
|
+
},
|
|
2338
|
+
hasSuggestions: false,
|
|
2339
|
+
messages: messages$2,
|
|
2340
|
+
schema: schema$1,
|
|
2341
|
+
type: "problem"
|
|
2342
|
+
}
|
|
2343
|
+
});
|
|
2344
|
+
//#endregion
|
|
2163
2345
|
//#region src/rules/toml-sort-keys/rule.ts
|
|
2164
2346
|
const RULE_NAME$1 = "toml-sort-keys";
|
|
2165
2347
|
const MESSAGE_ID = "unsorted";
|
|
@@ -2461,6 +2643,7 @@ const plugin = {
|
|
|
2461
2643
|
"no-unnecessary-use-callback": noUnnecessaryUseCallback,
|
|
2462
2644
|
"no-unnecessary-use-memo": noUnnecessaryUseMemo,
|
|
2463
2645
|
"prefer-destructuring-assignment": preferDestructuringAssignment,
|
|
2646
|
+
"purity": purity,
|
|
2464
2647
|
"toml-sort-keys": tomlSortKeys,
|
|
2465
2648
|
"yaml-block-key-blank-lines": yamlBlockKeyBlankLines
|
|
2466
2649
|
}
|