@gobing-ai/ts-rule-engine 0.4.2 → 0.4.4

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.
@@ -1 +1 @@
1
- {"version":3,"file":"ripgrep-evaluator.d.ts","sourceRoot":"","sources":["../../src/evaluators/ripgrep-evaluator.ts"],"names":[],"mappings":"AAAA,OAAO,EAAuB,KAAK,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAClF,OAAO,EACH,KAAK,cAAc,EAEnB,KAAK,WAAW,EAChB,KAAK,oBAAoB,EACzB,KAAK,aAAa,EACrB,MAAM,UAAU,CAAC;AAGlB;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,qBAAa,gBAAiB,YAAW,aAAa;IAClD,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAkB;gBAE/B,QAAQ,GAAE,eAA2C;IAIjE,oFAAoF;IAC9E,QAAQ,CAAC,IAAI,EAAE,cAAc,EAAE,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,oBAAoB,CAAC;CA2B5F;AA+ED;;;;;;;;;;;GAWG;AACH,wBAAgB,0BAA0B,CACtC,OAAO,EAAE,MAAM,GAChB;IAAE,UAAU,EAAE,IAAI,CAAA;CAAE,GAAG;IAAE,UAAU,EAAE,KAAK,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAK/D"}
1
+ {"version":3,"file":"ripgrep-evaluator.d.ts","sourceRoot":"","sources":["../../src/evaluators/ripgrep-evaluator.ts"],"names":[],"mappings":"AAAA,OAAO,EAAuB,KAAK,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAClF,OAAO,EACH,KAAK,cAAc,EAEnB,KAAK,WAAW,EAChB,KAAK,oBAAoB,EACzB,KAAK,aAAa,EACrB,MAAM,UAAU,CAAC;AAGlB;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,qBAAa,gBAAiB,YAAW,aAAa;IAClD,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAkB;gBAE/B,QAAQ,GAAE,eAA2C;IAIjE,oFAAoF;IAC9E,QAAQ,CAAC,IAAI,EAAE,cAAc,EAAE,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,oBAAoB,CAAC;CA8B5F;AAmFD;;;;;;;;;;;GAWG;AACH,wBAAgB,0BAA0B,CACtC,OAAO,EAAE,MAAM,GAChB;IAAE,UAAU,EAAE,IAAI,CAAA;CAAE,GAAG;IAAE,UAAU,EAAE,KAAK,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAK/D"}
@@ -44,10 +44,14 @@ export class RipgrepEvaluator {
44
44
  rejectOnError: false,
45
45
  label: 'rg',
46
46
  });
47
- // ripgrep exits 0 with matches, 1 when none, 2 on error. Treat 2 (or any non-0/1
48
- // with stderr) as a hard failure so a broken pattern or missing `rg` fails loud.
47
+ // ripgrep exits 0 with matches, 1 when none, 2 on error. A scoped rule whose
48
+ // positive globs match no files also exits 2 with "No files were searched";
49
+ // that means "this rule is not applicable in this repo", not a broken
50
+ // evaluator. Keep all other exit-2 cases loud.
49
51
  if (result.exitCode !== 0 && result.exitCode !== 1) {
50
52
  const detail = result.stderr.trim();
53
+ if (isNoFilesSearched(detail))
54
+ return { findings: [], fixes: [] };
51
55
  throw new Error(`rg failed (exit ${result.exitCode})${detail.length > 0 ? `: ${detail}` : ''}`);
52
56
  }
53
57
  return mode === 'require'
@@ -55,6 +59,9 @@ export class RipgrepEvaluator {
55
59
  : { findings: forbidFindings(rule, pattern, result.stdout), fixes: [] };
56
60
  }
57
61
  }
62
+ function isNoFilesSearched(stderr) {
63
+ return stderr.includes('No files were searched');
64
+ }
58
65
  /** Build the ripgrep argument list for the given mode and scope. */
