mirror of
https://github.com/pbakaus/impeccable.git
synced 2026-09-11 21:57:14 +03:00
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:
committed by
Abdul Wahab
co-authored by
Cursor
parent
af2e8b3ac3
commit
be87f5eb86
@@ -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);
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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({
|
||||||
|
|||||||
@@ -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']),
|
||||||
|
|||||||
Reference in New Issue
Block a user