@markup-carve/carve-lsp 0.1.3 → 0.1.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.
Files changed (120) hide show
  1. package/README.md +52 -13
  2. package/dist/analyze.d.ts +6 -0
  3. package/dist/analyze.js +39 -2
  4. package/dist/analyze.js.map +1 -1
  5. package/dist/analyze.test.js +7 -0
  6. package/dist/analyze.test.js.map +1 -1
  7. package/dist/codelens.js +12 -0
  8. package/dist/codelens.js.map +1 -1
  9. package/dist/codelens.test.js +5 -0
  10. package/dist/codelens.test.js.map +1 -1
  11. package/dist/colon-fence-kinds.test.d.ts +1 -0
  12. package/dist/colon-fence-kinds.test.js +128 -0
  13. package/dist/colon-fence-kinds.test.js.map +1 -0
  14. package/dist/colon-fences.d.ts +24 -0
  15. package/dist/colon-fences.js +174 -0
  16. package/dist/colon-fences.js.map +1 -0
  17. package/dist/colon-fences.test.d.ts +1 -0
  18. package/dist/colon-fences.test.js +23 -0
  19. package/dist/colon-fences.test.js.map +1 -0
  20. package/dist/completion.d.ts +5 -1
  21. package/dist/completion.js +40 -10
  22. package/dist/completion.js.map +1 -1
  23. package/dist/completion.test.js +7 -0
  24. package/dist/completion.test.js.map +1 -1
  25. package/dist/document-highlights.d.ts +2 -0
  26. package/dist/document-highlights.js +21 -0
  27. package/dist/document-highlights.js.map +1 -0
  28. package/dist/document-highlights.test.d.ts +1 -0
  29. package/dist/document-highlights.test.js +13 -0
  30. package/dist/document-highlights.test.js.map +1 -0
  31. package/dist/document-links.d.ts +3 -0
  32. package/dist/document-links.js +36 -0
  33. package/dist/document-links.js.map +1 -0
  34. package/dist/document-links.test.d.ts +1 -0
  35. package/dist/document-links.test.js +12 -0
  36. package/dist/document-links.test.js.map +1 -0
  37. package/dist/format-corpus.test.d.ts +1 -0
  38. package/dist/format-corpus.test.js +16 -0
  39. package/dist/format-corpus.test.js.map +1 -0
  40. package/dist/format.d.ts +3 -0
  41. package/dist/format.js +38 -74
  42. package/dist/format.js.map +1 -1
  43. package/dist/format.test.js +27 -32
  44. package/dist/format.test.js.map +1 -1
  45. package/dist/hover.js +20 -0
  46. package/dist/hover.js.map +1 -1
  47. package/dist/include-completion.d.ts +3 -0
  48. package/dist/include-completion.js +68 -0
  49. package/dist/include-completion.js.map +1 -0
  50. package/dist/include-completion.test.d.ts +1 -0
  51. package/dist/include-completion.test.js +15 -0
  52. package/dist/include-completion.test.js.map +1 -0
  53. package/dist/inlay-hints.d.ts +3 -0
  54. package/dist/inlay-hints.js +43 -0
  55. package/dist/inlay-hints.js.map +1 -0
  56. package/dist/inlay-hints.test.d.ts +1 -0
  57. package/dist/inlay-hints.test.js +10 -0
  58. package/dist/inlay-hints.test.js.map +1 -0
  59. package/dist/lint-actions.d.ts +3 -0
  60. package/dist/lint-actions.js +90 -0
  61. package/dist/lint-actions.js.map +1 -0
  62. package/dist/lint-actions.test.d.ts +1 -0
  63. package/dist/lint-actions.test.js +31 -0
  64. package/dist/lint-actions.test.js.map +1 -0
  65. package/dist/no-git-dependencies.test.d.ts +1 -0
  66. package/dist/no-git-dependencies.test.js +243 -0
  67. package/dist/no-git-dependencies.test.js.map +1 -0
  68. package/dist/packaging.test.d.ts +1 -0
  69. package/dist/packaging.test.js +69 -0
  70. package/dist/packaging.test.js.map +1 -0
  71. package/dist/selection.d.ts +2 -0
  72. package/dist/selection.js +36 -0
  73. package/dist/selection.js.map +1 -0
  74. package/dist/selection.test.d.ts +1 -0
  75. package/dist/selection.test.js +10 -0
  76. package/dist/selection.test.js.map +1 -0
  77. package/dist/semantic.d.ts +5 -3
  78. package/dist/semantic.js +31 -4
  79. package/dist/semantic.js.map +1 -1
  80. package/dist/semantic.test.js +5 -0
  81. package/dist/semantic.test.js.map +1 -1
  82. package/dist/server-capabilities.test.d.ts +1 -0
  83. package/dist/server-capabilities.test.js +84 -0
  84. package/dist/server-capabilities.test.js.map +1 -0
  85. package/dist/server.js +236 -20
  86. package/dist/server.js.map +1 -1
  87. package/dist/settings.d.ts +11 -0
  88. package/dist/settings.js +50 -0
  89. package/dist/settings.js.map +1 -0
  90. package/dist/settings.test.d.ts +1 -0
  91. package/dist/settings.test.js +22 -0
  92. package/dist/settings.test.js.map +1 -0
  93. package/dist/spec-0-1-4-constructs.test.d.ts +1 -0
  94. package/dist/spec-0-1-4-constructs.test.js +101 -0
  95. package/dist/spec-0-1-4-constructs.test.js.map +1 -0
  96. package/dist/table-diagnostics.d.ts +3 -0
  97. package/dist/table-diagnostics.js +60 -0
  98. package/dist/table-diagnostics.js.map +1 -0
  99. package/dist/table-diagnostics.test.d.ts +1 -0
  100. package/dist/table-diagnostics.test.js +12 -0
  101. package/dist/table-diagnostics.test.js.map +1 -0
  102. package/dist/workspace-graph.d.ts +25 -0
  103. package/dist/workspace-graph.js +94 -0
  104. package/dist/workspace-graph.js.map +1 -0
  105. package/dist/workspace-graph.test.d.ts +1 -0
  106. package/dist/workspace-graph.test.js +31 -0
  107. package/dist/workspace-graph.test.js.map +1 -0
  108. package/dist/workspace-index.d.ts +40 -0
  109. package/dist/workspace-index.js +254 -0
  110. package/dist/workspace-index.js.map +1 -0
  111. package/dist/workspace-index.test.d.ts +1 -0
  112. package/dist/workspace-index.test.js +65 -0
  113. package/dist/workspace-index.test.js.map +1 -0
  114. package/dist/workspace-loader.d.ts +11 -0
  115. package/dist/workspace-loader.js +50 -0
  116. package/dist/workspace-loader.js.map +1 -0
  117. package/dist/workspace-loader.test.d.ts +1 -0
  118. package/dist/workspace-loader.test.js +19 -0
  119. package/dist/workspace-loader.test.js.map +1 -0
  120. package/package.json +4 -3
