@jameswyse/oxc-config 0.2.0 → 0.4.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.
Files changed (52) hide show
  1. package/README.md +30 -186
  2. package/REFERENCE.md +105 -132
  3. package/dist/index.js +26 -0
  4. package/dist/lint.d.ts +1 -0
  5. package/dist/lint.js +124 -4
  6. package/dist/rules/electron-no-remote-module.js +3 -6
  7. package/dist/rules/explicit-api-states.js +7 -1
  8. package/dist/rules/import-boundaries.js +31 -6
  9. package/dist/rules/no-ambient-clock.js +104 -8
  10. package/dist/rules/no-derived-test-expectation.d.ts +1 -0
  11. package/dist/rules/no-derived-test-expectation.js +136 -0
  12. package/dist/rules/no-disabled-tls-verification.d.ts +1 -0
  13. package/dist/rules/no-disabled-tls-verification.js +42 -0
  14. package/dist/rules/no-env-access.js +62 -5
  15. package/dist/rules/no-fixed-test-delay.js +46 -5
  16. package/dist/rules/no-generic-object-guard.d.ts +1 -0
  17. package/dist/rules/no-generic-object-guard.js +279 -0
  18. package/dist/rules/no-get-password-form.d.ts +1 -0
  19. package/dist/rules/no-get-password-form.js +113 -0
  20. package/dist/rules/no-hardcoded-credential.d.ts +1 -0
  21. package/dist/rules/no-hardcoded-credential.js +45 -0
  22. package/dist/rules/no-import-time-effects.d.ts +1 -0
  23. package/dist/rules/no-import-time-effects.js +90 -0
  24. package/dist/rules/no-optional-test-action.d.ts +1 -0
  25. package/dist/rules/no-optional-test-action.js +45 -0
  26. package/dist/rules/no-secret-fallback.d.ts +1 -0
  27. package/dist/rules/no-secret-fallback.js +64 -0
  28. package/dist/rules/no-swallowed-errors.js +131 -16
  29. package/dist/rules/no-test-timeout-overrides.js +22 -9
  30. package/dist/rules/no-unchecked-fetch-response.d.ts +1 -0
  31. package/dist/rules/no-unchecked-fetch-response.js +91 -0
  32. package/dist/rules/no-unchecked-index-search.d.ts +1 -0
  33. package/dist/rules/no-unchecked-index-search.js +41 -0
  34. package/dist/rules/no-uncontrolled-test-clock.d.ts +1 -0
  35. package/dist/rules/no-uncontrolled-test-clock.js +112 -0
  36. package/dist/rules/no-unsettled-promise.d.ts +1 -0
  37. package/dist/rules/no-unsettled-promise.js +64 -0
  38. package/dist/rules/require-suppression-reason.d.ts +1 -0
  39. package/dist/rules/require-suppression-reason.js +32 -0
  40. package/dist/shared/index.d.ts +14 -1
  41. package/dist/shared/index.js +231 -91
  42. package/dist/vendor/anti-slop/effect/rules/no-service-constructor-imports.js +10 -2
  43. package/dist/vendor/anti-slop/rules/no-known-value-widening.js +1 -1
  44. package/dist/vendor/anti-slop/rules/no-module-mocking.js +56 -1
  45. package/dist/vendor/anti-slop/rules/no-runtime-typeof.js +30 -1
  46. package/dist/vendor/anti-slop/rules/no-shape-in-symbol-names.js +28 -1
  47. package/dist/vendor/anti-slop/rules/no-unknown-returns.js +1 -1
  48. package/dist/vendor/anti-slop/rules/no-unsafe-dictionary-type.js +2 -2
  49. package/dist/vendor/anti-slop/shared/dictionary-types.js +72 -89
  50. package/dist/vendor/anti-slop/shared/type-alias-resolution.d.ts +8 -2
  51. package/dist/vendor/anti-slop/shared/type-alias-resolution.js +2 -2
  52. package/package.json +7 -7
@@ -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
+ });
@@ -1,10 +1,34 @@
1
1
  import { defineRule } from "@oxlint/plugins";
