@descent-vtt/spec-guard 0.5.0 → 0.5.1

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/dist/text.js CHANGED
@@ -10,22 +10,33 @@
10
10
  /** Offsets at which each line begins. Index 0 is line 1. */
11
11
  export function lineStarts(source) {
12
12
  const starts = [0];
13
- for (let index = 0; index < source.length; index++) {
14
- if (source[index] === '\n')
15
- starts.push(index + 1);
13
+ // Driven by indexOf rather than a character loop. Same answer, one less
14
+ // bound to get wrong - `index < source.length` reads past the end harmlessly
15
+ // when loosened, so nothing could ever fail on it - and it hands the scan to
16
+ // the engine's own memchr instead of walking UTF-16 units in JavaScript.
17
+ for (let at = source.indexOf('\n'); at !== -1; at = source.indexOf('\n', at + 1)) {
18
+ starts.push(at + 1);
16
19
  }
17
20
  return starts;
18
21
  }
19
- /** Resolves an offset to a 1-based line and column, in O(log n). */
22
+ /**
23
+ * Resolves an offset to a 1-based line and column, in O(log n).
24
+ *
25
+ * The search is half-open - `high` is one past the last candidate - and narrows
26
+ * until exactly one candidate is left. Written with an inclusive `high` it
27
+ * needed a `- 1` that could be perturbed without changing any answer, because
28
+ * an out-of-range probe reads `undefined`, compares false, and converges anyway.
29
+ * There is no arithmetic here that a wrong value would leave working.
30
+ */
20
31
  export function locate(starts, index) {
21
32
  let low = 0;
22
- let high = starts.length - 1;
23
- while (low < high) {
24
- const mid = (low + high + 1) >> 1;
33
+ let high = starts.length;
34
+ while (high - low > 1) {
35
+ const mid = (low + high) >> 1;
25
36
  if (starts[mid] <= index)
26
37
  low = mid;
27
38
  else
28
- high = mid - 1;
39
+ high = mid;
29
40
  }
30
41
  return { line: low + 1, column: index - starts[low] + 1 };
31
42
  }
@@ -43,25 +54,25 @@ export function maskRanges(source, ranges) {
43
54
  // per character in the file, and measured at roughly half the throughput of
44
55
  // this on a 1 MB source. Both are O(n); only one of them is fast.
45
56
  // Sorted because the cursor only moves forward: an out-of-order range would
46
- // be clamped away by the guard below and silently never masked.
57
+ // be clamped away and silently never masked.
47
58
  const sorted = [...ranges].sort((a, b) => a[0] - b[0]);
48
59
  const parts = [];
49
60
  let cursor = 0;
50
61
  for (const [start, end] of sorted) {
51
62
  const from = Math.max(start, cursor);
52
63
  const to = Math.min(end, source.length);
53
- // Covers three things at once, which is why there is no separate filter:
54
- // an empty range, an inverted one, and a range already inside one that has
55
- // been masked. Letting the third through would move the cursor backwards
56
- // and duplicate the text between.
57
- if (to <= from)
58
- continue;
59
- if (from > cursor)
60
- parts.push(source.slice(cursor, from));
64
+ // No guard on the degenerate cases, because there is nothing left for one
65
+ // to do. An empty range, an inverted one and a range already inside one
66
+ // that has been masked all produce two empty or already-emitted slices,
67
+ // and advancing the cursor to the furthest point reached - rather than to
68
+ // `to`, which can lie behind it - is what stops it moving backwards and
69
+ // duplicating the text between. The special case used to be a branch whose
70
+ // only distinguishing input was one nothing produces; now it is arithmetic.
71
+ parts.push(source.slice(cursor, from));
61
72
  // No `u` flag: the replacement walks UTF-16 code units, so a surrogate pair
62
73
  // becomes two spaces and every later offset still lands where it did.
63
74
  parts.push(source.slice(from, to).replace(/[^\n]/g, ' '));
64
- cursor = to;
75
+ cursor = Math.max(from, to);
65
76
  }
66
77
  parts.push(source.slice(cursor));
67
78
  return parts.join('');
package/dist/text.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"text.js","sourceRoot":"","sources":["../src/text.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,4DAA4D;AAC5D,MAAM,UAAU,UAAU,CAAC,MAAc;IACvC,MAAM,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC;IACnB,KAAK,IAAI,KAAK,GAAG,CAAC,EAAE,KAAK,GAAG,MAAM,CAAC,MAAM,EAAE,KAAK,EAAE,EAAE,CAAC;QACnD,IAAI,MAAM,CAAC,KAAK,CAAC,KAAK,IAAI;YAAE,MAAM,CAAC,IAAI,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC;IACrD,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,oEAAoE;AACpE,MAAM,UAAU,MAAM,CAAC,MAAyB,EAAE,KAAa;IAC7D,IAAI,GAAG,GAAG,CAAC,CAAC;IACZ,IAAI,IAAI,GAAG,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC;IAC7B,OAAO,GAAG,GAAG,IAAI,EAAE,CAAC;QAClB,MAAM,GAAG,GAAG,CAAC,GAAG,GAAG,IAAI,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC;QAClC,IAAK,MAAM,CAAC,GAAG,CAAY,IAAI,KAAK;YAAE,GAAG,GAAG,GAAG,CAAC;;YAC3C,IAAI,GAAG,GAAG,GAAG,CAAC,CAAC;IACtB,CAAC;IACD,OAAO,EAAE,IAAI,EAAE,GAAG,GAAG,CAAC,EAAE,MAAM,EAAE,KAAK,GAAI,MAAM,CAAC,GAAG,CAAY,GAAG,CAAC,EAAE,CAAC;AACxE,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,UAAU,CAAC,MAAc,EAAE,MAA2C;IACpF,yEAAyE;IACzE,2EAA2E;IAC3E,4EAA4E;IAC5E,kEAAkE;IAClE,4EAA4E;IAC5E,gEAAgE;IAChE,MAAM,MAAM,GAAG,CAAC,GAAG,MAAM,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IACvD,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,IAAI,MAAM,GAAG,CAAC,CAAC;IAEf,KAAK,MAAM,CAAC,KAAK,EAAE,GAAG,CAAC,IAAI,MAAM,EAAE,CAAC;QAClC,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC;QACrC,MAAM,EAAE,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC;QACxC,yEAAyE;QACzE,2EAA2E;QAC3E,yEAAyE;QACzE,kCAAkC;QAClC,IAAI,EAAE,IAAI,IAAI;YAAE,SAAS;QACzB,IAAI,IAAI,GAAG,MAAM;YAAE,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC,CAAC;QAC1D,4EAA4E;QAC5E,sEAAsE;QACtE,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC,CAAC;QAC1D,MAAM,GAAG,EAAE,CAAC;IACd,CAAC;IAED,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC;IACjC,OAAO,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;AACxB,CAAC"}
1
+ {"version":3,"file":"text.js","sourceRoot":"","sources":["../src/text.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,4DAA4D;AAC5D,MAAM,UAAU,UAAU,CAAC,MAAc;IACvC,MAAM,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC;IACnB,wEAAwE;IACxE,6EAA6E;IAC7E,6EAA6E;IAC7E,yEAAyE;IACzE,KAAK,IAAI,EAAE,GAAG,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,EAAE,KAAK,CAAC,CAAC,EAAE,EAAE,GAAG,MAAM,CAAC,OAAO,CAAC,IAAI,EAAE,EAAE,GAAG,CAAC,CAAC,EAAE,CAAC;QACjF,MAAM,CAAC,IAAI,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC;IACtB,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,MAAM,CAAC,MAAyB,EAAE,KAAa;IAC7D,IAAI,GAAG,GAAG,CAAC,CAAC;IACZ,IAAI,IAAI,GAAG,MAAM,CAAC,MAAM,CAAC;IACzB,OAAO,IAAI,GAAG,GAAG,GAAG,CAAC,EAAE,CAAC;QACtB,MAAM,GAAG,GAAG,CAAC,GAAG,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC;QAC9B,IAAK,MAAM,CAAC,GAAG,CAAY,IAAI,KAAK;YAAE,GAAG,GAAG,GAAG,CAAC;;YAC3C,IAAI,GAAG,GAAG,CAAC;IAClB,CAAC;IACD,OAAO,EAAE,IAAI,EAAE,GAAG,GAAG,CAAC,EAAE,MAAM,EAAE,KAAK,GAAI,MAAM,CAAC,GAAG,CAAY,GAAG,CAAC,EAAE,CAAC;AACxE,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,UAAU,CAAC,MAAc,EAAE,MAA2C;IACpF,yEAAyE;IACzE,2EAA2E;IAC3E,4EAA4E;IAC5E,kEAAkE;IAClE,4EAA4E;IAC5E,6CAA6C;IAC7C,MAAM,MAAM,GAAG,CAAC,GAAG,MAAM,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IACvD,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,IAAI,MAAM,GAAG,CAAC,CAAC;IAEf,KAAK,MAAM,CAAC,KAAK,EAAE,GAAG,CAAC,IAAI,MAAM,EAAE,CAAC;QAClC,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC;QACrC,MAAM,EAAE,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC;QACxC,0EAA0E;QAC1E,wEAAwE;QACxE,wEAAwE;QACxE,0EAA0E;QAC1E,wEAAwE;QACxE,2EAA2E;QAC3E,4EAA4E;QAC5E,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC,CAAC;QACvC,4EAA4E;QAC5E,sEAAsE;QACtE,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC,CAAC;QAC1D,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;IAC9B,CAAC;IAED,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC;IACjC,OAAO,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;AACxB,CAAC"}
package/package.json CHANGED
@@ -1,73 +1,73 @@
1
- {
2
- "name": "@descent-vtt/spec-guard",
3
- "version": "0.5.0",
4
- "description": "Executable architecture assertions for Markdown specs & ADRs.",
5
- "keywords": [
6
- "adr",
7
- "architecture",
8
- "documentation",
9
- "drift",
10
- "lint",
11
- "markdown",
12
- "ripgrep",
13
- "spec"
14
- ],
15
- "license": "MIT",
16
- "type": "module",
17
- "engines": {
18
- "node": ">=22"
19
- },
20
- "bin": {
21
- "spec-guard": "bin/spec-guard.js"
22
- },
23
- "main": "./dist/index.js",
24
- "types": "./dist/index.d.ts",
25
- "exports": {
26
- ".": {
27
- "types": "./dist/index.d.ts",
28
- "default": "./dist/index.js"
29
- },
30
- "./package.json": "./package.json"
31
- },
32
- "files": [
33
- "bin",
34
- "dist",
35
- "CHANGELOG.md",
36
- "README.md",
37
- "LICENSE"
38
- ],
39
- "publishConfig": {
40
- "access": "public"
41
- },
42
- "scripts": {
43
- "build": "tsc -p tsconfig.build.json",
44
- "clean": "node -e \"require('node:fs').rmSync('dist',{recursive:true,force:true})\"",
45
- "lint": "tsc -p tsconfig.json --noEmit",
46
- "test": "vitest run",
47
- "test:watch": "vitest",
48
- "test:coverage": "vitest run --coverage",
49
- "test:mutation": "stryker run",
50
- "selfcheck": "node ./bin/spec-guard.js \"docs/**/*.md\" \"README.md\" --verbose",
51
- "prepublishOnly": "npm run clean && npm run build"
52
- },
53
- "devDependencies": {
54
- "@stryker-mutator/core": "^10.0.0",
55
- "@stryker-mutator/vitest-runner": "^10.0.0",
56
- "@types/node": "^26.4.1",
57
- "@vitest/coverage-v8": "^4.1.11",
58
- "@vscode/ripgrep": "^1.18.0",
59
- "typescript": "^7.0.2",
60
- "vitest": "^4.1.11"
61
- },
62
- "repository": {
63
- "type": "git",
64
- "url": "git+https://github.com/DescentVTT/spec-guard.git"
65
- },
66
- "bugs": {
67
- "url": "https://github.com/DescentVTT/spec-guard/issues"
68
- },
69
- "homepage": "https://github.com/DescentVTT/spec-guard#readme",
70
- "overrides": {
71
- "qs": "^6.16.0"
72
- }
73
- }
1
+ {
2
+ "name": "@descent-vtt/spec-guard",
3
+ "version": "0.5.1",
4
+ "description": "Executable architecture assertions for Markdown specs & ADRs.",
5
+ "keywords": [
6
+ "adr",
7
+ "architecture",
8
+ "documentation",
9
+ "drift",
10
+ "lint",
11
+ "markdown",
12
+ "ripgrep",
13
+ "spec"
14
+ ],
15
+ "license": "MIT",
16
+ "type": "module",
17
+ "engines": {
18
+ "node": ">=22"
19
+ },
20
+ "bin": {
21
+ "spec-guard": "bin/spec-guard.js"
22
+ },
23
+ "main": "./dist/index.js",
24
+ "types": "./dist/index.d.ts",
25
+ "exports": {
26
+ ".": {
27
+ "types": "./dist/index.d.ts",
28
+ "default": "./dist/index.js"
29
+ },
30
+ "./package.json": "./package.json"
31
+ },
32
+ "files": [
33
+ "bin",
34
+ "dist",
35
+ "CHANGELOG.md",
36
+ "README.md",
37
+ "LICENSE"
38
+ ],
39
+ "publishConfig": {
40
+ "access": "public"
41
+ },
42
+ "scripts": {
43
+ "build": "tsc -p tsconfig.build.json",
44
+ "clean": "node -e \"require('node:fs').rmSync('dist',{recursive:true,force:true})\"",
45
+ "lint": "tsc -p tsconfig.json --noEmit",
46
+ "test": "vitest run",
47
+ "test:watch": "vitest",
48
+ "test:coverage": "vitest run --coverage",
49
+ "test:mutation": "stryker run",
50
+ "selfcheck": "node ./bin/spec-guard.js \"docs/**/*.md\" \"README.md\" --verbose",
51
+ "prepublishOnly": "npm run clean && npm run build"
52
+ },
53
+ "devDependencies": {
54
+ "@stryker-mutator/core": "^10.0.0",
55
+ "@stryker-mutator/vitest-runner": "^10.0.0",
56
+ "@types/node": "^26.4.1",
57
+ "@vitest/coverage-v8": "^4.1.11",
58
+ "@vscode/ripgrep": "^1.18.0",
59
+ "typescript": "^7.0.2",
60
+ "vitest": "^4.1.11"
61
+ },
62
+ "repository": {
63
+ "type": "git",
64
+ "url": "git+https://github.com/DescentVTT/spec-guard.git"
65
+ },
66
+ "bugs": {
67
+ "url": "https://github.com/DescentVTT/spec-guard/issues"
68
+ },
69
+ "homepage": "https://github.com/DescentVTT/spec-guard#readme",
70
+ "overrides": {
71
+ "qs": "^6.16.0"
72
+ }
73
+ }