@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.
- package/README.md +52 -13
- package/dist/analyze.d.ts +6 -0
- package/dist/analyze.js +39 -2
- package/dist/analyze.js.map +1 -1
- package/dist/analyze.test.js +7 -0
- package/dist/analyze.test.js.map +1 -1
- package/dist/codelens.js +12 -0
- package/dist/codelens.js.map +1 -1
- package/dist/codelens.test.js +5 -0
- package/dist/codelens.test.js.map +1 -1
- package/dist/colon-fence-kinds.test.d.ts +1 -0
- package/dist/colon-fence-kinds.test.js +128 -0
- package/dist/colon-fence-kinds.test.js.map +1 -0
- package/dist/colon-fences.d.ts +24 -0
- package/dist/colon-fences.js +174 -0
- package/dist/colon-fences.js.map +1 -0
- package/dist/colon-fences.test.d.ts +1 -0
- package/dist/colon-fences.test.js +23 -0
- package/dist/colon-fences.test.js.map +1 -0
- package/dist/completion.d.ts +5 -1
- package/dist/completion.js +40 -10
- package/dist/completion.js.map +1 -1
- package/dist/completion.test.js +7 -0
- package/dist/completion.test.js.map +1 -1
- package/dist/document-highlights.d.ts +2 -0
- package/dist/document-highlights.js +21 -0
- package/dist/document-highlights.js.map +1 -0
- package/dist/document-highlights.test.d.ts +1 -0
- package/dist/document-highlights.test.js +13 -0
- package/dist/document-highlights.test.js.map +1 -0
- package/dist/document-links.d.ts +3 -0
- package/dist/document-links.js +36 -0
- package/dist/document-links.js.map +1 -0
- package/dist/document-links.test.d.ts +1 -0
- package/dist/document-links.test.js +12 -0
- package/dist/document-links.test.js.map +1 -0
- package/dist/format-corpus.test.d.ts +1 -0
- package/dist/format-corpus.test.js +16 -0
- package/dist/format-corpus.test.js.map +1 -0
- package/dist/format.d.ts +3 -0
- package/dist/format.js +38 -74
- package/dist/format.js.map +1 -1
- package/dist/format.test.js +27 -32
- package/dist/format.test.js.map +1 -1
- package/dist/hover.js +20 -0
- package/dist/hover.js.map +1 -1
- package/dist/include-completion.d.ts +3 -0
- package/dist/include-completion.js +68 -0
- package/dist/include-completion.js.map +1 -0
- package/dist/include-completion.test.d.ts +1 -0
- package/dist/include-completion.test.js +15 -0
- package/dist/include-completion.test.js.map +1 -0
- package/dist/inlay-hints.d.ts +3 -0
- package/dist/inlay-hints.js +43 -0
- package/dist/inlay-hints.js.map +1 -0
- package/dist/inlay-hints.test.d.ts +1 -0
- package/dist/inlay-hints.test.js +10 -0
- package/dist/inlay-hints.test.js.map +1 -0
- package/dist/lint-actions.d.ts +3 -0
- package/dist/lint-actions.js +90 -0
- package/dist/lint-actions.js.map +1 -0
- package/dist/lint-actions.test.d.ts +1 -0
- package/dist/lint-actions.test.js +31 -0
- package/dist/lint-actions.test.js.map +1 -0
- package/dist/no-git-dependencies.test.d.ts +1 -0
- package/dist/no-git-dependencies.test.js +243 -0
- package/dist/no-git-dependencies.test.js.map +1 -0
- package/dist/packaging.test.d.ts +1 -0
- package/dist/packaging.test.js +69 -0
- package/dist/packaging.test.js.map +1 -0
- package/dist/selection.d.ts +2 -0
- package/dist/selection.js +36 -0
- package/dist/selection.js.map +1 -0
- package/dist/selection.test.d.ts +1 -0
- package/dist/selection.test.js +10 -0
- package/dist/selection.test.js.map +1 -0
- package/dist/semantic.d.ts +5 -3
- package/dist/semantic.js +31 -4
- package/dist/semantic.js.map +1 -1
- package/dist/semantic.test.js +5 -0
- package/dist/semantic.test.js.map +1 -1
- package/dist/server-capabilities.test.d.ts +1 -0
- package/dist/server-capabilities.test.js +84 -0
- package/dist/server-capabilities.test.js.map +1 -0
- package/dist/server.js +236 -20
- package/dist/server.js.map +1 -1
- package/dist/settings.d.ts +11 -0
- package/dist/settings.js +50 -0
- package/dist/settings.js.map +1 -0
- package/dist/settings.test.d.ts +1 -0
- package/dist/settings.test.js +22 -0
- package/dist/settings.test.js.map +1 -0
- package/dist/spec-0-1-4-constructs.test.d.ts +1 -0
- package/dist/spec-0-1-4-constructs.test.js +101 -0
- package/dist/spec-0-1-4-constructs.test.js.map +1 -0
- package/dist/table-diagnostics.d.ts +3 -0
- package/dist/table-diagnostics.js +60 -0
- package/dist/table-diagnostics.js.map +1 -0
- package/dist/table-diagnostics.test.d.ts +1 -0
- package/dist/table-diagnostics.test.js +12 -0
- package/dist/table-diagnostics.test.js.map +1 -0
- package/dist/workspace-graph.d.ts +25 -0
- package/dist/workspace-graph.js +94 -0
- package/dist/workspace-graph.js.map +1 -0
- package/dist/workspace-graph.test.d.ts +1 -0
- package/dist/workspace-graph.test.js +31 -0
- package/dist/workspace-graph.test.js.map +1 -0
- package/dist/workspace-index.d.ts +40 -0
- package/dist/workspace-index.js +254 -0
- package/dist/workspace-index.js.map +1 -0
- package/dist/workspace-index.test.d.ts +1 -0
- package/dist/workspace-index.test.js +65 -0
- package/dist/workspace-index.test.js.map +1 -0
- package/dist/workspace-loader.d.ts +11 -0
- package/dist/workspace-loader.js +50 -0
- package/dist/workspace-loader.js.map +1 -0
- package/dist/workspace-loader.test.d.ts +1 -0
- package/dist/workspace-loader.test.js +19 -0
- package/dist/workspace-loader.test.js.map +1 -0
- 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,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"}
|
package/dist/semantic.d.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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');
|