@webpieces/ai-hook-rules 0.3.227 → 0.3.229
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/package.json +2 -2
- package/src/core/custom-rule-adapter.d.ts +2 -1
- package/src/core/custom-rule-adapter.js.map +1 -1
- package/src/core/fix-hint.d.ts +42 -0
- package/src/core/fix-hint.js +60 -0
- package/src/core/fix-hint.js.map +1 -0
- package/src/core/report.js +21 -5
- package/src/core/report.js.map +1 -1
- package/src/core/rule-base.d.ts +4 -3
- package/src/core/rule-base.js.map +1 -1
- package/src/core/rules/branch-creation-guard.d.ts +2 -1
- package/src/core/rules/branch-creation-guard.js +8 -7
- package/src/core/rules/branch-creation-guard.js.map +1 -1
- package/src/core/rules/catch-error-pattern.d.ts +2 -1
- package/src/core/rules/catch-error-pattern.js +9 -7
- package/src/core/rules/catch-error-pattern.js.map +1 -1
- package/src/core/rules/feature-branch-guard.d.ts +2 -1
- package/src/core/rules/feature-branch-guard.js +6 -6
- package/src/core/rules/feature-branch-guard.js.map +1 -1
- package/src/core/rules/max-file-lines.d.ts +2 -1
- package/src/core/rules/max-file-lines.js +4 -4
- package/src/core/rules/max-file-lines.js.map +1 -1
- package/src/core/rules/merge-in-progress-guard.d.ts +2 -1
- package/src/core/rules/merge-in-progress-guard.js +8 -12
- package/src/core/rules/merge-in-progress-guard.js.map +1 -1
- package/src/core/rules/no-any-unknown.d.ts +2 -1
- package/src/core/rules/no-any-unknown.js +7 -6
- package/src/core/rules/no-any-unknown.js.map +1 -1
- package/src/core/rules/no-destructure.d.ts +2 -1
- package/src/core/rules/no-destructure.js +7 -6
- package/src/core/rules/no-destructure.js.map +1 -1
- package/src/core/rules/no-implicit-any.d.ts +2 -1
- package/src/core/rules/no-implicit-any.js +6 -5
- package/src/core/rules/no-implicit-any.js.map +1 -1
- package/src/core/rules/no-js-files.d.ts +2 -1
- package/src/core/rules/no-js-files.js +6 -5
- package/src/core/rules/no-js-files.js.map +1 -1
- package/src/core/rules/no-symbol-di-tokens.d.ts +2 -1
- package/src/core/rules/no-symbol-di-tokens.js +11 -8
- package/src/core/rules/no-symbol-di-tokens.js.map +1 -1
- package/src/core/rules/no-unmanaged-exceptions.d.ts +2 -1
- package/src/core/rules/no-unmanaged-exceptions.js +15 -13
- package/src/core/rules/no-unmanaged-exceptions.js.map +1 -1
- package/src/core/rules/pr-creation-guard.d.ts +2 -1
- package/src/core/rules/pr-creation-guard.js +8 -13
- package/src/core/rules/pr-creation-guard.js.map +1 -1
- package/src/core/rules/pr-merge-cleanup.d.ts +3 -1
- package/src/core/rules/pr-merge-cleanup.js +11 -11
- package/src/core/rules/pr-merge-cleanup.js.map +1 -1
- package/src/core/rules/redirect-how-to-merge-main.d.ts +2 -1
- package/src/core/rules/redirect-how-to-merge-main.js +3 -10
- package/src/core/rules/redirect-how-to-merge-main.js.map +1 -1
- package/src/core/rules/require-return-type.d.ts +2 -1
- package/src/core/rules/require-return-type.js +7 -6
- package/src/core/rules/require-return-type.js.map +1 -1
- package/src/core/rules/throw-cause-required.d.ts +2 -1
- package/src/core/rules/throw-cause-required.js +11 -8
- package/src/core/rules/throw-cause-required.js.map +1 -1
- package/src/core/rules/validate-ts-in-src.d.ts +2 -1
- package/src/core/rules/validate-ts-in-src.js +5 -4
- package/src/core/rules/validate-ts-in-src.js.map +1 -1
- package/src/core/runner.js +3 -3
- package/src/core/runner.js.map +1 -1
- package/src/core/types.d.ts +6 -5
- package/src/core/types.js +3 -0
- package/src/core/types.js.map +1 -1
- package/src/index.d.ts +1 -0
- package/src/index.js +6 -1
- package/src/index.js.map +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"feature-branch-guard.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/ai-hook-rules/src/core/rules/feature-branch-guard.ts"],"names":[],"mappings":"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","sourcesContent":["import { execSync } from 'child_process';\n\nimport {\n FeatureBranchGuardConfig,\n DEFAULT_HANG_TIMEOUT_MINUTES,\n readMainSyncStatus,\n squashRecoverySteps,\n MainSyncStatus,\n} from '@webpieces/rules-config';\n\nimport type { FileContext, Violation } from '../types';\nimport { Violation as V } from '../types';\nimport { FileRuleBase } from '../rule-base';\nimport { toError } from '../to-error';\nimport { triggerMainSyncRefresh } from '../main-sync-refresh';\nimport { logGuardDecision, GuardDecision } from '../decision-log';\n\n/**\n * Comprehensive \"are you on a proper feature branch?\" guard — the single rule that blocks edits when\n * the branch isn't a healthy place to work. Four states, in priority order:\n * 1. On main (checked SYNCHRONOUSLY here) → block: create a feature branch.\n * 2. Branch already merged into main (merged PR) → block: your work is in main, branch off fresh.\n * 3. No fork point with origin/main → block: squash onto a new branch.\n * 4. origin/main moved & touches your files → block: merge main first.\n * States 2–4 are PRECOMPUTED into `.webpieces/main-sync-status.json` by the detached refresher, so\n * this check does NO network git (only a fast local `git rev-parse` for state 1). On every call it\n * fire-and-forget spawns the refresher so the NEXT call is fresh. Runs in the GUARDS hook (it's a\n * hookGuard); file-scoped, so only Write/Edit/MultiEdit are guarded — Bash passes through so the AI\n * can still run `pnpm wp-start-upsert-pr` and the rest of the recovery flow.\n */\nexport class FeatureBranchGuardRule extends FileRuleBase<FeatureBranchGuardConfig> {\n constructor(config: FeatureBranchGuardConfig) { super(config, 'feature-branch-guard'); }\n\n readonly description = 'Block edits unless you are on a proper feature branch (not main, not already-merged, forked, in sync with main).';\n override readonly files = ['**/*'];\n override readonly defaultOptions = {\n branchNamingConvention: '{whoami}/{featurename}',\n hangTimeoutMinutes: DEFAULT_HANG_TIMEOUT_MINUTES,\n };\n readonly fixHint = [\n 'You must be on a clean, up-to-date feature branch to edit code.',\n 'On main → create a feature branch. Already merged → branch off fresh main.',\n 'main moved/conflicts → `pnpm wp-start-upsert-pr` (merge), `/wp-merge` (resolve), `pnpm wp-finish-upsert-pr`.',\n 'Disable in webpieces.config.json under feature-branch-guard (mode OFF) if intentional.',\n ];\n\n check(ctx: FileContext): readonly Violation[] {\n // Only files inside the workspace root — guard has no jurisdiction, nothing worth logging.\n if (ctx.relativePath.startsWith('..')) return [];\n\n const branch = this.currentBranch(ctx.workspaceRoot);\n // Can't determine branch (e.g. not a git repo) → don't block. Fail-open.\n if (branch === null) return this.allow(ctx, branch, 'branch-undeterminable (fail-open)');\n\n // State 1: on main — synchronous, no cache needed.\n if (branch === 'main') {\n return this.block(ctx, branch, 'on-main', this.onMainMessage());\n }\n\n // Keep the cache warm for the next call. Detached; never blocks this edit.\n triggerMainSyncRefresh(ctx.workspaceRoot, this.config.hangTimeoutMinutes ?? DEFAULT_HANG_TIMEOUT_MINUTES);\n\n const status = readMainSyncStatus(ctx.workspaceRoot);\n // No cache yet (first edit of the session) → allow; the refresh we just spawned populates it\n // for the next call. Fail-open: never block on missing data.\n if (status === null) return this.allow(ctx, branch, 'no-sync-cache (fail-open)', 'cache=none');\n\n const cache = this.cacheSummary(status);\n // Stale cross-branch cache: the cached status is for a DIFFERENT branch (e.g. you just\n // switched branches and the refresh for this one hasn't landed yet). Never block on another\n // branch's signals — fail open; the refresh we just spawned rewrites it for this branch.\n if (status.branch !== branch) return this.allow(ctx, branch, 'stale-cross-branch-cache (fail-open)', cache);\n\n // State 2: this feature branch was already merged into main.\n if (status.branchAlreadyMerged) {\n const pr = status.mergedPr !== '' ? status.mergedPr : '?';\n return this.block(ctx, branch, `already-merged PR#${pr}`, this.alreadyMergedMessage(branch, status.mergedPr), cache);\n }\n // State 3: no fork point — main was merged into the branch.\n if (!status.hasForkPoint) {\n return this.block(ctx, branch, 'no-fork-point', this.noForkPointMessage(branch), cache);\n }\n // State 4: origin/main moved and touches files you changed.\n if (status.conflict) {\n return this.block(ctx, branch, 'main-moved-conflict', this.conflictMessage(status.conflictFiles), cache);\n }\n return this.allow(ctx, branch, 'clean-feature-branch', cache);\n }\n\n // One-line summary of the async-written cache that drove this decision, for the SYNC log — so a\n // wrong allow/block is traceable to the exact (possibly stale) main-sync-status.json read.\n private cacheSummary(status: MainSyncStatus): string {\n const merged = status.branchAlreadyMerged ? `PR#${status.mergedPr !== '' ? status.mergedPr : '?'}` : 'no';\n return `cache=${status.branch} merged=${merged} fork=${String(status.hasForkPoint)} conflict=${String(status.conflict)} ts=${status.timestamp}`;\n }\n\n // Log + return for the allow path. Centralizes the decision-log call so every exit of check()\n // is recorded with its reason + the async cache it read (this is the audit trail for \"why didn't\n // the guard fire?\"). `cache` is the summary of the main-sync-status.json that drove the decision.\n private allow(ctx: FileContext, branch: string | null, reason: string, cache: string = '-'): readonly Violation[] {\n this.logDecision(ctx, branch, 'ALLOW', reason, cache);\n return [];\n }\n\n private block(ctx: FileContext, branch: string, reason: string, message: string, cache: string = '-'): readonly Violation[] {\n this.logDecision(ctx, branch, 'BLOCK', reason, cache);\n return [new V(1, ctx.relativePath, message)];\n }\n\n private logDecision(ctx: FileContext, branch: string | null, verdict: 'ALLOW' | 'BLOCK', reason: string, cache: string): void {\n logGuardDecision(\n ctx.workspaceRoot,\n new GuardDecision('feature-branch-guard', ctx.tool, ctx.relativePath, branch ?? 'unknown', verdict, reason, cache),\n );\n }\n\n private currentBranch(workspaceRoot: string): string | null {\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n return execSync('git rev-parse --abbrev-ref HEAD', {\n cwd: workspaceRoot,\n encoding: 'utf8',\n stdio: ['pipe', 'pipe', 'pipe'],\n }).trim();\n } catch (err: unknown) {\n const error = toError(err);\n void error;\n return null;\n }\n }\n\n private onMainMessage(): string {\n const convention = this.config.branchNamingConvention ?? '{whoami}/{featurename}';\n return [\n 'You should not be working on main.',\n 'Do a `git pull origin main` to get latest, then create a feature branch based on the naming convention.',\n `Branch naming convention (from webpieces.config.json): ${convention}`,\n 'Example: git checkout -b ' + convention.replace(/<[^>]+>/g, 'value'),\n ].join('\\n');\n }\n\n private alreadyMergedMessage(branch: string, mergedPr: string): string {\n const pr = mergedPr !== '' ? ` (merged PR #${mergedPr})` : '';\n return [\n `This feature branch \"${branch}\" was already merged into main${pr}.`,\n 'Your work is in main — do NOT keep editing this stale branch (you will reconflict with main).',\n 'Start fresh:',\n ' 1. git checkout main',\n ' 2. git pull',\n ' 3. git checkout -b <new-feature-branch>',\n ].join('\\n');\n }\n\n private conflictMessage(conflictFiles: readonly string[]): string {\n const files = conflictFiles.length > 0\n ? conflictFiles.map((f: string): string => ` - ${f}`).join('\\n')\n : ' (see git diff)';\n return [\n 'origin/main moved and touched files you also changed since your fork point:',\n files,\n '',\n 'Merge main in before editing further:',\n ' 1. pnpm wp-start-upsert-pr ← merges main, writes 3-point conflict context',\n ' 2. /wp-merge ← resolve each conflicted file',\n ' 3. pnpm wp-finish-upsert-pr ← validate, build, push, upsert the PR',\n ].join('\\n');\n }\n\n private noForkPointMessage(branch: string): string {\n return [\n 'No fork point with origin/main — main appears to have been merged into this branch,',\n 'so a clean squash-merge is impossible. A human must redo the work on a fresh branch:',\n '',\n ...squashRecoverySteps(branch),\n ].join('\\n');\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"feature-branch-guard.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/ai-hook-rules/src/core/rules/feature-branch-guard.ts"],"names":[],"mappings":"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","sourcesContent":["import { execSync } from 'child_process';\n\nimport {\n FeatureBranchGuardConfig,\n DEFAULT_HANG_TIMEOUT_MINUTES,\n readMainSyncStatus,\n squashRecoverySteps,\n MainSyncStatus,\n} from '@webpieces/rules-config';\n\nimport type { FileContext, Violation } from '../types';\nimport { Violation as V } from '../types';\nimport { FileRuleBase } from '../rule-base';\nimport { FixHint, Option } from '../fix-hint';\nimport { toError } from '../to-error';\nimport { triggerMainSyncRefresh } from '../main-sync-refresh';\nimport { logGuardDecision, GuardDecision } from '../decision-log';\n\n/**\n * Comprehensive \"are you on a proper feature branch?\" guard — the single rule that blocks edits when\n * the branch isn't a healthy place to work. Four states, in priority order:\n * 1. On main (checked SYNCHRONOUSLY here) → block: create a feature branch.\n * 2. Branch already merged into main (merged PR) → block: your work is in main, branch off fresh.\n * 3. No fork point with origin/main → block: squash onto a new branch.\n * 4. origin/main moved & touches your files → block: merge main first.\n * States 2–4 are PRECOMPUTED into `.webpieces/main-sync-status.json` by the detached refresher, so\n * this check does NO network git (only a fast local `git rev-parse` for state 1). On every call it\n * fire-and-forget spawns the refresher so the NEXT call is fresh. Runs in the GUARDS hook (it's a\n * hookGuard); file-scoped, so only Write/Edit/MultiEdit are guarded — Bash passes through so the AI\n * can still run `pnpm wp-start-upsert-pr` and the rest of the recovery flow.\n */\nexport class FeatureBranchGuardRule extends FileRuleBase<FeatureBranchGuardConfig> {\n constructor(config: FeatureBranchGuardConfig) { super(config, 'feature-branch-guard'); }\n\n readonly description = 'Block edits unless you are on a proper feature branch (not main, not already-merged, forked, in sync with main).';\n override readonly files = ['**/*'];\n override readonly defaultOptions = {\n branchNamingConvention: '{whoami}/{featurename}',\n hangTimeoutMinutes: DEFAULT_HANG_TIMEOUT_MINUTES,\n };\n readonly fixHint = new FixHint(\n 'You are not on a clean, up-to-date feature branch.',\n 'You must be on a clean, up-to-date feature branch to edit code. Pick one:',\n [\n new Option('On main → create a feature branch. Already merged → branch off fresh main.', true),\n new Option('main moved/conflicts → `pnpm wp-start-upsert-pr` (merge), `/wp-merge` (resolve), `pnpm wp-finish-upsert-pr`.'),\n new Option('Disable in webpieces.config.json under feature-branch-guard (mode OFF) if intentional.'),\n ],\n );\n\n check(ctx: FileContext): readonly Violation[] {\n // Only files inside the workspace root — guard has no jurisdiction, nothing worth logging.\n if (ctx.relativePath.startsWith('..')) return [];\n\n const branch = this.currentBranch(ctx.workspaceRoot);\n // Can't determine branch (e.g. not a git repo) → don't block. Fail-open.\n if (branch === null) return this.allow(ctx, branch, 'branch-undeterminable (fail-open)');\n\n // State 1: on main — synchronous, no cache needed.\n if (branch === 'main') {\n return this.block(ctx, branch, 'on-main', this.onMainMessage());\n }\n\n // Keep the cache warm for the next call. Detached; never blocks this edit.\n triggerMainSyncRefresh(ctx.workspaceRoot, this.config.hangTimeoutMinutes ?? DEFAULT_HANG_TIMEOUT_MINUTES);\n\n const status = readMainSyncStatus(ctx.workspaceRoot);\n // No cache yet (first edit of the session) → allow; the refresh we just spawned populates it\n // for the next call. Fail-open: never block on missing data.\n if (status === null) return this.allow(ctx, branch, 'no-sync-cache (fail-open)', 'cache=none');\n\n const cache = this.cacheSummary(status);\n // Stale cross-branch cache: the cached status is for a DIFFERENT branch (e.g. you just\n // switched branches and the refresh for this one hasn't landed yet). Never block on another\n // branch's signals — fail open; the refresh we just spawned rewrites it for this branch.\n if (status.branch !== branch) return this.allow(ctx, branch, 'stale-cross-branch-cache (fail-open)', cache);\n\n // State 2: this feature branch was already merged into main.\n if (status.branchAlreadyMerged) {\n const pr = status.mergedPr !== '' ? status.mergedPr : '?';\n return this.block(ctx, branch, `already-merged PR#${pr}`, this.alreadyMergedMessage(branch, status.mergedPr), cache);\n }\n // State 3: no fork point — main was merged into the branch.\n if (!status.hasForkPoint) {\n return this.block(ctx, branch, 'no-fork-point', this.noForkPointMessage(branch), cache);\n }\n // State 4: origin/main moved and touches files you changed.\n if (status.conflict) {\n return this.block(ctx, branch, 'main-moved-conflict', this.conflictMessage(status.conflictFiles), cache);\n }\n return this.allow(ctx, branch, 'clean-feature-branch', cache);\n }\n\n // One-line summary of the async-written cache that drove this decision, for the SYNC log — so a\n // wrong allow/block is traceable to the exact (possibly stale) main-sync-status.json read.\n private cacheSummary(status: MainSyncStatus): string {\n const merged = status.branchAlreadyMerged ? `PR#${status.mergedPr !== '' ? status.mergedPr : '?'}` : 'no';\n return `cache=${status.branch} merged=${merged} fork=${String(status.hasForkPoint)} conflict=${String(status.conflict)} ts=${status.timestamp}`;\n }\n\n // Log + return for the allow path. Centralizes the decision-log call so every exit of check()\n // is recorded with its reason + the async cache it read (this is the audit trail for \"why didn't\n // the guard fire?\"). `cache` is the summary of the main-sync-status.json that drove the decision.\n private allow(ctx: FileContext, branch: string | null, reason: string, cache: string = '-'): readonly Violation[] {\n this.logDecision(ctx, branch, 'ALLOW', reason, cache);\n return [];\n }\n\n private block(ctx: FileContext, branch: string, reason: string, message: string, cache: string = '-'): readonly Violation[] {\n this.logDecision(ctx, branch, 'BLOCK', reason, cache);\n return [new V(1, ctx.relativePath, message)];\n }\n\n private logDecision(ctx: FileContext, branch: string | null, verdict: 'ALLOW' | 'BLOCK', reason: string, cache: string): void {\n logGuardDecision(\n ctx.workspaceRoot,\n new GuardDecision('feature-branch-guard', ctx.tool, ctx.relativePath, branch ?? 'unknown', verdict, reason, cache),\n );\n }\n\n private currentBranch(workspaceRoot: string): string | null {\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n return execSync('git rev-parse --abbrev-ref HEAD', {\n cwd: workspaceRoot,\n encoding: 'utf8',\n stdio: ['pipe', 'pipe', 'pipe'],\n }).trim();\n } catch (err: unknown) {\n const error = toError(err);\n void error;\n return null;\n }\n }\n\n private onMainMessage(): string {\n const convention = this.config.branchNamingConvention ?? '{whoami}/{featurename}';\n return [\n 'You should not be working on main.',\n 'Do a `git pull origin main` to get latest, then create a feature branch based on the naming convention.',\n `Branch naming convention (from webpieces.config.json): ${convention}`,\n 'Example: git checkout -b ' + convention.replace(/<[^>]+>/g, 'value'),\n ].join('\\n');\n }\n\n private alreadyMergedMessage(branch: string, mergedPr: string): string {\n const pr = mergedPr !== '' ? ` (merged PR #${mergedPr})` : '';\n return [\n `This feature branch \"${branch}\" was already merged into main${pr}.`,\n 'Your work is in main — do NOT keep editing this stale branch (you will reconflict with main).',\n 'Start fresh:',\n ' 1. git checkout main',\n ' 2. git pull',\n ' 3. git checkout -b <new-feature-branch>',\n ].join('\\n');\n }\n\n private conflictMessage(conflictFiles: readonly string[]): string {\n const files = conflictFiles.length > 0\n ? conflictFiles.map((f: string): string => ` - ${f}`).join('\\n')\n : ' (see git diff)';\n return [\n 'origin/main moved and touched files you also changed since your fork point:',\n files,\n '',\n 'Merge main in before editing further:',\n ' 1. pnpm wp-start-upsert-pr ← merges main, writes 3-point conflict context',\n ' 2. /wp-merge ← resolve each conflicted file',\n ' 3. pnpm wp-finish-upsert-pr ← validate, build, push, upsert the PR',\n ].join('\\n');\n }\n\n private noForkPointMessage(branch: string): string {\n return [\n 'No fork point with origin/main — main appears to have been merged into this branch,',\n 'so a clean squash-merge is impossible. A human must redo the work on a fresh branch:',\n '',\n ...squashRecoverySteps(branch),\n ].join('\\n');\n }\n}\n"]}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { MaxFileLinesConfig } from '@webpieces/rules-config';
|
|
2
2
|
import type { FileContext, Violation } from '../types';
|
|
3
3
|
import { FileRuleBase } from '../rule-base';
|
|
4
|
+
import { FixHint } from '../fix-hint';
|
|
4
5
|
export declare class MaxFileLinesRule extends FileRuleBase<MaxFileLinesConfig> {
|
|
5
6
|
constructor(config: MaxFileLinesConfig);
|
|
6
7
|
readonly description = "Cap file length at a configured line limit.";
|
|
@@ -8,6 +9,6 @@ export declare class MaxFileLinesRule extends FileRuleBase<MaxFileLinesConfig> {
|
|
|
8
9
|
readonly defaultOptions: {
|
|
9
10
|
limit: number;
|
|
10
11
|
};
|
|
11
|
-
|
|
12
|
+
get fixHint(): FixHint;
|
|
12
13
|
check(ctx: FileContext): readonly Violation[];
|
|
13
14
|
}
|
|
@@ -4,6 +4,7 @@ exports.MaxFileLinesRule = void 0;
|
|
|
4
4
|
const rules_config_1 = require("@webpieces/rules-config");
|
|
5
5
|
const types_1 = require("../types");
|
|
6
6
|
const rule_base_1 = require("../rule-base");
|
|
7
|
+
const fix_hint_1 = require("../fix-hint");
|
|
7
8
|
const DEFAULT_LIMIT = 900;
|
|
8
9
|
const INSTRUCT_FILE = 'webpieces.filesize.md';
|
|
9
10
|
class MaxFileLinesRule extends rule_base_1.FileRuleBase {
|
|
@@ -11,10 +12,9 @@ class MaxFileLinesRule extends rule_base_1.FileRuleBase {
|
|
|
11
12
|
description = 'Cap file length at a configured line limit.';
|
|
12
13
|
files = ['**/*.ts', '**/*.tsx'];
|
|
13
14
|
defaultOptions = { limit: DEFAULT_LIMIT };
|
|
14
|
-
fixHint
|
|
15
|
-
'READ .webpieces/instruct-ai/webpieces.filesize.md
|
|
16
|
-
|
|
17
|
-
];
|
|
15
|
+
get fixHint() {
|
|
16
|
+
return new fix_hint_1.FixHint('File exceeds the max-file-lines limit.', 'READ .webpieces/instruct-ai/webpieces.filesize.md and refactor to reduce the file size.', [], new fix_hint_1.DisableEscape(this.config.disableAllowed ?? true, '// eslint-disable-next-line @webpieces/max-file-lines (also suppresses the eslint rule)'));
|
|
17
|
+
}
|
|
18
18
|
check(ctx) {
|
|
19
19
|
const limit = this.config.limit ?? DEFAULT_LIMIT;
|
|
20
20
|
if (ctx.projectedFileLines <= limit)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"max-file-lines.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/ai-hook-rules/src/core/rules/max-file-lines.ts"],"names":[],"mappings":";;;AAAA,0DAAqF;AAGrF,oCAA0C;AAC1C,4CAA4C;
|
|
1
|
+
{"version":3,"file":"max-file-lines.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/ai-hook-rules/src/core/rules/max-file-lines.ts"],"names":[],"mappings":";;;AAAA,0DAAqF;AAGrF,oCAA0C;AAC1C,4CAA4C;AAC5C,0CAAqD;AAErD,MAAM,aAAa,GAAG,GAAG,CAAC;AAC1B,MAAM,aAAa,GAAG,uBAAuB,CAAC;AAE9C,MAAa,gBAAiB,SAAQ,wBAAgC;IAClE,YAAY,MAA0B,IAAI,KAAK,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAAC,CAAC,CAAC;IAEnE,WAAW,GAAG,6CAA6C,CAAC;IACnD,KAAK,GAAG,CAAC,SAAS,EAAE,UAAU,CAAC,CAAC;IAChC,cAAc,GAAG,EAAE,KAAK,EAAE,aAAa,EAAE,CAAC;IAC5D,IAAI,OAAO;QACP,OAAO,IAAI,kBAAO,CACd,wCAAwC,EACxC,yFAAyF,EACzF,EAAE,EACF,IAAI,wBAAa,CAAC,IAAI,CAAC,MAAM,CAAC,cAAc,IAAI,IAAI,EAAE,0FAA0F,CAAC,CACpJ,CAAC;IACN,CAAC;IAED,KAAK,CAAC,GAAgB;QAClB,MAAM,KAAK,GAAG,IAAI,CAAC,MAAM,CAAC,KAAK,IAAI,aAAa,CAAC;QACjD,IAAI,GAAG,CAAC,kBAAkB,IAAI,KAAK;YAAE,OAAO,EAAE,CAAC;QAC/C,IAAA,qCAAsB,EAAC,GAAG,CAAC,aAAa,EAAE,aAAa,CAAC,CAAC;QACzD,OAAO,CAAC,IAAI,iBAAC,CACT,CAAC,EACD,cAAc,MAAM,CAAC,GAAG,CAAC,kBAAkB,CAAC,SAAS,EACrD,gBAAgB,MAAM,CAAC,GAAG,CAAC,kBAAkB,CAAC,yBAAyB,MAAM,CAAC,KAAK,CAAC,sGAAsG,CAC7L,CAAC,CAAC;IACP,CAAC;CACJ;AAzBD,4CAyBC","sourcesContent":["import { MaxFileLinesConfig, writeTemplateIfMissing } from '@webpieces/rules-config';\n\nimport type { FileContext, Violation } from '../types';\nimport { Violation as V } from '../types';\nimport { FileRuleBase } from '../rule-base';\nimport { FixHint, DisableEscape } from '../fix-hint';\n\nconst DEFAULT_LIMIT = 900;\nconst INSTRUCT_FILE = 'webpieces.filesize.md';\n\nexport class MaxFileLinesRule extends FileRuleBase<MaxFileLinesConfig> {\n constructor(config: MaxFileLinesConfig) { super(config, 'max-file-lines'); }\n\n readonly description = 'Cap file length at a configured line limit.';\n override readonly files = ['**/*.ts', '**/*.tsx'];\n override readonly defaultOptions = { limit: DEFAULT_LIMIT };\n get fixHint(): FixHint {\n return new FixHint(\n 'File exceeds the max-file-lines limit.',\n 'READ .webpieces/instruct-ai/webpieces.filesize.md and refactor to reduce the file size.',\n [],\n new DisableEscape(this.config.disableAllowed ?? true, '// eslint-disable-next-line @webpieces/max-file-lines (also suppresses the eslint rule)'),\n );\n }\n\n check(ctx: FileContext): readonly Violation[] {\n const limit = this.config.limit ?? DEFAULT_LIMIT;\n if (ctx.projectedFileLines <= limit) return [];\n writeTemplateIfMissing(ctx.workspaceRoot, INSTRUCT_FILE);\n return [new V(\n 1,\n `(projected ${String(ctx.projectedFileLines)} lines)`,\n `File will be ${String(ctx.projectedFileLines)} lines, exceeding the ${String(limit)}-line limit. See .webpieces/instruct-ai/webpieces.filesize.md for detailed refactoring instructions.`,\n )];\n }\n}\n"]}
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { MergeInProgressGuardConfig } from '@webpieces/rules-config';
|
|
2
2
|
import type { BashContext, Violation } from '../types';
|
|
3
3
|
import { BashRuleBase } from '../rule-base';
|
|
4
|
+
import { FixHint } from '../fix-hint';
|
|
4
5
|
export declare class MergeInProgressGuardRule extends BashRuleBase<MergeInProgressGuardConfig> {
|
|
5
6
|
private readonly mergeCompleteCommand;
|
|
6
7
|
constructor(config: MergeInProgressGuardConfig);
|
|
7
8
|
readonly description = "Block commit/push/merge/PR while a 3-point merge marker is unvalidated, forcing the merge-complete command.";
|
|
8
|
-
get fixHint():
|
|
9
|
+
get fixHint(): FixHint;
|
|
9
10
|
check(ctx: BashContext): readonly Violation[];
|
|
10
11
|
}
|
|
@@ -7,15 +7,14 @@ const path = tslib_1.__importStar(require("path"));
|
|
|
7
7
|
const rules_config_1 = require("@webpieces/rules-config");
|
|
8
8
|
const types_1 = require("../types");
|
|
9
9
|
const rule_base_1 = require("../rule-base");
|
|
10
|
+
const fix_hint_1 = require("../fix-hint");
|
|
10
11
|
const DEFAULT_MERGE_COMPLETE_COMMAND = 'pnpm wp-finish-upsert-pr';
|
|
11
12
|
function fixHintFor(mergeCompleteCommand) {
|
|
12
|
-
return
|
|
13
|
-
'
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
'
|
|
17
|
-
'unblock commit/push/PR, render the dashboard, and create/update the PR.',
|
|
18
|
-
];
|
|
13
|
+
return new fix_hint_1.FixHint('A merge is in progress and not yet validated — this command is blocked.', 'A 3-point merge is in progress and not yet validated.\n'
|
|
14
|
+
+ 'Resolve the remaining conflicts in the working tree, then run:\n'
|
|
15
|
+
+ ` ${mergeCompleteCommand}\n`
|
|
16
|
+
+ 'That scans for leftover conflict markers and runs the build; only when green does it commit,\n'
|
|
17
|
+
+ 'unblock commit/push/PR, render the dashboard, and create/update the PR.');
|
|
19
18
|
}
|
|
20
19
|
// Returns the path of the first UNVALIDATED merge marker found, or null. We detect validation
|
|
21
20
|
// by a raw substring (no JSON.parse) so a malformed marker can never crash the guard.
|
|
@@ -60,11 +59,8 @@ class MergeInProgressGuardRule extends rule_base_1.BashRuleBase {
|
|
|
60
59
|
const marker = findUnvalidatedMerge(ctx.workspaceRoot);
|
|
61
60
|
if (!marker)
|
|
62
61
|
return [];
|
|
63
|
-
return [new types_1.Violation(1, truncate(ctx.command),
|
|
64
|
-
|
|
65
|
-
`Marker: ${marker}`,
|
|
66
|
-
`Finish resolving conflicts, then run: ${this.mergeCompleteCommand}`,
|
|
67
|
-
].join('\n'))];
|
|
62
|
+
return [new types_1.Violation(1, truncate(ctx.command), 'A merge is in progress and not yet validated — this command is blocked.\n'
|
|
63
|
+
+ `Marker: ${marker}`)];
|
|
68
64
|
}
|
|
69
65
|
}
|
|
70
66
|
exports.MergeInProgressGuardRule = MergeInProgressGuardRule;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"merge-in-progress-guard.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/ai-hook-rules/src/core/rules/merge-in-progress-guard.ts"],"names":[],"mappings":";;;;AAAA,+CAAyB;AACzB,mDAA6B;AAE7B,0DAAgI;AAGhI,oCAA0C;AAC1C,4CAA4C;
|
|
1
|
+
{"version":3,"file":"merge-in-progress-guard.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/ai-hook-rules/src/core/rules/merge-in-progress-guard.ts"],"names":[],"mappings":";;;;AAAA,+CAAyB;AACzB,mDAA6B;AAE7B,0DAAgI;AAGhI,oCAA0C;AAC1C,4CAA4C;AAC5C,0CAAsC;AAEtC,MAAM,8BAA8B,GAAG,0BAA0B,CAAC;AAElE,SAAS,UAAU,CAAC,oBAA4B;IAC5C,OAAO,IAAI,kBAAO,CACd,yEAAyE,EACzE,yDAAyD;UACvD,kEAAkE;UAClE,KAAK,oBAAoB,IAAI;UAC7B,gGAAgG;UAChG,yEAAyE,CAC9E,CAAC;AACN,CAAC;AAED,8FAA8F;AAC9F,sFAAsF;AACtF,SAAS,oBAAoB,CAAC,aAAqB;IAC/C,kGAAkG;IAClG,MAAM,YAAY,GAAG,IAAI,CAAC,IAAI,CAAC,aAAa,EAAE,gCAAiB,EAAE,6BAAc,CAAC,CAAC;IACjF,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,YAAY,CAAC;QAAE,OAAO,IAAI,CAAC;IAC9C,KAAK,MAAM,KAAK,IAAI,EAAE,CAAC,WAAW,CAAC,YAAY,CAAC,EAAE,CAAC;QAC/C,MAAM,MAAM,GAAG,IAAI,CAAC,IAAI,CAAC,YAAY,EAAE,KAAK,EAAE,qCAAsB,CAAC,CAAC;QACtE,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,MAAM,CAAC;YAAE,SAAS;QACrC,MAAM,GAAG,GAAG,EAAE,CAAC,YAAY,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;QAC5C,IAAI,CAAC,wBAAwB,CAAC,IAAI,CAAC,GAAG,CAAC;YAAE,OAAO,MAAM,CAAC;IAC3D,CAAC;IACD,OAAO,IAAI,CAAC;AAChB,CAAC;AAED,kEAAkE;AAClE,SAAS,oBAAoB,CAAC,GAAW;IACrC,OAAO,kBAAkB,CAAC,IAAI,CAAC,GAAG,CAAC;WAC5B,gBAAgB,CAAC,IAAI,CAAC,GAAG,CAAC;WAC1B,iBAAiB,CAAC,IAAI,CAAC,GAAG,CAAC;WAC3B,kBAAkB,CAAC,IAAI,CAAC,GAAG,CAAC;WAC5B,mCAAmC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AACzD,CAAC;AAED,SAAS,QAAQ,CAAC,CAAS;IACvB,MAAM,GAAG,GAAG,GAAG,CAAC;IAChB,OAAO,CAAC,CAAC,MAAM,IAAI,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,GAAG,GAAG,CAAC;AACvD,CAAC;AAED,MAAa,wBAAyB,SAAQ,wBAAwC;IACjE,oBAAoB,CAAS;IAE9C,YAAY,MAAkC;QAC1C,KAAK,CAAC,MAAM,EAAE,yBAAyB,CAAC,CAAC;QACzC,IAAI,CAAC,oBAAoB,GAAG,MAAM,CAAC,oBAAoB,IAAI,8BAA8B,CAAC;IAC9F,CAAC;IAEQ,WAAW,GAAG,6GAA6G,CAAC;IACrI,IAAI,OAAO,KAAc,OAAO,UAAU,CAAC,IAAI,CAAC,oBAAoB,CAAC,CAAC,CAAC,CAAC;IAExE,KAAK,CAAC,GAAgB;QAClB,IAAI,CAAC,oBAAoB,CAAC,GAAG,CAAC,OAAO,CAAC;YAAE,OAAO,EAAE,CAAC;QAClD,MAAM,MAAM,GAAG,oBAAoB,CAAC,GAAG,CAAC,aAAa,CAAC,CAAC;QACvD,IAAI,CAAC,MAAM;YAAE,OAAO,EAAE,CAAC;QACvB,OAAO,CAAC,IAAI,iBAAC,CACT,CAAC,EACD,QAAQ,CAAC,GAAG,CAAC,OAAO,CAAC,EACrB,2EAA2E;kBACzE,WAAW,MAAM,EAAE,CACxB,CAAC,CAAC;IACP,CAAC;CACJ;AAtBD,4DAsBC","sourcesContent":["import * as fs from 'fs';\nimport * as path from 'path';\n\nimport { WEBPIECES_TMP_DIR, MERGE_INFO_DIR, MERGE_IN_PROGRESS_FILE, MergeInProgressGuardConfig } from '@webpieces/rules-config';\n\nimport type { BashContext, Violation } from '../types';\nimport { Violation as V } from '../types';\nimport { BashRuleBase } from '../rule-base';\nimport { FixHint } from '../fix-hint';\n\nconst DEFAULT_MERGE_COMPLETE_COMMAND = 'pnpm wp-finish-upsert-pr';\n\nfunction fixHintFor(mergeCompleteCommand: string): FixHint {\n return new FixHint(\n 'A merge is in progress and not yet validated — this command is blocked.',\n 'A 3-point merge is in progress and not yet validated.\\n'\n + 'Resolve the remaining conflicts in the working tree, then run:\\n'\n + ` ${mergeCompleteCommand}\\n`\n + 'That scans for leftover conflict markers and runs the build; only when green does it commit,\\n'\n + 'unblock commit/push/PR, render the dashboard, and create/update the PR.',\n );\n}\n\n// Returns the path of the first UNVALIDATED merge marker found, or null. We detect validation\n// by a raw substring (no JSON.parse) so a malformed marker can never crash the guard.\nfunction findUnvalidatedMerge(workspaceRoot: string): string | null {\n // Per-feature merge dirs live under `.webpieces/merge-info/<feature>/`; scan that home's subdirs.\n const mergeInfoDir = path.join(workspaceRoot, WEBPIECES_TMP_DIR, MERGE_INFO_DIR);\n if (!fs.existsSync(mergeInfoDir)) return null;\n for (const entry of fs.readdirSync(mergeInfoDir)) {\n const marker = path.join(mergeInfoDir, entry, MERGE_IN_PROGRESS_FILE);\n if (!fs.existsSync(marker)) continue;\n const raw = fs.readFileSync(marker, 'utf8');\n if (!/\"validated\"\\s*:\\s*true/.test(raw)) return marker;\n }\n return null;\n}\n\n// Operations that would let an agent route around the merge gate.\nfunction isBlockedDuringMerge(cmd: string): boolean {\n return /\\bgit\\s+commit\\b/.test(cmd)\n || /\\bgit\\s+push\\b/.test(cmd)\n || /\\bgit\\s+merge\\b/.test(cmd)\n || /\\bgit\\s+rebase\\b/.test(cmd)\n || /\\bgh\\s+pr\\s+(create|edit|merge)\\b/.test(cmd);\n}\n\nfunction truncate(s: string): string {\n const MAX = 120;\n return s.length <= MAX ? s : s.slice(0, MAX) + '…';\n}\n\nexport class MergeInProgressGuardRule extends BashRuleBase<MergeInProgressGuardConfig> {\n private readonly mergeCompleteCommand: string;\n\n constructor(config: MergeInProgressGuardConfig) {\n super(config, 'merge-in-progress-guard');\n this.mergeCompleteCommand = config.mergeCompleteCommand ?? DEFAULT_MERGE_COMPLETE_COMMAND;\n }\n\n readonly description = 'Block commit/push/merge/PR while a 3-point merge marker is unvalidated, forcing the merge-complete command.';\n get fixHint(): FixHint { return fixHintFor(this.mergeCompleteCommand); }\n\n check(ctx: BashContext): readonly Violation[] {\n if (!isBlockedDuringMerge(ctx.command)) return [];\n const marker = findUnvalidatedMerge(ctx.workspaceRoot);\n if (!marker) return [];\n return [new V(\n 1,\n truncate(ctx.command),\n 'A merge is in progress and not yet validated — this command is blocked.\\n'\n + `Marker: ${marker}`,\n )];\n }\n}\n"]}
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { NoAnyUnknownConfig } from '@webpieces/rules-config';
|
|
2
2
|
import type { EditContext, Violation } from '../types';
|
|
3
3
|
import { EditRuleBase } from '../rule-base';
|
|
4
|
+
import { FixHint } from '../fix-hint';
|
|
4
5
|
export declare class NoAnyUnknownRule extends EditRuleBase<NoAnyUnknownConfig> {
|
|
5
6
|
constructor(config: NoAnyUnknownConfig);
|
|
6
7
|
readonly description = "Disallow the `any` keyword. Use concrete types or interfaces.";
|
|
7
8
|
readonly files: string[];
|
|
8
|
-
|
|
9
|
+
get fixHint(): FixHint;
|
|
9
10
|
check(ctx: EditContext): readonly Violation[];
|
|
10
11
|
}
|
|
@@ -4,26 +4,27 @@ exports.NoAnyUnknownRule = void 0;
|
|
|
4
4
|
const rules_config_1 = require("@webpieces/rules-config");
|
|
5
5
|
const types_1 = require("../types");
|
|
6
6
|
const rule_base_1 = require("../rule-base");
|
|
7
|
+
const fix_hint_1 = require("../fix-hint");
|
|
7
8
|
// This regex literal matches the token as text, not a TS type.
|
|
8
9
|
const ANY_PATTERN = /(?::\s*any\b|\bas\s+any\b|<any>|any\[\]|Array<any>|Promise<any>|Map<[^,<>]+,\s*any\s*>|Record<[^,<>]+,\s*any\s*>|Set<any>)/; // webpieces-disable no-any-unknown -- regex literal, not a type
|
|
9
10
|
class NoAnyUnknownRule extends rule_base_1.EditRuleBase {
|
|
10
11
|
constructor(config) { super(config, 'no-any-unknown'); }
|
|
11
12
|
description = 'Disallow the `any` keyword. Use concrete types or interfaces.';
|
|
12
13
|
files = ['**/*.ts', '**/*.tsx'];
|
|
13
|
-
fixHint
|
|
14
|
-
'
|
|
15
|
-
|
|
16
|
-
];
|
|
14
|
+
get fixHint() {
|
|
15
|
+
return new fix_hint_1.FixHint('`any` erases type information.', 'Use a concrete type: interface MyData { ... } or class MyData { ... } (or `unknown` with type guards).', [], new fix_hint_1.DisableEscape(this.config.disableAllowed ?? true, '// webpieces-disable no-any-unknown -- <one-line reason>'));
|
|
16
|
+
}
|
|
17
17
|
check(ctx) {
|
|
18
|
+
const disableAllowed = this.config.disableAllowed ?? true;
|
|
18
19
|
const violations = [];
|
|
19
20
|
for (let i = 0; i < ctx.strippedLines.length; i += 1) {
|
|
20
21
|
const stripped = ctx.strippedLines[i];
|
|
21
22
|
if (!ANY_PATTERN.test(stripped))
|
|
22
23
|
continue;
|
|
23
24
|
const lineNum = i + 1;
|
|
24
|
-
if (ctx.isLineDisabled(lineNum, rules_config_1.RULE_NAMES.NO_ANY_UNKNOWN))
|
|
25
|
+
if (disableAllowed && ctx.isLineDisabled(lineNum, rules_config_1.RULE_NAMES.NO_ANY_UNKNOWN))
|
|
25
26
|
continue;
|
|
26
|
-
violations.push(new types_1.Violation(lineNum, ctx.lines[i].trim()
|
|
27
|
+
violations.push(new types_1.Violation(lineNum, ctx.lines[i].trim()));
|
|
27
28
|
}
|
|
28
29
|
return violations;
|
|
29
30
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"no-any-unknown.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/ai-hook-rules/src/core/rules/no-any-unknown.ts"],"names":[],"mappings":";;;AAAA,0DAAyE;AAGzE,oCAA0C;AAC1C,4CAA4C;
|
|
1
|
+
{"version":3,"file":"no-any-unknown.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/ai-hook-rules/src/core/rules/no-any-unknown.ts"],"names":[],"mappings":";;;AAAA,0DAAyE;AAGzE,oCAA0C;AAC1C,4CAA4C;AAC5C,0CAAqD;AAErD,+DAA+D;AAC/D,MAAM,WAAW,GACb,4HAA4H,CAAC,CAAC,gEAAgE;AAElM,MAAa,gBAAiB,SAAQ,wBAAgC;IAClE,YAAY,MAA0B,IAAI,KAAK,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAAC,CAAC,CAAC;IAEnE,WAAW,GAAG,+DAA+D,CAAC;IACrE,KAAK,GAAG,CAAC,SAAS,EAAE,UAAU,CAAC,CAAC;IAClD,IAAI,OAAO;QACP,OAAO,IAAI,kBAAO,CACd,gCAAgC,EAChC,wGAAwG,EACxG,EAAE,EACF,IAAI,wBAAa,CAAC,IAAI,CAAC,MAAM,CAAC,cAAc,IAAI,IAAI,EAAE,0DAA0D,CAAC,CACpH,CAAC;IACN,CAAC;IAED,KAAK,CAAC,GAAgB;QAClB,MAAM,cAAc,GAAG,IAAI,CAAC,MAAM,CAAC,cAAc,IAAI,IAAI,CAAC;QAC1D,MAAM,UAAU,GAAQ,EAAE,CAAC;QAC3B,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,GAAG,CAAC,aAAa,CAAC,MAAM,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC;YACnD,MAAM,QAAQ,GAAG,GAAG,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC;YACtC,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,QAAQ,CAAC;gBAAE,SAAS;YAC1C,MAAM,OAAO,GAAG,CAAC,GAAG,CAAC,CAAC;YACtB,IAAI,cAAc,IAAI,GAAG,CAAC,cAAc,CAAC,OAAO,EAAE,yBAAU,CAAC,cAAc,CAAC;gBAAE,SAAS;YACvF,UAAU,CAAC,IAAI,CAAC,IAAI,iBAAC,CAAC,OAAO,EAAE,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;QACzD,CAAC;QACD,OAAO,UAAU,CAAC;IACtB,CAAC;CACJ;AA1BD,4CA0BC","sourcesContent":["import { NoAnyUnknownConfig, RULE_NAMES } from '@webpieces/rules-config';\n\nimport type { EditContext, Violation } from '../types';\nimport { Violation as V } from '../types';\nimport { EditRuleBase } from '../rule-base';\nimport { FixHint, DisableEscape } from '../fix-hint';\n\n// This regex literal matches the token as text, not a TS type.\nconst ANY_PATTERN =\n /(?::\\s*any\\b|\\bas\\s+any\\b|<any>|any\\[\\]|Array<any>|Promise<any>|Map<[^,<>]+,\\s*any\\s*>|Record<[^,<>]+,\\s*any\\s*>|Set<any>)/; // webpieces-disable no-any-unknown -- regex literal, not a type\n\nexport class NoAnyUnknownRule extends EditRuleBase<NoAnyUnknownConfig> {\n constructor(config: NoAnyUnknownConfig) { super(config, 'no-any-unknown'); }\n\n readonly description = 'Disallow the `any` keyword. Use concrete types or interfaces.';\n override readonly files = ['**/*.ts', '**/*.tsx'];\n get fixHint(): FixHint {\n return new FixHint(\n '`any` erases type information.',\n 'Use a concrete type: interface MyData { ... } or class MyData { ... } (or `unknown` with type guards).',\n [],\n new DisableEscape(this.config.disableAllowed ?? true, '// webpieces-disable no-any-unknown -- <one-line reason>'),\n );\n }\n\n check(ctx: EditContext): readonly Violation[] {\n const disableAllowed = this.config.disableAllowed ?? true;\n const violations: V[] = [];\n for (let i = 0; i < ctx.strippedLines.length; i += 1) {\n const stripped = ctx.strippedLines[i];\n if (!ANY_PATTERN.test(stripped)) continue;\n const lineNum = i + 1;\n if (disableAllowed && ctx.isLineDisabled(lineNum, RULE_NAMES.NO_ANY_UNKNOWN)) continue;\n violations.push(new V(lineNum, ctx.lines[i].trim()));\n }\n return violations;\n }\n}\n"]}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { NoDestructureConfig } from '@webpieces/rules-config';
|
|
2
2
|
import type { EditContext, Violation } from '../types';
|
|
3
3
|
import { EditRuleBase } from '../rule-base';
|
|
4
|
+
import { FixHint } from '../fix-hint';
|
|
4
5
|
export declare class NoDestructureRule extends EditRuleBase<NoDestructureConfig> {
|
|
5
6
|
constructor(config: NoDestructureConfig);
|
|
6
7
|
readonly description = "Disallow destructuring patterns. Assign the whole result and pass it around or access properties explicitly.";
|
|
@@ -8,6 +9,6 @@ export declare class NoDestructureRule extends EditRuleBase<NoDestructureConfig>
|
|
|
8
9
|
readonly defaultOptions: {
|
|
9
10
|
allowTopLevel: boolean;
|
|
10
11
|
};
|
|
11
|
-
|
|
12
|
+
get fixHint(): FixHint;
|
|
12
13
|
check(ctx: EditContext): readonly Violation[];
|
|
13
14
|
}
|
|
@@ -4,26 +4,27 @@ exports.NoDestructureRule = void 0;
|
|
|
4
4
|
const rules_config_1 = require("@webpieces/rules-config");
|
|
5
5
|
const types_1 = require("../types");
|
|
6
6
|
const rule_base_1 = require("../rule-base");
|
|
7
|
+
const fix_hint_1 = require("../fix-hint");
|
|
7
8
|
const VARIABLE_DESTRUCTURE = /\b(?:const|let|var)\s*\{/;
|
|
8
9
|
class NoDestructureRule extends rule_base_1.EditRuleBase {
|
|
9
10
|
constructor(config) { super(config, 'no-destructure'); }
|
|
10
11
|
description = 'Disallow destructuring patterns. Assign the whole result and pass it around or access properties explicitly.';
|
|
11
12
|
files = ['**/*.ts', '**/*.tsx'];
|
|
12
13
|
defaultOptions = { allowTopLevel: true };
|
|
13
|
-
fixHint
|
|
14
|
-
'
|
|
15
|
-
|
|
16
|
-
];
|
|
14
|
+
get fixHint() {
|
|
15
|
+
return new fix_hint_1.FixHint('Destructuring pattern on a call result.', 'Assign the whole result instead: const obj = methodCall(); then pass obj around or use obj.x.', [], new fix_hint_1.DisableEscape(this.config.disableAllowed ?? true, '// webpieces-disable no-destructure -- <reason>'));
|
|
16
|
+
}
|
|
17
17
|
check(ctx) {
|
|
18
|
+
const disableAllowed = this.config.disableAllowed ?? true;
|
|
18
19
|
const violations = [];
|
|
19
20
|
for (let i = 0; i < ctx.strippedLines.length; i += 1) {
|
|
20
21
|
const stripped = ctx.strippedLines[i];
|
|
21
22
|
if (!VARIABLE_DESTRUCTURE.test(stripped))
|
|
22
23
|
continue;
|
|
23
24
|
const lineNum = i + 1;
|
|
24
|
-
if (ctx.isLineDisabled(lineNum, rules_config_1.RULE_NAMES.NO_DESTRUCTURE))
|
|
25
|
+
if (disableAllowed && ctx.isLineDisabled(lineNum, rules_config_1.RULE_NAMES.NO_DESTRUCTURE))
|
|
25
26
|
continue;
|
|
26
|
-
violations.push(new types_1.Violation(lineNum, ctx.lines[i].trim()
|
|
27
|
+
violations.push(new types_1.Violation(lineNum, ctx.lines[i].trim()));
|
|
27
28
|
}
|
|
28
29
|
return violations;
|
|
29
30
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"no-destructure.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/ai-hook-rules/src/core/rules/no-destructure.ts"],"names":[],"mappings":";;;AAAA,0DAA0E;AAG1E,oCAA0C;AAC1C,4CAA4C;
|
|
1
|
+
{"version":3,"file":"no-destructure.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/ai-hook-rules/src/core/rules/no-destructure.ts"],"names":[],"mappings":";;;AAAA,0DAA0E;AAG1E,oCAA0C;AAC1C,4CAA4C;AAC5C,0CAAqD;AAErD,MAAM,oBAAoB,GAAG,0BAA0B,CAAC;AAExD,MAAa,iBAAkB,SAAQ,wBAAiC;IACpE,YAAY,MAA2B,IAAI,KAAK,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAAC,CAAC,CAAC;IAEpE,WAAW,GAAG,8GAA8G,CAAC;IACpH,KAAK,GAAG,CAAC,SAAS,EAAE,UAAU,CAAC,CAAC;IAChC,cAAc,GAAG,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC;IAC3D,IAAI,OAAO;QACP,OAAO,IAAI,kBAAO,CACd,yCAAyC,EACzC,+FAA+F,EAC/F,EAAE,EACF,IAAI,wBAAa,CAAC,IAAI,CAAC,MAAM,CAAC,cAAc,IAAI,IAAI,EAAE,iDAAiD,CAAC,CAC3G,CAAC;IACN,CAAC;IAED,KAAK,CAAC,GAAgB;QAClB,MAAM,cAAc,GAAG,IAAI,CAAC,MAAM,CAAC,cAAc,IAAI,IAAI,CAAC;QAC1D,MAAM,UAAU,GAAQ,EAAE,CAAC;QAC3B,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,GAAG,CAAC,aAAa,CAAC,MAAM,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC;YACnD,MAAM,QAAQ,GAAG,GAAG,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC;YACtC,IAAI,CAAC,oBAAoB,CAAC,IAAI,CAAC,QAAQ,CAAC;gBAAE,SAAS;YACnD,MAAM,OAAO,GAAG,CAAC,GAAG,CAAC,CAAC;YACtB,IAAI,cAAc,IAAI,GAAG,CAAC,cAAc,CAAC,OAAO,EAAE,yBAAU,CAAC,cAAc,CAAC;gBAAE,SAAS;YACvF,UAAU,CAAC,IAAI,CAAC,IAAI,iBAAC,CAAC,OAAO,EAAE,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;QACzD,CAAC;QACD,OAAO,UAAU,CAAC;IACtB,CAAC;CACJ;AA3BD,8CA2BC","sourcesContent":["import { NoDestructureConfig, RULE_NAMES } from '@webpieces/rules-config';\n\nimport type { EditContext, Violation } from '../types';\nimport { Violation as V } from '../types';\nimport { EditRuleBase } from '../rule-base';\nimport { FixHint, DisableEscape } from '../fix-hint';\n\nconst VARIABLE_DESTRUCTURE = /\\b(?:const|let|var)\\s*\\{/;\n\nexport class NoDestructureRule extends EditRuleBase<NoDestructureConfig> {\n constructor(config: NoDestructureConfig) { super(config, 'no-destructure'); }\n\n readonly description = 'Disallow destructuring patterns. Assign the whole result and pass it around or access properties explicitly.';\n override readonly files = ['**/*.ts', '**/*.tsx'];\n override readonly defaultOptions = { allowTopLevel: true };\n get fixHint(): FixHint {\n return new FixHint(\n 'Destructuring pattern on a call result.',\n 'Assign the whole result instead: const obj = methodCall(); then pass obj around or use obj.x.',\n [],\n new DisableEscape(this.config.disableAllowed ?? true, '// webpieces-disable no-destructure -- <reason>'),\n );\n }\n\n check(ctx: EditContext): readonly Violation[] {\n const disableAllowed = this.config.disableAllowed ?? true;\n const violations: V[] = [];\n for (let i = 0; i < ctx.strippedLines.length; i += 1) {\n const stripped = ctx.strippedLines[i];\n if (!VARIABLE_DESTRUCTURE.test(stripped)) continue;\n const lineNum = i + 1;\n if (disableAllowed && ctx.isLineDisabled(lineNum, RULE_NAMES.NO_DESTRUCTURE)) continue;\n violations.push(new V(lineNum, ctx.lines[i].trim()));\n }\n return violations;\n }\n}\n"]}
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { NoImplicitAnyConfig } from '@webpieces/rules-config';
|
|
2
2
|
import type { EditContext, Violation } from '../types';
|
|
3
3
|
import { EditRuleBase } from '../rule-base';
|
|
4
|
+
import { FixHint } from '../fix-hint';
|
|
4
5
|
export declare class NoImplicitAnyRule extends EditRuleBase<NoImplicitAnyConfig> {
|
|
5
6
|
constructor(config: NoImplicitAnyConfig);
|
|
6
7
|
readonly description = "Disallow function parameters without explicit type annotations (implicit-any).";
|
|
7
8
|
readonly files: string[];
|
|
8
|
-
|
|
9
|
+
get fixHint(): FixHint;
|
|
9
10
|
check(ctx: EditContext): readonly Violation[];
|
|
10
11
|
}
|
|
@@ -4,6 +4,7 @@ exports.NoImplicitAnyRule = void 0;
|
|
|
4
4
|
const rules_config_1 = require("@webpieces/rules-config");
|
|
5
5
|
const types_1 = require("../types");
|
|
6
6
|
const rule_base_1 = require("../rule-base");
|
|
7
|
+
const fix_hint_1 = require("../fix-hint");
|
|
7
8
|
const ARROW_PARAMS_RE = /\(([^()]*)\)\s*=>/g;
|
|
8
9
|
const FN_DECL_PARAMS_RE = /\bfunction\s*[\w$]*\s*\(([^()]*)\)/g;
|
|
9
10
|
function firstUntypedParam(paramsStr) {
|
|
@@ -47,16 +48,16 @@ class NoImplicitAnyRule extends rule_base_1.EditRuleBase {
|
|
|
47
48
|
constructor(config) { super(config, 'no-implicit-any'); }
|
|
48
49
|
description = 'Disallow function parameters without explicit type annotations (implicit-any).';
|
|
49
50
|
files = ['**/*.ts', '**/*.tsx'];
|
|
50
|
-
fixHint
|
|
51
|
-
'Add explicit types: (x: string) => ...
|
|
52
|
-
|
|
53
|
-
];
|
|
51
|
+
get fixHint() {
|
|
52
|
+
return new fix_hint_1.FixHint('A parameter has no type annotation (implicit any).', 'Add explicit types: (x: string) => ... or function foo(x: number).', [], new fix_hint_1.DisableEscape(this.config.disableAllowed ?? true, '// webpieces-disable no-implicit-any -- <one-line reason>'));
|
|
53
|
+
}
|
|
54
54
|
check(ctx) {
|
|
55
|
+
const disableAllowed = this.config.disableAllowed ?? true;
|
|
55
56
|
const violations = [];
|
|
56
57
|
for (let i = 0; i < ctx.strippedLines.length; i += 1) {
|
|
57
58
|
const stripped = ctx.strippedLines[i];
|
|
58
59
|
const lineNum = i + 1;
|
|
59
|
-
if (ctx.isLineDisabled(lineNum, rules_config_1.RULE_NAMES.NO_IMPLICIT_ANY))
|
|
60
|
+
if (disableAllowed && ctx.isLineDisabled(lineNum, rules_config_1.RULE_NAMES.NO_IMPLICIT_ANY))
|
|
60
61
|
continue;
|
|
61
62
|
const offender = findOffender(stripped);
|
|
62
63
|
if (!offender)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"no-implicit-any.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/ai-hook-rules/src/core/rules/no-implicit-any.ts"],"names":[],"mappings":";;;AAAA,0DAA0E;AAG1E,oCAA0C;AAC1C,4CAA4C;
|
|
1
|
+
{"version":3,"file":"no-implicit-any.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/ai-hook-rules/src/core/rules/no-implicit-any.ts"],"names":[],"mappings":"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","sourcesContent":["import { NoImplicitAnyConfig, RULE_NAMES } from '@webpieces/rules-config';\n\nimport type { EditContext, Violation } from '../types';\nimport { Violation as V } from '../types';\nimport { EditRuleBase } from '../rule-base';\nimport { FixHint, DisableEscape } from '../fix-hint';\n\nconst ARROW_PARAMS_RE = /\\(([^()]*)\\)\\s*=>/g;\nconst FN_DECL_PARAMS_RE = /\\bfunction\\s*[\\w$]*\\s*\\(([^()]*)\\)/g;\n\nfunction firstUntypedParam(paramsStr: string): string | null {\n if (paramsStr.includes('{') || paramsStr.includes('[')) return null;\n const parts = paramsStr.split(',').map((p: string) => p.trim()).filter((p: string) => p.length > 0);\n for (const part of parts) {\n if (part.startsWith('...')) continue;\n if (part.includes(':')) continue;\n if (part.includes('=')) continue;\n if (part === 'this') continue;\n if (/^[a-zA-Z_$][\\w$]*$/.test(part)) return part;\n }\n return null;\n}\n\nfunction findOffender(line: string): string | null {\n ARROW_PARAMS_RE.lastIndex = 0;\n let m: RegExpExecArray | null = ARROW_PARAMS_RE.exec(line);\n while (m !== null) {\n const bad = firstUntypedParam(m[1]);\n if (bad) return bad;\n m = ARROW_PARAMS_RE.exec(line);\n }\n FN_DECL_PARAMS_RE.lastIndex = 0;\n m = FN_DECL_PARAMS_RE.exec(line);\n while (m !== null) {\n const bad = firstUntypedParam(m[1]);\n if (bad) return bad;\n m = FN_DECL_PARAMS_RE.exec(line);\n }\n return null;\n}\n\nexport class NoImplicitAnyRule extends EditRuleBase<NoImplicitAnyConfig> {\n constructor(config: NoImplicitAnyConfig) { super(config, 'no-implicit-any'); }\n\n readonly description = 'Disallow function parameters without explicit type annotations (implicit-any).';\n override readonly files = ['**/*.ts', '**/*.tsx'];\n get fixHint(): FixHint {\n return new FixHint(\n 'A parameter has no type annotation (implicit any).',\n 'Add explicit types: (x: string) => ... or function foo(x: number).',\n [],\n new DisableEscape(this.config.disableAllowed ?? true, '// webpieces-disable no-implicit-any -- <one-line reason>'),\n );\n }\n\n check(ctx: EditContext): readonly Violation[] {\n const disableAllowed = this.config.disableAllowed ?? true;\n const violations: V[] = [];\n for (let i = 0; i < ctx.strippedLines.length; i += 1) {\n const stripped = ctx.strippedLines[i];\n const lineNum = i + 1;\n if (disableAllowed && ctx.isLineDisabled(lineNum, RULE_NAMES.NO_IMPLICIT_ANY)) continue;\n const offender = findOffender(stripped);\n if (!offender) continue;\n violations.push(new V(\n lineNum,\n ctx.lines[i].trim(),\n `Parameter \"${offender}\" has no type annotation. Add an explicit type to avoid implicit-any.`,\n ));\n }\n return violations;\n }\n}\n"]}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { NoJsFilesConfig } from '@webpieces/rules-config';
|
|
2
2
|
import type { FileContext, Violation } from '../types';
|
|
3
3
|
import { FileRuleBase } from '../rule-base';
|
|
4
|
+
import { FixHint } from '../fix-hint';
|
|
4
5
|
export declare class NoJsFilesRule extends FileRuleBase<NoJsFilesConfig> {
|
|
5
6
|
constructor(config: NoJsFilesConfig);
|
|
6
7
|
readonly description = "Disallow writing new .js/.jsx files. Use .ts/.tsx instead.";
|
|
@@ -8,6 +9,6 @@ export declare class NoJsFilesRule extends FileRuleBase<NoJsFilesConfig> {
|
|
|
8
9
|
readonly defaultOptions: {
|
|
9
10
|
allowedPaths: never[];
|
|
10
11
|
};
|
|
11
|
-
readonly fixHint:
|
|
12
|
+
readonly fixHint: FixHint;
|
|
12
13
|
check(ctx: FileContext): readonly Violation[];
|
|
13
14
|
}
|
|
@@ -4,22 +4,23 @@ exports.NoJsFilesRule = void 0;
|
|
|
4
4
|
const rules_config_1 = require("@webpieces/rules-config");
|
|
5
5
|
const types_1 = require("../types");
|
|
6
6
|
const rule_base_1 = require("../rule-base");
|
|
7
|
+
const fix_hint_1 = require("../fix-hint");
|
|
7
8
|
class NoJsFilesRule extends rule_base_1.FileRuleBase {
|
|
8
9
|
constructor(config) { super(config, 'no-js-files'); }
|
|
9
10
|
description = 'Disallow writing new .js/.jsx files. Use .ts/.tsx instead.';
|
|
10
11
|
files = ['**/*.js', '**/*.jsx'];
|
|
11
12
|
defaultOptions = { allowedPaths: [] };
|
|
12
|
-
fixHint = [
|
|
13
|
-
'Write a .ts or .tsx file instead of .js/.jsx.',
|
|
14
|
-
'If this path must be .js (
|
|
15
|
-
];
|
|
13
|
+
fixHint = new fix_hint_1.FixHint('Writing .js/.jsx files is not allowed. Use .ts/.tsx instead.', 'Pick one:', [
|
|
14
|
+
new fix_hint_1.Option('Write a .ts or .tsx file instead of .js/.jsx.', true),
|
|
15
|
+
new fix_hint_1.Option('If this path must be .js (generated/legacy), add it to no-js-files.allowedPaths in webpieces.config.json'),
|
|
16
|
+
]);
|
|
16
17
|
check(ctx) {
|
|
17
18
|
if (ctx.tool !== 'Write')
|
|
18
19
|
return [];
|
|
19
20
|
const allowedPaths = this.config.allowedPaths ?? [];
|
|
20
21
|
if ((0, rules_config_1.isPathExcluded)(ctx.relativePath, allowedPaths))
|
|
21
22
|
return [];
|
|
22
|
-
return [new types_1.Violation(1, ctx.relativePath
|
|
23
|
+
return [new types_1.Violation(1, ctx.relativePath)];
|
|
23
24
|
}
|
|
24
25
|
}
|
|
25
26
|
exports.NoJsFilesRule = NoJsFilesRule;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"no-js-files.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/ai-hook-rules/src/core/rules/no-js-files.ts"],"names":[],"mappings":";;;AAAA,0DAA0E;AAG1E,oCAA0C;AAC1C,4CAA4C;
|
|
1
|
+
{"version":3,"file":"no-js-files.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/ai-hook-rules/src/core/rules/no-js-files.ts"],"names":[],"mappings":";;;AAAA,0DAA0E;AAG1E,oCAA0C;AAC1C,4CAA4C;AAC5C,0CAA8C;AAE9C,MAAa,aAAc,SAAQ,wBAA6B;IAC5D,YAAY,MAAuB,IAAI,KAAK,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC,CAAC,CAAC;IAE7D,WAAW,GAAG,4DAA4D,CAAC;IAClE,KAAK,GAAG,CAAC,SAAS,EAAE,UAAU,CAAC,CAAC;IAChC,cAAc,GAAG,EAAE,YAAY,EAAE,EAAE,EAAE,CAAC;IAC/C,OAAO,GAAG,IAAI,kBAAO,CAC1B,8DAA8D,EAC9D,WAAW,EACX;QACI,IAAI,iBAAM,CAAC,+CAA+C,EAAE,IAAI,CAAC;QACjE,IAAI,iBAAM,CAAC,0GAA0G,CAAC;KACzH,CACJ,CAAC;IAEF,KAAK,CAAC,GAAgB;QAClB,IAAI,GAAG,CAAC,IAAI,KAAK,OAAO;YAAE,OAAO,EAAE,CAAC;QAEpC,MAAM,YAAY,GAAG,IAAI,CAAC,MAAM,CAAC,YAAY,IAAI,EAAE,CAAC;QAEpD,IAAI,IAAA,6BAAc,EAAC,GAAG,CAAC,YAAY,EAAE,YAAY,CAAC;YAAE,OAAO,EAAE,CAAC;QAE9D,OAAO,CAAC,IAAI,iBAAC,CAAC,CAAC,EAAE,GAAG,CAAC,YAAY,CAAC,CAAC,CAAC;IACxC,CAAC;CACJ;AAxBD,sCAwBC","sourcesContent":["import { NoJsFilesConfig, isPathExcluded } from '@webpieces/rules-config';\n\nimport type { FileContext, Violation } from '../types';\nimport { Violation as V } from '../types';\nimport { FileRuleBase } from '../rule-base';\nimport { FixHint, Option } from '../fix-hint';\n\nexport class NoJsFilesRule extends FileRuleBase<NoJsFilesConfig> {\n constructor(config: NoJsFilesConfig) { super(config, 'no-js-files'); }\n\n readonly description = 'Disallow writing new .js/.jsx files. Use .ts/.tsx instead.';\n override readonly files = ['**/*.js', '**/*.jsx'];\n override readonly defaultOptions = { allowedPaths: [] };\n readonly fixHint = new FixHint(\n 'Writing .js/.jsx files is not allowed. Use .ts/.tsx instead.',\n 'Pick one:',\n [\n new Option('Write a .ts or .tsx file instead of .js/.jsx.', true),\n new Option('If this path must be .js (generated/legacy), add it to no-js-files.allowedPaths in webpieces.config.json'),\n ],\n );\n\n check(ctx: FileContext): readonly Violation[] {\n if (ctx.tool !== 'Write') return [];\n\n const allowedPaths = this.config.allowedPaths ?? [];\n\n if (isPathExcluded(ctx.relativePath, allowedPaths)) return [];\n\n return [new V(1, ctx.relativePath)];\n }\n}\n"]}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { NoSymbolDiTokensConfig } from '@webpieces/rules-config';
|
|
2
2
|
import type { EditContext, Violation } from '../types';
|
|
3
3
|
import { EditRuleBase } from '../rule-base';
|
|
4
|
+
import { FixHint } from '../fix-hint';
|
|
4
5
|
export declare class NoSymbolDiTokensRule extends EditRuleBase<NoSymbolDiTokensConfig> {
|
|
5
6
|
constructor(config: NoSymbolDiTokensConfig);
|
|
6
7
|
readonly description = "Disallow Symbol() DI tokens outside explicitly configured paths. Use @provideSingleton() + inject-by-type instead.";
|
|
@@ -8,6 +9,6 @@ export declare class NoSymbolDiTokensRule extends EditRuleBase<NoSymbolDiTokensC
|
|
|
8
9
|
readonly defaultOptions: {
|
|
9
10
|
allowedPaths: never[];
|
|
10
11
|
};
|
|
11
|
-
|
|
12
|
+
get fixHint(): FixHint;
|
|
12
13
|
check(ctx: EditContext): readonly Violation[];
|
|
13
14
|
}
|
|
@@ -4,6 +4,7 @@ exports.NoSymbolDiTokensRule = void 0;
|
|
|
4
4
|
const rules_config_1 = require("@webpieces/rules-config");
|
|
5
5
|
const types_1 = require("../types");
|
|
6
6
|
const rule_base_1 = require("../rule-base");
|
|
7
|
+
const fix_hint_1 = require("../fix-hint");
|
|
7
8
|
const SYMBOL_DI_REGEX = /=\s*Symbol(?:\.for)?\(/;
|
|
8
9
|
const TEST_PATHS = [/\.test\.ts$/, /\.spec\.ts$/, /__tests__\//];
|
|
9
10
|
function globToRegex(pattern) {
|
|
@@ -48,25 +49,27 @@ class NoSymbolDiTokensRule extends rule_base_1.EditRuleBase {
|
|
|
48
49
|
description = 'Disallow Symbol() DI tokens outside explicitly configured paths. Use @provideSingleton() + inject-by-type instead.';
|
|
49
50
|
files = ['**/*.ts', '**/*.tsx'];
|
|
50
51
|
defaultOptions = { allowedPaths: [] };
|
|
51
|
-
fixHint
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
52
|
+
get fixHint() {
|
|
53
|
+
return new fix_hint_1.FixHint('Symbol() used as a DI token — we avoid Symbol DI tokens where we can.', 'Pick one:', [
|
|
54
|
+
new fix_hint_1.Option('Use @provideSingleton() on the class and inject by type — no Symbol needed.', true),
|
|
55
|
+
new fix_hint_1.Option('Implement an API interface — import the Symbol from the API definition and use @provideSingletonAs(TOKEN).'),
|
|
56
|
+
new fix_hint_1.Option('External lib class (DataSource, Anthropic, etc.) — bind<Cls>(Cls).toDynamicValue(...).inSingletonScope() — no Symbol.'),
|
|
57
|
+
], new fix_hint_1.DisableEscape(this.config.disableAllowed ?? true, '// webpieces-disable no-symbol-di-tokens -- <reason>'));
|
|
58
|
+
}
|
|
57
59
|
check(ctx) {
|
|
58
60
|
const allowedPaths = this.config.allowedPaths ?? [];
|
|
59
61
|
if (isAllowedPath(ctx.relativePath, allowedPaths))
|
|
60
62
|
return [];
|
|
63
|
+
const disableAllowed = this.config.disableAllowed ?? true;
|
|
61
64
|
const violations = [];
|
|
62
65
|
for (let i = 0; i < ctx.strippedLines.length; i += 1) {
|
|
63
66
|
const stripped = ctx.strippedLines[i];
|
|
64
67
|
if (!SYMBOL_DI_REGEX.test(stripped ?? ''))
|
|
65
68
|
continue;
|
|
66
69
|
const lineNum = i + 1;
|
|
67
|
-
if (ctx.isLineDisabled(lineNum, rules_config_1.RULE_NAMES.NO_SYMBOL_DI_TOKENS))
|
|
70
|
+
if (disableAllowed && ctx.isLineDisabled(lineNum, rules_config_1.RULE_NAMES.NO_SYMBOL_DI_TOKENS))
|
|
68
71
|
continue;
|
|
69
|
-
violations.push(new types_1.Violation(lineNum, ctx.lines[i]?.trim() ?? ''
|
|
72
|
+
violations.push(new types_1.Violation(lineNum, ctx.lines[i]?.trim() ?? ''));
|
|
70
73
|
}
|
|
71
74
|
return violations;
|
|
72
75
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"no-symbol-di-tokens.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/ai-hook-rules/src/core/rules/no-symbol-di-tokens.ts"],"names":[],"mappings":";;;AAAA,0DAA6E;AAG7E,oCAA0C;AAC1C,4CAA4C;
|
|
1
|
+
{"version":3,"file":"no-symbol-di-tokens.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/ai-hook-rules/src/core/rules/no-symbol-di-tokens.ts"],"names":[],"mappings":"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","sourcesContent":["import { NoSymbolDiTokensConfig, RULE_NAMES } from '@webpieces/rules-config';\n\nimport type { EditContext, Violation } from '../types';\nimport { Violation as V } from '../types';\nimport { EditRuleBase } from '../rule-base';\nimport { FixHint, Option, DisableEscape } from '../fix-hint';\n\nconst SYMBOL_DI_REGEX = /=\\s*Symbol(?:\\.for)?\\(/;\n\nconst TEST_PATHS: RegExp[] = [/\\.test\\.ts$/, /\\.spec\\.ts$/, /__tests__\\//];\n\nfunction globToRegex(pattern: string): RegExp {\n let re = '';\n let i = 0;\n while (i < pattern.length) {\n const ch = pattern[i];\n if (ch === '*') {\n if (pattern[i + 1] === '*') {\n re += '.*';\n i += 2;\n if (pattern[i] === '/') i += 1;\n continue;\n }\n re += '[^/]*';\n i += 1;\n continue;\n }\n if (ch === '?') {\n re += '[^/]';\n i += 1;\n continue;\n }\n if ('.+^$(){}|[]\\\\'.includes(ch)) {\n re += '\\\\' + ch;\n i += 1;\n continue;\n }\n re += ch;\n i += 1;\n }\n return new RegExp('^' + re + '$');\n}\n\nfunction isAllowedPath(relativePath: string, allowedPaths: readonly string[]): boolean {\n if (TEST_PATHS.some((re: RegExp) => re.test(relativePath))) return true;\n return allowedPaths.some((pattern: string) => globToRegex(pattern).test(relativePath));\n}\n\nexport class NoSymbolDiTokensRule extends EditRuleBase<NoSymbolDiTokensConfig> {\n constructor(config: NoSymbolDiTokensConfig) { super(config, 'no-symbol-di-tokens'); }\n\n readonly description = 'Disallow Symbol() DI tokens outside explicitly configured paths. Use @provideSingleton() + inject-by-type instead.';\n override readonly files = ['**/*.ts', '**/*.tsx'];\n override readonly defaultOptions = { allowedPaths: [] };\n get fixHint(): FixHint {\n return new FixHint(\n 'Symbol() used as a DI token — we avoid Symbol DI tokens where we can.',\n 'Pick one:',\n [\n new Option('Use @provideSingleton() on the class and inject by type — no Symbol needed.', true),\n new Option('Implement an API interface — import the Symbol from the API definition and use @provideSingletonAs(TOKEN).'),\n new Option('External lib class (DataSource, Anthropic, etc.) — bind<Cls>(Cls).toDynamicValue(...).inSingletonScope() — no Symbol.'),\n ],\n new DisableEscape(this.config.disableAllowed ?? true, '// webpieces-disable no-symbol-di-tokens -- <reason>'),\n );\n }\n\n check(ctx: EditContext): readonly Violation[] {\n const allowedPaths = this.config.allowedPaths ?? [];\n if (isAllowedPath(ctx.relativePath, allowedPaths)) return [];\n\n const disableAllowed = this.config.disableAllowed ?? true;\n const violations: V[] = [];\n for (let i = 0; i < ctx.strippedLines.length; i += 1) {\n const stripped = ctx.strippedLines[i];\n if (!SYMBOL_DI_REGEX.test(stripped ?? '')) continue;\n const lineNum = i + 1;\n if (disableAllowed && ctx.isLineDisabled(lineNum, RULE_NAMES.NO_SYMBOL_DI_TOKENS)) continue;\n violations.push(new V(lineNum, ctx.lines[i]?.trim() ?? ''));\n }\n return violations;\n }\n}\n"]}
|