2
- import { isGlobal, propertyName } from "../shared/index.js";
2
+ import { importedBinding, isGlobalValue, isNodeGlobalValue, moduleExportName, staticPropertyName, staticString, unwrapExpression, } from "../shared/index.js";
3
+ const processModules = new Set(["node:process", "process"]);
4
+ const environmentExports = new Set(["env", "default"]);
3
5
  function isImportMeta(node) {
4
- return (node?.type === "MetaProperty" && node.meta.name === "import" && node.property.name === "meta");
6
+ const value = unwrapExpression(node);
7
+ return (value.type === "MetaProperty" && value.meta.name === "import" && value.property.name === "meta");
8
+ }
9
+ function isProcessModule(sourceCode, node) {
10
+ const binding = importedBinding(sourceCode, node);
11
+ return (binding !== null &&
12
+ processModules.has(binding.source) &&
13
+ (binding.name === "*" || binding.name === "default"));
5
14
  }
6
15
  function isEnvironmentSource(sourceCode, node) {
7
- return isGlobal(sourceCode, node, "process") || isImportMeta(node);
16
+ if (!node) {
17
+ return false;
18
+ }
19
+ return (isNodeGlobalValue(sourceCode, node, "process") ||
20
+ isImportMeta(node) ||
21
+ isProcessModule(sourceCode, node));
22
+ }
23
+ function isReflectGet(sourceCode, node) {
24
+ const callee = unwrapExpression(node.callee);
25
+ const [target, key] = node.arguments;
26
+ return (callee.type === "MemberExpression" &&
27
+ staticPropertyName(callee) === "get" &&
28
+ isGlobalValue(sourceCode, callee.object, "Reflect") &&
29
+ target?.type !== "SpreadElement" &&
30
+ isEnvironmentSource(sourceCode, target) &&
31
+ staticString(key) === "env");
8
32
  }
