@getfootgun/footgun 0.2.1 → 0.2.2

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/lib/rules.mjs CHANGED
@@ -128,6 +128,13 @@ function isSafe(rule) {
128
128
  return v;
129
129
  }
130
130
 
131
+ // A line whose trimmed start is a comment marker is not matched (`#!` shebangs fall under `#`).
132
+ // Block comments are not parsed: only lines starting with `/*` or `*` are skipped.
133
+ const COMMENT_START = /^(?:\/\/|#|\/\*|\*|<!--|--)/;
134
+ // A rule whose pattern itself targets comment syntax (contains `<!--`, or starts with a comment marker) is not subject to the skip.
135
+ // For ordinary rules, `#[attr]`, `#include` and Markdown `*` bullets are skipped like any other comment-looking line.
136
+ const COMMENT_RULE = /<!--|^\^?(?:\\s\*|\(\?:|\()*(?:--|#|\/\/|\/\\\*|\\\*)/;
137
+
131
138
  export function matchRule(rule, relPath, text) {
132
139
  if (!isSafe(rule)) return [];
133
140
  if (!rule.files.some((g) => globMatch(g, relPath))) return [];
@@ -139,10 +146,12 @@ export function matchRule(rule, relPath, text) {
139
146
  return [];
140
147
  }
141
148
  if (unless && unless.test(text)) return [];
149
+ const skipComments = !COMMENT_RULE.test(rule.pattern);
142
150
  const hits = [];
143
151
  const lines = text.split(/\r?\n/);
144
152
  for (let i = 0; i < lines.length; i++) {
145
153
  if (lines[i].length > MAX_LINE) continue;
154
+ if (skipComments && COMMENT_START.test(lines[i].trimStart())) continue;
146
155
  if (re.test(lines[i])) hits.push(i + 1);
147
156
  }
148
157
  return hits;
package/lib/scan.mjs CHANGED
@@ -303,16 +303,16 @@ export async function scanProject(root, rules) {
303
303
  : [];
304
304
 
305
305
  const findings = [];
306
- const seen = new Set();
306
+ // one finding per trap per file, at its lowest matching line
307
+ const first = new Map();
307
308
  const hitRules = new Set();
308
309
  for (const { rel, d, line } of hits) {
309
310
  const ri = detects[d].ri;
310
- const key = `${ri}\0${rel}\0${line}`;
311
- if (seen.has(key)) continue;
312
- seen.add(key);
311
+ const key = `${ri}\0${rel}`;
313
312
  hitRules.add(ri);
314
- findings.push(finding(good[ri], rel, line, false));
313
+ if (!first.has(key) || line < first.get(key).line) first.set(key, { ri, rel, line });
315
314
  }
315
+ for (const { ri, rel, line } of first.values()) findings.push(finding(good[ri], rel, line, false));
316
316
  findings.sort((a, b) => (a.file < b.file ? -1 : a.file > b.file ? 1 : a.line - b.line));
317
317
  // a manual finding and the applicable list both need a ref narrower than "any version": a broad ref
318
318
  // only shows the package is present, which is enough to run detect rules but not to say the trap applies
package/package.json CHANGED
@@ -1 +1 @@
1
- { "name": "@getfootgun/footgun", "version": "0.2.1", "description": "Ask Footgun why a third-party API, platform, or AI tool just broke.", "bin": { "footgun": "bin/footgun.mjs" }, "scripts": { "prepublishOnly": "node ../scripts/check-publish.mjs" }, "type": "module", "license": "MIT", "files": ["bin", "lib", "README.md"], "engines": { "node": ">=20" } }
1
+ { "name": "@getfootgun/footgun", "version": "0.2.2", "description": "Ask Footgun why a third-party API, platform, or AI tool just broke.", "bin": { "footgun": "bin/footgun.mjs" }, "scripts": { "prepublishOnly": "node ../scripts/check-publish.mjs" }, "type": "module", "license": "MIT", "files": ["bin", "lib", "README.md"], "engines": { "node": ">=20" } }