@jameswyse/oxc-config 0.1.0 → 0.3.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 +30 -163
- package/REFERENCE.md +105 -132
- package/dist/format.d.ts +5 -3
- package/dist/index.js +26 -0
- package/dist/lint.d.ts +3 -1
- package/dist/lint.js +153 -14
- package/dist/rules/explicit-api-states.js +7 -1
- package/dist/rules/import-boundaries.js +41 -13
- package/dist/rules/no-derived-test-expectation.d.ts +1 -0
- package/dist/rules/no-derived-test-expectation.js +136 -0
- package/dist/rules/no-disabled-tls-verification.d.ts +1 -0
- package/dist/rules/no-disabled-tls-verification.js +42 -0
- package/dist/rules/no-fixed-test-delay.js +46 -5
- package/dist/rules/no-generic-object-guard.d.ts +1 -0
- package/dist/rules/no-generic-object-guard.js +279 -0
- package/dist/rules/no-get-password-form.d.ts +1 -0
- package/dist/rules/no-get-password-form.js +113 -0
- package/dist/rules/no-hardcoded-credential.d.ts +1 -0
- package/dist/rules/no-hardcoded-credential.js +45 -0
- package/dist/rules/no-import-time-effects.d.ts +1 -0
- package/dist/rules/no-import-time-effects.js +90 -0
- package/dist/rules/no-optional-test-action.d.ts +1 -0
- package/dist/rules/no-optional-test-action.js +45 -0
- package/dist/rules/no-secret-fallback.d.ts +1 -0
- package/dist/rules/no-secret-fallback.js +64 -0
- package/dist/rules/no-swallowed-errors.js +131 -16
- package/dist/rules/no-test-timeout-overrides.js +22 -9
- package/dist/rules/no-unchecked-fetch-response.d.ts +1 -0
- package/dist/rules/no-unchecked-fetch-response.js +91 -0
- package/dist/rules/no-unchecked-index-search.d.ts +1 -0
- package/dist/rules/no-unchecked-index-search.js +41 -0
- package/dist/rules/no-uncontrolled-test-clock.d.ts +1 -0
- package/dist/rules/no-uncontrolled-test-clock.js +112 -0
- package/dist/rules/no-unsettled-promise.d.ts +1 -0
- package/dist/rules/no-unsettled-promise.js +64 -0
- package/dist/rules/require-suppression-reason.d.ts +1 -0
- package/dist/rules/require-suppression-reason.js +32 -0
- package/dist/shared/index.d.ts +9 -0
- package/dist/shared/index.js +70 -0
- package/dist/vendor/anti-slop/effect/rules/no-service-constructor-imports.js +10 -2
- package/dist/vendor/anti-slop/rules/no-known-value-widening.js +1 -1
- package/dist/vendor/anti-slop/rules/no-module-mocking.js +56 -1
- package/dist/vendor/anti-slop/rules/no-runtime-typeof.js +30 -1
- package/dist/vendor/anti-slop/rules/no-shape-in-symbol-names.js +28 -1
- package/dist/vendor/anti-slop/rules/no-unknown-returns.js +1 -1
- package/dist/vendor/anti-slop/rules/no-unsafe-dictionary-type.js +2 -2
- package/dist/vendor/anti-slop/shared/dictionary-types.js +72 -89
- package/dist/vendor/anti-slop/shared/type-alias-resolution.d.ts +8 -2
- package/dist/vendor/anti-slop/shared/type-alias-resolution.js +2 -2
- package/package.json +7 -7
package/dist/index.js
CHANGED
|
@@ -10,15 +10,28 @@ import { noAmbientClock } from "./rules/no-ambient-clock.js";
|
|
|
10
10
|
import { noAmbientRandom } from "./rules/no-ambient-random.js";
|
|
11
11
|
import { noComments } from "./rules/no-comments.js";
|
|
12
12
|
import { noDeepTernary } from "./rules/no-deep-ternary.js";
|
|
13
|
+
import { noDerivedTestExpectation } from "./rules/no-derived-test-expectation.js";
|
|
14
|
+
import { noDisabledTlsVerification } from "./rules/no-disabled-tls-verification.js";
|
|
13
15
|
import { noEditProofAssertions } from "./rules/no-edit-proof-assertions.js";
|
|
14
16
|
import { noEnvAccess } from "./rules/no-env-access.js";
|
|
15
17
|
import { noFixedTestDelay } from "./rules/no-fixed-test-delay.js";
|
|
16
18
|
import { noForwardingModule } from "./rules/no-forwarding-module.js";
|
|
17
19
|
import { noGenericFileSuffix } from "./rules/no-generic-file-suffix.js";
|
|
20
|
+
import { noGenericObjectGuard } from "./rules/no-generic-object-guard.js";
|
|
21
|
+
import { noGetPasswordForm } from "./rules/no-get-password-form.js";
|
|
22
|
+
import { noHardcodedCredential } from "./rules/no-hardcoded-credential.js";
|
|
23
|
+
import { noImportTimeEffects } from "./rules/no-import-time-effects.js";
|
|
24
|
+
import { noOptionalTestAction } from "./rules/no-optional-test-action.js";
|
|
18
25
|
import { noPassThroughFunction } from "./rules/no-pass-through-function.js";
|
|
26
|
+
import { noSecretFallback } from "./rules/no-secret-fallback.js";
|
|
19
27
|
import { noSwallowedErrors } from "./rules/no-swallowed-errors.js";
|
|
20
28
|
import { noTestTimeoutOverrides } from "./rules/no-test-timeout-overrides.js";
|
|
29
|
+
import { noUncheckedFetchResponse } from "./rules/no-unchecked-fetch-response.js";
|
|
30
|
+
import { noUncheckedIndexSearch } from "./rules/no-unchecked-index-search.js";
|
|
31
|
+
import { noUncontrolledTestClock } from "./rules/no-uncontrolled-test-clock.js";
|
|
32
|
+
import { noUnsettledPromise } from "./rules/no-unsettled-promise.js";
|
|
21
33
|
import { noVacuousTestAssertions } from "./rules/no-vacuous-test-assertions.js";
|
|
34
|
+
import { requireSuppressionReason } from "./rules/require-suppression-reason.js";
|
|
22
35
|
import { serverActionGuard } from "./rules/server-action-guard.js";
|
|
23
36
|
import { statementSpacing } from "./rules/statement-spacing.js";
|
|
24
37
|
import { typeGuardName } from "./rules/type-guard-name.js";
|
|
@@ -66,29 +79,42 @@ const wyse = definePlugin({
|
|
|
66
79
|
"no-comments": noComments,
|
|
67
80
|
"no-conditional-empty-object-spread": noConditionalEmptyObjectSpread,
|
|
68
81
|
"no-deep-ternary": noDeepTernary,
|
|
82
|
+
"no-derived-test-expectation": noDerivedTestExpectation,
|
|
83
|
+
"no-disabled-tls-verification": noDisabledTlsVerification,
|
|
69
84
|
"no-edit-proof-assertions": noEditProofAssertions,
|
|
70
85
|
"no-env-access": noEnvAccess,
|
|
71
86
|
"no-fixed-test-delay": noFixedTestDelay,
|
|
72
87
|
"no-forwarding-module": noForwardingModule,
|
|
73
88
|
"no-generic-file-suffix": noGenericFileSuffix,
|
|
89
|
+
"no-generic-object-guard": noGenericObjectGuard,
|
|
90
|
+
"no-get-password-form": noGetPasswordForm,
|
|
91
|
+
"no-hardcoded-credential": noHardcodedCredential,
|
|
92
|
+
"no-import-time-effects": noImportTimeEffects,
|
|
74
93
|
"no-known-value-widening": noKnownValueWidening,
|
|
75
94
|
"no-manual-effect-error-tag": noManualEffectErrorTag,
|
|
76
95
|
"no-module-mocking": noModuleMocking,
|
|
77
96
|
"no-object-parameters": noObjectParameters,
|
|
97
|
+
"no-optional-test-action": noOptionalTestAction,
|
|
78
98
|
"no-pass-through-function": noPassThroughFunction,
|
|
79
99
|
"no-reduce-accumulator-copy": noReduceAccumulatorCopy,
|
|
80
100
|
"no-reflect-apply": noReflectApply,
|
|
81
101
|
"no-reflect-get": noReflectGet,
|
|
82
102
|
"no-runtime-typeof": noRuntimeTypeof,
|
|
103
|
+
"no-secret-fallback": noSecretFallback,
|
|
83
104
|
"no-service-constructor-imports": noServiceConstructorImports,
|
|
84
105
|
"no-shape-in-symbol-names": noForbiddenTermInSymbolNames,
|
|
85
106
|
"no-swallowed-errors": noSwallowedErrors,
|
|
86
107
|
"no-test-timeout-overrides": noTestTimeoutOverrides,
|
|
108
|
+
"no-unchecked-fetch-response": noUncheckedFetchResponse,
|
|
109
|
+
"no-unchecked-index-search": noUncheckedIndexSearch,
|
|
110
|
+
"no-uncontrolled-test-clock": noUncontrolledTestClock,
|
|
87
111
|
"no-unknown-returns": noUnknownReturns,
|
|
88
112
|
"no-unknown-type-aliases": noUnknownTypeAliases,
|
|
89
113
|
"no-unsafe-dictionary-type": noUnsafeDictionaryType,
|
|
114
|
+
"no-unsettled-promise": noUnsettledPromise,
|
|
90
115
|
"no-vacuous-test-assertions": noVacuousTestAssertions,
|
|
91
116
|
"no-widen-then-assert": noWidenThenAssert,
|
|
117
|
+
"require-suppression-reason": requireSuppressionReason,
|
|
92
118
|
"server-action-guard": serverActionGuard,
|
|
93
119
|
"statement-spacing": statementSpacing,
|
|
94
120
|
"type-guard-name": typeGuardName,
|
package/dist/lint.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { DummyRuleMap, OxlintConfig, OxlintOverride } from "oxlint";
|
|
2
2
|
export type FileScope = boolean | string[];
|
|
3
3
|
export interface LintOptions {
|
|
4
|
+
root?: string;
|
|
4
5
|
typeAware?: boolean;
|
|
5
6
|
node?: FileScope;
|
|
6
7
|
react?: FileScope;
|
|
@@ -23,6 +24,7 @@ export interface ImportBoundary {
|
|
|
23
24
|
ignore?: string[];
|
|
24
25
|
deny: string[];
|
|
25
26
|
message: string;
|
|
27
|
+
types?: boolean;
|
|
26
28
|
}
|
|
27
29
|
export type SharedLintConfig = OxlintConfig & {
|
|
28
30
|
ignorePatterns: string[];
|
|
@@ -30,4 +32,4 @@ export type SharedLintConfig = OxlintConfig & {
|
|
|
30
32
|
rules: DummyRuleMap;
|
|
31
33
|
};
|
|
32
34
|
export declare const defaultIgnores: string[];
|
|
33
|
-
export declare function createLintConfig({ typeAware, node, react, nextjs, vitest, jest, effect, tests, env, boundaries, serverActionGuards, clock, random, electron, }?: LintOptions): SharedLintConfig;
|
|
35
|
+
export declare function createLintConfig({ root, typeAware, node, react, nextjs, vitest, jest, effect, tests, env, boundaries, serverActionGuards, clock, random, electron, }?: LintOptions): SharedLintConfig;
|
package/dist/lint.js
CHANGED
|
@@ -4,6 +4,7 @@ import { fileURLToPath } from "node:url";
|
|
|
4
4
|
const require = createRequire(import.meta.url);
|
|
5
5
|
const sourceFiles = ["**/*.{js,jsx,mjs,cjs,ts,tsx,mts,cts}"];
|
|
6
6
|
const typeScriptFiles = ["**/*.{ts,tsx,mts,cts}"];
|
|
7
|
+
const javaScriptFiles = ["**/*.{js,jsx,mjs,cjs}"];
|
|
7
8
|
const testFiles = [
|
|
8
9
|
"**/*.{test,spec}.{js,jsx,mjs,cjs,ts,tsx,mts,cts}",
|
|
9
10
|
"**/__tests__/**/*.{js,jsx,mjs,cjs,ts,tsx,mts,cts}",
|
|
@@ -35,12 +36,19 @@ const defaultExportFiles = [
|
|
|
35
36
|
"**/*.stories.{js,jsx,mjs,ts,tsx,mts}",
|
|
36
37
|
"**/.storybook/**/*.{js,jsx,mjs,cjs,ts,tsx,mts,cts}",
|
|
37
38
|
];
|
|
39
|
+
const programFiles = [
|
|
40
|
+
"**/{bin,cli,deploy,tools,test,tests,e2e,fixtures}/**/*.{js,jsx,mjs,cjs,ts,tsx,mts,cts}",
|
|
41
|
+
"**/.storybook/**/*.{js,jsx,mjs,cjs,ts,tsx,mts,cts}",
|
|
42
|
+
"**/{main,server,worker,instrumentation,instrumentation-client,healthcheck}.{js,jsx,mjs,cjs,ts,tsx,mts,cts}",
|
|
43
|
+
];
|
|
38
44
|
const endToEndFiles = ["**/e2e/**", "**/*.e2e.{js,jsx,mjs,cjs,ts,tsx,mts,cts}"];
|
|
39
45
|
const electronRendererFiles = ["src/renderer/**/*.{js,jsx,mjs,cjs,ts,tsx,mts,cts}"];
|
|
40
46
|
const testRules = {
|
|
41
47
|
"wyse/no-fixed-test-delay": "error",
|
|
42
48
|
"wyse/no-vacuous-test-assertions": "error",
|
|
43
49
|
"wyse/no-edit-proof-assertions": "error",
|
|
50
|
+
"wyse/no-optional-test-action": "error",
|
|
51
|
+
"wyse/no-derived-test-expectation": "warn",
|
|
44
52
|
"wyse/companion-has-owner": "warn",
|
|
45
53
|
};
|
|
46
54
|
const basePlugins = ["eslint", "unicorn", "oxc", "import", "promise", "typescript"];
|
|
@@ -70,6 +78,43 @@ function reactScope(react, nextjs) {
|
|
|
70
78
|
const files = [...(react || []), ...(nextjs || [])];
|
|
71
79
|
return files.length > 0 ? [...new Set(files)] : false;
|
|
72
80
|
}
|
|
81
|
+
const nativeGuards = {
|
|
82
|
+
...errors("eslint", [
|
|
83
|
+
"array-callback-return",
|
|
84
|
+
"no-array-constructor",
|
|
85
|
+
"no-case-declarations",
|
|
86
|
+
"no-constructor-return",
|
|
87
|
+
"no-fallthrough",
|
|
88
|
+
"no-loop-func",
|
|
89
|
+
"no-new-func",
|
|
90
|
+
"no-new-wrappers",
|
|
91
|
+
"no-proto",
|
|
92
|
+
"no-prototype-builtins",
|
|
93
|
+
"no-self-compare",
|
|
94
|
+
"no-unreachable-loop",
|
|
95
|
+
"no-useless-assignment",
|
|
96
|
+
"no-var",
|
|
97
|
+
"prefer-object-has-own",
|
|
98
|
+
"prefer-rest-params",
|
|
99
|
+
]),
|
|
100
|
+
"eslint/prefer-regex-literals": ["error", { disallowRedundantWrapping: true }],
|
|
101
|
+
...errors("unicorn", [
|
|
102
|
+
"error-message",
|
|
103
|
+
"new-for-builtins",
|
|
104
|
+
"no-instanceof-array",
|
|
105
|
+
"no-new-buffer",
|
|
106
|
+
"prefer-array-find",
|
|
107
|
+
"prefer-array-flat-map",
|
|
108
|
+
"prefer-array-some",
|
|
109
|
+
"prefer-import-meta-properties",
|
|
110
|
+
"prefer-node-protocol",
|
|
111
|
+
"prefer-response-static-json",
|
|
112
|
+
"prefer-string-trim-start-end",
|
|
113
|
+
]),
|
|
114
|
+
"import/no-mutable-exports": "error",
|
|
115
|
+
"oxc/no-const-enum": "error",
|
|
116
|
+
};
|
|
117
|
+
const javaScriptOnlyRules = errors("eslint", ["no-throw-literal", "prefer-promise-reject-errors"]);
|
|
73
118
|
const baseErrors = errors("wyse", [
|
|
74
119
|
"no-array-filter-map",
|
|
75
120
|
"no-reduce-accumulator-copy",
|
|
@@ -99,10 +144,23 @@ const typeAwareRules = {
|
|
|
99
144
|
"no-unsafe-return",
|
|
100
145
|
"no-unsafe-type-assertion",
|
|
101
146
|
"only-throw-error",
|
|
147
|
+
"prefer-promise-reject-errors",
|
|
102
148
|
"prefer-readonly",
|
|
149
|
+
"prefer-reduce-type-parameter",
|
|
103
150
|
"return-await",
|
|
104
151
|
"switch-exhaustiveness-check",
|
|
152
|
+
"use-unknown-in-catch-callback-variable",
|
|
105
153
|
]),
|
|
154
|
+
"typescript/restrict-plus-operands": [
|
|
155
|
+
"error",
|
|
156
|
+
{
|
|
157
|
+
allowAny: false,
|
|
158
|
+
allowBoolean: false,
|
|
159
|
+
allowNullish: false,
|
|
160
|
+
allowNumberAndString: false,
|
|
161
|
+
allowRegExp: false,
|
|
162
|
+
},
|
|
163
|
+
],
|
|
106
164
|
"typescript/no-floating-promises": [
|
|
107
165
|
"error",
|
|
108
166
|
{
|
|
@@ -250,17 +308,24 @@ const testRuleNames = [
|
|
|
250
308
|
"no-duplicate-hooks",
|
|
251
309
|
"no-focused-tests",
|
|
252
310
|
"no-identical-title",
|
|
311
|
+
"no-interpolation-in-snapshots",
|
|
253
312
|
"no-standalone-expect",
|
|
313
|
+
"no-unneeded-async-expect-function",
|
|
314
|
+
"prefer-comparison-matcher",
|
|
315
|
+
"prefer-equality-matcher",
|
|
316
|
+
"prefer-strict-equal",
|
|
317
|
+
"prefer-to-contain",
|
|
318
|
+
"prefer-to-have-length",
|
|
254
319
|
"valid-describe-callback",
|
|
255
320
|
"valid-expect",
|
|
256
321
|
"valid-expect-in-promise",
|
|
257
322
|
];
|
|
258
|
-
export function createLintConfig({ typeAware = true, node = false, react = false, nextjs = false, vitest = false, jest = false, effect = false, tests = [], env, boundaries = [], serverActionGuards, clock, random, electron = false, } = {}) {
|
|
259
|
-
const nextRoots = filesFor(nextjs, ["."]).map((
|
|
260
|
-
const normalized =
|
|
323
|
+
export function createLintConfig({ root, typeAware = true, node = false, react = false, nextjs = false, vitest = false, jest = false, effect = false, tests = [], env, boundaries = [], serverActionGuards, clock, random, electron = false, } = {}) {
|
|
324
|
+
const nextRoots = filesFor(nextjs, ["."]).map((appRoot) => {
|
|
325
|
+
const normalized = appRoot.replace(/^\.\//, "").replace(/\/+$/, "");
|
|
261
326
|
return normalized === "." || normalized === "" ? "" : `${normalized}/`;
|
|
262
327
|
});
|
|
263
|
-
const nextFiles = nextRoots.flatMap((
|
|
328
|
+
const nextFiles = nextRoots.flatMap((appRoot) => sourceFiles.map((pattern) => `${appRoot}${pattern}`));
|
|
264
329
|
const reactFiles = reactScope(react, nextFiles);
|
|
265
330
|
const allTestFiles = [
|
|
266
331
|
...new Set([
|
|
@@ -273,13 +338,32 @@ export function createLintConfig({ typeAware = true, node = false, react = false
|
|
|
273
338
|
const jsPlugins = [wysePlugin];
|
|
274
339
|
const typescriptRules = {
|
|
275
340
|
...errors("typescript", [
|
|
276
|
-
"ban-ts-comment",
|
|
277
341
|
"consistent-type-imports",
|
|
278
342
|
"no-explicit-any",
|
|
279
343
|
"no-non-null-assertion",
|
|
280
344
|
"no-empty-object-type",
|
|
281
345
|
"no-unnecessary-type-constraint",
|
|
282
346
|
]),
|
|
347
|
+
"typescript/ban-ts-comment": [
|
|
348
|
+
"error",
|
|
349
|
+
{
|
|
350
|
+
"ts-expect-error": { descriptionFormat: "^\\s*-{2,}\\s+\\S" },
|
|
351
|
+
"ts-ignore": true,
|
|
352
|
+
"ts-nocheck": true,
|
|
353
|
+
"ts-check": false,
|
|
354
|
+
},
|
|
355
|
+
],
|
|
356
|
+
"typescript/consistent-type-assertions": [
|
|
357
|
+
"error",
|
|
358
|
+
{
|
|
359
|
+
assertionStyle: "as",
|
|
360
|
+
objectLiteralTypeAssertions: "never",
|
|
361
|
+
arrayLiteralTypeAssertions: "never",
|
|
362
|
+
},
|
|
363
|
+
],
|
|
364
|
+
"typescript/method-signature-style": ["error", "property"],
|
|
365
|
+
"typescript/no-import-type-side-effects": "error",
|
|
366
|
+
"typescript/no-unsafe-function-type": "error",
|
|
283
367
|
"wyse/type-guard-name": "error",
|
|
284
368
|
};
|
|
285
369
|
const overrides = [
|
|
@@ -297,11 +381,28 @@ export function createLintConfig({ typeAware = true, node = false, react = false
|
|
|
297
381
|
files: defaultExportFiles,
|
|
298
382
|
rules: { "import/no-default-export": "off" },
|
|
299
383
|
},
|
|
384
|
+
{ files: javaScriptFiles, rules: javaScriptOnlyRules },
|
|
300
385
|
{ files: allTestFiles, rules: testRules },
|
|
301
386
|
{
|
|
302
387
|
files: allTestFiles,
|
|
303
388
|
excludeFiles: endToEndFiles,
|
|
304
|
-
rules: {
|
|
389
|
+
rules: {
|
|
390
|
+
"wyse/no-test-timeout-overrides": "warn",
|
|
391
|
+
"wyse/no-uncontrolled-test-clock": "error",
|
|
392
|
+
},
|
|
393
|
+
},
|
|
394
|
+
{
|
|
395
|
+
files: sourceFiles,
|
|
396
|
+
excludeFiles: allTestFiles,
|
|
397
|
+
rules: {
|
|
398
|
+
"wyse/no-unchecked-fetch-response": "error",
|
|
399
|
+
"wyse/no-unsettled-promise": "error",
|
|
400
|
+
},
|
|
401
|
+
},
|
|
402
|
+
{
|
|
403
|
+
files: sourceFiles,
|
|
404
|
+
excludeFiles: [...allTestFiles, ...entrypointFiles, ...programFiles],
|
|
405
|
+
rules: { "wyse/no-import-time-effects": "warn" },
|
|
305
406
|
},
|
|
306
407
|
];
|
|
307
408
|
if (env !== undefined && env.length > 0) {
|
|
@@ -325,9 +426,12 @@ export function createLintConfig({ typeAware = true, node = false, react = false
|
|
|
325
426
|
]
|
|
326
427
|
: boundaries;
|
|
327
428
|
if (importBoundaries.length > 0) {
|
|
429
|
+
if (root === undefined) {
|
|
430
|
+
throw new TypeError("Import boundaries resolve from the config file, so pass root: import.meta.dirname.");
|
|
431
|
+
}
|
|
328
432
|
overrides.push({
|
|
329
433
|
files: sourceFiles,
|
|
330
|
-
rules: { "wyse/import-boundaries": ["error", { boundaries: importBoundaries }] },
|
|
434
|
+
rules: { "wyse/import-boundaries": ["error", { root, boundaries: importBoundaries }] },
|
|
331
435
|
});
|
|
332
436
|
}
|
|
333
437
|
if (electron) {
|
|
@@ -348,7 +452,11 @@ export function createLintConfig({ typeAware = true, node = false, react = false
|
|
|
348
452
|
});
|
|
349
453
|
}
|
|
350
454
|
if (typeAware) {
|
|
351
|
-
overrides.unshift({ files: typeScriptFiles, rules: typeAwareRules }
|
|
455
|
+
overrides.unshift({ files: typeScriptFiles, rules: typeAwareRules }, {
|
|
456
|
+
files: typeScriptFiles,
|
|
457
|
+
excludeFiles: allTestFiles,
|
|
458
|
+
rules: { "typescript/no-deprecated": "warn" },
|
|
459
|
+
});
|
|
352
460
|
}
|
|
353
461
|
if (node) {
|
|
354
462
|
overrides.push({
|
|
@@ -376,14 +484,38 @@ export function createLintConfig({ typeAware = true, node = false, react = false
|
|
|
376
484
|
...accessibilityRules,
|
|
377
485
|
"react/exhaustive-deps": "error",
|
|
378
486
|
"react/no-array-index-key": "error",
|
|
487
|
+
"wyse/no-get-password-form": "error",
|
|
379
488
|
"react/react-in-jsx-scope": "off",
|
|
489
|
+
"jsx-a11y/control-has-associated-label": [
|
|
490
|
+
"error",
|
|
491
|
+
{
|
|
492
|
+
ignoreElements: [
|
|
493
|
+
"audio",
|
|
494
|
+
"canvas",
|
|
495
|
+
"embed",
|
|
496
|
+
"input",
|
|
497
|
+
"td",
|
|
498
|
+
"textarea",
|
|
499
|
+
"tr",
|
|
500
|
+
"video",
|
|
501
|
+
],
|
|
502
|
+
},
|
|
503
|
+
],
|
|
504
|
+
"jsx-a11y/label-has-associated-control": ["error", { depth: 25 }],
|
|
380
505
|
"jsx-a11y/no-noninteractive-tabindex": ["error", { roles: ["region", "tabpanel"] }],
|
|
381
506
|
"jsx-a11y/prefer-tag-over-role": "off",
|
|
507
|
+
"react/checked-requires-onchange-or-readonly": "error",
|
|
508
|
+
"react/jsx-no-useless-fragment": ["error", { allowExpressions: true }],
|
|
509
|
+
"react/no-unknown-property": "error",
|
|
382
510
|
},
|
|
383
511
|
}, {
|
|
384
512
|
files: reactFiles,
|
|
385
513
|
excludeFiles: allTestFiles,
|
|
386
514
|
rules: { ...reactEffectRules, ...browserPerformanceRules },
|
|
515
|
+
}, {
|
|
516
|
+
files: reactFiles,
|
|
517
|
+
excludeFiles: [...endToEndFiles, "**/tests/**"],
|
|
518
|
+
rules: { "react/rules-of-hooks": "error" },
|
|
387
519
|
});
|
|
388
520
|
}
|
|
389
521
|
if (nextFiles.length > 0) {
|
|
@@ -393,11 +525,11 @@ export function createLintConfig({ typeAware = true, node = false, react = false
|
|
|
393
525
|
plugins: [...basePlugins, "react", "jsx-a11y", "nextjs"],
|
|
394
526
|
rules: nextRules,
|
|
395
527
|
}, {
|
|
396
|
-
files: nextRoots.flatMap((
|
|
397
|
-
`${
|
|
398
|
-
`${
|
|
399
|
-
`${
|
|
400
|
-
`${
|
|
528
|
+
files: nextRoots.flatMap((appRoot) => ["", "src/"].flatMap((source) => [
|
|
529
|
+
`${appRoot}${source}app/**/{page,layout,template,loading,error,global-error,not-found,global-not-found,forbidden,unauthorized,default}.{js,jsx,ts,tsx}`,
|
|
530
|
+
`${appRoot}${source}app/**/{sitemap,robots,manifest,icon,apple-icon,opengraph-image,twitter-image}.{js,jsx,ts,tsx}`,
|
|
531
|
+
`${appRoot}${source}pages/**/*.{js,jsx,ts,tsx}`,
|
|
532
|
+
`${appRoot}${source}{middleware,proxy}.{js,ts}`,
|
|
401
533
|
])),
|
|
402
534
|
excludeFiles: allTestFiles,
|
|
403
535
|
rules: { "import/no-default-export": "off" },
|
|
@@ -414,7 +546,7 @@ export function createLintConfig({ typeAware = true, node = false, react = false
|
|
|
414
546
|
plugins: [...basePlugins, name],
|
|
415
547
|
env: { [name]: true },
|
|
416
548
|
rules: {
|
|
417
|
-
...errors(name, testRuleNames),
|
|
549
|
+
...errors(name, name === "jest" ? [...testRuleNames, "no-done-callback"] : testRuleNames),
|
|
418
550
|
...testRules,
|
|
419
551
|
[`${name}/no-standalone-expect`]: [
|
|
420
552
|
"error",
|
|
@@ -438,12 +570,19 @@ export function createLintConfig({ typeAware = true, node = false, react = false
|
|
|
438
570
|
env: { es2022: true },
|
|
439
571
|
ignorePatterns: defaultIgnores,
|
|
440
572
|
rules: {
|
|
573
|
+
...nativeGuards,
|
|
441
574
|
...baseErrors,
|
|
442
575
|
...typescriptRules,
|
|
443
576
|
"wyse/no-runtime-typeof": ["warn", { allowInTypeGuards: true }],
|
|
444
577
|
"wyse/no-comments": "error",
|
|
578
|
+
"wyse/require-suppression-reason": "error",
|
|
445
579
|
"wyse/statement-spacing": "error",
|
|
446
580
|
"wyse/no-generic-file-suffix": "error",
|
|
581
|
+
"wyse/no-generic-object-guard": "error",
|
|
582
|
+
"wyse/no-disabled-tls-verification": "error",
|
|
583
|
+
"wyse/no-hardcoded-credential": "error",
|
|
584
|
+
"wyse/no-secret-fallback": "error",
|
|
585
|
+
"wyse/no-unchecked-index-search": "error",
|
|
447
586
|
"wyse/no-swallowed-errors": "warn",
|
|
448
587
|
"wyse/explicit-api-states": "warn",
|
|
449
588
|
"wyse/no-pass-through-function": "warn",
|
|
@@ -29,6 +29,12 @@ function isSetter(name, fn, parameter) {
|
|
|
29
29
|
return (/^(?:set|update)\p{Lu}/u.test(name.slice(name.lastIndexOf(".") + 1)) &&
|
|
30
30
|
fn.params.at(-1) === parameter);
|
|
31
31
|
}
|
|
32
|
+
function isPredicateSubject(fn, identifier) {
|
|
33
|
+
const predicate = fn.returnType?.typeAnnotation;
|
|
34
|
+
return (predicate?.type === "TSTypePredicate" &&
|
|
35
|
+
predicate.parameterName.type === "Identifier" &&
|
|
36
|
+
predicate.parameterName.name === identifier.name);
|
|
37
|
+
}
|
|
32
38
|
function functionValue(node) {
|
|
33
39
|
return node?.type === "ArrowFunctionExpression" || node?.type === "FunctionExpression"
|
|
34
40
|
? node
|
|
@@ -92,7 +98,7 @@ export const explicitApiStates = defineRule({
|
|
|
92
98
|
}
|
|
93
99
|
for (const parameter of fn.params) {
|
|
94
100
|
const identifier = booleanParameter(parameter);
|
|
95
|
-
if (identifier && !isSetter(name, fn, parameter)) {
|
|
101
|
+
if (identifier && !isSetter(name, fn, parameter) && !isPredicateSubject(fn, identifier)) {
|
|
96
102
|
context.report({
|
|
97
103
|
node: identifier,
|
|
98
104
|
messageId: "boolean",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { dirname, resolve as resolvePath } from "node:path";
|
|
2
2
|
import { defineRule } from "@oxlint/plugins";
|
|
3
|
-
import {
|
|
3
|
+
import { importedBinding, isGlobal, matchesAny, projectPath, variableFor, } from "../shared/index.js";
|
|
4
4
|
function isStringList(value) {
|
|
5
5
|
return Array.isArray(value) && value.every((item) => typeof item === "string");
|
|
6
6
|
}
|
|
@@ -8,24 +8,37 @@ function parseBoundary(value) {
|
|
|
8
8
|
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
9
9
|
throw new TypeError("Each import boundary must be an object.");
|
|
10
10
|
}
|
|
11
|
-
const { files, ignore = [], deny, message } = value;
|
|
11
|
+
const { files, ignore = [], deny, message, types = false } = value;
|
|
12
12
|
if (!isStringList(files) ||
|
|
13
13
|
!isStringList(ignore) ||
|
|
14
14
|
!isStringList(deny) ||
|
|
15
|
-
typeof message !== "string"
|
|
15
|
+
typeof message !== "string" ||
|
|
16
|
+
typeof types !== "boolean") {
|
|
16
17
|
throw new TypeError("Each import boundary needs files, deny and message settings.");
|
|
17
18
|
}
|
|
18
|
-
return { files, ignore, deny, message };
|
|
19
|
+
return { files, ignore, deny, message, types };
|
|
19
20
|
}
|
|
20
|
-
function
|
|
21
|
+
function parseSettings(options) {
|
|
21
22
|
const [option] = options;
|
|
22
23
|
if (typeof option !== "object" ||
|
|
23
24
|
option === null ||
|
|
24
25
|
Array.isArray(option) ||
|
|
26
|
+
typeof option.root !== "string" ||
|
|
25
27
|
!Array.isArray(option.boundaries)) {
|
|
26
|
-
throw new TypeError("Configure import boundaries with a boundaries array.");
|
|
28
|
+
throw new TypeError("Configure import boundaries with a root and a boundaries array.");
|
|
27
29
|
}
|
|
28
|
-
return option.boundaries.map(parseBoundary);
|
|
30
|
+
return { root: option.root, boundaries: option.boundaries.map(parseBoundary) };
|
|
31
|
+
}
|
|
32
|
+
const nodeModuleSources = new Set(["module", "node:module"]);
|
|
33
|
+
function isRequire(sourceCode, callee) {
|
|
34
|
+
if (isGlobal(sourceCode, callee, "require")) {
|
|
35
|
+
return true;
|
|
36
|
+
}
|
|
37
|
+
const definition = variableFor(sourceCode, callee)?.defs[0];
|
|
38
|
+
const init = definition?.node.type === "VariableDeclarator" ? definition.node.init : null;
|
|
39
|
+
const factory = init?.type === "CallExpression" ? init.callee : null;
|
|
40
|
+
const binding = factory === null ? null : importedBinding(sourceCode, factory);
|
|
41
|
+
return (binding !== null && nodeModuleSources.has(binding.source) && binding.name === "createRequire");
|
|
29
42
|
}
|
|
30
43
|
export const importBoundaries = defineRule({
|
|
31
44
|
meta: {
|
|
@@ -35,6 +48,7 @@ export const importBoundaries = defineRule({
|
|
|
35
48
|
{
|
|
36
49
|
type: "object",
|
|
37
50
|
properties: {
|
|
51
|
+
root: { type: "string" },
|
|
38
52
|
boundaries: {
|
|
39
53
|
type: "array",
|
|
40
54
|
items: {
|
|
@@ -44,33 +58,35 @@ export const importBoundaries = defineRule({
|
|
|
44
58
|
ignore: { type: "array", items: { type: "string" } },
|
|
45
59
|
deny: { type: "array", items: { type: "string" } },
|
|
46
60
|
message: { type: "string" },
|
|
61
|
+
types: { type: "boolean" },
|
|
47
62
|
},
|
|
48
63
|
required: ["files", "deny", "message"],
|
|
49
64
|
additionalProperties: false,
|
|
50
65
|
},
|
|
51
66
|
},
|
|
52
67
|
},
|
|
53
|
-
required: ["boundaries"],
|
|
68
|
+
required: ["root", "boundaries"],
|
|
54
69
|
additionalProperties: false,
|
|
55
70
|
},
|
|
56
71
|
],
|
|
57
72
|
messages: { boundary: "Do not import '{{specifier}}' here. {{message}}" },
|
|
58
73
|
},
|
|
59
74
|
create(context) {
|
|
60
|
-
const
|
|
61
|
-
const
|
|
75
|
+
const { root, boundaries } = parseSettings(context.options);
|
|
76
|
+
const file = projectPath(root, context.filename);
|
|
77
|
+
const active = boundaries.filter((boundary) => matchesAny(file, boundary.files) && !matchesAny(file, boundary.ignore));
|
|
62
78
|
if (active.length === 0) {
|
|
63
79
|
return {};
|
|
64
80
|
}
|
|
65
81
|
function check(source, typeOnly) {
|
|
66
|
-
if (
|
|
82
|
+
if (source?.type !== "Literal" || typeof source.value !== "string") {
|
|
67
83
|
return;
|
|
68
84
|
}
|
|
69
85
|
const specifier = source.value;
|
|
70
86
|
const target = specifier.startsWith(".")
|
|
71
|
-
? projectPath(
|
|
87
|
+
? projectPath(root, resolvePath(dirname(context.filename), specifier))
|
|
72
88
|
: specifier;
|
|
73
|
-
const boundary = active.find(({ deny }) => matchesAny(target, deny));
|
|
89
|
+
const boundary = active.find(({ deny, types }) => (types || !typeOnly) && matchesAny(target, deny));
|
|
74
90
|
if (boundary) {
|
|
75
91
|
context.report({
|
|
76
92
|
node: source,
|
|
@@ -94,6 +110,18 @@ export const importBoundaries = defineRule({
|
|
|
94
110
|
ImportExpression(node) {
|
|
95
111
|
check(node.source, false);
|
|
96
112
|
},
|
|
113
|
+
TSImportType(node) {
|
|
114
|
+
check(node.source, true);
|
|
115
|
+
},
|
|
116
|
+
TSExternalModuleReference(node) {
|
|
117
|
+
check(node.expression, node.parent.type === "TSImportEqualsDeclaration" && node.parent.importKind === "type");
|
|
118
|
+
},
|
|
119
|
+
CallExpression(node) {
|
|
120
|
+
const [source] = node.arguments;
|
|
121
|
+
if (node.arguments.length === 1 && isRequire(context.sourceCode, node.callee)) {
|
|
122
|
+
check(source, false);
|
|
123
|
+
}
|
|
124
|
+
},
|
|
97
125
|
};
|
|
98
126
|
},
|
|
99
127
|
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const noDerivedTestExpectation: import("@oxlint/plugins").Rule;
|