@commonplaces/oxlint-plugin 0.3.1 → 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.
@@ -0,0 +1,86 @@
1
+ import { defineRule } from "@oxlint/plugins";
2
+ import { Array, Option, Schema } from "effect";
3
+ import { operation } from "../native-reference.js";
4
+ import { propertyName } from "../syntax.js";
5
+ const Paths = Schema.Array(Schema.NonEmptyString);
6
+ const BrowserOptions = Schema.Struct({ runtimeOwners: Paths, registryOwners: Paths });
7
+ const ClientOptions = Schema.Struct({ browserOwners: Paths, serverOwners: Paths });
8
+ const pathsSchema = {
9
+ type: "array",
10
+ items: { type: "string", minLength: 1 },
11
+ uniqueItems: true,
12
+ };
13
+ const owns = (context, paths) => {
14
+ const filename = context.filename.replaceAll("\\", "/");
15
+ const cwd = context.cwd.replaceAll("\\", "/").replace(/\/$/u, "");
16
+ return Array.some(paths, (path) => filename === `${cwd}/${path}`);
17
+ };
18
+ export const browserRuntimeOwnership = defineRule({
19
+ meta: {
20
+ type: "problem",
21
+ schema: [
22
+ {
23
+ type: "object",
24
+ properties: { runtimeOwners: pathsSchema, registryOwners: pathsSchema },
25
+ required: ["runtimeOwners", "registryOwners"],
26
+ additionalProperties: false,
27
+ },
28
+ ],
29
+ messages: {
30
+ runtime: "Construct Atom.runtime only in its configured runtime owners.",
31
+ registry: "Mount RegistryProvider only in its configured registry owner.",
32
+ },
33
+ },
34
+ create(context) {
35
+ // Oxlint owns synchronous option validation; malformed permissions fail closed.
36
+ const options = Array.head(context.options).pipe(Option.flatMap(Schema.decodeUnknownOption(BrowserOptions)), Option.getOrElse(() => ({ runtimeOwners: [], registryOwners: [] })));
37
+ return {
38
+ CallExpression(node) {
39
+ if (operation(context, node.callee, "effect/reactivity/Atom", "runtime") &&
40
+ !owns(context, options.runtimeOwners))
41
+ context.report({ node, messageId: "runtime" });
42
+ },
43
+ ImportDeclaration(node) {
44
+ if (node.importKind === "type" ||
45
+ !node.source.value.startsWith("@effect/atom-react") ||
46
+ owns(context, options.registryOwners))
47
+ return;
48
+ if (Array.some(node.specifiers, (specifier) => specifier.type === "ImportNamespaceSpecifier" ||
49
+ (specifier.type === "ImportSpecifier" &&
50
+ specifier.importKind !== "type" &&
51
+ Option.contains(propertyName(specifier.imported), "RegistryProvider"))))
52
+ context.report({ node, messageId: "registry" });
53
+ },
54
+ };
55
+ },
56
+ });
57
+ export const nativeHttpClientOwnership = defineRule({
58
+ meta: {
59
+ type: "problem",
60
+ schema: [
61
+ {
62
+ type: "object",
63
+ properties: { browserOwners: pathsSchema, serverOwners: pathsSchema },
64
+ required: ["browserOwners", "serverOwners"],
65
+ additionalProperties: false,
66
+ },
67
+ ],
68
+ messages: {
69
+ browser: "Construct AtomHttpApi.Service only in its configured browser owner.",
70
+ server: "Construct HttpApiClient only in its configured binding adapter or response tests.",
71
+ },
72
+ },
73
+ create(context) {
74
+ const options = Array.head(context.options).pipe(Option.flatMap(Schema.decodeUnknownOption(ClientOptions)), Option.getOrElse(() => ({ browserOwners: [], serverOwners: [] })));
75
+ return {
76
+ CallExpression(node) {
77
+ if (operation(context, node.callee, "effect/reactivity/AtomHttpApi", "Service") &&
78
+ !owns(context, options.browserOwners))
79
+ context.report({ node, messageId: "browser" });
80
+ if (Array.some(["make", "makeWith"], (method) => operation(context, node.callee, "effect/http-api/HttpApiClient", method)) &&
81
+ !owns(context, options.serverOwners))
82
+ context.report({ node, messageId: "server" });
83
+ },
84
+ };
85
+ },
86
+ });
@@ -0,0 +1,11 @@
1
+ import type { Context, ESTree, Visitor } from "@oxlint/plugins";
2
+ import { Option } from "effect";
3
+ export declare const rule: (message: string, create: (context: Context) => Visitor) => import("@oxlint/plugins").Rule;
4
+ export declare const report: (context: Context, node: ESTree.Node) => void;
5
+ export declare const testFile: (filename: string) => boolean;
6
+ export declare const isFunction: (node: ESTree.Node) => node is ESTree.ArrowFunctionExpression | ESTree.Function;
7
+ export declare const contains: (context: Context, node: ESTree.Node, accepts: (node: ESTree.Node) => boolean) => boolean;
8
+ export declare const memberName: (context: Context, node: ESTree.Node, module: string) => Option.Option<string>;
9
+ export declare const returns: (node: ESTree.Node) => readonly ESTree.Node[];
10
+ export declare const effectCallback: (context: Context, node: ESTree.CallExpression) => Option.Option<ESTree.ArrowFunctionExpression | ESTree.Function>;
11
+ export declare const plainStringSchema: (context: Context, node: ESTree.Node, seen?: readonly ESTree.Node[]) => boolean;
@@ -0,0 +1,54 @@
1
+ import { defineRule } from "@oxlint/plugins";
2
+ import { Array, Option } from "effect";
3
+ import { childNodes, propertyName } from "../syntax.js";
4
+ import { referenceOf, valueOf } from "../native-reference.js";
5
+ export const rule = (message, create) => defineRule({ meta: { type: "problem", schema: [], messages: { policy: message } }, create });
6
+ export const report = (context, node) => context.report({ node, messageId: "policy" });
7
+ export const testFile = (filename) => /(?:\.(?:test|spec)\.[cm]?[jt]sx?$|\/(?:test|tests|testing)\/)/u.test(filename.replaceAll("\\", "/"));
8
+ export const isFunction = (node) => node.type === "ArrowFunctionExpression" ||
9
+ node.type === "FunctionExpression" ||
10
+ node.type === "FunctionDeclaration";
11
+ export const contains = (context, node, accepts) => accepts(node) ||
12
+ Array.some(childNodes(context, node), (child) => contains(context, child, accepts));
13
+ export const memberName = (context, node, module) => referenceOf(context, node).pipe(Option.filter((reference) => reference.module === module), Option.flatMap((reference) => reference.name));
14
+ export const returns = (node) => {
15
+ if (!isFunction(node) || node.body === null)
16
+ return [];
17
+ if (node.body.type !== "BlockStatement")
18
+ return [node.body];
19
+ return Array.flatMap(node.body.body, (statement) => statement.type === "ReturnStatement"
20
+ ? Option.toArray(Option.fromNullishOr(statement.argument))
21
+ : []);
22
+ };
23
+ export const effectCallback = (context, node) => {
24
+ const callee = node.callee.type === "CallExpression" ? node.callee.callee : node.callee;
25
+ const method = memberName(context, callee, "effect/Effect");
26
+ if (!Option.exists(method, (name) => name === "fn" || name === "fnUntraced"))
27
+ return Option.none();
28
+ return Array.head(node.arguments).pipe(Option.filter(isFunction));
29
+ };
30
+ // Brand discovery follows the Schema expression itself, never an unrelated
31
+ // callback's nested brand or a same-named, shadowed local object.
32
+ export const plainStringSchema = (context, node, seen = []) => {
33
+ if (Array.some(seen, (previous) => previous === node))
34
+ return false;
35
+ const next = [...seen, node];
36
+ const resolved = valueOf(context, node);
37
+ if (Option.isSome(resolved) && resolved.value !== node)
38
+ return plainStringSchema(context, resolved.value, next);
39
+ if (Option.exists(memberName(context, node, "effect/Schema"), (name) => name === "String" || name === "Trimmed"))
40
+ return true;
41
+ if (node.type !== "CallExpression")
42
+ return false;
43
+ const method = memberName(context, node.callee, "effect/Schema");
44
+ if (Option.exists(method, (name) => name === "Array" || name === "Option" || name === "optional" || name === "optionalKey")) {
45
+ return Array.head(node.arguments).pipe(Option.exists((argument) => plainStringSchema(context, argument, next)));
46
+ }
47
+ if (node.callee.type !== "MemberExpression")
48
+ return false;
49
+ if (!Option.exists(propertyName(node.callee.property), (name) => name === "pipe" || name === "check" || name === "make"))
50
+ return false;
51
+ const branded = Array.some(node.arguments, (argument) => argument.type === "CallExpression" &&
52
+ Option.contains(memberName(context, argument.callee, "effect/Schema"), "brand"));
53
+ return !branded && plainStringSchema(context, node.callee.object, next);
54
+ };
@@ -0,0 +1,14 @@
1
+ export declare const syntaxRules: {
2
+ "no-conditional-object-spread": import("@oxlint/plugins").Rule;
3
+ "no-in-operator": import("@oxlint/plugins").Rule;
4
+ "no-nullish-comparison": import("@oxlint/plugins").Rule;
5
+ "no-native-collections": import("@oxlint/plugins").Rule;
6
+ "curried-collections": import("@oxlint/plugins").Rule;
7
+ "no-wrapper-composition": import("@oxlint/plugins").Rule;
8
+ "no-nullable-boundary-leak": import("@oxlint/plugins").Rule;
9
+ "no-primitive-failure": import("@oxlint/plugins").Rule;
10
+ "require-try-promise-catch": import("@oxlint/plugins").Rule;
11
+ "no-defecting-layer": import("@oxlint/plugins").Rule;
12
+ "named-effect-operations": import("@oxlint/plugins").Rule;
13
+ "object-operation-input": import("@oxlint/plugins").Rule;
14
+ };
@@ -0,0 +1,172 @@
1
+ import { Array, Option } from "effect";
2
+ import { propertyName } from "../syntax.js";
3
+ import { operation } from "../native-reference.js";
4
+ import { effectCallback, isFunction, memberName, report, returns, rule, testFile, } from "./shared.js";
5
+ const primitive = (node) => node.type === "Literal" ||
6
+ (node.type === "Identifier" && node.name === "undefined") ||
7
+ (node.type === "TemplateLiteral" && node.expressions.length === 0) ||
8
+ (node.type === "UnaryExpression" && Array.contains(["void", "-", "+", "!"], node.operator));
9
+ export const syntaxRules = {
10
+ "no-conditional-object-spread": rule("Use Schema-owned optional fields instead of conditional object spreads.", (context) => ({
11
+ SpreadElement(node) {
12
+ if (node.parent.type === "ObjectExpression" &&
13
+ (node.argument.type === "ConditionalExpression" ||
14
+ node.argument.type === "LogicalExpression"))
15
+ report(context, node);
16
+ },
17
+ })),
18
+ "no-in-operator": rule("Use Schema or Effect Record/HashMap operations instead of probing keys with in.", (context) => ({
19
+ BinaryExpression(node) {
20
+ if (node.operator === "in")
21
+ report(context, node);
22
+ },
23
+ })),
24
+ "no-nullish-comparison": rule("Keep absence as Option and use its matching operations.", (context) => ({
25
+ BinaryExpression(node) {
26
+ if (Array.contains(["===", "!==", "==", "!="], node.operator) &&
27
+ Array.some([node.left, node.right], (side) => (side.type === "Literal" && side.value === null) ||
28
+ (side.type === "Identifier" && side.name === "undefined")))
29
+ report(context, node);
30
+ },
31
+ })),
32
+ "no-native-collections": rule("Use immutable Effect HashMap and HashSet instead of native mutable collections.", (context) => ({
33
+ NewExpression(node) {
34
+ if (node.callee.type === "Identifier" &&
35
+ Array.contains(["Map", "Set", "WeakMap", "WeakSet"], node.callee.name))
36
+ report(context, node);
37
+ },
38
+ })),
39
+ "curried-collections": rule("Use curried Effect collection operations so transformations remain pipeable.", (context) => ({
40
+ CallExpression(node) {
41
+ if (node.arguments.length < 2)
42
+ return;
43
+ if (Array.some(["Array", "Chunk", "HashMap", "HashSet", "Option", "Record"], (module) => memberName(context, node.callee, `effect/${module}`).pipe(Option.exists((name) => Array.contains([
44
+ "every",
45
+ "filter",
46
+ "findFirst",
47
+ "flatMap",
48
+ "forEach",
49
+ "map",
50
+ "partition",
51
+ "reduce",
52
+ "some",
53
+ "sort",
54
+ ], name)))))
55
+ report(context, node);
56
+ },
57
+ })),
58
+ "no-wrapper-composition": rule("Use a pipe for sequential transformations and keep one-use mapper callbacks inline.", (context) => ({
59
+ CallExpression(node) {
60
+ if (node.callee.type === "Identifier" &&
61
+ Array.some(node.arguments, (argument) => argument.type === "CallExpression"))
62
+ report(context, node);
63
+ const mapper = Option.exists(memberName(context, node.callee, "effect/Effect"), (name) => name === "map" || name === "mapError") ||
64
+ Option.exists(memberName(context, node.callee, "effect/rpc/Rpc"), (name) => name === "map" || name === "mapError") ||
65
+ Option.exists(memberName(context, node.callee, "effect/Schema"), (name) => /^(?:decode|encode)/u.test(name));
66
+ if (mapper)
67
+ Array.forEach(node.arguments, (argument) => {
68
+ if (argument.type === "Identifier" &&
69
+ /^(?:map|to|decode|encode|normalize)[A-Z]\w*$/u.test(argument.name))
70
+ report(context, argument);
71
+ });
72
+ },
73
+ })),
74
+ "no-nullable-boundary-leak": rule("Keep internal absence as Option; confine null to an owning external codec.", (context) => testFile(context.filename)
75
+ ? {}
76
+ : {
77
+ CallExpression(node) {
78
+ if (operation(context, node.callee, "effect/Option", "getOrNull") ||
79
+ operation(context, node.callee, "effect/Schema", "NullOr"))
80
+ report(context, node);
81
+ },
82
+ Property(node) {
83
+ if (node.value.type === "Literal" && node.value.value === null)
84
+ report(context, node.value);
85
+ },
86
+ }),
87
+ "no-primitive-failure": rule("Use an owner-named tagged error instead of a primitive Effect failure.", (context) => ({
88
+ CallExpression(node) {
89
+ const method = memberName(context, node.callee, "effect/Effect");
90
+ const argument = Array.head(node.arguments);
91
+ if (Option.isNone(argument))
92
+ return;
93
+ if ((Option.contains(method, "fail") && primitive(argument.value)) ||
94
+ (Option.exists(method, (name) => name === "failSync" || name === "mapError") &&
95
+ Array.some(returns(argument.value), primitive)))
96
+ report(context, node);
97
+ },
98
+ })),
99
+ "require-try-promise-catch": rule("Map Promise rejection explicitly with Effect.tryPromise({ try, catch }).", (context) => ({
100
+ CallExpression(node) {
101
+ if (!operation(context, node.callee, "effect/Effect", "tryPromise"))
102
+ return;
103
+ const argument = Array.head(node.arguments);
104
+ if (Option.isNone(argument) ||
105
+ argument.value.type !== "ObjectExpression" ||
106
+ !Array.every(["try", "catch"], (name) => argument.value.type === "ObjectExpression" &&
107
+ Array.some(argument.value.properties, (property) => property.type === "Property" && Option.contains(propertyName(property.key), name))))
108
+ report(context, node);
109
+ },
110
+ })),
111
+ "no-defecting-layer": rule("Preserve typed Layer construction failures instead of Layer.orDie.", (context) => ({
112
+ CallExpression(node) {
113
+ if (operation(context, node.callee, "effect/Layer", "orDie"))
114
+ report(context, node);
115
+ },
116
+ })),
117
+ "named-effect-operations": rule("Name reusable operations with Effect.fn or Effect.fnUntraced rather than returning Effect.gen from a plain function.", (context) => ({
118
+ VariableDeclarator(node) {
119
+ if (node.init &&
120
+ isFunction(node.init) &&
121
+ Array.some(returns(node.init), (value) => value.type === "CallExpression" &&
122
+ operation(context, value.callee, "effect/Effect", "gen")))
123
+ report(context, node.id);
124
+ },
125
+ ExportNamedDeclaration(node) {
126
+ if (node.declaration?.type === "FunctionDeclaration" &&
127
+ Array.some(returns(node.declaration), (value) => value.type === "CallExpression" &&
128
+ operation(context, value.callee, "effect/Effect", "gen")))
129
+ report(context, node.declaration);
130
+ },
131
+ })),
132
+ "object-operation-input": rule("Reusable Effect operations accept no arguments or one Schema-owned object input.", (context) => ({
133
+ ExportNamedDeclaration(node) {
134
+ if (!/(?:^|\/)actors\/[^/]+-(?:state|recovery)\.ts$/u.test(context.filename))
135
+ return;
136
+ const declaration = node.declaration;
137
+ if (declaration?.type === "FunctionDeclaration" && declaration.params.length > 1)
138
+ report(context, declaration);
139
+ if (declaration?.type === "VariableDeclaration")
140
+ Array.forEach(declaration.declarations, (value) => {
141
+ if (value.init && isFunction(value.init) && value.init.params.length > 1)
142
+ report(context, value.id);
143
+ });
144
+ },
145
+ CallExpression(node) {
146
+ effectCallback(context, node).pipe(Option.map((callback) => {
147
+ const parameter = Array.head(callback.params);
148
+ const type = parameter.pipe(Option.flatMap((value) => value.type === "TSParameterProperty"
149
+ ? Option.none()
150
+ : Option.fromNullishOr(value.typeAnnotation?.typeAnnotation)));
151
+ if (callback.params.length > 1 ||
152
+ parameter.pipe(Option.exists((value) => value.type === "RestElement")) ||
153
+ type.pipe(Option.exists((value) => Array.contains([
154
+ "TSArrayType",
155
+ "TSBigIntKeyword",
156
+ "TSBooleanKeyword",
157
+ "TSFunctionType",
158
+ "TSLiteralType",
159
+ "TSNeverKeyword",
160
+ "TSNullKeyword",
161
+ "TSNumberKeyword",
162
+ "TSStringKeyword",
163
+ "TSSymbolKeyword",
164
+ "TSTupleType",
165
+ "TSUndefinedKeyword",
166
+ "TSVoidKeyword",
167
+ ], value.type))))
168
+ report(context, node);
169
+ }));
170
+ },
171
+ })),
172
+ };
@@ -0,0 +1,2 @@
1
+ declare const _default: import("@oxlint/plugins").Plugin;
2
+ export default _default;
@@ -0,0 +1,19 @@
1
+ import { eslintCompatPlugin } from "@oxlint/plugins";
2
+ import { syntaxRules } from "./conventions/syntax.js";
3
+ import { contractRules } from "./conventions/contracts.js";
4
+ import { codecRules } from "./conventions/codecs.js";
5
+ import { lifetimeRules } from "./conventions/lifetimes.js";
6
+ import { browserRuntimeOwnership, nativeHttpClientOwnership } from "./conventions/ownership.js";
7
+ import { boundedBuildMemo } from "./conventions/bounded-build-memo.js";
8
+ export default eslintCompatPlugin({
9
+ meta: { name: "effect-conventions" },
10
+ rules: {
11
+ ...syntaxRules,
12
+ ...contractRules,
13
+ ...codecRules,
14
+ ...lifetimeRules,
15
+ "browser-runtime-ownership": browserRuntimeOwnership,
16
+ "native-http-client-ownership": nativeHttpClientOwnership,
17
+ "bounded-build-memo": boundedBuildMemo,
18
+ },
19
+ });
package/dist/effect.js CHANGED
@@ -1,6 +1,7 @@
1
+ import { requireServiceOperationInstrumentation } from "./service-instrumentation.js";
1
2
  import { defineRule } from "@oxlint/plugins";