9
33
  export const noEnvAccess = defineRule({
10
34
  meta: {
@@ -19,14 +43,47 @@ export const noEnvAccess = defineRule({
19
43
  const sourceCode = context.sourceCode;
20
44
  return {
21
45
  MemberExpression(node) {
22
- if (propertyName(node) === "env" && isEnvironmentSource(sourceCode, node.object)) {
46
+ if (staticPropertyName(node) === "env" && isEnvironmentSource(sourceCode, node.object)) {
23
47
  context.report({ node, messageId: "env" });
24
48
  }
25
49
  },
26
50
  VariableDeclarator(node) {
27
51
  if (node.id.type === "ObjectPattern" &&
28
52
  isEnvironmentSource(sourceCode, node.init) &&
29
- node.id.properties.some((property) => property.type === "Property" && propertyName(property) === "env")) {
53
+ node.id.properties.some((property) => property.type === "Property" && staticPropertyName(property) === "env")) {
54
+ context.report({ node, messageId: "env" });
55
+ }
56
+ },
57
+ CallExpression(node) {
58
+ if (isReflectGet(sourceCode, node)) {
59
+ context.report({ node, messageId: "env" });
60
+ }
61
+ },
62
+ ImportDeclaration(node) {
63
+ if (!processModules.has(node.source.value) || node.importKind === "type") {
64
+ return;
65
+ }
66
+ for (const specifier of node.specifiers) {
67
+ if (specifier.type === "ImportSpecifier" &&
68
+ specifier.importKind !== "type" &&
69
+ moduleExportName(specifier.imported) === "env") {
70
+ context.report({ node: specifier, messageId: "env" });
71
+ }
72
+ }
73
+ },
74
+ ExportNamedDeclaration(node) {
75
+ if (!node.source || !processModules.has(node.source.value) || node.exportKind === "type") {
76
+ return;
77
+ }
78
+ for (const specifier of node.specifiers) {
79
+ if (specifier.exportKind !== "type" &&
80
+ environmentExports.has(moduleExportName(specifier.local))) {
81
+ context.report({ node: specifier, messageId: "env" });
82
+ }
83
+ }
84
+ },
85
+ ExportAllDeclaration(node) {
86
+ if (processModules.has(node.source.value) && node.exportKind !== "type") {
30
87
  context.report({ node, messageId: "env" });
31
88
  }
32
89
  },
@@ -1,6 +1,21 @@
1
1
  import { defineRule } from "@oxlint/plugins";
2
- import { variableFor, isGlobal, propertyName, isPlaywright, apiReference, testContext, } from "../shared/index.js";
3
- function numericLiteral(node) {
2
+ import { variableFor, isGlobal, propertyName, isPlaywright, apiReference, isEffectModule, testContext, } from "../shared/index.js";
3
+ function constantValue(sourceCode, node) {
4
+ if (node?.type !== "Identifier") {
5
+ return node;
6
+ }
7
+ const [definition, ...others] = variableFor(sourceCode, node)?.defs ?? [];
8
+ const declarator = definition?.node;
9
+ return others.length === 0 &&
10
+ definition?.type === "Variable" &&
11
+ definition.parent?.type === "VariableDeclaration" &&
12
+ definition.parent.kind === "const" &&
13
+ declarator?.type === "VariableDeclarator"
14
+ ? declarator.init
15
+ : node;
16
+ }
17
+ function numericLiteral(sourceCode, argument) {
18
+ const node = constantValue(sourceCode, argument);
4
19
  return ((node?.type === "Literal" && typeof node.value === "number") ||
5
20
  (node?.type === "UnaryExpression" &&
6
21
  ["+", "-"].includes(node.operator) &&
@@ -64,7 +79,7 @@ function timerPromise(sourceCode, node) {
64
79
  const timer = singleExpression(executor.body);
65
80
  if (timer?.type !== "CallExpression" ||
66
81
  !globalTimer(sourceCode, timer.callee) ||
67
- !numericLiteral(timer.arguments[1])) {
82
+ !numericLiteral(sourceCode, timer.arguments[1])) {
68
83
  return false;
69
84
  }
70
85
  return resolvesWithoutValue(sourceCode, timer.arguments[0], resolve);
@@ -86,6 +101,26 @@ function playwrightPage(sourceCode, node, enclosing) {
86
101
  variableFor(sourceCode, binding) === variableFor(sourceCode, node));
87
102
  });
88
103
  }
104
+ function isFixedDuration(sourceCode, argument) {
105
+ const node = constantValue(sourceCode, argument);
106
+ if (node?.type === "Literal") {
107
+ return typeof node.value === "string" || typeof node.value === "number";
108
+ }
109
+ const owner = node?.type === "CallExpression" && node.callee.type === "MemberExpression"
110
+ ? node.callee.object
111
+ : null;
112
+ return (node?.type === "CallExpression" &&
113
+ owner !== null &&
114
+ isEffectModule(sourceCode, owner, "Duration") &&
115
+ node.arguments.length > 0 &&
116
+ node.arguments.every((item) => numericLiteral(sourceCode, item)));
117
+ }
118
+ function isEffectSleep(sourceCode, node) {
119
+ return (node.callee.type === "MemberExpression" &&
120
+ propertyName(node.callee) === "sleep" &&
121
+ isEffectModule(sourceCode, node.callee.object, "Effect") &&
122
+ isFixedDuration(sourceCode, node.arguments[0]));
123
+ }
89
124
  export const noFixedTestDelay = defineRule({
90
125
  meta: {
91
126
  type: "problem",
@@ -99,6 +134,12 @@ export const noFixedTestDelay = defineRule({
99
134
  const sourceCode = context.sourceCode;
100
135
  return {
101
136
  CallExpression(node) {
137
+ if (node.parent.type === "YieldExpression" &&
138
+ node.parent.delegate &&
139
+ isEffectSleep(sourceCode, node) &&
140
+ testContext(sourceCode, node)) {
141
+ context.report({ node, messageId: "delay" });
142
+ }
102
143
  if (node.parent.type !== "AwaitExpression") {
103
144
  return;
104
145
  }
@@ -109,10 +150,10 @@ export const noFixedTestDelay = defineRule({
109
150
  const api = apiReference(sourceCode, node.callee);
110
151
  const importedTimer = api?.name === "setTimeout" &&
111
152
  ["node:timers/promises", "timers/promises"].includes(api.module) &&
112
- numericLiteral(node.arguments[0]);
153
+ numericLiteral(sourceCode, node.arguments[0]);
113
154
  const browserTimer = node.callee.type === "MemberExpression" &&
114
155
  propertyName(node.callee) === "waitForTimeout" &&
115
- numericLiteral(node.arguments[0]) &&
156
+ numericLiteral(sourceCode, node.arguments[0]) &&
116
157
  playwrightPage(sourceCode, node.callee.object, enclosing);
117
158
  if (importedTimer || browserTimer) {
118
159
  context.report({ node, messageId: "delay" });
@@ -0,0 +1 @@
1
+ export declare const noGenericObjectGuard: import("@oxlint/plugins").Rule;
@@ -0,0 +1,279 @@
1
+ import { defineRule } from "@oxlint/plugins";
2
+ import { isGlobal, propertyName, unwrapExpression } from "../shared/index.js";
3
+ function unwrap(node) {
4
+ const value = unwrapExpression(node);
5
+ return value.type === "ChainExpression" ? unwrap(value.expression) : value;
6
+ }
7
+ function combined(checks) {
8
+ if (checks.includes(null)) {
9
+ return null;
10
+ }
11
+ if (checks.includes("object")) {
12
+ return "object";
13
+ }
14
+ return checks.includes("function") ? "function" : "supporting";
15
+ }
16
+ function isSubject(node, subject) {
17
+ const value = unwrap(node);
18
+ return value.type === "Identifier" && value.name === subject;
19
+ }
20
+ function isLiteral(node, value) {
21
+ const literal = unwrap(node);
22
+ return literal.type === "Literal" && literal.value === value;
23
+ }
24
+ function isUndefined(node) {
25
+ const value = unwrap(node);
26
+ return value.type === "Identifier" && value.name === "undefined";
27
+ }
28
+ function comparedPair(node, subject, matches) {
29
+ return ((isSubject(node.left, subject) && matches(node.right)) ||
30
+ (isSubject(node.right, subject) && matches(node.left)));
31
+ }
32
+ function isTypeofSubject(node, subject) {
33
+ const value = unwrap(node);
34
+ return value.type === "UnaryExpression" && value.operator === "typeof"
35
+ ? isSubject(value.argument, subject)
36
+ : false;
37
+ }
38
+ function typeofCheck(node, subject) {
39
+ const [typeofSide, literal] = isTypeofSubject(node.left, subject)
40
+ ? [node.left, node.right]
41
+ : [node.right, node.left];
42
+ if (!isTypeofSubject(typeofSide, subject) || !["===", "=="].includes(node.operator)) {
43
+ return null;
44
+ }
45
+ if (isLiteral(literal, "object")) {
46
+ return "object";
47
+ }
48
+ return isLiteral(literal, "function") ? "function" : null;
49
+ }
50
+ function isArrayCheck(sourceCode, node, subject) {
51
+ const call = unwrap(node);
52
+ return (call.type === "CallExpression" &&
53
+ call.callee.type === "MemberExpression" &&
54
+ propertyName(call.callee) === "isArray" &&
55
+ isGlobal(sourceCode, call.callee.object, "Array") &&
56
+ call.arguments.length === 1 &&
57
+ call.arguments[0] !== undefined &&
58
+ isSubject(call.arguments[0], subject));
59
+ }
60
+ function isPrototypeRead(sourceCode, node, subject) {
61
+ const call = unwrap(node);
62
+ return (call.type === "CallExpression" &&
63
+ call.callee.type === "MemberExpression" &&
64
+ propertyName(call.callee) === "getPrototypeOf" &&
65
+ isGlobal(sourceCode, call.callee.object, "Object") &&
66
+ call.arguments[0] !== undefined &&
67
+ isSubject(call.arguments[0], subject));
68
+ }
69
+ function isObjectPrototype(sourceCode, node) {
70
+ const value = unwrap(node);
71
+ return (value.type === "MemberExpression" &&
72
+ propertyName(value) === "prototype" &&
73
+ isGlobal(sourceCode, value.object, "Object"));
74
+ }
75
+ function isObjectTag(sourceCode, node, subject) {
76
+ const call = unwrap(node);
77
+ const tag = call.type === "CallExpression" && call.callee.type === "MemberExpression"
78
+ ? call.callee.object
79
+ : null;
80
+ return (call.type === "CallExpression" &&
81
+ call.callee.type === "MemberExpression" &&
82
+ propertyName(call.callee) === "call" &&
83
+ call.arguments[0] !== undefined &&
84
+ isSubject(call.arguments[0], subject) &&
85
+ tag?.type === "MemberExpression" &&
86
+ propertyName(tag) === "toString" &&
87
+ isObjectPrototype(sourceCode, tag.object));
88
+ }
89
+ function provesObject(sourceCode, value, expected, subject) {
90
+ return ((isPrototypeRead(sourceCode, value, subject) &&
91
+ (isLiteral(expected, null) || isObjectPrototype(sourceCode, expected))) ||
92
+ (isObjectTag(sourceCode, value, subject) && isLiteral(expected, "[object Object]")));
93
+ }
94
+ function equalityCheck(sourceCode, comparison, subject) {
95
+ const typeofResult = typeofCheck(comparison, subject);
96
+ if (typeofResult !== null) {
97
+ return typeofResult;
98
+ }
99
+ if (provesObject(sourceCode, comparison.left, comparison.right, subject) ||
100
+ provesObject(sourceCode, comparison.right, comparison.left, subject)) {
101
+ return "object";
102
+ }
103
+ return null;
104
+ }
105
+ function objectCheck(sourceCode, node, subject) {
106
+ const value = unwrap(node);
107
+ if (value.type === "LogicalExpression" && value.operator !== "??") {
108
+ return combined([
109
+ objectCheck(sourceCode, value.left, subject),
110
+ objectCheck(sourceCode, value.right, subject),
111
+ ]);
112
+ }
113
+ if (value.type === "UnaryExpression" && value.operator === "!") {
114
+ const argument = unwrap(value.argument);
115
+ if (isArrayCheck(sourceCode, argument, subject)) {
116
+ return "supporting";
117
+ }
118
+ return argument.type === "UnaryExpression" && argument.operator === "!"
119
+ ? objectCheck(sourceCode, argument.argument, subject)
120
+ : null;
121
+ }
122
+ if (isSubject(value, subject)) {
123
+ return "supporting";
124
+ }
125
+ if (value.type === "CallExpression" && isGlobal(sourceCode, value.callee, "Boolean")) {
126
+ const [argument] = value.arguments;
127
+ return argument !== undefined && isSubject(argument, subject) ? "supporting" : null;
128
+ }
129
+ if (value.type !== "BinaryExpression") {
130
+ return null;
131
+ }
132
+ if (value.operator === "instanceof") {
133
+ return isSubject(value.left, subject) && isGlobal(sourceCode, value.right, "Object")
134
+ ? "object"
135
+ : null;
136
+ }
137
+ if (["!==", "!="].includes(value.operator)) {
138
+ return comparedPair(value, subject, (other) => isLiteral(other, null) || isUndefined(other))
139
+ ? "supporting"
140
+ : null;
141
+ }
142
+ return ["===", "=="].includes(value.operator) ? equalityCheck(sourceCode, value, subject) : null;
143
+ }
144
+ function rejection(sourceCode, node, subject) {
145
+ const value = unwrap(node);
146
+ if (value.type === "LogicalExpression" && value.operator !== "??") {
147
+ return combined([
148
+ rejection(sourceCode, value.left, subject),
149
+ rejection(sourceCode, value.right, subject),
150
+ ]);
151
+ }
152
+ if (value.type === "UnaryExpression" && value.operator === "!") {
153
+ return objectCheck(sourceCode, value.argument, subject);
154
+ }
155
+ if (isArrayCheck(sourceCode, value, subject)) {
156
+ return "supporting";
157
+ }
158
+ if (value.type !== "BinaryExpression") {
159
+ return null;
160
+ }
161
+ if (["===", "=="].includes(value.operator)) {
162
+ return comparedPair(value, subject, (other) => isLiteral(other, null) || isUndefined(other))
163
+ ? "supporting"
164
+ : null;
165
+ }
166
+ return ["!==", "!="].includes(value.operator)
167
+ ? equalityCheck(sourceCode, { left: value.left, operator: "===", right: value.right }, subject)
168
+ : null;
169
+ }
170
+ function isNamedHelper(fn) {
171
+ const holder = fn.parent;
172
+ return (fn.type === "FunctionDeclaration" ||
173
+ (holder.type === "VariableDeclarator" && holder.init === fn) ||
174
+ (holder.type === "Property" && holder.value === fn) ||
175
+ (holder.type === "PropertyDefinition" && holder.value === fn) ||
176
+ holder.type === "MethodDefinition");
177
+ }
178
+ const openTypes = new Set(["TSUnknownKeyword", "TSAnyKeyword"]);
179
+ const weakClaims = new Set(["TSObjectKeyword", "TSUnknownKeyword", "TSAnyKeyword"]);
180
+ function isWeakClaim(claim) {
181
+ if (weakClaims.has(claim.type) ||
182
+ (claim.type === "TSTypeLiteral" && claim.members.length === 0)) {
183
+ return true;
184
+ }
185
+ const [argument] = claim.type === "TSTypeReference" ? (claim.typeArguments?.params ?? []) : [];
186
+ return (claim.type === "TSTypeReference" &&
187
+ claim.typeName.type === "Identifier" &&
188
+ claim.typeName.name === "NonNullable" &&
189
+ argument !== undefined &&
190
+ isWeakClaim(argument));
191
+ }
192
+ function unprovenClaim(sourceCode, fn, parameter) {
193
+ const predicate = fn.returnType?.typeAnnotation;
194
+ const parameterType = parameter.typeAnnotation?.typeAnnotation;
195
+ if (predicate?.type !== "TSTypePredicate" ||
196
+ predicate.parameterName.type !== "Identifier" ||
197
+ predicate.parameterName.name !== parameter.name ||
198
+ predicate.typeAnnotation === null ||
199
+ parameterType === undefined ||
200
+ !openTypes.has(parameterType.type)) {
201
+ return null;
202
+ }
203
+ const claim = predicate.typeAnnotation.typeAnnotation;
204
+ return isWeakClaim(claim) ? null : sourceCode.getText(claim);
205
+ }
206
+ function returnedCheck(sourceCode, node, subject) {
207
+ return isLiteral(node, true) ? "supporting" : objectCheck(sourceCode, node, subject);
208
+ }
209
+ function rejectedCheck(sourceCode, statement, subject) {
210
+ if (statement.type !== "IfStatement" || statement.alternate !== null) {
211
+ return null;
212
+ }
213
+ const consequent = statement.consequent;
214
+ const exit = consequent.type === "BlockStatement" && consequent.body.length === 1
215
+ ? consequent.body[0]
216
+ : consequent;
217
+ return exit?.type === "ReturnStatement" &&
218
+ exit.argument !== null &&
219
+ isLiteral(exit.argument, false)
220
+ ? rejection(sourceCode, statement.test, subject)
221
+ : null;
222
+ }
223
+ function guardProof(sourceCode, fn, subject) {
224
+ if (fn.body === null) {
225
+ return null;
226
+ }
227
+ if (fn.body.type !== "BlockStatement") {
228
+ return returnedCheck(sourceCode, fn.body, subject);
229
+ }
230
+ const statements = fn.body.body;
231
+ const last = statements.at(-1);
232
+ if (last?.type !== "ReturnStatement" || last.argument === null) {
233
+ return null;
234
+ }
235
+ return combined([
236
+ ...statements.slice(0, -1).map((statement) => rejectedCheck(sourceCode, statement, subject)),
237
+ returnedCheck(sourceCode, last.argument, subject),
238
+ ]);
239
+ }
240
+ export const noGenericObjectGuard = defineRule({
241
+ meta: {
242
+ type: "suggestion",
243
+ docs: {
244
+ description: "Disallow guards such as isRecord that only prove a value is a non-null object, and guards that claim a type they never check.",
245
+ },
246
+ schema: [],
247
+ messages: {
248
+ unproven: "This guard claims `{{subject}}` is `{{claim}}` but only checks that it exists. Check the fields the type promises, or parse the value with a schema.",
249
+ guard: "This guard only proves `{{subject}}` is an object, which says nothing about its fields. Parse the value with a schema where it enters, or narrow inline where only its object-ness matters.",
250
+ },
251
+ },
252
+ create(context) {
253
+ const sourceCode = context.sourceCode;
254
+ function check(fn) {
255
+ const [parameter, ...rest] = fn.params;
256
+ if (parameter?.type !== "Identifier" || rest.length > 0) {
257
+ return;
258
+ }
259
+ const proof = guardProof(sourceCode, fn, parameter.name);
260
+ if (proof === "object" && isNamedHelper(fn)) {
261
+ context.report({ node: fn, messageId: "guard", data: { subject: parameter.name } });
262
+ return;
263
+ }
264
+ const claim = unprovenClaim(sourceCode, fn, parameter);
265
+ if (claim !== null && proof === "supporting") {
266
+ context.report({
267
+ node: fn,
268
+ messageId: "unproven",
269
+ data: { subject: parameter.name, claim },
270
+ });
271
+ }
272
+ }
273
+ return {
274
+ ArrowFunctionExpression: check,
275
+ FunctionDeclaration: check,
276
+ FunctionExpression: check,
277
+ };
278
+ },
279
+ });
@@ -0,0 +1 @@
1
+ export declare const noGetPasswordForm: import("@oxlint/plugins").Rule;
@@ -0,0 +1,113 @@
1
+ import { defineRule } from "@oxlint/plugins";
2
+ const secretAutocomplete = new Set(["current-password", "new-password", "one-time-code"]);
3
+ function isOmitted(item) {
4
+ const value = item.value?.type === "JSXExpressionContainer" ? item.value.expression : null;
5
+ return ((value?.type === "Identifier" && value.name === "undefined") ||
6
+ (value?.type === "Literal" && value.value === null));
7
+ }
8
+ function attribute(element, name) {
9
+ return element.attributes.find((item) => item.type === "JSXAttribute" &&
10
+ item.name.type === "JSXIdentifier" &&
11
+ item.name.name === name &&
12
+ !isOmitted(item));
13
+ }
14
+ function stringValue(item) {
15
+ const value = item?.value?.type === "JSXExpressionContainer" ? item.value.expression : item?.value;
16
+ if (value?.type === "TemplateLiteral" && value.expressions.length === 0) {
17
+ return value.quasis[0]?.value.cooked ?? null;
18
+ }
19
+ return value?.type === "Literal" && typeof value.value === "string" ? value.value : null;
20
+ }
21
+ function literalValue(item) {
22
+ return stringValue(item)?.toLowerCase() ?? null;
23
+ }
24
+ function isElement(element, name) {
25
+ return element.name.type === "JSXIdentifier" && element.name.name === name;
26
+ }
27
+ function isSecretInput(element) {
28
+ return (isElement(element, "input") &&
29
+ (literalValue(attribute(element, "type")) === "password" ||
30
+ (literalValue(attribute(element, "autoComplete")) ?? "")
31
+ .split(/\s+/u)
32
+ .some((token) => secretAutocomplete.has(token))));
33
+ }
34
+ function isGetMethod(item) {
35
+ const method = literalValue(item);
36
+ return method !== null && method !== "post" && method !== "dialog";
37
+ }
38
+ function submitsByGet(element) {
39
+ const action = attribute(element, "action");
40
+ const method = attribute(element, "method");
41
+ return (!element.attributes.some((item) => item.type === "JSXSpreadAttribute") &&
42
+ (action === undefined || literalValue(action) !== null) &&
43
+ (method === undefined || isGetMethod(method)));
44
+ }
45
+ function submitsByGetOverride(element) {
46
+ const type = literalValue(attribute(element, "type"));
47
+ const method = attribute(element, "formMethod");
48
+ const submits = isElement(element, "button")
49
+ ? type !== "button" && type !== "reset"
50
+ : isElement(element, "input") && (type === "submit" || type === "image");
51
+ return submits && method !== undefined && isGetMethod(method);
52
+ }
53
+ function owningForms(control, forms) {
54
+ const target = attribute(control, "form");
55
+ if (target !== undefined) {
56
+ const id = stringValue(target);
57
+ return id === null || id === ""
58
+ ? []
59
+ : forms.filter((form) => stringValue(attribute(form.openingElement, "id")) === id);
60
+ }
61
+ for (let ancestor = control.parent.parent; ancestor !== null; ancestor = ancestor.parent) {
62
+ if (ancestor.type === "JSXElement" && isElement(ancestor.openingElement, "form")) {
63
+ return [ancestor];
64
+ }
65
+ }
66
+ return [];
67
+ }
68
+ export const noGetPasswordForm = defineRule({
69
+ meta: {
70
+ type: "problem",
71
+ docs: { description: "Require forms with password fields to submit by POST." },
72
+ schema: [],
73
+ messages: {
74
+ get: 'This form has a password field but submits by GET, so a submission before JavaScript loads puts the password in the URL. Add `method="post"` or pass a function `action`.',
75
+ override: 'This button submits a form with a password field by GET, so a submission before JavaScript loads puts the password in the URL. Remove `formMethod` or set it to "post".',
76
+ },
77
+ },
78
+ create(context) {
79
+ const forms = [];
80
+ const controls = [];
81
+ return {
82
+ JSXElement(node) {
83
+ if (isElement(node.openingElement, "form")) {
84
+ forms.push(node);
85
+ }
86
+ },
87
+ JSXOpeningElement(node) {
88
+ if (isElement(node, "input") || isElement(node, "button")) {
89
+ controls.push(node);
90
+ }
91
+ },
92
+ "Program:exit"() {
93
+ const owned = new Map();
94
+ for (const control of controls) {
95
+ for (const form of owningForms(control, forms)) {
96
+ owned.set(form, [...(owned.get(form) ?? []), control]);
97
+ }
98
+ }
99
+ for (const [form, items] of owned) {
100
+ if (!items.some(isSecretInput)) {
101
+ continue;
102
+ }
103
+ if (submitsByGet(form.openingElement)) {
104
+ context.report({ node: form.openingElement, messageId: "get" });
105
+ }
106
+ for (const submitter of items.filter(submitsByGetOverride)) {
107
+ context.report({ node: submitter, messageId: "override" });
108
+ }
109
+ }
110
+ },
111
+ };
112
+ },
113
+ });
@@ -0,0 +1 @@
1
+ export declare const noHardcodedCredential: import("@oxlint/plugins").Rule;
@@ -0,0 +1,45 @@
1
+ import { defineRule } from "@oxlint/plugins";
2
+ const credentials = [
3
+ [
4
+ "private key",
5
+ /-----BEGIN (?:RSA |EC |DSA |OPENSSH |ENCRYPTED )?PRIVATE KEY-----[\s\S]*?(?:-----END|$)/gu,
6
+ ],
7
+ ["AWS access key", /\b(?:AKIA|ASIA)[0-9A-Z]{16}\b/gu],
8
+ ["GitHub token", /\b(?:gh[pousr]_[A-Za-z0-9]{36,}|github_pat_[A-Za-z0-9_]{60,})\b/gu],
9
+ ["Stripe live key", /\b(?:sk|rk)_live_[0-9A-Za-z]{20,}\b/gu],
10
+ ["Slack token", /\bxox[abposr]-[0-9A-Za-z-]{10,}\b/gu],
11
+ ["Google API key", /\bAIza[0-9A-Za-z_-]{35}\b/gu],
12
+ ["Anthropic or OpenAI key", /\bsk-(?:ant-[a-z0-9]+-|proj-)[A-Za-z0-9_-]{32,}/gu],
13
+ ];
14
+ const placeholder = /example|fake|dummy|placeholder|redacted|x{6,}|abcdefgh|12345678/iu;
15
+ function credentialIn(text) {
16
+ return (credentials.find(([, pattern]) => [...text.matchAll(pattern)].some((match) => !placeholder.test(match[0])))?.[0] ?? null);
17
+ }
18
+ export const noHardcodedCredential = defineRule({
19
+ meta: {
20
+ type: "problem",
21
+ docs: { description: "Disallow credentials written into source code." },
22
+ schema: [],
23
+ messages: {
24
+ credential: "This looks like a real {{kind}}. Revoke it, then load the credential from the environment or a secret store.",
25
+ },
26
+ },
27
+ create(context) {
28
+ function check(node, text) {
29
+ const kind = credentialIn(text);
30
+ if (kind !== null) {
31
+ context.report({ node, messageId: "credential", data: { kind } });
32
+ }
33
+ }
34
+ return {
35
+ Literal(node) {
36
+ if (typeof node.value === "string") {
37
+ check(node, node.value);
38
+ }
39
+ },
40
+ TemplateElement(node) {
41
+ check(node, node.value.raw);
42
+ },
43
+ };
44
+ },
45
+ });
@@ -0,0 +1 @@
1
+ export declare const noImportTimeEffects: import("@oxlint/plugins").Rule;