59
66
  function buildArgs(pattern, mode, multiline, include, exclude) {
60
67
  const args = [];
@@ -42,8 +42,8 @@ export class TsdocExportEvaluator {
42
42
  for (const { file, content } of files) {
43
43
  const lines = content.split('\n');
44
44
  for (const site of findExports(lines, requested)) {
45
- if (!precededByJsdoc(lines, site.line)) {
46
- findings.push(createFinding(rule, `Exported ${site.kind} "${site.name}" is missing a JSDoc comment`, file, {
45
+ if (!precededByDoc(lines, site.line)) {
46
+ findings.push(createFinding(rule, `Exported ${site.kind} "${site.name}" is missing a doc comment`, file, {
47
47
  line: site.line,
48
48
  code: 'tsdoc:missing',
49
49
  }));
@@ -71,17 +71,19 @@ function findExports(lines, requested) {
71
71
  return sites;
72
72
  }
73
73
  /**
74
- * True when a JSDoc block precedes a declaration.
74
+ * True when a documentation comment precedes a declaration.
75
75
  *
76
- * Walks backward from the line before the declaration, skipping blank lines,
77
- * JSDoc body lines (`* …`), and decorator / JSDoc-tag lines (`@…`), then
78
- * checks whether the nearest meaningful line closes (or is) a JSDoc comment.
76
+ * Accepts both JSDoc blocks (`/** … *\/`) and line comments (`// …`) as valid
77
+ * preceding documentation. Walks backward from the line before the declaration,
78
+ * skipping blank lines, JSDoc body lines (`* …`), decorator / JSDoc-tag lines
79
+ * (`@…`), and contiguous `//` comment lines, then checks whether the nearest
80
+ * meaningful line is a JSDoc fence, a `//` comment, or the start of a `//` block.
79
81
  */
80
- function precededByJsdoc(lines, declarationLine) {
82
+ function precededByDoc(lines, declarationLine) {
81
83
  let cursor = declarationLine - 2; // zero-based line above the declaration
82
84
  while (cursor >= 0) {
83
85
  const prev = lines[cursor]?.trim() ?? '';
84
- // Blank line between JSDoc closing fence and declaration — keep walking.
86
+ // Blank line between comment and declaration — keep walking.
85
87
  if (prev === '') {
86
88
  cursor -= 1;
87
89
  continue;
@@ -90,13 +92,17 @@ function precededByJsdoc(lines, declarationLine) {
90
92
  if (prev.endsWith('*/') || prev.startsWith('/**')) {
91
93
  return true;
92
94
  }
95
+ // Line comment (// …) — accepted as documentation.
96
+ if (prev.startsWith('//')) {
97
+ return true;
98
+ }
93
99
  // JSDoc body line (* …) or decorator / tag (@…). Check after the
94
- // fence so `*/` is never misidentified as a body line.
100
+ // fences so `*/` is never misidentified as a body line.
95
101
  if (prev.startsWith('*') || prev.startsWith('@')) {
96
102
  cursor -= 1;
97
103
  continue;
98
104
  }
99
- // Non-JSDoc, non-decorator line — no JSDoc precedes this declaration.
105
+ // Non-comment, non-decorator line — no documentation precedes this declaration.
100
106
  return false;
101
107
  }
102
108
  return false;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gobing-ai/ts-rule-engine",
3
- "version": "0.4.2",
3
+ "version": "0.4.4",
4
4
  "description": "@gobing-ai/ts-rule-engine — Constraint rule schemas, loading, evaluation, and result formatting.",
5
5
  "keywords": [
6
6
  "typescript",
@@ -49,10 +49,10 @@
49
49
  "release": "echo 'Manual publish is disabled. Releases go through GitHub Actions via Trusted Publishing — push a tag: git tag @gobing-ai/ts-rule-engine-v<version> && git push --tags' && exit 1"
50
50
  },
51
51
  "dependencies": {
52
- "@gobing-ai/ts-ai-runner": "^0.4.2",
53
- "@gobing-ai/ts-infra": "^0.4.2",
54
- "@gobing-ai/ts-runtime": "^0.4.2",
55
- "@gobing-ai/ts-db": "^0.4.2",
52
+ "@gobing-ai/ts-ai-runner": "^0.4.4",
53
+ "@gobing-ai/ts-infra": "^0.4.4",
54
+ "@gobing-ai/ts-runtime": "^0.4.4",
55
+ "@gobing-ai/ts-db": "^0.4.4",
56
56
  "yaml": "^2.7.0",
57
57
  "zod": "^4.1.0"
58
58
  },
@@ -1,7 +1,7 @@
1
- # TSDoc export rules — every public export must carry a JSDoc comment.
1
+ # TSDoc export rules — every public export must carry a doc comment (JSDoc `/** */` or line `//`).
2
2
  rules:
3
3
  - id: every-export-has-tsdoc
4
- description: "Every export (function, class, interface, type, const, enum) must have a /** ... */ JSDoc comment describing what it is and why."
4
+ description: "Every export (function, class, interface, type, const, enum) must have a preceding doc comment — either a JSDoc block (`/** ... */`) or a line comment (`// ...`) describing what it is and why."
5
5
  severity: error
6
6
  evaluator:
7
7
  type: tsdoc-export
@@ -55,10 +55,13 @@ export class RipgrepEvaluator implements RuleEvaluator {
55
55
  label: 'rg',
56
56
  });
57
57
 
58
- // ripgrep exits 0 with matches, 1 when none, 2 on error. Treat 2 (or any non-0/1
59
- // with stderr) as a hard failure so a broken pattern or missing `rg` fails loud.
58
+ // ripgrep exits 0 with matches, 1 when none, 2 on error. A scoped rule whose
59
+ // positive globs match no files also exits 2 with "No files were searched";
60
+ // that means "this rule is not applicable in this repo", not a broken
61
+ // evaluator. Keep all other exit-2 cases loud.
60
62
  if (result.exitCode !== 0 && result.exitCode !== 1) {
61
63
  const detail = result.stderr.trim();
64
+ if (isNoFilesSearched(detail)) return { findings: [], fixes: [] };
62
65
  throw new Error(`rg failed (exit ${result.exitCode})${detail.length > 0 ? `: ${detail}` : ''}`);
63
66
  }
64
67
 
@@ -68,6 +71,10 @@ export class RipgrepEvaluator implements RuleEvaluator {
68
71
  }
69
72
  }
70
73
 
74
+ function isNoFilesSearched(stderr: string): boolean {
75
+ return stderr.includes('No files were searched');
76
+ }
77
+
71
78
  /** Build the ripgrep argument list for the given mode and scope. */
72
79
  function buildArgs(pattern: string, mode: string, multiline: boolean, include: string[], exclude: string[]): string[] {
73
80
  const args: string[] = [];
@@ -62,9 +62,9 @@ export class TsdocExportEvaluator implements RuleEvaluator {
62
62
  for (const { file, content } of files) {
63
63
  const lines = content.split('\n');
64
64
  for (const site of findExports(lines, requested)) {
65
- if (!precededByJsdoc(lines, site.line)) {
65
+ if (!precededByDoc(lines, site.line)) {
66
66
  findings.push(
67
- createFinding(rule, `Exported ${site.kind} "${site.name}" is missing a JSDoc comment`, file, {
67
+ createFinding(rule, `Exported ${site.kind} "${site.name}" is missing a doc comment`, file, {
68
68
  line: site.line,
69
69
  code: 'tsdoc:missing',
70
70
  }),
@@ -94,17 +94,19 @@ function findExports(lines: string[], requested: ReadonlySet<ExportKind>): Expor
94
94
  }
95
95
 
96
96
  /**
97
- * True when a JSDoc block precedes a declaration.
97
+ * True when a documentation comment precedes a declaration.
98
98
  *
99
- * Walks backward from the line before the declaration, skipping blank lines,
100
- * JSDoc body lines (`* …`), and decorator / JSDoc-tag lines (`@…`), then
101
- * checks whether the nearest meaningful line closes (or is) a JSDoc comment.
99
+ * Accepts both JSDoc blocks (`/** … *\/`) and line comments (`// …`) as valid
100
+ * preceding documentation. Walks backward from the line before the declaration,
101
+ * skipping blank lines, JSDoc body lines (`* …`), decorator / JSDoc-tag lines
102
+ * (`@…`), and contiguous `//` comment lines, then checks whether the nearest
103
+ * meaningful line is a JSDoc fence, a `//` comment, or the start of a `//` block.
102
104
  */
103
- function precededByJsdoc(lines: string[], declarationLine: number): boolean {
105
+ function precededByDoc(lines: string[], declarationLine: number): boolean {
104
106
  let cursor = declarationLine - 2; // zero-based line above the declaration
105
107
  while (cursor >= 0) {
106
108
  const prev = lines[cursor]?.trim() ?? '';
107
- // Blank line between JSDoc closing fence and declaration — keep walking.
109
+ // Blank line between comment and declaration — keep walking.
108
110
  if (prev === '') {
109
111
  cursor -= 1;
110
112
  continue;
@@ -113,13 +115,17 @@ function precededByJsdoc(lines: string[], declarationLine: number): boolean {
113
115
  if (prev.endsWith('*/') || prev.startsWith('/**')) {
114
116
  return true;
115
117
  }
118
+ // Line comment (// …) — accepted as documentation.
119
+ if (prev.startsWith('//')) {
120
+ return true;
121
+ }
116
122
  // JSDoc body line (* …) or decorator / tag (@…). Check after the
117
- // fence so `*/` is never misidentified as a body line.
123
+ // fences so `*/` is never misidentified as a body line.
118
124
  if (prev.startsWith('*') || prev.startsWith('@')) {
119
125
  cursor -= 1;
120
126
  continue;
121
127
  }
122
- // Non-JSDoc, non-decorator line — no JSDoc precedes this declaration.
128
+ // Non-comment, non-decorator line — no documentation precedes this declaration.
123
129
  return false;
124
130
  }
125
131
  return false;