2
- import { Array, Option, Predicate, Schema } from "effect";
3
- import { childNodes, propertyName, variableInScope } from "./syntax.js";
3
+ import { Array, Option, Schema } from "effect";
4
+ import { propertyName } from "./syntax.js";
4
5
  const memberExpressionName = (node) => {
5
6
  if (node?.type !== "MemberExpression" || node.object?.type !== "Identifier") {
6
7
  return Option.none();
@@ -215,7 +216,6 @@ const noManualTag = defineRule({
215
216
  },
216
217
  });
217
218
  const RuntimeOptions = Schema.Struct({ allowedFiles: Schema.Array(Schema.String) });
218
- const ServiceOptions = Schema.Struct({ serviceNames: Schema.Array(Schema.String) });
219
219
  const noRuntimeExecutionOutsideMain = defineRule({
220
220
  meta: {
221
221
  type: "problem",
@@ -253,89 +253,6 @@ const noRuntimeExecutionOutsideMain = defineRule({
253
253
  };
254
254
  },
255
255
  });
256
- const instrumentationName = (node) => {
257
- if (node.type !== "CallExpression" ||
258
- node.callee.type !== "MemberExpression" ||
259
- node.callee.object.type !== "Identifier" ||
260
- node.callee.object.name !== "Effect" ||
261
- !Option.exists(propertyName(node.callee.property), (name) => Array.contains(["fn", "withSpan"], name)))
262
- return Option.none();
263
- return Array.head(node.arguments).pipe(Option.flatMap((argument) => argument.type === "Literal" && Predicate.isString(argument.value)
264
- ? Option.some(argument.value)
265
- : Option.none()));
266
- };
267
- const containsInstrumentation = (context, node, name) => Option.contains(instrumentationName(node), name) ||
268
- Array.some(childNodes(context, node), (child) => containsInstrumentation(context, child, name));
269
- const operationValue = (context, node) => {
270
- if (node.type !== "Identifier")
271
- return Option.some(node);
272
- return variableInScope(context.sourceCode.getScope(node), node.name).pipe(Option.flatMap((variable) => Array.head(variable.defs)), Option.flatMap((definition) => definition.node.type === "VariableDeclarator"
273
- ? Option.fromNullishOr(definition.node.init)
274
- : Option.none()));
275
- };
276
- const requireServiceOperationInstrumentation = defineRule({
277
- meta: {
278
- type: "problem",
279
- schema: [
280
- {
281
- type: "object",
282
- properties: { serviceNames: { type: "array", items: { type: "string", minLength: 1 } } },
283
- required: ["serviceNames"],
284
- additionalProperties: false,
285
- },
286
- ],
287
- docs: {
288
- description: "Require exact tracing names on configured public Effect service operations.",
289
- },
290
- messages: {
291
- missingInstrumentation: "Instrument {{operation}} with Effect.fn or Effect.withSpan using that exact name.",
292
- unsupportedOperation: "Declare public Effect service operations as named object properties so tracing can be checked.",
293
- },
294
- },
295
- create(context) {
296
- // Oxlint requires a synchronous create callback; invalid options stop linting.
297
- const options = Schema.decodeUnknownSync(ServiceOptions)(Option.getOrUndefined(Array.head(context.options)));
298
- return {
299
- CallExpression(node) {
300
- if (node.callee.type !== "MemberExpression" ||
301
- node.callee.object.type !== "Identifier" ||
302
- !Array.contains(options.serviceNames, node.callee.object.name) ||
303
- !Option.contains(propertyName(node.callee.property), "of"))
304
- return;
305
- const service = node.callee.object.name;
306
- const operations = Array.head(node.arguments);
307
- Option.match(operations, {
308
- onNone: () => { },
309
- onSome: (argument) => {
310
- if (argument.type !== "ObjectExpression")
311
- return;
312
- Array.forEach(argument.properties, (operation) => {
313
- if (operation.type !== "Property") {
314
- context.report({ messageId: "unsupportedOperation", node: operation });
315
- return;
316
- }
317
- Option.match(propertyName(operation.key), {
318
- onNone: () => {
319
- context.report({ messageId: "unsupportedOperation", node: operation });
320
- },
321
- onSome: (name) => {
322
- const fullName = `${service}.${name}`;
323
- if (!Option.exists(operationValue(context, operation.value), (value) => containsInstrumentation(context, value, fullName))) {
324
- context.report({
325
- messageId: "missingInstrumentation",
326
- data: { operation: fullName },
327
- node: operation,
328
- });
329
- }
330
- },
331
- });
332
- });
333
- },
334
- });
335
- },
336
- };
337
- },
338
- });
339
256
  export default {
340
257
  meta: { name: "effect" },
341
258
  rules: {
package/dist/index.d.ts CHANGED
@@ -30,6 +30,7 @@ export declare const effect: {
30
30
  "effect/no-instanceof": "error";
31
31
  "effect/no-manual-tag": "error";
32
32
  "effect/no-raw-fetch": "error";
33
+ "effect/require-service-operation-instrumentation": "error";
33
34
  "strict-effect/static-layers": "error";
34
35
  "strict-effect/subprocess-pipe-capture": "error";
35
36
  "strict-effect/no-runtime-type-assertions": "error";
@@ -47,6 +48,41 @@ export declare const effect: {
47
48
  "anti-slop-effect/no-service-constructor-imports": "error";
48
49
  };
49
50
  };
51
+ /** Optional strict coding conventions; boundary permissions remain project facts. */
52
+ export declare const conventions: {
53
+ jsPlugins: string[];
54
+ rules: Record<`effect-conventions/${string}`, "error">;
55
+ };
56
+ /** Optional React/TanStack, MDX, semantic typography and route conventions. */
57
+ export declare const web: {
58
+ plugins: ("jsx-a11y" | "react" | "react-perf")[];
59
+ jsPlugins: string[];
60
+ rules: Record<`web/${string}`, "error">;
61
+ };
62
+ /** Native type-aware checks. Run Oxlint with --type-aware. */
63
+ export declare const typescript: {
64
+ plugins: ("import" | "typescript" | "unicorn" | "vitest")[];
65
+ categories: {
66
+ correctness: "error";
67
+ };
68
+ rules: {
69
+ "no-ternary": "error";
70
+ "typescript/consistent-type-assertions": ["error", {
71
+ assertionStyle: "never";
72
+ }];
73
+ "typescript/no-explicit-any": "error";
74
+ "typescript/no-non-null-assertion": "error";
75
+ "typescript/await-thenable": "error";
76
+ "typescript/no-floating-promises": "error";
77
+ "typescript/no-misused-promises": "error";
78
+ "typescript/no-unsafe-argument": "error";
79
+ "typescript/no-unsafe-assignment": "error";
80
+ "typescript/no-unsafe-call": "error";
81
+ "typescript/no-unsafe-member-access": "error";
82
+ "typescript/no-unsafe-return": "error";
83
+ "typescript/no-unsafe-type-assertion": "error";
84
+ };
85
+ };
50
86
  /** Native Alchemy configuration and secret-source checks. */
51
87
  export declare const alchemy: {
52
88
  jsPlugins: string[];
@@ -57,3 +93,90 @@ export declare const alchemy: {
57
93
  "strict-effect/native-cloudflare-cache": "error";
58
94
  };
59
95
  };
96
+ /** Complete strict Effect application profile; exact project owners stay local. */
97
+ export declare const strict: {
98
+ extends: ({
99
+ jsPlugins: string[];
100
+ rules: {
101
+ "anti-slop/no-chained-type-assertions": "error";
102
+ "anti-slop/no-conditional-empty-object-spread": "error";
103
+ "anti-slop/no-known-value-widening": "error";
104
+ "anti-slop/no-module-mocking": "error";
105
+ "anti-slop/no-object-parameters": "error";
106
+ "anti-slop/no-reflect-apply": "error";
107
+ "anti-slop/no-reflect-get": "error";
108
+ "anti-slop/no-runtime-typeof": "error";
109
+ "anti-slop/no-shape-in-symbol-names": "error";
110
+ "anti-slop/no-unknown-parameters": "error";
111
+ "anti-slop/no-unknown-returns": "error";
112
+ "anti-slop/no-unknown-type-aliases": "error";
113
+ "anti-slop/no-unsafe-dictionary-type": "error";
114
+ "anti-slop/no-widen-then-assert": "error";
115
+ "anti-slop/require-safety-comment-for-type-assertion": "error";
116
+ "package/no-cross-package-source-imports": "error";
117
+ };
118
+ } | {
119
+ jsPlugins: string[];
120
+ rules: {
121
+ "effect/no-ambient-process": "error";
122
+ "effect/no-async-await-promise": "error";
123
+ "effect/no-console": "error";
124
+ "effect/no-instanceof": "error";
125
+ "effect/no-manual-tag": "error";
126
+ "effect/no-raw-fetch": "error";
127
+ "effect/require-service-operation-instrumentation": "error";
128
+ "strict-effect/static-layers": "error";
129
+ "strict-effect/subprocess-pipe-capture": "error";
130
+ "strict-effect/no-runtime-type-assertions": "error";
131
+ "strict-effect/no-unchecked-index": "error";
132
+ "strict-effect/no-native-at": "error";
133
+ "strict-effect/tagged-error-name": "error";
134
+ "strict-effect/error-constructor-new": "error";
135
+ "strict-effect/no-promise-workflow": "error";
136
+ "strict-effect/no-unsafe-option-unwrap": "error";
137
+ "strict-effect/no-unchecked-json": "error";
138
+ "strict-effect/no-native-work": "error";
139
+ "strict-effect/no-imperative-collections": "error";
140
+ "strict-effect/no-native-client-replica": "error";
141
+ "bun/no-ambient-host-api": "error";
142
+ "anti-slop-effect/no-service-constructor-imports": "error";
143
+ };
144
+ } | {
145
+ jsPlugins: string[];
146
+ rules: Record<`effect-conventions/${string}`, "error">;
147
+ } | {
148
+ plugins: ("jsx-a11y" | "react" | "react-perf")[];
149
+ jsPlugins: string[];
150
+ rules: Record<`web/${string}`, "error">;
151
+ } | {
152
+ plugins: ("import" | "typescript" | "unicorn" | "vitest")[];
153
+ categories: {
154
+ correctness: "error";
155
+ };
156
+ rules: {
157
+ "no-ternary": "error";
158
+ "typescript/consistent-type-assertions": ["error", {
159
+ assertionStyle: "never";
160
+ }];
161
+ "typescript/no-explicit-any": "error";
162
+ "typescript/no-non-null-assertion": "error";
163
+ "typescript/await-thenable": "error";
164
+ "typescript/no-floating-promises": "error";
165
+ "typescript/no-misused-promises": "error";
166
+ "typescript/no-unsafe-argument": "error";
167
+ "typescript/no-unsafe-assignment": "error";
168
+ "typescript/no-unsafe-call": "error";
169
+ "typescript/no-unsafe-member-access": "error";
170
+ "typescript/no-unsafe-return": "error";
171
+ "typescript/no-unsafe-type-assertion": "error";
172
+ };
173
+ } | {
174
+ jsPlugins: string[];
175
+ rules: {
176
+ "alchemy-secrets/native-sources": "error";
177
+ "alchemy-secrets/explicit-stack-sources": "error";
178
+ "alchemy-secrets/no-doppler-wrapper": "error";
179
+ "strict-effect/native-cloudflare-cache": "error";
180
+ };
181
+ })[];
182
+ };