pr-checkmate 4.0.1 → 4.1.0

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,3 +1,4 @@
1
+ import { Document, LineCounter } from 'yaml';
1
2
  import { Check } from '../../types.js';
2
3
  /**
3
4
  * True when a parser problem describes something actually wrong with the file —
@@ -6,6 +7,20 @@ import { Check } from '../../types.js';
6
7
  export declare function isFileDefect(problem: {
7
8
  code?: string;
8
9
  }): boolean;
10
+ /**
11
+ * Aliases (`*name`) that point at an anchor which does not exist before them.
12
+ *
13
+ * The parser does not treat this as a parse error: it builds the alias node and only
14
+ * throws when the document is converted to a JS value, which is exactly what a CI
15
+ * runner does next. So `doc.errors` stays empty for a pipeline that reuses a step via
16
+ * `*update-knowledge` after its `&update-knowledge` anchor was deleted, and the check
17
+ * used to pass a file Bitbucket rejects. Found by walking the alias nodes.
18
+ */
19
+ export declare function unresolvedAliases(doc: Document.Parsed, lineCounter: LineCounter): {
20
+ name: string;
21
+ line: number;
22
+ col: number;
23
+ }[];
9
24
  /**
10
25
  * Bundled YAML linter (the `yaml` package — no runner dependency, always
11
26
  * available like Markdown/Spellcheck). Parses every changed `.yml`/`.yaml` file
@@ -5,6 +5,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.yamlLintCheck = void 0;
7
7
  exports.isFileDefect = isFileDefect;
8
+ exports.unresolvedAliases = unresolvedAliases;
8
9
  const node_fs_1 = __importDefault(require("node:fs"));
9
10
  const node_path_1 = __importDefault(require("node:path"));
10
11
  const yaml_1 = require("yaml");
@@ -38,6 +39,27 @@ const NON_DEFECT_CODES = new Set(['TAG_RESOLVE_FAILED']);
38
39
  function isFileDefect(problem) {
39
40
  return problem.code === undefined || !NON_DEFECT_CODES.has(problem.code);
40
41
  }
42
+ /**
43
+ * Aliases (`*name`) that point at an anchor which does not exist before them.
44
+ *
45
+ * The parser does not treat this as a parse error: it builds the alias node and only
46
+ * throws when the document is converted to a JS value, which is exactly what a CI
47
+ * runner does next. So `doc.errors` stays empty for a pipeline that reuses a step via
48
+ * `*update-knowledge` after its `&update-knowledge` anchor was deleted, and the check
49
+ * used to pass a file Bitbucket rejects. Found by walking the alias nodes.
50
+ */
51
+ function unresolvedAliases(doc, lineCounter) {
52
+ const found = [];
53
+ (0, yaml_1.visit)(doc, {
54
+ Alias(_key, node) {
55
+ if (node.resolve(doc) !== undefined)
56
+ return;
57
+ const { line, col } = lineCounter.linePos(node.range?.[0] ?? 0);
58
+ found.push({ name: node.source, line, col });
59
+ },
60
+ });
61
+ return found;
62
+ }
41
63
  /**
42
64
  * Bundled YAML linter (the `yaml` package — no runner dependency, always
43
65
  * available like Markdown/Spellcheck). Parses every changed `.yml`/`.yaml` file
@@ -81,8 +103,18 @@ exports.yamlLintCheck = {
81
103
  }
82
104
  // parseAllDocuments never throws — it collects problems on each document,
83
105
  // so a single malformed file cannot abort the whole check.
84
- const docs = (0, yaml_1.parseAllDocuments)(source, { prettyErrors: true });
106
+ const lineCounter = new yaml_1.LineCounter();
107
+ const docs = (0, yaml_1.parseAllDocuments)(source, { prettyErrors: true, lineCounter });
85
108
  for (const doc of docs) {
109
+ for (const alias of unresolvedAliases(doc, lineCounter)) {
110
+ issues.push({
111
+ file,
112
+ blocking: true,
113
+ line: alias.line,
114
+ col: alias.col,
115
+ message: `Unresolved alias "*${alias.name}" (the anchor must be declared before the alias)`,
116
+ });
117
+ }
86
118
  const problems = [
87
119
  ...doc.errors.map(p => ({ problem: p, blocking: true })),
88
120
  ...doc.warnings.map(p => ({ problem: p, blocking: false })),
@@ -1,3 +1,4 @@
1
+ import { Document, LineCounter } from 'yaml';
1
2
  import { Check } from '../../types.js';
2
3
  /**
3
4
  * True when a parser problem describes something actually wrong with the file —
@@ -6,6 +7,20 @@ import { Check } from '../../types.js';
6
7
  export declare function isFileDefect(problem: {
7
8
  code?: string;
8
9
  }): boolean;
10
+ /**
11
+ * Aliases (`*name`) that point at an anchor which does not exist before them.
12
+ *
13
+ * The parser does not treat this as a parse error: it builds the alias node and only
14
+ * throws when the document is converted to a JS value, which is exactly what a CI
15
+ * runner does next. So `doc.errors` stays empty for a pipeline that reuses a step via
16
+ * `*update-knowledge` after its `&update-knowledge` anchor was deleted, and the check
17
+ * used to pass a file Bitbucket rejects. Found by walking the alias nodes.
18
+ */
19
+ export declare function unresolvedAliases(doc: Document.Parsed, lineCounter: LineCounter): {
20
+ name: string;
21
+ line: number;
22
+ col: number;
23
+ }[];
9
24
  /**
10
25
  * Bundled YAML linter (the `yaml` package — no runner dependency, always
11
26
  * available like Markdown/Spellcheck). Parses every changed `.yml`/`.yaml` file
@@ -1,6 +1,6 @@
1
1
  import fs from 'node:fs';
2
2
  import path from 'node:path';
3
- import { parseAllDocuments } from 'yaml';
3
+ import { LineCounter, parseAllDocuments, visit } from 'yaml';
4
4
  import { logger, matchesAnyGlob } from '../../../utils/index.js';
5
5
  import { resolveTargetFiles } from '../../targets.js';
6
6
  import { pass, skip } from '../../types.js';
@@ -31,6 +31,27 @@ const NON_DEFECT_CODES = new Set(['TAG_RESOLVE_FAILED']);
31
31
  export function isFileDefect(problem) {
32
32
  return problem.code === undefined || !NON_DEFECT_CODES.has(problem.code);
33
33
  }
34
+ /**
35
+ * Aliases (`*name`) that point at an anchor which does not exist before them.
36
+ *
37
+ * The parser does not treat this as a parse error: it builds the alias node and only
38
+ * throws when the document is converted to a JS value, which is exactly what a CI
39
+ * runner does next. So `doc.errors` stays empty for a pipeline that reuses a step via
40
+ * `*update-knowledge` after its `&update-knowledge` anchor was deleted, and the check
41
+ * used to pass a file Bitbucket rejects. Found by walking the alias nodes.
42
+ */
43
+ export function unresolvedAliases(doc, lineCounter) {
44
+ const found = [];
45
+ visit(doc, {
46
+ Alias(_key, node) {
47
+ if (node.resolve(doc) !== undefined)
48
+ return;
49
+ const { line, col } = lineCounter.linePos(node.range?.[0] ?? 0);
50
+ found.push({ name: node.source, line, col });
51
+ },
52
+ });
53
+ return found;
54
+ }
34
55
  /**
35
56
  * Bundled YAML linter (the `yaml` package — no runner dependency, always
36
57
  * available like Markdown/Spellcheck). Parses every changed `.yml`/`.yaml` file
@@ -74,8 +95,18 @@ export const yamlLintCheck = {
74
95
  }
75
96
  // parseAllDocuments never throws — it collects problems on each document,
76
97
  // so a single malformed file cannot abort the whole check.
77
- const docs = parseAllDocuments(source, { prettyErrors: true });
98
+ const lineCounter = new LineCounter();
99
+ const docs = parseAllDocuments(source, { prettyErrors: true, lineCounter });
78
100
  for (const doc of docs) {
101
+ for (const alias of unresolvedAliases(doc, lineCounter)) {
102
+ issues.push({
103
+ file,
104
+ blocking: true,
105
+ line: alias.line,
106
+ col: alias.col,
107
+ message: `Unresolved alias "*${alias.name}" (the anchor must be declared before the alias)`,
108
+ });
109
+ }
79
110
  const problems = [
80
111
  ...doc.errors.map(p => ({ problem: p, blocking: true })),
81
112
  ...doc.warnings.map(p => ({ problem: p, blocking: false })),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pr-checkmate",
3
- "version": "4.0.1",
3
+ "version": "4.1.0",
4
4
  "description": "Security-first PR gate for CI: secret scanning, grype dependency scanning, SBOM — plus linting and formatting across 11 languages",
5
5
  "keywords": [
6
6
  "github-actions",