@commonplaces/oxlint-plugin 0.0.0-stage → 0.1.1

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 (59) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +130 -2
  3. package/dist/alchemy.d.ts +2 -0
  4. package/dist/alchemy.js +196 -0
  5. package/dist/anti-slop/effect/index.d.ts +3 -0
  6. package/dist/anti-slop/effect/index.js +10 -0
  7. package/dist/anti-slop/effect/rules/no-service-constructor-imports.d.ts +2 -0
  8. package/dist/anti-slop/effect/rules/no-service-constructor-imports.js +45 -0
  9. package/dist/anti-slop/index.d.ts +3 -0
  10. package/dist/anti-slop/index.js +38 -0
  11. package/dist/anti-slop/rules/no-chained-type-assertions.d.ts +2 -0
  12. package/dist/anti-slop/rules/no-chained-type-assertions.js +57 -0
  13. package/dist/anti-slop/rules/no-conditional-empty-object-spread.d.ts +2 -0
  14. package/dist/anti-slop/rules/no-conditional-empty-object-spread.js +36 -0
  15. package/dist/anti-slop/rules/no-known-value-widening.d.ts +2 -0
  16. package/dist/anti-slop/rules/no-known-value-widening.js +171 -0
  17. package/dist/anti-slop/rules/no-module-mocking.d.ts +2 -0
  18. package/dist/anti-slop/rules/no-module-mocking.js +73 -0
  19. package/dist/anti-slop/rules/no-object-parameters.d.ts +2 -0
  20. package/dist/anti-slop/rules/no-object-parameters.js +86 -0
  21. package/dist/anti-slop/rules/no-reflect-apply.d.ts +2 -0
  22. package/dist/anti-slop/rules/no-reflect-apply.js +25 -0
  23. package/dist/anti-slop/rules/no-reflect-get.d.ts +2 -0
  24. package/dist/anti-slop/rules/no-reflect-get.js +25 -0
  25. package/dist/anti-slop/rules/no-runtime-typeof.d.ts +2 -0
  26. package/dist/anti-slop/rules/no-runtime-typeof.js +47 -0
  27. package/dist/anti-slop/rules/no-shape-in-symbol-names.d.ts +2 -0
  28. package/dist/anti-slop/rules/no-shape-in-symbol-names.js +33 -0
  29. package/dist/anti-slop/rules/no-unknown-parameters.d.ts +2 -0
  30. package/dist/anti-slop/rules/no-unknown-parameters.js +69 -0
  31. package/dist/anti-slop/rules/no-unknown-returns.d.ts +2 -0
  32. package/dist/anti-slop/rules/no-unknown-returns.js +77 -0
  33. package/dist/anti-slop/rules/no-unknown-type-aliases.d.ts +2 -0
  34. package/dist/anti-slop/rules/no-unknown-type-aliases.js +58 -0
  35. package/dist/anti-slop/rules/no-unsafe-dictionary-type.d.ts +2 -0
  36. package/dist/anti-slop/rules/no-unsafe-dictionary-type.js +111 -0
  37. package/dist/anti-slop/rules/no-widen-then-assert.d.ts +2 -0
  38. package/dist/anti-slop/rules/no-widen-then-assert.js +255 -0
  39. package/dist/anti-slop/rules/require-safety-comment-for-type-assertion.d.ts +2 -0
  40. package/dist/anti-slop/rules/require-safety-comment-for-type-assertion.js +51 -0
  41. package/dist/anti-slop/shared/dictionary-types.d.ts +21 -0
  42. package/dist/anti-slop/shared/dictionary-types.js +374 -0
  43. package/dist/anti-slop/shared/lexical-type-parameters.d.ts +3 -0
  44. package/dist/anti-slop/shared/lexical-type-parameters.js +25 -0
  45. package/dist/anti-slop/shared/reflect-method.d.ts +3 -0
  46. package/dist/anti-slop/shared/reflect-method.js +24 -0
  47. package/dist/bun.d.ts +9 -0
  48. package/dist/bun.js +30 -0
  49. package/dist/effect.d.ts +17 -0
  50. package/dist/effect.js +352 -0
  51. package/dist/index.d.ts +57 -0
  52. package/dist/index.js +62 -0
  53. package/dist/package.d.ts +9 -0
  54. package/dist/package.js +50 -0
  55. package/dist/strict-effect.d.ts +2 -0
  56. package/dist/strict-effect.js +2381 -0
  57. package/dist/syntax.d.ts +5 -0
  58. package/dist/syntax.js +26 -0
  59. package/package.json +77 -4
