@jameswyse/oxc-config 0.2.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 -186
- package/REFERENCE.md +105 -132
- package/dist/index.js +26 -0
- package/dist/lint.d.ts +1 -0
- package/dist/lint.js +124 -4
- package/dist/rules/explicit-api-states.js +7 -1
- package/dist/rules/import-boundaries.js +31 -6
- 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
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,7 +308,14 @@ 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",
|
|
@@ -273,13 +338,32 @@ export function createLintConfig({ root, typeAware = true, node = false, react =
|
|
|
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({ root, typeAware = true, node = false, react =
|
|
|
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) {
|
|
@@ -351,7 +452,11 @@ export function createLintConfig({ root, typeAware = true, node = false, react =
|
|
|
351
452
|
});
|
|
352
453
|
}
|
|
353
454
|
if (typeAware) {
|
|
354
|
-
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
|
+
});
|
|
355
460
|
}
|
|
356
461
|
if (node) {
|
|
357
462
|
overrides.push({
|
|
@@ -379,6 +484,7 @@ export function createLintConfig({ root, typeAware = true, node = false, react =
|
|
|
379
484
|
...accessibilityRules,
|
|
380
485
|
"react/exhaustive-deps": "error",
|
|
381
486
|
"react/no-array-index-key": "error",
|
|
487
|
+
"wyse/no-get-password-form": "error",
|
|
382
488
|
"react/react-in-jsx-scope": "off",
|
|
383
489
|
"jsx-a11y/control-has-associated-label": [
|
|
384
490
|
"error",
|
|
@@ -398,11 +504,18 @@ export function createLintConfig({ root, typeAware = true, node = false, react =
|
|
|
398
504
|
"jsx-a11y/label-has-associated-control": ["error", { depth: 25 }],
|
|
399
505
|
"jsx-a11y/no-noninteractive-tabindex": ["error", { roles: ["region", "tabpanel"] }],
|
|
400
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",
|
|
401
510
|
},
|
|
402
511
|
}, {
|
|
403
512
|
files: reactFiles,
|
|
404
513
|
excludeFiles: allTestFiles,
|
|
405
514
|
rules: { ...reactEffectRules, ...browserPerformanceRules },
|
|
515
|
+
}, {
|
|
516
|
+
files: reactFiles,
|
|
517
|
+
excludeFiles: [...endToEndFiles, "**/tests/**"],
|
|
518
|
+
rules: { "react/rules-of-hooks": "error" },
|
|
406
519
|
});
|
|
407
520
|
}
|
|
408
521
|
if (nextFiles.length > 0) {
|
|
@@ -433,7 +546,7 @@ export function createLintConfig({ root, typeAware = true, node = false, react =
|
|
|
433
546
|
plugins: [...basePlugins, name],
|
|
434
547
|
env: { [name]: true },
|
|
435
548
|
rules: {
|
|
436
|
-
...errors(name, testRuleNames),
|
|
549
|
+
...errors(name, name === "jest" ? [...testRuleNames, "no-done-callback"] : testRuleNames),
|
|
437
550
|
...testRules,
|
|
438
551
|
[`${name}/no-standalone-expect`]: [
|
|
439
552
|
"error",
|
|
@@ -457,12 +570,19 @@ export function createLintConfig({ root, typeAware = true, node = false, react =
|
|
|
457
570
|
env: { es2022: true },
|
|
458
571
|
ignorePatterns: defaultIgnores,
|
|
459
572
|
rules: {
|
|
573
|
+
...nativeGuards,
|
|
460
574
|
...baseErrors,
|
|
461
575
|
...typescriptRules,
|
|
462
576
|
"wyse/no-runtime-typeof": ["warn", { allowInTypeGuards: true }],
|
|
463
577
|
"wyse/no-comments": "error",
|
|
578
|
+
"wyse/require-suppression-reason": "error",
|
|
464
579
|
"wyse/statement-spacing": "error",
|
|
465
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",
|
|
466
586
|
"wyse/no-swallowed-errors": "warn",
|
|
467
587
|
"wyse/explicit-api-states": "warn",
|
|
468
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,14 +8,15 @@ 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
21
|
function parseSettings(options) {
|
|
21
22
|
const [option] = options;
|
|
@@ -28,6 +29,17 @@ function parseSettings(options) {
|
|
|
28
29
|
}
|
|
29
30
|
return { root: option.root, boundaries: option.boundaries.map(parseBoundary) };
|
|
30
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");
|
|
42
|
+
}
|
|
31
43
|
export const importBoundaries = defineRule({
|
|
32
44
|
meta: {
|
|
33
45
|
type: "problem",
|
|
@@ -46,6 +58,7 @@ export const importBoundaries = defineRule({
|
|
|
46
58
|
ignore: { type: "array", items: { type: "string" } },
|
|
47
59
|
deny: { type: "array", items: { type: "string" } },
|
|
48
60
|
message: { type: "string" },
|
|
61
|
+
types: { type: "boolean" },
|
|
49
62
|
},
|
|
50
63
|
required: ["files", "deny", "message"],
|
|
51
64
|
additionalProperties: false,
|
|
@@ -66,14 +79,14 @@ export const importBoundaries = defineRule({
|
|
|
66
79
|
return {};
|
|
67
80
|
}
|
|
68
81
|
function check(source, typeOnly) {
|
|
69
|
-
if (
|
|
82
|
+
if (source?.type !== "Literal" || typeof source.value !== "string") {
|
|
70
83
|
return;
|
|
71
84
|
}
|
|
72
85
|
const specifier = source.value;
|
|
73
86
|
const target = specifier.startsWith(".")
|
|
74
87
|
? projectPath(root, resolvePath(dirname(context.filename), specifier))
|
|
75
88
|
: specifier;
|
|
76
|
-
const boundary = active.find(({ deny }) => matchesAny(target, deny));
|
|
89
|
+
const boundary = active.find(({ deny, types }) => (types || !typeOnly) && matchesAny(target, deny));
|
|
77
90
|
if (boundary) {
|
|
78
91
|
context.report({
|
|
79
92
|
node: source,
|
|
@@ -97,6 +110,18 @@ export const importBoundaries = defineRule({
|
|
|
97
110
|
ImportExpression(node) {
|
|
98
111
|
check(node.source, false);
|
|
99
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
|
+
},
|
|
100
125
|
};
|
|
101
126
|
},
|
|
102
127
|
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const noDerivedTestExpectation: import("@oxlint/plugins").Rule;
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import { dirname, join } from "node:path";
|
|
2
|
+
import { defineRule } from "@oxlint/plugins";
|
|
3
|
+
import { apiReference, childNodes, projectPath, propertyName, variableFor, } from "../shared/index.js";
|
|
4
|
+
const comparisonMatchers = new Set([
|
|
5
|
+
"toBe",
|
|
6
|
+
"toEqual",
|
|
7
|
+
"toStrictEqual",
|
|
8
|
+
"toMatchObject",
|
|
9
|
+
"toContain",
|
|
10
|
+
"toContainEqual",
|
|
11
|
+
"toMatch",
|
|
12
|
+
"toHaveProperty",
|
|
13
|
+
"toBeCloseTo",
|
|
14
|
+
]);
|
|
15
|
+
const testSupport = /(?:^|[/.])(?:tests?|__tests__|e2e|testing|fixtures?|mocks?|fakes?|stubs?|builders?|factories|support|helpers?)(?:[/.]|$)/u;
|
|
16
|
+
const projectSpecifier = /^(?:\.{1,2}\/|@\/|~\/|#)/u;
|
|
17
|
+
function isProductionImport(sourceCode, directory, callee) {
|
|
18
|
+
const definition = variableFor(sourceCode, callee)?.defs[0];
|
|
19
|
+
const declaration = definition?.parent;
|
|
20
|
+
if (definition?.type !== "ImportBinding" || declaration?.type !== "ImportDeclaration") {
|
|
21
|
+
return false;
|
|
22
|
+
}
|
|
23
|
+
const source = declaration.source.value;
|
|
24
|
+
const path = source.startsWith(".") ? join(directory, source) : source;
|
|
25
|
+
return projectSpecifier.test(source) && !testSupport.test(path.replaceAll("\\", "/"));
|
|
26
|
+
}
|
|
27
|
+
function calledName(sourceCode, directory, callee) {
|
|
28
|
+
if (callee.type === "Identifier") {
|
|
29
|
+
return isProductionImport(sourceCode, directory, callee) ? callee.name : null;
|
|
30
|
+
}
|
|
31
|
+
const name = callee.type === "MemberExpression" ? propertyName(callee) : null;
|
|
32
|
+
const namespace = callee.type === "MemberExpression" ? callee.object : null;
|
|
33
|
+
return namespace?.type === "Identifier" &&
|
|
34
|
+
name !== null &&
|
|
35
|
+
variableFor(sourceCode, namespace)?.defs[0]?.node.type === "ImportNamespaceSpecifier" &&
|
|
36
|
+
isProductionImport(sourceCode, directory, namespace)
|
|
37
|
+
? `${namespace.name}.${name}`
|
|
38
|
+
: null;
|
|
39
|
+
}
|
|
40
|
+
function constantValue(sourceCode, node) {
|
|
41
|
+
const variable = node.type === "Identifier" ? variableFor(sourceCode, node) : null;
|
|
42
|
+
const definition = variable?.defs[0];
|
|
43
|
+
return variable !== null &&
|
|
44
|
+
definition?.type === "Variable" &&
|
|
45
|
+
definition.parent?.type === "VariableDeclaration" &&
|
|
46
|
+
definition.parent.kind === "const" &&
|
|
47
|
+
definition.node.type === "VariableDeclarator" &&
|
|
48
|
+
definition.node.id.type === "Identifier" &&
|
|
49
|
+
definition.node.init !== null
|
|
50
|
+
? { variable, value: definition.node.init }
|
|
51
|
+
: null;
|
|
52
|
+
}
|
|
53
|
+
function isExpectedArgument(node) {
|
|
54
|
+
const call = node.parent;
|
|
55
|
+
return (call?.type === "CallExpression" &&
|
|
56
|
+
call.arguments.some((argument) => argument === node) &&
|
|
57
|
+
call.callee.type === "MemberExpression" &&
|
|
58
|
+
comparisonMatchers.has(propertyName(call.callee) ?? ""));
|
|
59
|
+
}
|
|
60
|
+
function subjectRoots(sourceCode, argument) {
|
|
61
|
+
const constant = constantValue(sourceCode, argument);
|
|
62
|
+
return constant === null ? [argument] : [argument, constant.value];
|
|
63
|
+
}
|
|
64
|
+
function expectedRoot(sourceCode, argument) {
|
|
65
|
+
const constant = constantValue(sourceCode, argument);
|
|
66
|
+
return constant !== null &&
|
|
67
|
+
constant.variable.references.every((reference) => !reference.isRead() || isExpectedArgument(reference.identifier))
|
|
68
|
+
? constant.value
|
|
69
|
+
: argument;
|
|
70
|
+
}
|
|
71
|
+
function productionCalls(sourceCode, directory, root) {
|
|
72
|
+
const names = [];
|
|
73
|
+
const pending = [root];
|
|
74
|
+
for (let node = pending.pop(); node !== undefined; node = pending.pop()) {
|
|
75
|
+
const name = node.type === "CallExpression" ? calledName(sourceCode, directory, node.callee) : null;
|
|
76
|
+
if (name !== null) {
|
|
77
|
+
names.push(name);
|
|
78
|
+
}
|
|
79
|
+
if (node.type !== "ArrowFunctionExpression" && node.type !== "FunctionExpression") {
|
|
80
|
+
pending.push(...childNodes(node, sourceCode.visitorKeys));
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
return names;
|
|
84
|
+
}
|
|
85
|
+
function positiveExpectation(sourceCode, node) {
|
|
86
|
+
let current = node;
|
|
87
|
+
while (current.type === "MemberExpression") {
|
|
88
|
+
if (propertyName(current) === "not") {
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
91
|
+
current = current.object;
|
|
92
|
+
}
|
|
93
|
+
return current.type === "CallExpression" &&
|
|
94
|
+
apiReference(sourceCode, current.callee)?.name === "expect"
|
|
95
|
+
? current
|
|
96
|
+
: null;
|
|
97
|
+
}
|
|
98
|
+
export const noDerivedTestExpectation = defineRule({
|
|
99
|
+
meta: {
|
|
100
|
+
type: "suggestion",
|
|
101
|
+
docs: {
|
|
102
|
+
description: "Disallow expected values computed by the production code under test.",
|
|
103
|
+
},
|
|
104
|
+
schema: [],
|
|
105
|
+
messages: {
|
|
106
|
+
derived: "The expected value calls `{{name}}` from production code, so a bug in it changes both sides and the assertion still passes. Write the expected value out, or derive it independently.",
|
|
107
|
+
},
|
|
108
|
+
},
|
|
109
|
+
create(context) {
|
|
110
|
+
const sourceCode = context.sourceCode;
|
|
111
|
+
const directory = dirname(projectPath(context.cwd, context.filename));
|
|
112
|
+
return {
|
|
113
|
+
CallExpression(node) {
|
|
114
|
+
const matcher = node.callee.type === "MemberExpression" ? propertyName(node.callee) : null;
|
|
115
|
+
const expectation = node.callee.type === "MemberExpression" &&
|
|
116
|
+
matcher !== null &&
|
|
117
|
+
comparisonMatchers.has(matcher)
|
|
118
|
+
? positiveExpectation(sourceCode, node.callee.object)
|
|
119
|
+
: null;
|
|
120
|
+
if (expectation === null) {
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
const subjectCalls = new Set(expectation.arguments
|
|
124
|
+
.flatMap((argument) => subjectRoots(sourceCode, argument))
|
|
125
|
+
.flatMap((root) => productionCalls(sourceCode, directory, root)));
|
|
126
|
+
for (const argument of node.arguments) {
|
|
127
|
+
const name = productionCalls(sourceCode, directory, expectedRoot(sourceCode, argument)).find((call) => !subjectCalls.has(call));
|
|
128
|
+
if (name !== undefined) {
|
|
129
|
+
context.report({ node: argument, messageId: "derived", data: { name } });
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
},
|
|
134
|
+
};
|
|
135
|
+
},
|
|
136
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const noDisabledTlsVerification: import("@oxlint/plugins").Rule;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { defineRule } from "@oxlint/plugins";
|
|
2
|
+
import { isGlobal, staticPropertyName, unwrapExpression } from "../shared/index.js";
|
|
3
|
+
function isFalse(node) {
|
|
4
|
+
const value = unwrapExpression(node);
|
|
5
|
+
return value.type === "Literal" && value.value === false;
|
|
6
|
+
}
|
|
7
|
+
function isDisabledFlag(node) {
|
|
8
|
+
const value = unwrapExpression(node);
|
|
9
|
+
return (value.type === "Literal" && (value.value === "0" || value.value === 0)) || isFalse(value);
|
|
10
|
+
}
|
|
11
|
+
export const noDisabledTlsVerification = defineRule({
|
|
12
|
+
meta: {
|
|
13
|
+
type: "problem",
|
|
14
|
+
docs: { description: "Disallow turning off TLS certificate verification." },
|
|
15
|
+
schema: [],
|
|
16
|
+
messages: {
|
|
17
|
+
disabled: "This turns off TLS certificate verification, so any server can impersonate the real one. Trust the right certificate authority instead, such as with the `ca` option or `NODE_EXTRA_CA_CERTS`.",
|
|
18
|
+
},
|
|
19
|
+
},
|
|
20
|
+
create(context) {
|
|
21
|
+
const sourceCode = context.sourceCode;
|
|
22
|
+
return {
|
|
23
|
+
Property(node) {
|
|
24
|
+
if (staticPropertyName(node) === "rejectUnauthorized" && isFalse(node.value)) {
|
|
25
|
+
context.report({ node, messageId: "disabled" });
|
|
26
|
+
}
|
|
27
|
+
},
|
|
28
|
+
AssignmentExpression(node) {
|
|
29
|
+
const target = unwrapExpression(node.left);
|
|
30
|
+
const environment = target.type === "MemberExpression" ? unwrapExpression(target.object) : null;
|
|
31
|
+
if (target.type === "MemberExpression" &&
|
|
32
|
+
staticPropertyName(target) === "NODE_TLS_REJECT_UNAUTHORIZED" &&
|
|
33
|
+
environment?.type === "MemberExpression" &&
|
|
34
|
+
staticPropertyName(environment) === "env" &&
|
|
35
|
+
isGlobal(sourceCode, unwrapExpression(environment.object), "process") &&
|
|
36
|
+
isDisabledFlag(node.right)) {
|
|
37
|
+
context.report({ node, messageId: "disabled" });
|
|
38
|
+
}
|
|
39
|
+
},
|
|
40
|
+
};
|
|
41
|
+
},
|
|
42
|
+
});
|