diff --git a/package.json b/package.json index 92aacd9..fbcafae 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,7 @@ "scripts": { "check": "pnpm lint && pnpm test && pnpm typecheck && pnpm check:skill-assets", "lint": "oxlint src", - "test": "tsx src/rules/require-readable-spacing.test.ts && tsx src/rules/require-readable-spacing-cli.test.ts && tsx src/rules/no-array-filter-map.test.ts && tsx src/rules/no-reduce-accumulator-copy.test.ts && tsx src/rules/no-chained-type-assertions.test.ts && tsx src/rules/no-conditional-empty-object-spread.test.ts && tsx src/rules/no-unsafe-dictionary-type.test.ts && tsx src/rules/no-known-value-widening.test.ts && tsx src/rules/no-object-parameters.test.ts && tsx src/rules/no-unknown-parameters.test.ts && tsx src/rules/no-runtime-typeof.test.ts && tsx src/rules/no-shape-in-symbol-names.test.ts && tsx src/rules/no-unknown-returns.test.ts && tsx src/rules/no-unknown-type-aliases.test.ts && tsx src/rules/no-widen-then-assert.test.ts && tsx src/rules/no-reflect-get.test.ts && tsx src/rules/no-reflect-apply.test.ts && tsx src/rules/no-module-mocking.test.ts && tsx src/rules/require-safety-comment-for-type-assertion.test.ts && tsx src/effect/rules/no-service-constructor-imports.test.ts && tsx src/effect/rules/no-manual-tag-comparison.test.ts && tsx src/effect/rules/no-manual-effect-error-tag.test.ts && tsx src/effect/rules/no-manual-tagged-construction.test.ts && tsx src/effect/rules/prefer-effect-match.test.ts", + "test": "tsx src/rules/require-readable-spacing.test.ts && tsx src/rules/require-readable-spacing-cli.test.ts && tsx src/rules/no-array-filter-map.test.ts && tsx src/rules/no-reduce-accumulator-copy.test.ts && tsx src/rules/no-chained-type-assertions.test.ts && tsx src/rules/no-conditional-empty-object-spread.test.ts && tsx src/rules/no-unsafe-dictionary-type.test.ts && tsx src/rules/no-known-value-widening.test.ts && tsx src/rules/no-object-parameters.test.ts && tsx src/rules/no-unknown-parameters.test.ts && tsx src/rules/no-runtime-typeof.test.ts && tsx src/rules/no-shape-in-symbol-names.test.ts && tsx src/rules/no-unknown-returns.test.ts && tsx src/rules/no-unknown-type-aliases.test.ts && tsx src/rules/no-widen-then-assert.test.ts && tsx src/rules/no-widen-then-assert-cli.test.ts && tsx src/rules/no-reflect-get.test.ts && tsx src/rules/no-reflect-apply.test.ts && tsx src/rules/no-module-mocking.test.ts && tsx src/rules/require-safety-comment-for-type-assertion.test.ts && tsx src/effect/rules/no-service-constructor-imports.test.ts && tsx src/effect/rules/no-manual-tag-comparison.test.ts && tsx src/effect/rules/no-manual-effect-error-tag.test.ts && tsx src/effect/rules/no-manual-tagged-construction.test.ts && tsx src/effect/rules/prefer-effect-match.test.ts", "typecheck": "tsc --noEmit", "sync:skill-assets": "node scripts/sync-skill-assets.mjs", "check:skill-assets": "node scripts/sync-skill-assets.mjs --check" diff --git a/skills/install-anti-slop/assets/anti-slop/rules/no-widen-then-assert.ts b/skills/install-anti-slop/assets/anti-slop/rules/no-widen-then-assert.ts index c5e07f7..5b1c696 100644 --- a/skills/install-anti-slop/assets/anti-slop/rules/no-widen-then-assert.ts +++ b/skills/install-anti-slop/assets/anti-slop/rules/no-widen-then-assert.ts @@ -325,15 +325,16 @@ export const noWidenThenAssertRule = defineRule({ }, }, createOnce(context) { - let scopes: Parameters[0] = []; + let scopes: Parameters[0] | null = null; const checkAssertion = (node: ESTree.TSAsExpression | ESTree.TSTypeAssertion) => { const expression = assertedExpression(node); if (expression.type !== "Identifier") return; - const variable = resolvedVariableForIdentifier(scopes, expression); + const fileScopes = (scopes ??= context.sourceCode.scopeManager.scopes); + const variable = resolvedVariableForIdentifier(fileScopes, expression); if (variable === null) return; - const widened = widenedBinding(variable, scopes); + const widened = widenedBinding(variable, fileScopes); if ( widened === null || node.start <= widened.declaredAt || @@ -357,7 +358,7 @@ export const noWidenThenAssertRule = defineRule({ return { Program() { - scopes = context.sourceCode.scopeManager.scopes; + scopes = null; }, TSAsExpression: checkAssertion, TSTypeAssertion: checkAssertion, diff --git a/src/rules/no-widen-then-assert-cli.test.ts b/src/rules/no-widen-then-assert-cli.test.ts new file mode 100644 index 0000000..42d7db9 --- /dev/null +++ b/src/rules/no-widen-then-assert-cli.test.ts @@ -0,0 +1,43 @@ +/// + +import assert from "node:assert/strict"; +import { spawnSync } from "node:child_process"; +import { mkdtempSync, rmSync, writeFileSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { basename, join } from "node:path"; +import { fileURLToPath } from "node:url"; + +const probeRoot = mkdtempSync(join(tmpdir(), "anti-slop-widen-")); +const config = join(probeRoot, "oxlint.json"); +const first = join(probeRoot, "first.ts"); +const second = join(probeRoot, "second.ts"); +const violation = + "const source = { id: 'a' }; const widened: unknown = source; export const parsed = widened as { readonly id: string };\n"; + +try { + writeFileSync( + config, + JSON.stringify({ + jsPlugins: [ + { name: "anti-slop", specifier: fileURLToPath(new URL("../index.ts", import.meta.url)) }, + ], + rules: { "anti-slop/no-widen-then-assert": "error" }, + }), + ); + // Scopes are read lazily and cached per file; offsets differ so a stale cache cannot resolve. + writeFileSync(first, violation); + writeFileSync(second, `export const padding = 0;\n${violation}`); + + const result = spawnSync( + "pnpm", + ["exec", "oxlint", "--config", config, "--threads", "1", "--format", "json", first, second], + { encoding: "utf8" }, + ); + const { diagnostics } = JSON.parse(result.stdout) as { diagnostics: { filename: string }[] }; + assert.deepEqual(diagnostics.map(({ filename }) => basename(filename)).sort(), [ + "first.ts", + "second.ts", + ]); +} finally { + rmSync(probeRoot, { recursive: true, force: true }); +} diff --git a/src/rules/no-widen-then-assert.ts b/src/rules/no-widen-then-assert.ts index c5e07f7..5b1c696 100644 --- a/src/rules/no-widen-then-assert.ts +++ b/src/rules/no-widen-then-assert.ts @@ -325,15 +325,16 @@ export const noWidenThenAssertRule = defineRule({ }, }, createOnce(context) { - let scopes: Parameters[0] = []; + let scopes: Parameters[0] | null = null; const checkAssertion = (node: ESTree.TSAsExpression | ESTree.TSTypeAssertion) => { const expression = assertedExpression(node); if (expression.type !== "Identifier") return; - const variable = resolvedVariableForIdentifier(scopes, expression); + const fileScopes = (scopes ??= context.sourceCode.scopeManager.scopes); + const variable = resolvedVariableForIdentifier(fileScopes, expression); if (variable === null) return; - const widened = widenedBinding(variable, scopes); + const widened = widenedBinding(variable, fileScopes); if ( widened === null || node.start <= widened.declaredAt || @@ -357,7 +358,7 @@ export const noWidenThenAssertRule = defineRule({ return { Program() { - scopes = context.sourceCode.scopeManager.scopes; + scopes = null; }, TSAsExpression: checkAssertion, TSTypeAssertion: checkAssertion,