@agjs/tsforge 0.1.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/bin/tsforge.js +2 -0
- package/package.json +35 -0
- package/src/agent/agent.constants.ts +382 -0
- package/src/agent/agent.types.ts +34 -0
- package/src/agent/index.ts +4 -0
- package/src/agent/model-agent.ts +297 -0
- package/src/agent/tool-repair.ts +194 -0
- package/src/agent/tools.ts +190 -0
- package/src/browser/checks.ts +96 -0
- package/src/browser/index.ts +8 -0
- package/src/browser/oracle.ts +303 -0
- package/src/classify.ts +48 -0
- package/src/cli.ts +1333 -0
- package/src/config/config.constants.ts +9 -0
- package/src/config/flags.ts +32 -0
- package/src/config/index.ts +8 -0
- package/src/config/tsforge-config.ts +301 -0
- package/src/constitution/baseline.ts +257 -0
- package/src/detect-gate.ts +498 -0
- package/src/eval/eval.types.ts +36 -0
- package/src/eval/index.ts +3 -0
- package/src/eval/judge.ts +62 -0
- package/src/eval/score.ts +39 -0
- package/src/files/create.ts +22 -0
- package/src/files/edit.ts +193 -0
- package/src/files/files.constants.ts +11 -0
- package/src/files/files.types.ts +81 -0
- package/src/files/hashline-format.ts +110 -0
- package/src/files/hashline.ts +689 -0
- package/src/files/index.ts +19 -0
- package/src/index.ts +8 -0
- package/src/inference/index.ts +6 -0
- package/src/inference/inference.constants.ts +34 -0
- package/src/inference/inference.types.ts +123 -0
- package/src/inference/openai-compatible.ts +113 -0
- package/src/inference/stream-guard.ts +161 -0
- package/src/inference/stream.ts +370 -0
- package/src/inference/transport.ts +78 -0
- package/src/inference/wire.ts +0 -0
- package/src/lib/fs/fs.ts +126 -0
- package/src/lib/fs/fs.types.ts +5 -0
- package/src/lib/fs/index.ts +3 -0
- package/src/lib/fs/process.ts +146 -0
- package/src/lib/guards/guards.ts +9 -0
- package/src/lib/guards/index.ts +1 -0
- package/src/lib/json/index.ts +1 -0
- package/src/lib/json/json.ts +12 -0
- package/src/lib/scope/index.ts +2 -0
- package/src/lib/scope/scope.constants.ts +3 -0
- package/src/lib/scope/scope.ts +40 -0
- package/src/loop/astgrep-fix.ts +228 -0
- package/src/loop/feedback/feedback.ts +138 -0
- package/src/loop/feedback/index.ts +8 -0
- package/src/loop/feedback/meta-rule-docs.ts +41 -0
- package/src/loop/feedback/meta-rule-feedback.ts +61 -0
- package/src/loop/feedback/rule-docs.generated.json +112 -0
- package/src/loop/feedback/rule-docs.ts +342 -0
- package/src/loop/index.ts +19 -0
- package/src/loop/loop.constants.ts +68 -0
- package/src/loop/loop.types.ts +99 -0
- package/src/loop/prompt/index.ts +2 -0
- package/src/loop/prompt/project-map.ts +69 -0
- package/src/loop/prompt/prompt.ts +107 -0
- package/src/loop/quality.ts +174 -0
- package/src/loop/rule-docs.generated.json +367 -0
- package/src/loop/run-spec.ts +88 -0
- package/src/loop/run.ts +400 -0
- package/src/loop/session.ts +1410 -0
- package/src/loop/tools/add-dependency.ts +71 -0
- package/src/loop/tools/condense.ts +498 -0
- package/src/loop/tools/edit-hashline.ts +80 -0
- package/src/loop/tools/execute-tool.ts +80 -0
- package/src/loop/tools/file-ops.ts +323 -0
- package/src/loop/tools/index.ts +2 -0
- package/src/loop/tools/lsp-ops.ts +222 -0
- package/src/loop/tools/scaffold-routes.ts +68 -0
- package/src/loop/tools/scaffold-ui.ts +62 -0
- package/src/loop/tools/scaffold-web.ts +35 -0
- package/src/loop/tools/tool-context.ts +126 -0
- package/src/loop/ttsr-defaults.ts +53 -0
- package/src/loop/ttsr.ts +322 -0
- package/src/loop/turn.ts +856 -0
- package/src/lsp/index.ts +2 -0
- package/src/lsp/lsp.types.ts +56 -0
- package/src/lsp/service.ts +500 -0
- package/src/meta-rules/context.ts +195 -0
- package/src/meta-rules/index.ts +9 -0
- package/src/meta-rules/meta-rules.types.ts +47 -0
- package/src/meta-rules/parsers/package-json-parser.ts +51 -0
- package/src/meta-rules/registry.ts +37 -0
- package/src/meta-rules/rules/ci/workflow-actions-pinned.ts +59 -0
- package/src/meta-rules/rules/ci/workflow-runner-pinned.ts +57 -0
- package/src/meta-rules/rules/ci/workflow-timeout-required.ts +114 -0
- package/src/meta-rules/rules/config/tsconfig-paths-exist.ts +117 -0
- package/src/meta-rules/rules/config/tsconfig-strict.ts +91 -0
- package/src/meta-rules/rules/source-text/no-eslint-disable-comments.ts +34 -0
- package/src/meta-rules/rules/source-text/no-ts-suppressions.ts +38 -0
- package/src/meta-rules/rules/supply-chain/no-overlapping-libs.ts +57 -0
- package/src/meta-rules/rules/supply-chain/package-exact-deps.ts +55 -0
- package/src/meta-rules/rules/testing/test-sibling-required.ts +110 -0
- package/src/meta-rules/runner.ts +64 -0
- package/src/models-config.ts +196 -0
- package/src/render/ansi.ts +289 -0
- package/src/render/banner.ts +113 -0
- package/src/render/box.ts +134 -0
- package/src/render/index.ts +7 -0
- package/src/render/markdown.ts +123 -0
- package/src/render/render.types.ts +21 -0
- package/src/render/stream-markdown.ts +128 -0
- package/src/render/style.ts +26 -0
- package/src/rule-packs/bullmq/index.ts +39 -0
- package/src/rule-packs/bullmq/rules/index.ts +7 -0
- package/src/rule-packs/bullmq/rules/job-name-must-be-constant.ts +141 -0
- package/src/rule-packs/bullmq/rules/job-options-must-set-attempts.ts +174 -0
- package/src/rule-packs/bullmq/rules/no-blocking-concurrency-zero.ts +103 -0
- package/src/rule-packs/bullmq/rules/queue-options-must-set-removeoncomplete.ts +130 -0
- package/src/rule-packs/bullmq/rules/queue-options-must-set-removeonfail.ts +130 -0
- package/src/rule-packs/bullmq/rules/worker-must-implement-close.ts +182 -0
- package/src/rule-packs/bullmq/rules/worker-must-listen-failed.ts +140 -0
- package/src/rule-packs/bullmq/utils.ts +334 -0
- package/src/rule-packs/code-flow/index.ts +25 -0
- package/src/rule-packs/code-flow/rules/index.ts +3 -0
- package/src/rule-packs/code-flow/rules/no-bare-date-now.ts +138 -0
- package/src/rule-packs/code-flow/rules/no-template-trim-empty-ternary.ts +87 -0
- package/src/rule-packs/code-flow/rules/prefer-early-return.ts +80 -0
- package/src/rule-packs/code-flow/utils/prefer-early-return.ts +132 -0
- package/src/rule-packs/comment-hygiene/index.ts +25 -0
- package/src/rule-packs/comment-hygiene/rules/index.ts +3 -0
- package/src/rule-packs/comment-hygiene/rules/no-historical-comments.ts +102 -0
- package/src/rule-packs/comment-hygiene/rules/no-narration-comments.ts +83 -0
- package/src/rule-packs/comment-hygiene/rules/no-pr-reference-comments.ts +90 -0
- package/src/rule-packs/create-rule.ts +9 -0
- package/src/rule-packs/drizzle/index.ts +41 -0
- package/src/rule-packs/drizzle/rules/account-scoped-tables-require-where.ts +371 -0
- package/src/rule-packs/drizzle/rules/index.ts +8 -0
- package/src/rule-packs/drizzle/rules/no-nested-db-transaction.ts +127 -0
- package/src/rule-packs/drizzle/rules/no-raw-sql-outside-allowlist.ts +100 -0
- package/src/rule-packs/drizzle/rules/relations-must-cover-fks.ts +209 -0
- package/src/rule-packs/drizzle/rules/schema-files-must-not-import-driver.ts +127 -0
- package/src/rule-packs/drizzle/rules/schema-files-must-only-export-schema.ts +149 -0
- package/src/rule-packs/drizzle/rules/tables-must-have-timestamps.ts +312 -0
- package/src/rule-packs/drizzle/rules/timestamp-must-specify-mode.ts +166 -0
- package/src/rule-packs/drizzle/utils.ts +115 -0
- package/src/rule-packs/elysia/index.ts +43 -0
- package/src/rule-packs/elysia/rules/consistent-status-via-set.ts +69 -0
- package/src/rule-packs/elysia/rules/no-decorate-state-collision.ts +276 -0
- package/src/rule-packs/elysia/rules/no-separate-model-interfaces.ts +144 -0
- package/src/rule-packs/elysia/rules/prefer-destructured-context.ts +155 -0
- package/src/rule-packs/elysia/rules/prefer-direct-return.ts +176 -0
- package/src/rule-packs/elysia/rules/prefer-static-services.ts +159 -0
- package/src/rule-packs/elysia/rules/prefer-throw-status.ts +151 -0
- package/src/rule-packs/elysia/rules/require-hooks-before-routes.ts +209 -0
- package/src/rule-packs/elysia/rules/require-plugin-name.ts +107 -0
- package/src/rule-packs/elysia/utils/elysiaChain.ts +306 -0
- package/src/rule-packs/env-access/index.ts +23 -0
- package/src/rule-packs/env-access/rules/index.ts +2 -0
- package/src/rule-packs/env-access/rules/no-direct-process-env.ts +133 -0
- package/src/rule-packs/env-access/rules/no-process-exit.ts +95 -0
- package/src/rule-packs/i18n-keys/index.ts +19 -0
- package/src/rule-packs/i18n-keys/rules/static-translation-key-exists.ts +173 -0
- package/src/rule-packs/index.ts +139 -0
- package/src/rule-packs/jwt-cookies/index.ts +25 -0
- package/src/rule-packs/jwt-cookies/rules/auth-cookie-must-be-httponly.ts +150 -0
- package/src/rule-packs/jwt-cookies/rules/auth-cookie-must-be-secure-in-prod.ts +149 -0
- package/src/rule-packs/jwt-cookies/rules/bcrypt-rounds-min.ts +195 -0
- package/src/rule-packs/jwt-cookies/utils.ts +188 -0
- package/src/rule-packs/oauth-security/index.ts +25 -0
- package/src/rule-packs/oauth-security/rules/pkce-required-for-oidc.ts +296 -0
- package/src/rule-packs/oauth-security/rules/state-must-be-redis-backed.ts +193 -0
- package/src/rule-packs/oauth-security/rules/state-ttl-bounded.ts +219 -0
- package/src/rule-packs/oauth-security/utils.ts +127 -0
- package/src/rule-packs/react-component-architecture/index.ts +35 -0
- package/src/rule-packs/react-component-architecture/rules/component-folder-structure.ts +123 -0
- package/src/rule-packs/react-component-architecture/rules/forwardref-display-name.ts +93 -0
- package/src/rule-packs/react-component-architecture/rules/index-must-reexport-default.ts +123 -0
- package/src/rule-packs/react-component-architecture/rules/max-hooks-per-file.ts +122 -0
- package/src/rule-packs/react-component-architecture/rules/no-cross-feature-imports.ts +170 -0
- package/src/rule-packs/react-component-architecture/rules/no-inline-jsx-functions.ts +66 -0
- package/src/rule-packs/react-component-architecture/utils.ts +47 -0
- package/src/rule-packs/rule-packs.types.ts +18 -0
- package/src/rule-packs/structured-logging/index.ts +26 -0
- package/src/rule-packs/structured-logging/rules/mask-pii-fields.ts +221 -0
- package/src/rule-packs/structured-logging/rules/no-error-stringify.ts +217 -0
- package/src/rule-packs/structured-logging/rules/require-event-field.ts +136 -0
- package/src/rule-packs/structured-logging/utils/logger.ts +104 -0
- package/src/rule-packs/tanstack-query/index.ts +20 -0
- package/src/rule-packs/tanstack-query/rules/prefix-query-key-must-use-set-queries-data.ts +321 -0
- package/src/rule-packs/test-conventions/index.ts +23 -0
- package/src/rule-packs/test-conventions/rules/index.ts +2 -0
- package/src/rule-packs/test-conventions/rules/no-focused-tests.ts +170 -0
- package/src/rule-packs/test-conventions/rules/test-file-mirrors-source.ts +127 -0
- package/src/rule-packs/utils.ts +142 -0
- package/src/session-store.ts +359 -0
- package/src/spec/generate-tests.ts +213 -0
- package/src/spec/index.ts +5 -0
- package/src/spec/parse.ts +152 -0
- package/src/spec/review-tests.ts +162 -0
- package/src/spec/spec.constants.ts +13 -0
- package/src/spec/spec.types.ts +79 -0
- package/src/stack-detection/detect.ts +246 -0
- package/src/stack-detection/index.ts +3 -0
- package/src/stack-detection/packs.ts +174 -0
- package/src/stack-detection/stack-detection.types.ts +47 -0
- package/src/validate/accept.ts +49 -0
- package/src/validate/errors.ts +35 -0
- package/src/validate/index.ts +12 -0
- package/src/validate/parse.ts +148 -0
- package/src/validate/run-tests.ts +59 -0
- package/src/validate/validate.ts +40 -0
- package/src/validate/validate.types.ts +52 -0
- package/src/web-components.ts +638 -0
- package/src/web-coverage.ts +89 -0
- package/src/web-routes.ts +151 -0
- package/src/web-templates.ts +1011 -0
- package/strict.eslint.config.mjs +84 -0
- package/strict.web.eslint.config.mjs +185 -0
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
// tsforge's BUNDLED strict-TypeScript config — the toolchain it brings to ANY
|
|
2
|
+
// project to enforce idiomatic strict TS on the model's output, regardless of
|
|
3
|
+
// what (if anything) the target repo has configured. Deliberately SYNTACTIC-ONLY
|
|
4
|
+
// rules (no type-aware rules / no `parserOptions.project`) so it runs on any .ts
|
|
5
|
+
// without the target's deps or a compiling tsconfig. The type-aware floor (tsc
|
|
6
|
+
// strict + noUncheckedIndexedAccess, no-unsafe-*) is layered separately when a
|
|
7
|
+
// tsconfig is available. This is what lifts the local model: the casts / `any` /
|
|
8
|
+
// `!` / over-annotation it habitually emits become errors it must fix.
|
|
9
|
+
//
|
|
10
|
+
// Stack-aware rule packs are loaded via TSFORGE_PACKS env var (comma-separated
|
|
11
|
+
// pack IDs), allowing the gate to inject stack-specific rules dynamically.
|
|
12
|
+
// Rule overrides are loaded via TSFORGE_RULE_OVERRIDES env var (JSON-encoded
|
|
13
|
+
// map of bare rule names to "error" | "warn" | "off").
|
|
14
|
+
import tseslint from "typescript-eslint";
|
|
15
|
+
|
|
16
|
+
// Load stack-aware packs if TSFORGE_PACKS env var is set
|
|
17
|
+
let packConfig = [];
|
|
18
|
+
const packIds = (process.env.TSFORGE_PACKS ?? "").split(",").filter(Boolean);
|
|
19
|
+
let ruleOverrides = {};
|
|
20
|
+
|
|
21
|
+
if (process.env.TSFORGE_RULE_OVERRIDES !== undefined) {
|
|
22
|
+
try {
|
|
23
|
+
ruleOverrides = JSON.parse(process.env.TSFORGE_RULE_OVERRIDES);
|
|
24
|
+
if (typeof ruleOverrides !== "object" || ruleOverrides === null) {
|
|
25
|
+
ruleOverrides = {};
|
|
26
|
+
}
|
|
27
|
+
} catch {
|
|
28
|
+
// If parsing fails, silently ignore overrides
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (packIds.length > 0) {
|
|
33
|
+
try {
|
|
34
|
+
const { buildPackEslintConfig } = await import(
|
|
35
|
+
"./src/rule-packs/index.ts"
|
|
36
|
+
);
|
|
37
|
+
const { plugin, rules } = buildPackEslintConfig(packIds, ruleOverrides);
|
|
38
|
+
packConfig = [
|
|
39
|
+
{
|
|
40
|
+
files: ["**/*.ts", "**/*.tsx"],
|
|
41
|
+
plugins: { tsforge: plugin },
|
|
42
|
+
rules,
|
|
43
|
+
},
|
|
44
|
+
];
|
|
45
|
+
} catch {
|
|
46
|
+
// If pack loading fails, silently continue without them
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export default tseslint.config(
|
|
51
|
+
{ ignores: ["**/node_modules/**", "**/dist/**", "**/build/**"] },
|
|
52
|
+
{
|
|
53
|
+
files: ["**/*.ts", "**/*.tsx"],
|
|
54
|
+
languageOptions: { parser: tseslint.parser },
|
|
55
|
+
plugins: { "@typescript-eslint": tseslint.plugin },
|
|
56
|
+
rules: {
|
|
57
|
+
// The idioms the model habitually violates — all caught WITHOUT type info.
|
|
58
|
+
"@typescript-eslint/consistent-type-assertions": [
|
|
59
|
+
"error",
|
|
60
|
+
{ assertionStyle: "never" },
|
|
61
|
+
],
|
|
62
|
+
"@typescript-eslint/no-explicit-any": "error",
|
|
63
|
+
"@typescript-eslint/no-non-null-assertion": "error",
|
|
64
|
+
"@typescript-eslint/no-inferrable-types": "error",
|
|
65
|
+
"@typescript-eslint/naming-convention": [
|
|
66
|
+
"error",
|
|
67
|
+
{ selector: "interface", format: ["PascalCase"], prefix: ["I"] },
|
|
68
|
+
],
|
|
69
|
+
"prefer-const": "error",
|
|
70
|
+
"prefer-template": "error",
|
|
71
|
+
"no-var": "error",
|
|
72
|
+
eqeqeq: ["error", "always"],
|
|
73
|
+
curly: ["error", "all"],
|
|
74
|
+
"no-restricted-syntax": [
|
|
75
|
+
"error",
|
|
76
|
+
{
|
|
77
|
+
selector: "TSEnumDeclaration",
|
|
78
|
+
message: "Use 'as const' object literals instead of enums.",
|
|
79
|
+
},
|
|
80
|
+
],
|
|
81
|
+
},
|
|
82
|
+
},
|
|
83
|
+
...packConfig
|
|
84
|
+
);
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
// tsforge's bundled strict config for WEB stacks (React/Vue/Svelte via Vite).
|
|
2
|
+
// Like strict.eslint.config.mjs, it ENFORCES `I`-prefixed interfaces (project
|
|
3
|
+
// house style — `IIssue`, `IButtonProps`), with ONE exemption: library module-
|
|
4
|
+
// augmentation interfaces whose name the library dictates and you cannot rename
|
|
5
|
+
// (e.g. TanStack Router's `interface Register`). Differs from the core config in
|
|
6
|
+
// one other way: it allows `as const` (banning only value-changing `as`/`<Foo>`
|
|
7
|
+
// via AST selectors), since `as const` is idiomatic for typed literal registries.
|
|
8
|
+
//
|
|
9
|
+
// Stack-aware rule packs are loaded via TSFORGE_PACKS env var (comma-separated
|
|
10
|
+
// pack IDs), allowing the gate to inject stack-specific rules dynamically.
|
|
11
|
+
import tseslint from "typescript-eslint";
|
|
12
|
+
import stylistic from "@stylistic/eslint-plugin";
|
|
13
|
+
|
|
14
|
+
// Load stack-aware packs if TSFORGE_PACKS env var is set
|
|
15
|
+
let packConfig = [];
|
|
16
|
+
const packIds = (process.env.TSFORGE_PACKS ?? "").split(",").filter(Boolean);
|
|
17
|
+
if (packIds.length > 0) {
|
|
18
|
+
try {
|
|
19
|
+
const { buildPackEslintConfig } = await import(
|
|
20
|
+
"./src/rule-packs/index.ts"
|
|
21
|
+
);
|
|
22
|
+
const { plugin, rules } = buildPackEslintConfig(packIds);
|
|
23
|
+
packConfig = [
|
|
24
|
+
{
|
|
25
|
+
files: ["**/*.ts", "**/*.tsx"],
|
|
26
|
+
plugins: { tsforge: plugin },
|
|
27
|
+
rules,
|
|
28
|
+
},
|
|
29
|
+
];
|
|
30
|
+
} catch {
|
|
31
|
+
// If pack loading fails, silently continue without them
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Custom rule: ONE React component per .tsx file (boringstack). The classic
|
|
36
|
+
// eslint-plugin-react/no-multi-comp crashes on ESLint 10 and @eslint-react has no
|
|
37
|
+
// equivalent, so we enforce it directly. A "component" = a TOP-LEVEL PascalCase
|
|
38
|
+
// `function`, or a `const PascalCase = (…) => …` whose init is a function. This
|
|
39
|
+
// excludes TanStack's `const Route = createFileRoute(...)(...)` (init is a call,
|
|
40
|
+
// not a function), so a route file's `Route` + its page component is fine.
|
|
41
|
+
const oneComponentPerFile = {
|
|
42
|
+
meta: {
|
|
43
|
+
type: "suggestion",
|
|
44
|
+
messages: {
|
|
45
|
+
multi:
|
|
46
|
+
"One component per file (boringstack): '{{name}}' is a second component — move it to its own file.",
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
create(context) {
|
|
50
|
+
if (!context.filename.endsWith(".tsx")) {
|
|
51
|
+
return {};
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const isComponentName = (name) => /^[A-Z]/.test(name);
|
|
55
|
+
const isFn = (node) =>
|
|
56
|
+
node?.type === "ArrowFunctionExpression" ||
|
|
57
|
+
node?.type === "FunctionExpression";
|
|
58
|
+
|
|
59
|
+
return {
|
|
60
|
+
Program(program) {
|
|
61
|
+
const components = [];
|
|
62
|
+
|
|
63
|
+
for (const statement of program.body) {
|
|
64
|
+
const decl =
|
|
65
|
+
statement.type === "ExportNamedDeclaration" &&
|
|
66
|
+
statement.declaration !== null
|
|
67
|
+
? statement.declaration
|
|
68
|
+
: statement;
|
|
69
|
+
|
|
70
|
+
if (
|
|
71
|
+
decl.type === "FunctionDeclaration" &&
|
|
72
|
+
decl.id !== null &&
|
|
73
|
+
isComponentName(decl.id.name)
|
|
74
|
+
) {
|
|
75
|
+
components.push(decl.id);
|
|
76
|
+
} else if (decl.type === "VariableDeclaration") {
|
|
77
|
+
for (const d of decl.declarations) {
|
|
78
|
+
if (
|
|
79
|
+
d.id.type === "Identifier" &&
|
|
80
|
+
isComponentName(d.id.name) &&
|
|
81
|
+
isFn(d.init)
|
|
82
|
+
) {
|
|
83
|
+
components.push(d.id);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
for (const id of components.slice(1)) {
|
|
90
|
+
context.report({
|
|
91
|
+
node: id,
|
|
92
|
+
messageId: "multi",
|
|
93
|
+
data: { name: id.name },
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
},
|
|
97
|
+
};
|
|
98
|
+
},
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
export default tseslint.config(
|
|
102
|
+
{ ignores: ["**/node_modules/**", "**/dist/**", "**/build/**"] },
|
|
103
|
+
{
|
|
104
|
+
files: ["**/*.ts", "**/*.tsx"],
|
|
105
|
+
languageOptions: { parser: tseslint.parser },
|
|
106
|
+
plugins: {
|
|
107
|
+
"@typescript-eslint": tseslint.plugin,
|
|
108
|
+
"@stylistic": stylistic,
|
|
109
|
+
boringstack: { rules: { "one-component-per-file": oneComponentPerFile } },
|
|
110
|
+
...packConfig
|
|
111
|
+
.filter(
|
|
112
|
+
(cfg) => cfg.plugins !== undefined && cfg.plugins.tsforge !== undefined
|
|
113
|
+
)
|
|
114
|
+
.reduce((acc, cfg) => ({ ...acc, ...cfg.plugins }), {}),
|
|
115
|
+
},
|
|
116
|
+
rules: {
|
|
117
|
+
// NOTE: we do NOT use `consistent-type-assertions: never` here — that also
|
|
118
|
+
// bans `as const`, which is idiomatic and the cleanest escape for typed
|
|
119
|
+
// literal/tuple data (and it makes a fixed array a tuple, so literal-index
|
|
120
|
+
// access is defined, not `T | undefined`). Instead we ban only the
|
|
121
|
+
// value-changing forms (`x as Foo`, `<Foo>x`) via AST selectors below.
|
|
122
|
+
"@typescript-eslint/no-explicit-any": "error",
|
|
123
|
+
"@typescript-eslint/no-non-null-assertion": "error",
|
|
124
|
+
"@typescript-eslint/no-inferrable-types": "error",
|
|
125
|
+
// I-prefixed interfaces (house style), EXCEPT library-mandated augmentation
|
|
126
|
+
// names you cannot rename (TanStack Router's `interface Register`).
|
|
127
|
+
"@typescript-eslint/naming-convention": [
|
|
128
|
+
"error",
|
|
129
|
+
{
|
|
130
|
+
selector: "interface",
|
|
131
|
+
format: ["PascalCase"],
|
|
132
|
+
prefix: ["I"],
|
|
133
|
+
filter: { regex: "^(Register)$", match: false },
|
|
134
|
+
},
|
|
135
|
+
],
|
|
136
|
+
// ONE React component per .tsx file (boringstack). Enforced by the custom
|
|
137
|
+
// rule defined above — eslint-plugin-react/no-multi-comp crashes on ESLint 10
|
|
138
|
+
// and @eslint-react has no equivalent, so we ship our own.
|
|
139
|
+
"boringstack/one-component-per-file": "error",
|
|
140
|
+
"prefer-const": "error",
|
|
141
|
+
"prefer-template": "error",
|
|
142
|
+
"no-var": "error",
|
|
143
|
+
// Blank-line discipline (mirrors the core config) — the model rarely gets
|
|
144
|
+
// spacing right, so prettier --write + this rule's --fix make it free. Uses
|
|
145
|
+
// @stylistic (the rule's maintained home; the core rule is deprecated and
|
|
146
|
+
// spams `usedDeprecatedRules` into eslint's --format json gate output).
|
|
147
|
+
"@stylistic/padding-line-between-statements": [
|
|
148
|
+
"error",
|
|
149
|
+
{ blankLine: "always", prev: "import", next: "*" },
|
|
150
|
+
{ blankLine: "any", prev: "import", next: "import" },
|
|
151
|
+
{ blankLine: "always", prev: "*", next: "return" },
|
|
152
|
+
{ blankLine: "always", prev: "*", next: "throw" },
|
|
153
|
+
{ blankLine: "always", prev: ["const", "let", "var"], next: "*" },
|
|
154
|
+
{
|
|
155
|
+
blankLine: "any",
|
|
156
|
+
prev: ["const", "let", "var"],
|
|
157
|
+
next: ["const", "let", "var"],
|
|
158
|
+
},
|
|
159
|
+
{ blankLine: "always", prev: "block-like", next: "*" },
|
|
160
|
+
{ blankLine: "always", prev: "*", next: "block-like" },
|
|
161
|
+
],
|
|
162
|
+
eqeqeq: ["error", "always"],
|
|
163
|
+
curly: ["error", "all"],
|
|
164
|
+
"no-restricted-syntax": [
|
|
165
|
+
"error",
|
|
166
|
+
{
|
|
167
|
+
selector: "TSEnumDeclaration",
|
|
168
|
+
message: "Use 'as const' object literals instead of enums.",
|
|
169
|
+
},
|
|
170
|
+
{
|
|
171
|
+
selector: "TSAsExpression[typeAnnotation.typeName.name!='const']",
|
|
172
|
+
message:
|
|
173
|
+
"No `as` type casts — type it properly (annotate, narrow, or guard). `as const` is allowed.",
|
|
174
|
+
},
|
|
175
|
+
{
|
|
176
|
+
selector: "TSTypeAssertion",
|
|
177
|
+
message:
|
|
178
|
+
"No angle-bracket type assertions — type it properly. `as const` is allowed.",
|
|
179
|
+
},
|
|
180
|
+
],
|
|
181
|
+
...packConfig.reduce((acc, cfg) => ({ ...acc, ...(cfg.rules ?? {}) }), {}),
|
|
182
|
+
},
|
|
183
|
+
},
|
|
184
|
+
...packConfig
|
|
185
|
+
);
|