Fix: refuse inert exact ignore-value entries (#662)

ignore-value stored exact values for rules that cannot extract one, so the entries never matched. Refuse them and point at "*" --file.

AI assistance: implemented with Cursor Grok 4.6.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Abdul Wahab
2026-08-28 05:43:36 +05:00
committed by Abdul Wahab
co-authored by Cursor
parent af2e8b3ac3
commit be87f5eb86
4 changed files with 38 additions and 0 deletions
+4
View File
@@ -7,6 +7,7 @@ import {
readDetectionConfig, readDetectionConfig,
readRawDetectionConfig, readRawDetectionConfig,
writeDetectionConfig, writeDetectionConfig,
extractFindingIgnoreValue,
} from '../../lib/impeccable-config.mjs'; } from '../../lib/impeccable-config.mjs';
const ACTION_ALIASES = new Map([ const ACTION_ALIASES = new Map([
@@ -235,6 +236,9 @@ function addFile(cwd, args) {
function addValue(cwd, args) { function addValue(cwd, args) {
const { local, rest } = parseScope(args); const { local, rest } = parseScope(args);
const parsed = parseValueArgs(rest); const parsed = parseValueArgs(rest);
if (parsed.value !== '*' && !extractFindingIgnoreValue({ antipattern: parsed.rule, ignoreValue: parsed.value })) {
throw new Error(`${parsed.rule} has no extractable ignore value. Use impeccable ignores add-value ${parsed.rule} "*" --file <glob> to suppress it in matching files.`);
}
const config = readScopeConfig(cwd, local); const config = readScopeConfig(cwd, local);
const key = ignoreValueKey(parsed); const key = ignoreValueKey(parsed);
const existing = config.ignoreValues.find((entry) => ignoreValueKey(entry) === key); const existing = config.ignoreValues.find((entry) => ignoreValueKey(entry) === key);
+5
View File
@@ -35,6 +35,7 @@ import {
ensureHookGitExcludes, ensureHookGitExcludes,
normalizeIgnoreValue, normalizeIgnoreValue,
normalizeIgnoreValueEntries, normalizeIgnoreValueEntries,
extractFindingIgnoreValue,
} from './hook-lib.mjs'; } from './hook-lib.mjs';
const ACTIONS = new Set(['status', 'on', 'off', 'ignore-rule', 'ignore-file', 'ignore-value', 'reset']); const ACTIONS = new Set(['status', 'on', 'off', 'ignore-rule', 'ignore-file', 'ignore-value', 'reset']);
@@ -713,6 +714,10 @@ function addIgnoreValue(cwd, args) {
throw new Error(`Wildcard value ignores must be scoped with --file <glob>, e.g. ${IMPECCABLE_COMMAND} hooks ignore-value design-system-font-size "*" --file "src/widget.js". To suppress the rule project-wide use ${projectWide}.`); throw new Error(`Wildcard value ignores must be scoped with --file <glob>, e.g. ${IMPECCABLE_COMMAND} hooks ignore-value design-system-font-size "*" --file "src/widget.js". To suppress the rule project-wide use ${projectWide}.`);
} }
if (parsed.value !== '*' && !extractFindingIgnoreValue({ antipattern: parsed.rule, ignoreValue: parsed.value })) {
throw new Error(`${parsed.rule} has no extractable ignore value. Use ${IMPECCABLE_COMMAND} hooks ignore-value ${parsed.rule} "*" --file <glob> to suppress it in matching files.`);
}
const local = parsed.local; const local = parsed.local;
const config = mergeDetectorConfig(readRawDetectorConfig(cwd, { local })); const config = mergeDetectorConfig(readRawDetectorConfig(cwd, { local }));
// Key on the file scope too: the same rule/value legitimately appears more than // Key on the file scope too: the same rule/value legitimately appears more than
+7
View File
@@ -115,6 +115,13 @@ describe('impeccable ignores CLI', () => {
expect(existsSync(join(root, '.impeccable', 'config.json'))).toBe(false); expect(existsSync(join(root, '.impeccable', 'config.json'))).toBe(false);
}); });
test('rejects exact values for rules that cannot extract one', () => {
const result = run(['add-value', 'side-tab', 'Inter']);
expect(result.status).not.toBe(0);
expect(result.stderr).toMatch(/side-tab has no extractable ignore value.*add-value side-tab "\*" --file <glob>/);
expect(existsSync(join(root, '.impeccable', 'config.json'))).toBe(false);
});
test('removes an existing broad wildcard value ignore', () => { test('removes an existing broad wildcard value ignore', () => {
mkdirSync(join(root, '.impeccable'), { recursive: true }); mkdirSync(join(root, '.impeccable'), { recursive: true });
writeFileSync(join(root, '.impeccable', 'config.json'), JSON.stringify({ writeFileSync(join(root, '.impeccable', 'config.json'), JSON.stringify({
+22
View File
@@ -819,6 +819,28 @@ describe('hook-admin.mjs', () => {
assert.equal(fs.existsSync(getConfigPath(cwd)), false, 'a refused ignore must not write config'); assert.equal(fs.existsSync(getConfigPath(cwd)), false, 'a refused ignore must not write config');
}); });
it('ignore-value refuses exact values for rules that cannot extract one', () => {
assert.throws(
() => runAdmin(['ignore-value', 'cramped-padding', 'padding: 4px 8px']),
/cramped-padding has no extractable ignore value.*ignore-value cramped-padding "\*" --file <glob>/,
);
assert.throws(
() => runAdmin(['ignore-value', 'side-tab', 'Inter', '--file', 'a.css']),
/side-tab has no extractable ignore value.*ignore-value side-tab "\*" --file <glob>/,
);
assert.equal(fs.existsSync(getConfigPath(cwd)), false, 'a refused ignore must not write config');
const out = runAdmin(['ignore-value', 'overused-font', 'Inter']);
assert.match(out, /Added overused-font=inter/);
runAdmin(['ignore-value', 'cramped-padding', '*', '--file', 'index.html']);
const shared = JSON.parse(fs.readFileSync(getConfigPath(cwd), 'utf-8')).detector;
assert.equal(shared.ignoreValues.filter((e) => e.rule === 'cramped-padding').length, 1);
const entry = shared.ignoreValues.find((e) => e.rule === 'cramped-padding');
assert.equal(entry.value, '*');
assert.deepEqual(entry.files, ['index.html']);
});
it('ignore-value --file requires a glob', () => { it('ignore-value --file requires a glob', () => {
assert.throws( assert.throws(
() => runAdmin(['ignore-value', 'side-tab', '*', '--file']), () => runAdmin(['ignore-value', 'side-tab', '*', '--file']),