mirror of
https://github.com/pbakaus/impeccable.git
synced 2026-09-12 06:06:37 +03:00
A file-scoped wildcard ignore (add-value <rule> "*" --file <glob>) silently no-op'd for rules with no extractable value, such as side-tab. isIgnoredFindingValue bailed on an empty value before the wildcard/file-scope branch could run. Require a value only on the specific-value path; let the scoped wildcard match on rule + file. Mirrored in skill/scripts/hook-lib.mjs for CLI/hook parity.
This commit is contained in:
@@ -459,11 +459,13 @@ export function filterDetectionFindings(findings, config) {
|
||||
function isIgnoredFindingValue(finding, ignoreValues) {
|
||||
if (!Array.isArray(ignoreValues) || ignoreValues.length === 0) return false;
|
||||
const rule = normalizeIgnoreRule(finding.antipattern);
|
||||
if (!rule) return false;
|
||||
// File-scoped wildcards suppress rules with no extractable value, such as side-tab.
|
||||
const value = extractFindingIgnoreValue(finding);
|
||||
if (!rule || !value) return false;
|
||||
return ignoreValues.some((entry) => {
|
||||
if (entry.rule !== rule) return false;
|
||||
const wildcardValue = entry.value === '*';
|
||||
if (entry.rule !== rule || (!wildcardValue && !ignoreValueMatches(rule, entry.value, value))) return false;
|
||||
if (!wildcardValue && (!value || !ignoreValueMatches(rule, entry.value, value))) return false;
|
||||
if (!Array.isArray(entry.files) || entry.files.length === 0) return !wildcardValue;
|
||||
return findingMatchesScopedIgnoreFile(finding, entry.files);
|
||||
});
|
||||
|
||||
@@ -711,11 +711,13 @@ export function filterFindings(findings, _content, _ext, config) {
|
||||
function isIgnoredFindingValue(finding, ignoreValues) {
|
||||
if (!Array.isArray(ignoreValues) || ignoreValues.length === 0) return false;
|
||||
const rule = normalizeIgnoreRule(finding.antipattern);
|
||||
if (!rule) return false;
|
||||
// File-scoped wildcards suppress rules with no extractable value, such as side-tab.
|
||||
const value = extractFindingIgnoreValue(finding);
|
||||
if (!rule || !value) return false;
|
||||
return ignoreValues.some((entry) => {
|
||||
if (entry.rule !== rule) return false;
|
||||
const wildcardValue = entry.value === '*';
|
||||
if (entry.rule !== rule || (!wildcardValue && !ignoreValueMatches(rule, entry.value, value))) return false;
|
||||
if (!wildcardValue && (!value || !ignoreValueMatches(rule, entry.value, value))) return false;
|
||||
if (!Array.isArray(entry.files) || entry.files.length === 0) return !wildcardValue;
|
||||
return findingMatchesScopedIgnoreFile(finding, entry.files);
|
||||
});
|
||||
|
||||
@@ -27,6 +27,16 @@ describe('impeccable ignores CLI', () => {
|
||||
return result;
|
||||
}
|
||||
|
||||
function detect(args, options = {}) {
|
||||
const result = spawnSync(process.execPath, [CLI, 'detect', '--json', ...args], {
|
||||
cwd: root,
|
||||
encoding: 'utf-8',
|
||||
...options,
|
||||
});
|
||||
if (result.error) throw result.error;
|
||||
return result;
|
||||
}
|
||||
|
||||
function readConfig(name = 'config.json') {
|
||||
return JSON.parse(readFileSync(join(root, '.impeccable', name), 'utf-8'));
|
||||
}
|
||||
@@ -65,6 +75,39 @@ describe('impeccable ignores CLI', () => {
|
||||
expect(raw.detector.ignoreValues).toEqual([]);
|
||||
});
|
||||
|
||||
test('file-scoped wildcard value ignores suppress non-value-bearing rules only in matching files', () => {
|
||||
mkdirSync(join(root, 'components'), { recursive: true });
|
||||
const triangle = [
|
||||
'export function TopicCard() {',
|
||||
' return (',
|
||||
' <div style={{',
|
||||
' width: 0, height: 0,',
|
||||
" borderLeft: '7px solid transparent',",
|
||||
" borderRight: '7px solid transparent',",
|
||||
" borderTop: '7px solid #fff',",
|
||||
' }} />',
|
||||
' );',
|
||||
'}',
|
||||
'',
|
||||
].join('\n');
|
||||
writeFileSync(join(root, 'components', 'TopicCard.jsx'), triangle);
|
||||
writeFileSync(join(root, 'components', 'Other.jsx'), triangle.replace('TopicCard', 'Other'));
|
||||
|
||||
const before = detect(['components/TopicCard.jsx']);
|
||||
expect(before.status).toBe(2);
|
||||
expect(before.stdout).toContain('side-tab');
|
||||
|
||||
expect(run(['add-value', 'side-tab', '*', '--file', '**/TopicCard.jsx']).status).toBe(0);
|
||||
|
||||
const afterTarget = detect(['components/TopicCard.jsx']);
|
||||
expect(afterTarget.status).toBe(0);
|
||||
expect(afterTarget.stdout.trim()).toBe('[]');
|
||||
|
||||
const afterOther = detect(['components/Other.jsx']);
|
||||
expect(afterOther.status).toBe(2);
|
||||
expect(afterOther.stdout).toContain('side-tab');
|
||||
});
|
||||
|
||||
test('rejects broad wildcard value ignores', () => {
|
||||
const result = run(['add-value', 'design-system-color', '*']);
|
||||
expect(result.status).not.toBe(0);
|
||||
|
||||
@@ -199,6 +199,18 @@ describe('cli/lib/impeccable-config', () => {
|
||||
]);
|
||||
});
|
||||
|
||||
test('filterDetectionFindings honors file-scoped wildcard ignores for non-value-bearing rules', () => {
|
||||
const findings = [
|
||||
{ antipattern: 'side-tab', file: join(root, 'components', 'TopicCard.jsx'), line: 331, snippet: "borderLeft: '7px solid" },
|
||||
{ antipattern: 'side-tab', file: join(root, 'components', 'Other.jsx'), line: 12, snippet: "borderLeft: '7px solid" },
|
||||
];
|
||||
const filtered = filterDetectionFindings(findings, {
|
||||
ignoreRules: [],
|
||||
ignoreValues: [{ rule: 'side-tab', value: '*', files: ['**/TopicCard.jsx'] }],
|
||||
});
|
||||
expect(filtered.map((f) => `${f.antipattern}:${f.line}`)).toEqual(['side-tab:12']);
|
||||
});
|
||||
|
||||
test('filterDetectionFindings matches equivalent design-system color values', () => {
|
||||
const findings = [
|
||||
{ antipattern: 'design-system-color', file: join(root, 'src', 'rgb.css'), line: 1, ignoreValue: 'rgb(139, 92, 246)' },
|
||||
|
||||
Reference in New Issue
Block a user