@@ -0,0 +1,171 @@
1
+ import { Array, Option } from "effect";
2
+ import { variableInScope } from "../../syntax.js";
3
+ import { defineRule } from "@oxlint/plugins";
4
+ import { classifyWideningTarget, createTypeEnvironment, isKnownEvidenceExpression, } from "../shared/dictionary-types.js";
5
+ function unwrapExpression(expression) {
6
+ const current = expression;
7
+ if (current.type === "ParenthesizedExpression" ||
8
+ current.type === "TSAsExpression" ||
9
+ current.type === "TSSatisfiesExpression" ||
10
+ current.type === "TSTypeAssertion" ||
11
+ current.type === "TSNonNullExpression") {
12
+ return unwrapExpression(current.expression);
13
+ }
14
+ return current;
15
+ }
16
+ function resolveVariable(sourceCode, identifier) {
17
+ return Option.getOrNull(variableInScope(sourceCode.getScope(identifier), identifier.name));
18
+ }
19
+ function variableDeclarator(variable) {
20
+ if (variable.defs.length !== 1)
21
+ return null;
22
+ const [definition] = variable.defs;
23
+ return definition?.type === "Variable" && definition.node.type === "VariableDeclarator"
24
+ ? definition.node
25
+ : null;
26
+ }
27
+ function isStableConstVariable(variable, declarator) {
28
+ return (declarator.parent.type === "VariableDeclaration" &&
29
+ declarator.parent.kind === "const" &&
30
+ Array.every(variable.references, (reference) => reference.init || !reference.isWrite()));
31
+ }
32
+ function hasKnownEvidence(sourceCode, expression, visitedVariables = []) {
33
+ if (isKnownEvidenceExpression(expression))
34
+ return true;
35
+ const unwrapped = unwrapExpression(expression);
36
+ if (unwrapped.type !== "Identifier")
37
+ return false;
38
+ const variable = resolveVariable(sourceCode, unwrapped);
39
+ if (variable === null || Array.some(visitedVariables, (previous) => previous === variable))
40
+ return false;
41
+ const declarator = variableDeclarator(variable);
42
+ if (declarator === null ||
43
+ declarator.init === null ||
44
+ !isStableConstVariable(variable, declarator)) {
45
+ return false;
46
+ }
47
+ return hasKnownEvidence(sourceCode, declarator.init, [...visitedVariables, variable]);
48
+ }
49
+ function annotationTarget(annotation, environment) {
50
+ return annotation === null || annotation === undefined
51
+ ? null
52
+ : classifyWideningTarget(annotation.typeAnnotation, environment);
53
+ }
54
+ function enclosingFunction(node) {
55
+ const current = node.parent;
56
+ if (current === null || current.type === "Program")
57
+ return null;
58
+ return current.type === "ArrowFunctionExpression" ||
59
+ current.type === "FunctionDeclaration" ||
60
+ current.type === "FunctionExpression"
61
+ ? current
62
+ : enclosingFunction(current);
63
+ }
64
+ function sourceKeyName(sourceCode, key) {
65
+ if (key.type === "Identifier" || key.type === "PrivateIdentifier")
66
+ return key.name;
67
+ if (key.type === "Literal")
68
+ return String(key.value);
69
+ return sourceCode.getText(key);
70
+ }
71
+ function functionName(sourceCode, owner) {
72
+ if (owner === null)
73
+ return "anonymous function";
74
+ if (owner.id !== null)
75
+ return owner.id.name;
76
+ const parent = owner.parent;
77
+ if (parent.type === "VariableDeclarator" && parent.id.type === "Identifier")
78
+ return parent.id.name;
79
+ if (parent.type === "MethodDefinition")
80
+ return sourceKeyName(sourceCode, parent.key);
81
+ return "anonymous function";
82
+ }
83
+ function isEmptyObjectExpression(expression) {
84
+ const unwrapped = unwrapExpression(expression);
85
+ return unwrapped.type === "ObjectExpression" && unwrapped.properties.length === 0;
86
+ }
87
+ function isDictionaryAccumulatorTarget(destination) {
88
+ return destination.kind === "open dictionary" || destination.kind === "generic container";
89
+ }
90
+ function hasParentAssertion(node) {
91
+ return node.parent?.type === "TSAsExpression" || node.parent?.type === "TSTypeAssertion";
92
+ }
93
+ /** Detect sound syntactic cases where a known value is explicitly widened and loses evidence. */
94
+ export const noKnownValueWideningRule = defineRule({
95
+ meta: {
96
+ type: "problem",
97
+ docs: {
98
+ description: "Disallow syntactically established values from flowing into explicitly broad or anonymous target types that discard useful evidence.",
99
+ },
100
+ messages: {
101
+ widening: "The explicit {{target}} type on {{subject}} discards known type evidence. Keep inference, validate with `satisfies`, or use a named owner contract.",
102
+ },
103
+ },
104
+ create(context) {
105
+ const environment = createTypeEnvironment(context.sourceCode.ast);
106
+ const reportFlow = (expression, destination, subject) => {
107
+ if (destination === null)
108
+ return;
109
+ if (isDictionaryAccumulatorTarget(destination) && isEmptyObjectExpression(expression)) {
110
+ return;
111
+ }
112
+ if (!hasKnownEvidence(context.sourceCode, expression))
113
+ return;
114
+ context.report({
115
+ node: expression,
116
+ messageId: "widening",
117
+ data: { subject, target: destination.kind },
118
+ });
119
+ };
120
+ const targetFromAnnotation = (annotation) => annotationTarget(annotation, environment);
121
+ return {
122
+ VariableDeclarator(node) {
123
+ if (node.init === null || node.id.type !== "Identifier")
124
+ return;
125
+ reportFlow(node.init, targetFromAnnotation(node.id.typeAnnotation), `binding \`${node.id.name}\``);
126
+ },
127
+ PropertyDefinition(node) {
128
+ if (node.value === null)
129
+ return;
130
+ reportFlow(node.value, targetFromAnnotation(node.typeAnnotation), `property \`${sourceKeyName(context.sourceCode, node.key)}\``);
131
+ },
132
+ AccessorProperty(node) {
133
+ if (node.value === null)
134
+ return;
135
+ reportFlow(node.value, targetFromAnnotation(node.typeAnnotation), `property \`${sourceKeyName(context.sourceCode, node.key)}\``);
136
+ },
137
+ AssignmentExpression(node) {
138
+ if (node.operator !== "=" || node.left.type !== "Identifier")
139
+ return;
140
+ const variable = resolveVariable(context.sourceCode, node.left);
141
+ if (variable === null)
142
+ return;
143
+ const declarator = variableDeclarator(variable);
144
+ if (declarator === null || declarator.id.type !== "Identifier")
145
+ return;
146
+ reportFlow(node.right, targetFromAnnotation(declarator.id.typeAnnotation), `binding \`${declarator.id.name}\``);
147
+ },
148
+ ReturnStatement(node) {
149
+ if (node.argument === null)
150
+ return;
151
+ const owner = enclosingFunction(node);
152
+ reportFlow(node.argument, targetFromAnnotation(owner?.returnType), `return value of \`${functionName(context.sourceCode, owner)}\``);
153
+ },
154
+ ArrowFunctionExpression(node) {
155
+ if (node.body.type === "BlockStatement")
156
+ return;
157
+ reportFlow(node.body, targetFromAnnotation(node.returnType), `return value of \`${functionName(context.sourceCode, node)}\``);
158
+ },
159
+ TSAsExpression(node) {
160
+ if (hasParentAssertion(node))
161
+ return;
162
+ reportFlow(node.expression, classifyWideningTarget(node.typeAnnotation, environment), "assertion");
163
+ },
164
+ TSTypeAssertion(node) {
165
+ if (hasParentAssertion(node))
166
+ return;
167
+ reportFlow(node.expression, classifyWideningTarget(node.typeAnnotation, environment), "assertion");
168
+ },
169
+ };
170
+ },
171
+ });
@@ -0,0 +1,2 @@
1
+ /** Ban test framework module mocking in favor of real dependency seams. */
2
+ export declare const noModuleMockingRule: import("@oxlint/plugins").Rule;
@@ -0,0 +1,73 @@
1
+ import { Array, Option } from "effect";
2
+ import { variableInScope } from "../../syntax.js";
3
+ import { defineRule } from "@oxlint/plugins";
4
+ const moduleMockMethods = ["doMock", "mock", "unstable_mockModule"];
5
+ function resolveVariable(sourceCode, identifier) {
6
+ return Option.getOrNull(variableInScope(sourceCode.getScope(identifier), identifier.name));
7
+ }
8
+ function importedName(node) {
9
+ if (node.type !== "ImportSpecifier")
10
+ return null;
11
+ return node.imported.type === "Identifier" ? node.imported.name : node.imported.value;
12
+ }
13
+ function isTestFrameworkObject(sourceCode, expression) {
14
+ if (expression.type !== "Identifier")
15
+ return false;
16
+ if ((expression.name === "vi" || expression.name === "jest") &&
17
+ sourceCode.isGlobalReference(expression)) {
18
+ return true;
19
+ }
20
+ const variable = resolveVariable(sourceCode, expression);
21
+ if (variable === null || variable.defs.length === 0) {
22
+ return expression.name === "vi" || expression.name === "jest";
23
+ }
24
+ return Array.some(variable.defs, (definition) => {
25
+ if (definition.type !== "ImportBinding" || definition.parent?.type !== "ImportDeclaration") {
26
+ return false;
27
+ }
28
+ const source = definition.parent.source.value;
29
+ const name = importedName(definition.node);
30
+ return ((source === "vitest" && name === "vi") || (source === "@jest/globals" && name === "jest"));
31
+ });
32
+ }
33
+ function moduleMockCall(sourceCode, callee) {
34
+ if (!("property" in callee) || !("object" in callee) || !("computed" in callee))
35
+ return false;
36
+ if (!isTestFrameworkObject(sourceCode, callee.object))
37
+ return false;
38
+ const property = callee.property;
39
+ const method = callee.computed
40
+ ? property.type === "Literal" &&
41
+ (property.value === "doMock" ||
42
+ property.value === "mock" ||
43
+ property.value === "unstable_mockModule")
44
+ ? property.value
45
+ : null
46
+ : property.type === "Identifier"
47
+ ? property.name
48
+ : null;
49
+ return method !== null && Array.contains(moduleMockMethods, method);
50
+ }
51
+ /** Ban test framework module mocking in favor of real dependency seams. */
52
+ export const noModuleMockingRule = defineRule({
53
+ meta: {
54
+ type: "problem",
55
+ docs: {
56
+ description: "Disallow Vitest and Jest module mocking; tests must replace dependencies through real interfaces.",
57
+ },
58
+ messages: {
59
+ moduleMock: "Replace module mocking with dependency injection through a real interface, service layer, or faithful test implementation.",
60
+ },
61
+ },
62
+ createOnce(context) {
63
+ return {
64
+ CallExpression(node) {
65
+ if (node.callee.type === "Super" || node.callee.type === "V8IntrinsicExpression")
66
+ return;
67
+ if (moduleMockCall(context.sourceCode, node.callee)) {
68
+ context.report({ node, messageId: "moduleMock" });
69
+ }
70
+ },
71
+ };
72
+ },
73
+ });
@@ -0,0 +1,2 @@
1
+ /** Ban the broad object type on function inputs, including local aliases to object. */
2
+ export declare const noObjectParametersRule: import("@oxlint/plugins").Rule;
@@ -0,0 +1,86 @@
1
+ import { Array, HashMap, Option } from "effect";
2
+ import { createTypeEnvironment } from "../shared/dictionary-types.js";
3
+ import { defineRule } from "@oxlint/plugins";
4
+ import { lexicalTypeParameterNames } from "../shared/lexical-type-parameters.js";
5
+ function parameterAnnotation(parameter) {
6
+ if (parameter.type === "TSParameterProperty") {
7
+ return parameterAnnotation(parameter.parameter);
8
+ }
9
+ if (parameter.type === "RestElement") {
10
+ return parameter.typeAnnotation ?? parameterAnnotation(parameter.argument);
11
+ }
12
+ if (parameter.type === "AssignmentPattern") {
13
+ return parameter.typeAnnotation ?? parameter.left.typeAnnotation;
14
+ }
15
+ return parameter.typeAnnotation;
16
+ }
17
+ function parameterName(parameter, sourceCode) {
18
+ return parameter.type === "Identifier"
19
+ ? parameter.name
20
+ : sourceCode.getText(parameter).replace(/\s*:\s*object\s*$/u, "");
21
+ }
22
+ /** Ban the broad object type on function inputs, including local aliases to object. */
23
+ export const noObjectParametersRule = defineRule({
24
+ meta: {
25
+ type: "problem",
26
+ docs: {
27
+ description: "Disallow object function parameters; inputs must use an owner-provided type and be parsed at their boundary.",
28
+ },
29
+ messages: {
30
+ objectParameter: "Parameter `{{parameter}}` uses the broad `object` type. Accept a named owner type; parse external input at its boundary before calling this function.",
31
+ },
32
+ },
33
+ create(context) {
34
+ const aliases = createTypeEnvironment(context.sourceCode.ast).aliases;
35
+ const resolvesToObject = (type, shadowedAliases, visited = []) => {
36
+ if (type.type === "TSObjectKeyword")
37
+ return true;
38
+ if (type.type === "TSParenthesizedType")
39
+ return resolvesToObject(type.typeAnnotation, shadowedAliases, visited);
40
+ if (type.type === "TSUnionType") {
41
+ return Array.some(type.types, (member) => resolvesToObject(member, shadowedAliases, visited));
42
+ }
43
+ if (type.type !== "TSTypeReference" ||
44
+ type.typeName.type !== "Identifier" ||
45
+ (type.typeArguments !== null &&
46
+ type.typeArguments !== undefined &&
47
+ type.typeArguments.params.length > 0) ||
48
+ Array.contains(visited, type.typeName.name) ||
49
+ Array.contains(shadowedAliases, type.typeName.name)) {
50
+ return false;
51
+ }
52
+ const alias = Option.getOrUndefined(HashMap.get(aliases, type.typeName.name));
53
+ if (alias === undefined || alias.typeParameters != null)
54
+ return false;
55
+ const nextVisited = [...visited, type.typeName.name];
56
+ return resolvesToObject(alias.typeAnnotation, shadowedAliases, nextVisited);
57
+ };
58
+ const checkParameters = (node) => {
59
+ const shadowedAliases = lexicalTypeParameterNames(node, context);
60
+ Array.forEach(node.params, (parameter) => {
61
+ const annotation = parameterAnnotation(parameter);
62
+ if (annotation === null || annotation === undefined)
63
+ return;
64
+ if (!resolvesToObject(annotation.typeAnnotation, shadowedAliases))
65
+ return;
66
+ context.report({
67
+ node: annotation.typeAnnotation,
68
+ messageId: "objectParameter",
69
+ data: { parameter: parameterName(parameter, context.sourceCode) },
70
+ });
71
+ });
72
+ };
73
+ return {
74
+ ArrowFunctionExpression: checkParameters,
75
+ FunctionDeclaration: checkParameters,
76
+ FunctionExpression: checkParameters,
77
+ TSCallSignatureDeclaration: checkParameters,
78
+ TSConstructSignatureDeclaration: checkParameters,
79
+ TSConstructorType: checkParameters,
80
+ TSDeclareFunction: checkParameters,
81
+ TSEmptyBodyFunctionExpression: checkParameters,
82
+ TSFunctionType: checkParameters,
83
+ TSMethodSignature: checkParameters,
84
+ };
85
+ },
86
+ });
@@ -0,0 +1,2 @@
1
+ /** Ban Reflect.apply, which bypasses ordinary typed function calls. */
2
+ export declare const noReflectApplyRule: import("@oxlint/plugins").Rule;
@@ -0,0 +1,25 @@
1
+ import { defineRule } from "@oxlint/plugins";
2
+ import { isGlobalReflectMethodCall } from "../shared/reflect-method.js";
3
+ /** Ban Reflect.apply, which bypasses ordinary typed function calls. */
4
+ export const noReflectApplyRule = defineRule({
5
+ meta: {
6
+ type: "problem",
7
+ docs: {
8
+ description: "Disallow Reflect.apply; call typed functions directly or model dynamic dispatch behind an interface.",
9
+ },
10
+ messages: {
11
+ reflectApply: "Replace `Reflect.apply` with a typed function call. Model dynamic dispatch behind a named interface.",
12
+ },
13
+ },
14
+ createOnce(context) {
15
+ return {
16
+ CallExpression(node) {
17
+ if (node.callee.type === "Super" || node.callee.type === "V8IntrinsicExpression")
18
+ return;
19
+ if (isGlobalReflectMethodCall(context.sourceCode, node.callee, "apply")) {
20
+ context.report({ node, messageId: "reflectApply" });
21
+ }
22
+ },
23
+ };
24
+ },
25
+ });
@@ -0,0 +1,2 @@
1
+ /** Ban Reflect.get, which bypasses ordinary property access and useful type evidence. */
2
+ export declare const noReflectGetRule: import("@oxlint/plugins").Rule;
@@ -0,0 +1,25 @@
1
+ import { defineRule } from "@oxlint/plugins";
2
+ import { isGlobalReflectMethodCall } from "../shared/reflect-method.js";
3
+ /** Ban Reflect.get, which bypasses ordinary property access and useful type evidence. */
4
+ export const noReflectGetRule = defineRule({
5
+ meta: {
6
+ type: "problem",
7
+ docs: {
8
+ description: "Disallow Reflect.get; use typed property access or parse dynamic input into a domain type.",
9
+ },
10
+ messages: {
11
+ reflectGet: "Replace `Reflect.get` with typed property access. Parse dynamic input into a named domain type before reading it.",
12
+ },
13
+ },
14
+ createOnce(context) {
15
+ return {
16
+ CallExpression(node) {
17
+ if (node.callee.type === "Super" || node.callee.type === "V8IntrinsicExpression")
18
+ return;
19
+ if (isGlobalReflectMethodCall(context.sourceCode, node.callee, "get")) {
20
+ context.report({ node, messageId: "reflectGet" });
21
+ }
22
+ },
23
+ };
24
+ },
25
+ });
@@ -0,0 +1,2 @@
1
+ /** Disallow runtime typeof checks that narrow unparsed values instead of decoding them. */
2
+ export declare const noRuntimeTypeofRule: import("@oxlint/plugins").Rule;
@@ -0,0 +1,47 @@
1
+ import { Array, Option, Schema } from "effect";
2
+ import { defineRule } from "@oxlint/plugins";
3
+ function isRuntimeFunction(node) {
4
+ return (node.type === "ArrowFunctionExpression" ||
5
+ node.type === "FunctionDeclaration" ||
6
+ node.type === "FunctionExpression");
7
+ }
8
+ function isInsideTypeGuard(node) {
9
+ const current = node.parent;
10
+ if (current === null || current.type === "Program")
11
+ return false;
12
+ return isRuntimeFunction(current)
13
+ ? current.returnType?.typeAnnotation.type === "TSTypePredicate"
14
+ : isInsideTypeGuard(current);
15
+ }
16
+ /** Disallow runtime typeof checks that narrow unparsed values instead of decoding them. */
17
+ export const noRuntimeTypeofRule = defineRule({
18
+ meta: {
19
+ type: "problem",
20
+ docs: {
21
+ description: "Disallow runtime typeof checks; external values must be decoded into meaningful types at their I/O boundary.",
22
+ },
23
+ messages: {
24
+ runtimeTypeof: "A `typeof` check narrows a representation without establishing its contract. Parse input at its I/O boundary, then branch on the domain value.",
25
+ },
26
+ schema: [
27
+ {
28
+ type: "object",
29
+ properties: {
30
+ allowInTypeGuards: { type: "boolean" },
31
+ },
32
+ additionalProperties: false,
33
+ },
34
+ ],
35
+ defaultOptions: [{ allowInTypeGuards: false }],
36
+ },
37
+ createOnce(context) {
38
+ return {
39
+ UnaryExpression(node) {
40
+ const allowInTypeGuards = Schema.is(Schema.Struct({ allowInTypeGuards: Schema.Literal(true) }))(Option.getOrUndefined(Array.head(context.options)));
41
+ if (node.operator === "typeof" && (!allowInTypeGuards || !isInsideTypeGuard(node))) {
42
+ context.report({ node, messageId: "runtimeTypeof" });
43
+ }
44
+ },
45
+ };
46
+ },
47
+ });
@@ -0,0 +1,2 @@
1
+ /** Ban the case-insensitive substring "shape" in every JavaScript and TypeScript symbol name. */
2
+ export declare const noForbiddenTermInSymbolNamesRule: import("@oxlint/plugins").Rule;
@@ -0,0 +1,33 @@
1
+ import { defineRule } from "@oxlint/plugins";
2
+ const FORBIDDEN_SYMBOL_NAME = "shape";
3
+ function containsForbiddenSymbolName(name) {
4
+ return name.toLowerCase().includes(FORBIDDEN_SYMBOL_NAME);
5
+ }
6
+ /** Ban the case-insensitive substring "shape" in every JavaScript and TypeScript symbol name. */
7
+ export const noForbiddenTermInSymbolNamesRule = defineRule({
8
+ meta: {
9
+ type: "problem",
10
+ docs: {
11
+ description: 'Disallow the case-insensitive substring "shape" in JavaScript, TypeScript, private, and JSX symbol names.',
12
+ },
13
+ messages: {
14
+ forbiddenSymbolName: 'Rename symbol "{{name}}" for its domain role; "shape" describes structure rather than ownership.',
15
+ },
16
+ },
17
+ createOnce(context) {
18
+ const reportForbiddenSymbolName = (node) => {
19
+ if (!containsForbiddenSymbolName(node.name))
20
+ return;
21
+ context.report({
22
+ node,
23
+ messageId: "forbiddenSymbolName",
24
+ data: { name: node.name },
25
+ });
26
+ };
27
+ return {
28
+ Identifier: reportForbiddenSymbolName,
29
+ PrivateIdentifier: reportForbiddenSymbolName,
30
+ JSXIdentifier: reportForbiddenSymbolName,
31
+ };
32
+ },
33
+ });
@@ -0,0 +1,2 @@
1
+ /** Disallow unknown inputs except explicitly named error-cause enrichment. */
2
+ export declare const noUnknownParametersRule: import("@oxlint/plugins").Rule;
@@ -0,0 +1,69 @@
1
+ import { Array } from "effect";
2
+ import { defineRule } from "@oxlint/plugins";
3
+ function parameterAnnotation(parameter) {
4
+ if (parameter.type === "TSParameterProperty") {
5
+ return parameterAnnotation(parameter.parameter);
6
+ }
7
+ if (parameter.type === "RestElement") {
8
+ return parameter.typeAnnotation ?? parameterAnnotation(parameter.argument);
9
+ }
10
+ if (parameter.type === "AssignmentPattern") {
11
+ return parameter.typeAnnotation ?? parameter.left.typeAnnotation;
12
+ }
13
+ return parameter.typeAnnotation;
14
+ }
15
+ function parameterName(parameter, sourceText) {
16
+ if (parameter.type === "TSParameterProperty") {
17
+ return parameterName(parameter.parameter, sourceText);
18
+ }
19
+ if (parameter.type === "AssignmentPattern") {
20
+ return parameterName(parameter.left, sourceText);
21
+ }
22
+ if (parameter.type === "RestElement") {
23
+ return parameterName(parameter.argument, sourceText);
24
+ }
25
+ return parameter.type === "Identifier"
26
+ ? parameter.name
27
+ : sourceText.replace(/\s*:\s*unknown\s*$/u, "");
28
+ }
29
+ /** Disallow unknown inputs except explicitly named error-cause enrichment. */
30
+ export const noUnknownParametersRule = defineRule({
31
+ meta: {
32
+ type: "problem",
33
+ docs: {
34
+ description: "Disallow explicitly unknown function parameters except `cause`; decode unknown input at its I/O boundary instead.",
35
+ },
36
+ messages: {
37
+ unknownParameter: "Parameter `{{parameter}}` leaves input unparsed. Accept a named domain type; run the expected schema or parser at the I/O boundary before calling this function.",
38
+ },
39
+ },
40
+ createOnce(context) {
41
+ const checkParameters = (node) => {
42
+ Array.forEach(node.params, (parameter) => {
43
+ const annotation = parameterAnnotation(parameter);
44
+ if (annotation?.typeAnnotation.type !== "TSUnknownKeyword")
45
+ return;
46
+ const name = parameterName(parameter, context.sourceCode.getText(parameter));
47
+ if (name === "cause")
48
+ return;
49
+ context.report({
50
+ node: annotation.typeAnnotation,
51
+ messageId: "unknownParameter",
52
+ data: { parameter: name },
53
+ });
54
+ });
55
+ };
56
+ return {
57
+ ArrowFunctionExpression: checkParameters,
58
+ FunctionDeclaration: checkParameters,
59
+ FunctionExpression: checkParameters,
60
+ TSCallSignatureDeclaration: checkParameters,
61
+ TSConstructSignatureDeclaration: checkParameters,
62
+ TSConstructorType: checkParameters,
63
+ TSDeclareFunction: checkParameters,
64
+ TSEmptyBodyFunctionExpression: checkParameters,
65
+ TSFunctionType: checkParameters,
66
+ TSMethodSignature: checkParameters,
67
+ };
68
+ },
69
+ });
@@ -0,0 +1,2 @@
1
+ /** Ban function contracts that return unknown instead of a parsed domain type. */
2
+ export declare const noUnknownReturnsRule: import("@oxlint/plugins").Rule;
@@ -0,0 +1,77 @@
1
+ import { Array, HashMap, Option } from "effect";
2
+ import { createTypeEnvironment } from "../shared/dictionary-types.js";
3
+ import { defineRule } from "@oxlint/plugins";
4
+ import { lexicalTypeParameterNames } from "../shared/lexical-type-parameters.js";
5
+ function referencedAliasName(type) {
6
+ if (type.type === "TSParenthesizedType")
7
+ return referencedAliasName(type.typeAnnotation);
8
+ if (type.type !== "TSTypeReference" || type.typeName.type !== "Identifier")
9
+ return null;
10
+ return type.typeArguments === null ||
11
+ type.typeArguments === undefined ||
12
+ type.typeArguments.params.length === 0
13
+ ? type.typeName.name
14
+ : null;
15
+ }
16
+ /** Ban function contracts that return unknown instead of a parsed domain type. */
17
+ export const noUnknownReturnsRule = defineRule({
18
+ meta: {
19
+ type: "problem",
20
+ docs: {
21
+ description: "Disallow functions whose explicit return contract is unknown or Promise<unknown>.",
22
+ },
23
+ messages: {
24
+ unknownReturn: "This function exposes `unknown` to its caller. Parse the value at its boundary and return a named domain type.",
25
+ },
26
+ },
27
+ create(context) {
28
+ const aliases = createTypeEnvironment(context.sourceCode.ast).aliases;
29
+ const resolvesToUnknown = (type, shadowedAliases, visited = []) => {
30
+ if (type.type === "TSUnknownKeyword")
31
+ return true;
32
+ if (type.type === "TSParenthesizedType") {
33
+ return resolvesToUnknown(type.typeAnnotation, shadowedAliases, visited);
34
+ }
35
+ if (type.type === "TSUnionType") {
36
+ return Array.some(type.types, (member) => resolvesToUnknown(member, shadowedAliases, visited));
37
+ }
38
+ if (type.type === "TSTypeReference" &&
39
+ type.typeName.type === "Identifier" &&
40
+ (type.typeName.name === "Promise" || type.typeName.name === "PromiseLike")) {
41
+ const value = Option.getOrUndefined(Array.head(type.typeArguments?.params ?? []));
42
+ return value !== undefined && resolvesToUnknown(value, shadowedAliases, visited);
43
+ }
44
+ const name = referencedAliasName(type);
45
+ if (name === null || Array.contains(visited, name) || Array.contains(shadowedAliases, name))
46
+ return false;
47
+ const alias = Option.getOrUndefined(HashMap.get(aliases, name));
48
+ if (alias === undefined ||
49
+ (alias.typeParameters !== null && alias.typeParameters !== undefined)) {
50
+ return false;
51
+ }
52
+ const nextVisited = [...visited, name];
53
+ return resolvesToUnknown(alias.typeAnnotation, shadowedAliases, nextVisited);
54
+ };
55
+ const checkReturnType = (node) => {
56
+ const annotation = node.returnType;
57
+ if (annotation === null || annotation === undefined)
58
+ return;
59
+ if (!resolvesToUnknown(annotation.typeAnnotation, lexicalTypeParameterNames(node, context))) {
60
+ return;
61
+ }
62
+ context.report({ node: annotation.typeAnnotation, messageId: "unknownReturn" });
63
+ };
64
+ return {
65
+ ArrowFunctionExpression: checkReturnType,
66
+ FunctionDeclaration: checkReturnType,
67
+ FunctionExpression: checkReturnType,
68
+ TSCallSignatureDeclaration: checkReturnType,
69
+ TSConstructSignatureDeclaration: checkReturnType,
70
+ TSConstructorType: checkReturnType,
71
+ TSDeclareFunction: checkReturnType,
72
+ TSEmptyBodyFunctionExpression: checkReturnType,
73
+ TSFunctionType: checkReturnType,
74
+ TSMethodSignature: checkReturnType,
75
+ };
76
+ },
77
+ });
@@ -0,0 +1,2 @@
1
+ /** Ban named aliases that merely conceal TypeScript's unknown top type. */
2
+ export declare const noUnknownTypeAliasesRule: import("@oxlint/plugins").Rule;