@@ -0,0 +1,31 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import { lintCodeActions } from './lint-actions.js';
4
+ const diagnostic = (code) => ({
5
+ code,
6
+ message: code,
7
+ range: { start: { line: 0, character: 0 }, end: { line: 0, character: 1 } },
8
+ });
9
+ test('offers safe lint quick fixes', () => {
10
+ const raw = lintCodeActions('file:///a.crv', '```raw html\n', [diagnostic('raw-block-syntax')]);
11
+ assert.equal(raw[0]?.edit?.changes?.['file:///a.crv']?.[0]?.newText, '```=html');
12
+ const quote = lintCodeActions('file:///a.crv', '>text\n', [diagnostic('blockquote-marker-without-space')]);
13
+ assert.equal(quote[0]?.edit?.changes?.['file:///a.crv']?.[0]?.newText, ' ');
14
+ });
15
+ test('creates missing footnote and link-reference definitions', () => {
16
+ const footnote = lintCodeActions('file:///a.crv', 'See [^n].\n', [diagnostic('unresolved-footnote')]);
17
+ assert.match(footnote[0]?.edit?.changes?.['file:///a.crv']?.[0]?.newText ?? '', /\[\^n\]:/);
18
+ const link = lintCodeActions('file:///a.crv', 'See [x][r].\n', [diagnostic('unresolved-reference-link')]);
19
+ assert.match(link[0]?.edit?.changes?.['file:///a.crv']?.[0]?.newText ?? '', /\[r\]:/);
20
+ });
21
+ test('offers both explicit intentions for a near colon closer', () => {
22
+ const near = { ...diagnostic('colon-fence-length-mismatch'), data: { expectedWidth: 4 } };
23
+ const actions = lintCodeActions('file:///a.crv', ':::\n', [near]);
24
+ assert.deepEqual(actions.map((action) => action.title), [
25
+ 'Resize to 4 colons and close the container',
26
+ 'Preserve the colon run as literal text',
27
+ ]);
28
+ assert.equal(actions[0]?.edit?.changes?.['file:///a.crv']?.[0]?.newText, '::::');
29
+ assert.equal(actions[1]?.edit?.changes?.['file:///a.crv']?.[0]?.newText, '\\');
30
+ });
31
+ //# sourceMappingURL=lint-actions.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lint-actions.test.js","sourceRoot":"","sources":["../src/lint-actions.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,243 @@
1
+ import assert from 'node:assert/strict';
2
+ import { spawnSync } from 'node:child_process';
3
+ import { mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
4
+ import { tmpdir } from 'node:os';
5
+ import { join } from 'node:path';
6
+ import test, { after } from 'node:test';
7
+ import { fileURLToPath } from 'node:url';
8
+ /**
9
+ * The publish-time guard must detect a git dependency by what a spec IS NOT.
10
+ *
11
+ * WHY THIS FILE EXISTS. `ci.yml` already runs `tools/no-git-dependencies.mjs` on
12
+ * every pull request - but only against the manifest committed here, which is
13
+ * clean. So that step passes with the guard's logic in ANY state: delete the
14
+ * whole rule and it still exits zero, because there is nothing in this manifest
15
+ * for it to find. Running the script against a MUTATED manifest is what makes
16
+ * the logic load-bearing rather than the manifest.
17
+ *
18
+ * WHY THE GUARD WAS WIDENED. The rule was already an inversion, but the "not"
19
+ * was still spelled as a list of protocols plus a slash test, and both miss
20
+ * npm's scp-style URL: it carries no protocol, and when the repository sits at
21
+ * the root of its host it carries no slash either. That is the third hole in
22
+ * this guard family in this org - `git+https://` in pandoc-carve, `github:`
23
+ * here, then the bare `owner/repo#ref` shorthand - and each earlier fix added
24
+ * the next spelling to the list, which is why there was a next one (#123).
25
+ * markup-carve/carve-grammars#299 replaced the tail of the list with a catch-all
26
+ * over the characters no registry range can contain; this is that shape.
27
+ *
28
+ * Both directions are asserted. A rejection has to exit non-zero AND name the
29
+ * field, quote the spec and give a reason; an acceptance has to exit zero. So a
30
+ * guard stuck at always-pass and a guard stuck at always-fail each fail here,
31
+ * which is the property the CI step alone does not have.
32
+ *
33
+ * The `npm:` alias row is the near miss that keeps the rule from collapsing into
34
+ * "reject anything with a slash or an at-sign". An alias resolves from the
35
+ * registry, so rejecting it would be an over-broad fix that breaks a manifest
36
+ * that is fine - and an over-rejecting guard gets switched off by whoever hits
37
+ * it, which is worse than the hole.
38
+ */
39
+ const root = fileURLToPath(new URL('..', import.meta.url));
40
+ const guard = join(root, 'tools', 'no-git-dependencies.mjs');
41
+ const manifestPath = join(root, 'package.json');
42
+ const baseline = JSON.parse(readFileSync(manifestPath, 'utf8'));
43
+ const scratch = mkdtempSync(join(tmpdir(), 'carve-lsp-guard-'));
44
+ after(() => rmSync(scratch, { recursive: true, force: true }));
45
+ /** Run the REAL script against a manifest written to a scratch file. */
46
+ const runGuard = (manifest) => {
47
+ const path = join(scratch, 'package.json');
48
+ writeFileSync(path, JSON.stringify(manifest, null, 2), 'utf8');
49
+ const run = spawnSync(process.execPath, [guard, path], { encoding: 'utf8' });
50
+ assert.equal(run.error, undefined, `spawning the guard failed: ${run.error}`);
51
+ assert.notEqual(run.status, null, 'the guard was killed by a signal rather than exiting');
52
+ return { status: run.status, out: `${run.stdout}${run.stderr}` };
53
+ };
54
+ const ENGINE = '@markup-carve/carve';
55
+ const withDep = (spec) => ({
56
+ ...baseline,
57
+ dependencies: { ...baseline.dependencies, [ENGINE]: spec },
58
+ });
59
+ /**
60
+ * Each row: what the spec is, the manifest carrying it, and whether a consumer's
61
+ * npm could install it from the registry alone. Cross-checked while writing
62
+ * against `npm-package-arg`, the module npm itself classifies a spec with -
63
+ * every `reject` row is one npa either calls `git` or refuses to classify at
64
+ * all, and every `accept` row is one it calls version, range, tag or alias.
65
+ */
66
+ const MATRIX = [
67
+ ['a github: pin', withDep('github:markup-carve/carve-js#61f824d'), 'reject'],
68
+ [
69
+ 'a git+https:// pin',
70
+ withDep('git+https://github.com/markup-carve/carve-js.git#61f824d'),
71
+ 'reject',
72
+ ],
73
+ [
74
+ 'a bare owner/repo#sha shorthand',
75
+ withDep('markup-carve/carve-js#61f824d5d5724bfaa26dd07dc5c159249a66c977'),
76
+ 'reject',
77
+ ],
78
+ [
79
+ 'a git spec in optionalDependencies',
80
+ { ...baseline, optionalDependencies: { 'some-tool': 'github:markup-carve/some-tool#abc123' } },
81
+ 'reject',
82
+ ],
83
+ // The two rows this ticket is about. The first still carries a slash, so the
84
+ // previous version caught it by accident; the second does not, and was
85
+ // accepted.
86
+ ['an scp-style git URL', withDep('git@github.com:markup-carve/carve-js.git'), 'reject'],
87
+ ['an scp-style git URL with no slash', withDep('git@example.com:repo.git'), 'reject'],
88
+ // Caught by the catch-all rather than by a pattern named for them, which is
89
+ // the point of the catch-all: neither was anticipated and both leaked before.
90
+ ['a workspace: protocol spec', withDep('workspace:*'), 'reject'],
91
+ ['a catalog: protocol spec', withDep('catalog:default'), 'reject'],
92
+ ['an exact version', withDep('0.1.4'), 'accept'],
93
+ ['a caret range', withDep('^0.1.4'), 'accept'],
94
+ ['a dist-tag', withDep('latest'), 'accept'],
95
+ ['a compound range', withDep('>=0.1.4 <0.2.0'), 'accept'],
96
+ ['a prerelease version', withDep('0.2.0-beta.1'), 'accept'],
97
+ ['an npm: alias', withDep('npm:@markup-carve/carve@^0.1.4'), 'accept'],
98
+ /*
99
+ * ALIAS TARGETS (#126, markup-carve/carve-grammars#302). `npm:` used to
100
+ * return early and clean - an unconditional ALLOW on a prefix, which is the
101
+ * same denylist mistake as the URL-prefix list this file replaced, pointing
102
+ * the other way: it never asked what the alias pointed AT.
103
+ * `npm-package-arg` refuses every reject row here.
104
+ *
105
+ * BOTH DIRECTIONS, and the accept rows are the load-bearing half. The way a
106
+ * fix for this goes wrong is over-rejection - the rule collapsing into
107
+ * "reject anything with a slash or an at-sign" - and an over-rejecting guard
108
+ * is worse than the hole, because the next person who hits it switches the
109
+ * guard off.
110
+ */
111
+ ['an npm: alias to a plain package', withDep('npm:some-pkg'), 'accept'],
112
+ ['an npm: alias to a range', withDep('npm:some-pkg@^1.0.0'), 'accept'],
113
+ ['an npm: alias to a scoped package', withDep('npm:@scope/pkg@^1.0.0'), 'accept'],
114
+ ['an npm: alias to a dist-tag', withDep('npm:some-pkg@latest'), 'accept'],
115
+ // `npm:foo@` is npm's `*`, the same alias as `npm:foo`.
116
+ ['an npm: alias with an empty target', withDep('npm:some-pkg@'), 'accept'],
117
+ // No separating `@` at all, so there is no target to recurse into - the
118
+ // whole remainder is the alias's NAME, and that is what has to be refused.
119
+ ['an npm: alias whose name is a git URL', withDep('npm:git+https://x/y'), 'reject'],
120
+ ['an npm: alias whose name is a github: spec', withDep('npm:github:owner/repo'), 'reject'],
121
+ ['an npm: alias whose name is a shorthand', withDep('npm:owner/repo'), 'reject'],
122
+ // With a target, which recurses back through the same rule.
123
+ ['an npm: alias targeting github:', withDep('npm:foo@github:owner/repo'), 'reject'],
124
+ ['an npm: alias targeting git+https://', withDep('npm:foo@git+https://x/y.git'), 'reject'],
125
+ ['an npm: alias targeting a shorthand', withDep('npm:foo@owner/repo'), 'reject'],
126
+ ['an npm: alias targeting file:', withDep('npm:foo@file:../x'), 'reject'],
127
+ ['an npm: alias targeting workspace:', withDep('npm:foo@workspace:*'), 'reject'],
128
+ ['an npm: alias with no name at all', withDep('npm:'), 'reject'],
129
+ ['a nested npm: alias', withDep('npm:foo@npm:bar@1'), 'reject'],
130
+ /*
131
+ * PUNCTUATION npm REFUSES, found by `codex review` on #126 one layer under
132
+ * the alias hole it was reviewing. npm rejects a package name or a dist-tag
133
+ * carrying "any characters that encodeURIComponent encodes" - its own words -
134
+ * and the guard accepted all of these.
135
+ *
136
+ * The accept rows below it are why this is two character rules and not one. A
137
+ * semver RANGE is not held to the tag rule: `^1.0.0` and `1.0.0 || 2.0.0`
138
+ * carry characters encodeURIComponent encodes, and rejecting those would be
139
+ * the over-reach this guard is written to avoid. So does `my!tag`, which is a
140
+ * perfectly valid dist-tag and looks like nonsense.
141
+ */
142
+ ['an alias name with a fragment', withDep('npm:foo#bar'), 'reject'],
143
+ ['an alias name with a query', withDep('npm:foo?bar'), 'reject'],
144
+ ['an alias name with a percent', withDep('npm:foo%bar'), 'reject'],
145
+ ['an alias scope with a fragment', withDep('npm:@scope#bar/pkg'), 'reject'],
146
+ ['an alias targeting a bad tag', withDep('npm:foo@#bad'), 'reject'],
147
+ ['a bare spec with a fragment', withDep('#bad'), 'reject'],
148
+ ['a bare spec with braces', withDep('{x}'), 'reject'],
149
+ ['a bare spec with a comma', withDep('a,b'), 'reject'],
150
+ ['a dist-tag that only looks odd', withDep('my!tag'), 'accept'],
151
+ ['a dist-tag with parentheses', withDep('a(b)'), 'accept'],
152
+ ['a version with build metadata', withDep('1.0.0+build.1'), 'accept'],
153
+ ['an alias to a tilde-led name', withDep('npm:~foo'), 'accept'],
154
+ // A range's whitespace need not be a space, which is why the class above
155
+ // admits all of it. Also found by `codex review`, and the reason the guard is
156
+ // knowingly lenient on one spelling - see the whitespace note in the script.
157
+ ['a range separated by a tab', withDep('>=1\t<2'), 'accept'],
158
+ ['a range broken across lines', withDep('1.0.0\n|| 2.0.0'), 'accept'],
159
+ // A scope with no package. Also `codex review`, and the third thing it found
160
+ // under this one change: every PATTERN written for an alias name grew a
161
+ // fallback branch that let something through, which is why the shipped rule
162
+ // counts `/`-separated pieces instead of matching a shape.
163
+ ['a scope with no package', withDep('npm:@foo'), 'reject'],
164
+ ['a scope with no package and a target', withDep('npm:@foo@1'), 'reject'],
165
+ ['a scoped name with a third segment', withDep('npm:@scope/pkg/extra'), 'reject'],
166
+ // npm's leading `.`/`_` rule is on the WHOLE name, so it never reaches a
167
+ // scope - the fourth thing `codex review` found here, and the second
168
+ // over-rejection. All three of the accept rows were rejected by the version
169
+ // that applied the rule per segment.
170
+ ['an alias to an underscored scope', withDep('npm:@_scope/pkg'), 'accept'],
171
+ ['an alias to an underscored package in a scope', withDep('npm:@scope/_pkg'), 'accept'],
172
+ ['an alias to a dotted scope', withDep('npm:@.scope/pkg'), 'accept'],
173
+ ['an alias to a dotted package in a scope', withDep('npm:@scope/.pkg'), 'reject'],
174
+ ['an alias to an underscored bare name', withDep('npm:_pkg'), 'reject'],
175
+ // npm reads the alias prefix case-insensitively, and a case-sensitive test
176
+ // sent `NPM:foo` down to the catch-all where its `:` got it rejected. The
177
+ // fifth thing `codex review` found, and the third over-rejection - it only
178
+ // became visible once there was something past the prefix worth reaching.
179
+ ['an upper-case alias prefix', withDep('NPM:some-pkg'), 'accept'],
180
+ ['a mixed-case alias prefix', withDep('Npm:some-pkg@^1.0.0'), 'accept'],
181
+ ['a nested alias in upper case', withDep('npm:foo@NPM:bar'), 'reject'],
182
+ // A target carrying leading whitespace, which slipped past the nested-alias
183
+ // test and was then trimmed and read as a good alias one frame down. The
184
+ // sixth and last thing `codex review` found on this change.
185
+ ['a nested alias behind a space', withDep('npm:foo@ npm:bar'), 'reject'],
186
+ ['a git target behind a space', withDep('npm:foo@ github:o/r'), 'reject'],
187
+ ['a range behind a space', withDep('npm:foo@ ^1.0.0'), 'accept'],
188
+ // A leading dot is a local path to npm, whatever follows it - `.`, `..`,
189
+ // `./x` and `.x` are all directories, and none reaches the registry.
190
+ ['a bare dot', withDep('.'), 'reject'],
191
+ ['a parent directory', withDep('..'), 'reject'],
192
+ ['a relative path', withDep('./local'), 'reject'],
193
+ ['a dot-led tag lookalike', withDep('.x'), 'reject'],
194
+ ['an alias targeting a directory', withDep('npm:foo@.'), 'reject'],
195
+ // The near miss on that rule: a dot INSIDE a tag is ordinary.
196
+ ['a tag ending in a dot', withDep('x.'), 'accept'],
197
+ ];
198
+ for (const [label, manifest, verdict] of MATRIX) {
199
+ test(`${verdict}s ${label}`, () => {
200
+ const { status, out } = runGuard(manifest);
201
+ if (verdict === 'accept') {
202
+ assert.equal(status, 0, `the guard rejected ${label}, which installs fine:\n${out}`);
203
+ return;
204
+ }
205
+ assert.notEqual(status, 0, `the guard passed ${label}, which needs git at install time`);
206
+ // A non-zero exit alone would also come from a crash, so the report has to
207
+ // name what it found.
208
+ const optional = (manifest.optionalDependencies ?? {});
209
+ const [field, spec] = Object.keys(optional).length
210
+ ? ['optionalDependencies', Object.values(optional)[0]]
211
+ : ['dependencies', manifest.dependencies[ENGINE]];
212
+ assert.ok(out.includes(field), `the report does not name the field it found it in:\n${out}`);
213
+ assert.ok(out.includes(spec), `the report does not quote the offending spec:\n${out}`);
214
+ assert.match(out, /registry/i, `the report does not say why:\n${out}`);
215
+ });
216
+ }
217
+ test("accepts this repo's own manifest", () => {
218
+ // The engine is declared as the exact registry version the v0.1.4 draft is
219
+ // cut from. This row is what keeps that publishable: a widening of the rule
220
+ // that also rejected the committed manifest would block the release.
221
+ const run = spawnSync(process.execPath, [guard, manifestPath], { encoding: 'utf8' });
222
+ assert.equal(run.status, 0, `the guard rejects the manifest that is committed here:\n${run.stdout}${run.stderr}`);
223
+ });
224
+ test('devDependencies are left alone', () => {
225
+ // A contributor's git devDependency costs a consumer nothing, and rejecting it
226
+ // would be the same over-reach the npm: alias row guards against.
227
+ const { status } = runGuard({
228
+ ...baseline,
229
+ devDependencies: { ...baseline.devDependencies, something: 'github:someone/something#abc' },
230
+ });
231
+ assert.equal(status, 0, 'a git devDependency was rejected; only installed fields are in scope');
232
+ });
233
+ test('both workflows run this script and not their own copy of the rule', () => {
234
+ // The guard is worth nothing if a workflow still carries an inline prefix list
235
+ // beside it, so both halves are pinned for each: the call is there, and no
236
+ // workflow greps the manifest for a spelling of its own.
237
+ for (const workflow of ['ci.yml', 'release.yml']) {
238
+ const text = readFileSync(join(root, '.github', 'workflows', workflow), 'utf8');
239
+ assert.ok(text.includes('node tools/no-git-dependencies.mjs'), `${workflow} does not call tools/no-git-dependencies.mjs`);
240
+ assert.doesNotMatch(text, /github:\|git|\^\(github\|git/, `${workflow} still carries a prefix-list filter beside the script`);
241
+ }
242
+ });
243
+ //# sourceMappingURL=no-git-dependencies.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"no-git-dependencies.test.js","sourceRoot":"","sources":["../src/no-git-dependencies.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,69 @@
1
+ import assert from 'node:assert/strict';
2
+ import { execFileSync } from 'node:child_process';
3
+ import { mkdirSync, mkdtempSync, readFileSync, rmSync, symlinkSync } from 'node:fs';
4
+ import { tmpdir } from 'node:os';
5
+ import { join } from 'node:path';
6
+ import test, { after } from 'node:test';
7
+ import { fileURLToPath } from 'node:url';
8
+ /**
9
+ * What an `exports` map does is decided by Node's resolver, against an
10
+ * INSTALLED package. Reading this repository's own manifest with `readFileSync`
11
+ * and asserting a key is present answers a different question - "what does the
12
+ * file say" - and such an assertion passes just as happily with the entry
13
+ * deleted. carve-js had exactly that test and it did (carve-js#1260).
14
+ *
15
+ * So this file asks the resolver instead. A scratch directory gets the
16
+ * `node_modules` layout an install produces, this package is linked into it,
17
+ * and a real `node` reads the specifier back from that directory the way a
18
+ * consumer's CI step would.
19
+ *
20
+ * The link points at the repository root, so `tsconfig.json` and `src/` are
21
+ * present on disk under the installed name. That is what makes the
22
+ * encapsulation assertion below mean something: those paths are reachable and
23
+ * still have to be refused.
24
+ */
25
+ const root = fileURLToPath(new URL('..', import.meta.url));
26
+ const manifest = JSON.parse(readFileSync(join(root, 'package.json'), 'utf8'));
27
+ const consumer = mkdtempSync(join(tmpdir(), 'carve-lsp-consumer-'));
28
+ mkdirSync(join(consumer, 'node_modules', '@markup-carve'), { recursive: true });
29
+ symlinkSync(root, join(consumer, 'node_modules', '@markup-carve', 'carve-lsp'), 'dir');
30
+ after(() => rmSync(consumer, { recursive: true, force: true }));
31
+ const run = (script) => execFileSync(process.execPath, ['-e', script], {
32
+ cwd: consumer,
33
+ encoding: 'utf8',
34
+ stdio: ['ignore', 'pipe', 'pipe'],
35
+ }).trim();
36
+ // Probed with `import`, not `require`: `.` names an `import` condition and no
37
+ // `require` one, so a require probe would answer ERR_PACKAGE_PATH_NOT_EXPORTED
38
+ // for a subpath that is wide open, and the two failures would be
39
+ // indistinguishable.
40
+ const codeOf = (specifier) => run(`import(${JSON.stringify(specifier)}).then(() => console.log('RESOLVED'),` +
41
+ ` (e) => console.log(e.code ?? String(e)))`);
42
+ test('reads the installed version back through the package specifier', () => {
43
+ // The question a version-pinning CI step asks. Closed, it throws
44
+ // ERR_PACKAGE_PATH_NOT_EXPORTED, which reads as "this package is not
45
+ // installed" rather than "this subpath is closed" - so whoever hits it goes
46
+ // and audits their install before they suspect a manifest. That failure
47
+ // mode, not the missing capability, is the cost.
48
+ const version = run(`console.log(require(${JSON.stringify(`${manifest.name}/package.json`)}).version)`);
49
+ assert.equal(version, manifest.version);
50
+ });
51
+ test('reads it back under import as well as require', () => {
52
+ // Both resolvers consult the same map, but only one of them is what a given
53
+ // shell one-liner in CI happens to use.
54
+ const version = run(`import(${JSON.stringify(`${manifest.name}/package.json`)}, { with: { type: 'json' } })` +
55
+ `.then((m) => console.log(m.default.version))`);
56
+ assert.equal(version, manifest.version);
57
+ });
58
+ test('opens that one file and not the directory holding it', () => {
59
+ // The failure this guards: widening the map with a `./*` wildcard to fix the
60
+ // two above. That publishes the whole checkout as importable API - `src/`
61
+ // included - and nothing else here would notice.
62
+ assert.equal(codeOf(`${manifest.name}/tsconfig.json`), 'ERR_PACKAGE_PATH_NOT_EXPORTED');
63
+ assert.equal(codeOf(`${manifest.name}/src/analyze.ts`), 'ERR_PACKAGE_PATH_NOT_EXPORTED');
64
+ assert.equal(codeOf(`${manifest.name}/dist/analyze.js`), 'ERR_PACKAGE_PATH_NOT_EXPORTED');
65
+ });
66
+ test('still resolves the entry point the map already named', () => {
67
+ assert.equal(codeOf(manifest.name), 'RESOLVED');
68
+ });
69
+ //# sourceMappingURL=packaging.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"packaging.test.js","sourceRoot":"","sources":["../src/packaging.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ import { type Position, type SelectionRange } from 'vscode-languageserver/node.js';
2
+ export declare function selectionRanges(uri: string, source: string, positions: Position[]): SelectionRange[];
@@ -0,0 +1,36 @@
1
+ import { scanDocument } from './workspace-index.js';
2
+ export function selectionRanges(uri, source, positions) {
3
+ const lines = source.replace(/\r\n?/g, '\n').split('\n');
4
+ const tokens = scanDocument(uri, source);
5
+ return positions.map((position) => {
6
+ const lineText = lines[position.line] ?? '';
7
+ const lineRange = {
8
+ start: { line: position.line, character: 0 },
9
+ end: { line: position.line, character: lineText.length },
10
+ };
11
+ const documentRange = {
12
+ start: { line: 0, character: 0 },
13
+ end: { line: Math.max(0, lines.length - 1), character: lines.at(-1)?.length ?? 0 },
14
+ };
15
+ const parent = { range: lineRange, parent: { range: documentRange } };
16
+ const token = tokens.find((candidate) => contains(candidate.range, position));
17
+ if (token)
18
+ return { range: token.range, parent };
19
+ const word = wordRange(lineText, position);
20
+ return word ? { range: word, parent } : parent;
21
+ });
22
+ }
23
+ function contains(range, position) {
24
+ return position.line === range.start.line && position.character >= range.start.character && position.character <= range.end.character;
25
+ }
26
+ function wordRange(line, position) {
27
+ for (const match of line.matchAll(/[\p{L}\p{N}_-]+/gu)) {
28
+ const start = match.index;
29
+ const end = start + match[0].length;
30
+ if (position.character >= start && position.character <= end) {
31
+ return { start: { line: position.line, character: start }, end: { line: position.line, character: end } };
32
+ }
33
+ }
34
+ return null;
35
+ }
36
+ //# sourceMappingURL=selection.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"selection.js","sourceRoot":"","sources":["../src/selection.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAA;AAEnD,MAAM,UAAU,eAAe,CAAC,GAAW,EAAE,MAAc,EAAE,SAAqB;IAChF,MAAM,KAAK,GAAG,MAAM,CAAC,OAAO,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAA;IACxD,MAAM,MAAM,GAAG,YAAY,CAAC,GAAG,EAAE,MAAM,CAAC,CAAA;IACxC,OAAO,SAAS,CAAC,GAAG,CAAC,CAAC,QAAQ,EAAE,EAAE;QAChC,MAAM,QAAQ,GAAG,KAAK,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,EAAE,CAAA;QAC3C,MAAM,SAAS,GAAU;YACvB,KAAK,EAAE,EAAE,IAAI,EAAE,QAAQ,CAAC,IAAI,EAAE,SAAS,EAAE,CAAC,EAAE;YAC5C,GAAG,EAAE,EAAE,IAAI,EAAE,QAAQ,CAAC,IAAI,EAAE,SAAS,EAAE,QAAQ,CAAC,MAAM,EAAE;SACzD,CAAA;QACD,MAAM,aAAa,GAAU;YAC3B,KAAK,EAAE,EAAE,IAAI,EAAE,CAAC,EAAE,SAAS,EAAE,CAAC,EAAE;YAChC,GAAG,EAAE,EAAE,IAAI,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,EAAE,SAAS,EAAE,KAAK,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,MAAM,IAAI,CAAC,EAAE;SACnF,CAAA;QACD,MAAM,MAAM,GAAmB,EAAE,KAAK,EAAE,SAAS,EAAE,MAAM,EAAE,EAAE,KAAK,EAAE,aAAa,EAAE,EAAE,CAAA;QACrF,MAAM,KAAK,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC,SAAS,EAAE,EAAE,CAAC,QAAQ,CAAC,SAAS,CAAC,KAAK,EAAE,QAAQ,CAAC,CAAC,CAAA;QAC7E,IAAI,KAAK;YAAE,OAAO,EAAE,KAAK,EAAE,KAAK,CAAC,KAAK,EAAE,MAAM,EAAE,CAAA;QAChD,MAAM,IAAI,GAAG,SAAS,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAA;QAC1C,OAAO,IAAI,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC,MAAM,CAAA;IAChD,CAAC,CAAC,CAAA;AACJ,CAAC;AAED,SAAS,QAAQ,CAAC,KAAY,EAAE,QAAkB;IAChD,OAAO,QAAQ,CAAC,IAAI,KAAK,KAAK,CAAC,KAAK,CAAC,IAAI,IAAI,QAAQ,CAAC,SAAS,IAAI,KAAK,CAAC,KAAK,CAAC,SAAS,IAAI,QAAQ,CAAC,SAAS,IAAI,KAAK,CAAC,GAAG,CAAC,SAAS,CAAA;AACvI,CAAC;AAED,SAAS,SAAS,CAAC,IAAY,EAAE,QAAkB;IACjD,KAAK,MAAM,KAAK,IAAI,IAAI,CAAC,QAAQ,CAAC,mBAAmB,CAAC,EAAE,CAAC;QACvD,MAAM,KAAK,GAAG,KAAK,CAAC,KAAM,CAAA;QAC1B,MAAM,GAAG,GAAG,KAAK,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,CAAA;QACnC,IAAI,QAAQ,CAAC,SAAS,IAAI,KAAK,IAAI,QAAQ,CAAC,SAAS,IAAI,GAAG,EAAE,CAAC;YAC7D,OAAO,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,QAAQ,CAAC,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,EAAE,GAAG,EAAE,EAAE,IAAI,EAAE,QAAQ,CAAC,IAAI,EAAE,SAAS,EAAE,GAAG,EAAE,EAAE,CAAA;QAC3G,CAAC;IACH,CAAC;IACD,OAAO,IAAI,CAAA;AACb,CAAC"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,10 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import { selectionRanges } from './selection.js';
4
+ test('expands identifier to line to document', () => {
5
+ const selected = selectionRanges('file:///a.crv', 'See [^note].\n\nTail.\n', [{ line: 0, character: 7 }])[0];
6
+ assert.equal(selected.range.start.character, 6);
7
+ assert.equal(selected.parent?.range.end.character, 12);
8
+ assert.equal(selected.parent?.parent?.range.end.line, 3);
9
+ });
10
+ //# sourceMappingURL=selection.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"selection.test.js","sourceRoot":"","sources":["../src/selection.test.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,WAAW,CAAA;AAC5B,OAAO,MAAM,MAAM,oBAAoB,CAAA;AACvC,OAAO,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAA;AAEhD,IAAI,CAAC,wCAAwC,EAAE,GAAG,EAAE;IAClD,MAAM,QAAQ,GAAG,eAAe,CAAC,eAAe,EAAE,yBAAyB,EAAE,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,SAAS,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAE,CAAA;IAC7G,MAAM,CAAC,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC,KAAK,CAAC,SAAS,EAAE,CAAC,CAAC,CAAA;IAC/C,MAAM,CAAC,KAAK,CAAC,QAAQ,CAAC,MAAM,EAAE,KAAK,CAAC,GAAG,CAAC,SAAS,EAAE,EAAE,CAAC,CAAA;IACtD,MAAM,CAAC,KAAK,CAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC,CAAA;AAC1D,CAAC,CAAC,CAAA"}
@@ -1,16 +1,18 @@
1
- import { type SemanticTokens } from 'vscode-languageserver/node.js';
1
+ import { SemanticTokensBuilder, type SemanticTokens } from 'vscode-languageserver/node.js';
2
2
  export declare const semanticTokenTypes: readonly ["keyword", "string", "comment", "property", "variable", "number", "operator", "type"];
3
3
  export declare const semanticTokenModifiers: readonly ["declaration", "definition", "readonly"];
4
4
  type TokenType = (typeof semanticTokenTypes)[number];
5
5
  type TokenModifier = (typeof semanticTokenModifiers)[number];
6
- interface Token {
6
+ export interface Token {
7
7
  line: number;
8
8
  character: number;
9
9
  length: number;
10
10
  type: TokenType;
11
11
  modifiers?: TokenModifier[];
12
12
  }
13
- export declare function buildSemanticTokens(source: string): SemanticTokens;
13
+ export declare function buildSemanticTokens(source: string, builder?: SemanticTokensBuilder): SemanticTokens;
14
+ export declare function buildSemanticTokensRange(source: string, startLine: number, endLine: number): SemanticTokens;
15
+ export declare function updateSemanticTokens(source: string, builder: SemanticTokensBuilder, previousResultId: string): SemanticTokens | import("vscode-languageserver-types").SemanticTokensDelta;
14
16
  export declare function semanticTokens(source: string): Token[];
15
17
  /**
16
18
  * Scan a single line for constructs the AST path misses:
package/dist/semantic.js CHANGED
@@ -14,12 +14,24 @@ export const semanticTokenTypes = [
14
14
  export const semanticTokenModifiers = ['declaration', 'definition', 'readonly'];
15
15
  const tokenTypeIndex = new Map(semanticTokenTypes.map((type, index) => [type, index]));
16
16
  const tokenModifierIndex = new Map(semanticTokenModifiers.map((modifier, index) => [modifier, index]));
17
- export function buildSemanticTokens(source) {
17
+ export function buildSemanticTokens(source, builder = new SemanticTokensBuilder()) {
18
+ fillBuilder(builder, semanticTokens(source));
19
+ return builder.build();
20
+ }
21
+ export function buildSemanticTokensRange(source, startLine, endLine) {
18
22
  const builder = new SemanticTokensBuilder();
19
- for (const token of semanticTokens(source)) {
23
+ fillBuilder(builder, semanticTokens(source).filter((token) => token.line >= startLine && token.line <= endLine));
24
+ return builder.build();
25
+ }
26
+ export function updateSemanticTokens(source, builder, previousResultId) {
27
+ builder.previousResult(previousResultId);
28
+ fillBuilder(builder, semanticTokens(source));
29
+ return builder.buildEdits();
30
+ }
31
+ function fillBuilder(builder, tokens) {
32
+ for (const token of tokens) {
20
33
  builder.push(token.line, token.character, token.length, tokenTypeIndex.get(token.type) ?? 0, modifierMask(token.modifiers ?? []));
21
34
  }
22
- return builder.build();
23
35
  }
24
36
  export function semanticTokens(source) {
25
37
  try {
@@ -126,7 +138,11 @@ function collectBlock(tokens, lines, node) {
126
138
  collectInline(tokens, lines, node.caption);
127
139
  break;
128
140
  case 'table':
129
- pushPosition(tokens, lines, node.pos, 'string');
141
+ if (node.pos) {
142
+ for (let line = node.pos.startLine - 1; line < node.pos.endLine; line += 1) {
143
+ scanTableLine(tokens, line, lines[line] ?? '');
144
+ }
145
+ }
130
146
  if (node.caption)
131
147
  collectInline(tokens, lines, node.caption);
132
148
  for (const row of node.rows) {
@@ -145,6 +161,17 @@ function collectBlock(tokens, lines, node) {
145
161
  break;
146
162
  }
147
163
  }
164
+ function scanTableLine(tokens, line, text) {
165
+ for (const pipe of text.matchAll(/\|/g))
166
+ push(tokens, line, pipe.index, 1, 'operator');
167
+ for (const marker of text.matchAll(/\|=?\s*([<>~^v]{1,2})(?=\{| )/g)) {
168
+ const run = marker[1];
169
+ push(tokens, line, marker.index + marker[0].indexOf(run), run.length, 'keyword');
170
+ }
171
+ for (const attr of text.matchAll(/\b(aligns|valigns|widths|footer-rows|header-rows|header-cols)=/g)) {
172
+ push(tokens, line, attr.index, attr[1].length, 'property');
173
+ }
174
+ }
148
175
  function collectFigureTarget(tokens, lines, node) {
149
176
  if (node.type === 'image')
150
177
  pushPosition(tokens, lines, node.pos, 'string');