doc-detective 4.29.1 → 4.30.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.
Files changed (61) hide show
  1. package/bin/doc-detective-lsp.js +6 -0
  2. package/dist/cli.js +2 -0
  3. package/dist/cli.js.map +1 -1
  4. package/dist/common/src/validate.d.ts +16 -1
  5. package/dist/common/src/validate.d.ts.map +1 -1
  6. package/dist/common/src/validate.js +18 -1
  7. package/dist/common/src/validate.js.map +1 -1
  8. package/dist/core/tests/saveScreenshot.d.ts.map +1 -1
  9. package/dist/core/tests/saveScreenshot.js +101 -59
  10. package/dist/core/tests/saveScreenshot.js.map +1 -1
  11. package/dist/index.cjs +55 -36
  12. package/dist/lsp/command.d.ts +12 -0
  13. package/dist/lsp/command.d.ts.map +1 -0
  14. package/dist/lsp/command.js +24 -0
  15. package/dist/lsp/command.js.map +1 -0
  16. package/dist/lsp/completion.d.ts +27 -0
  17. package/dist/lsp/completion.d.ts.map +1 -0
  18. package/dist/lsp/completion.js +114 -0
  19. package/dist/lsp/completion.js.map +1 -0
  20. package/dist/lsp/diagnostics.d.ts +21 -0
  21. package/dist/lsp/diagnostics.d.ts.map +1 -0
  22. package/dist/lsp/diagnostics.js +137 -0
  23. package/dist/lsp/diagnostics.js.map +1 -0
  24. package/dist/lsp/gate.d.ts +30 -0
  25. package/dist/lsp/gate.d.ts.map +1 -0
  26. package/dist/lsp/gate.js +80 -0
  27. package/dist/lsp/gate.js.map +1 -0
  28. package/dist/lsp/hover.d.ts +9 -0
  29. package/dist/lsp/hover.d.ts.map +1 -0
  30. package/dist/lsp/hover.js +56 -0
  31. package/dist/lsp/hover.js.map +1 -0
  32. package/dist/lsp/inline.d.ts +25 -0
  33. package/dist/lsp/inline.d.ts.map +1 -0
  34. package/dist/lsp/inline.js +208 -0
  35. package/dist/lsp/inline.js.map +1 -0
  36. package/dist/lsp/json/positions.d.ts +51 -0
  37. package/dist/lsp/json/positions.d.ts.map +1 -0
  38. package/dist/lsp/json/positions.js +101 -0
  39. package/dist/lsp/json/positions.js.map +1 -0
  40. package/dist/lsp/messages.d.ts +27 -0
  41. package/dist/lsp/messages.d.ts.map +1 -0
  42. package/dist/lsp/messages.js +33 -0
  43. package/dist/lsp/messages.js.map +1 -0
  44. package/dist/lsp/model.d.ts +27 -0
  45. package/dist/lsp/model.d.ts.map +1 -0
  46. package/dist/lsp/model.js +49 -0
  47. package/dist/lsp/model.js.map +1 -0
  48. package/dist/lsp/registry.d.ts +61 -0
  49. package/dist/lsp/registry.d.ts.map +1 -0
  50. package/dist/lsp/registry.js +124 -0
  51. package/dist/lsp/registry.js.map +1 -0
  52. package/dist/lsp/server.d.ts +40 -0
  53. package/dist/lsp/server.d.ts.map +1 -0
  54. package/dist/lsp/server.js +64 -0
  55. package/dist/lsp/server.js.map +1 -0
  56. package/dist/lsp/yaml/positions.d.ts +32 -0
  57. package/dist/lsp/yaml/positions.d.ts.map +1 -0
  58. package/dist/lsp/yaml/positions.js +92 -0
  59. package/dist/lsp/yaml/positions.js.map +1 -0
  60. package/package.json +6 -2
  61. package/scripts/postinstall.js +144 -1
@@ -0,0 +1,137 @@
1
+ import { DiagnosticSeverity, } from "vscode-languageserver";
2
+ import { validate } from "../common/src/validate.js";
3
+ import { classifyDocument } from "./gate.js";
4
+ import { buildModel } from "./model.js";
5
+ import { fileTypeForUri, computeInlineDiagnostics } from "./inline.js";
6
+ import { DIAGNOSTIC_SOURCE, ACTION_KEYED_MESSAGE, V2_DEPRECATION_MESSAGE, schemaMessage, } from "./messages.js";
7
+ // Re-exported so existing importers (and tests) keep a single entry point.
8
+ export { DIAGNOSTIC_SOURCE, ACTION_KEYED_MESSAGE, V2_DEPRECATION_MESSAGE, schemaMessage, };
9
+ const SCHEMA_FOR_CLASS = {
10
+ spec: "spec_v3",
11
+ config: "config_v3",
12
+ };
13
+ function offsetRangeToLspRange(doc, range) {
14
+ return {
15
+ start: doc.positionAt(range.start),
16
+ end: doc.positionAt(range.end),
17
+ };
18
+ }
19
+ /**
20
+ * Is `instancePath` inside (or equal to) the step at `pointer`? Used to drop
21
+ * the raw `anyOf` schema errors for a step we've already explained with the
22
+ * friendly action-keyed diagnostic.
23
+ */
24
+ function isUnderPointer(instancePath, pointer) {
25
+ return instancePath === pointer || instancePath.startsWith(pointer + "/");
26
+ }
27
+ /** Is `pointer` inside (or equal to) `instancePath`? (The inverse relation.) */
28
+ function pointerIsUnder(pointer, instancePath) {
29
+ return pointer === instancePath || pointer.startsWith(instancePath + "/");
30
+ }
31
+ /**
32
+ * Should this schema error be dropped because an action-keyed step already
33
+ * explains it? Two cases: (1) the error sits at/under the offending step, or
34
+ * (2) it's a vague container `anyOf`/`oneOf` failure on an *ancestor* of that
35
+ * step — the propagation of the same problem up to the test/spec level, which
36
+ * says only "must match a schema in anyOf" and is useless next to the precise
37
+ * action-keyed diagnostic.
38
+ */
39
+ export function isSuppressedByActionKeyed(instancePath, keyword, suppressedPointers) {
40
+ return suppressedPointers.some((pointer) => {
41
+ if (isUnderPointer(instancePath, pointer))
42
+ return true;
43
+ if ((keyword === "anyOf" || keyword === "oneOf") &&
44
+ pointerIsUnder(pointer, instancePath)) {
45
+ return true;
46
+ }
47
+ return false;
48
+ });
49
+ }
50
+ /**
51
+ * Compute all diagnostics for a document. Pure over (uri, text): no filesystem,
52
+ * no network. Handles JSON and YAML specs/configs. Returns `[]` for anything the
53
+ * detection gate doesn't recognize and for empty/unparseable buffers (beyond
54
+ * the syntax errors themselves).
55
+ */
56
+ export function computeDiagnostics(doc) {
57
+ const text = doc.getText();
58
+ // Markup files (markdown/asciidoc/html/dita) carry tests inline, not as a
59
+ // whole-file spec — route them to the inline pipeline. Naturally silent on
60
+ // files without any Doc Detective statements.
61
+ const markupFileType = fileTypeForUri(doc.uri);
62
+ if (markupFileType)
63
+ return computeInlineDiagnostics(doc, markupFileType);
64
+ const cls = classifyDocument({ uri: doc.uri, text });
65
+ if (!cls)
66
+ return [];
67
+ const model = buildModel(doc.uri, text);
68
+ /* c8 ignore next - classify only returns non-null for .json/.yaml/.yml, which buildModel handles */
69
+ if (!model)
70
+ return [];
71
+ // Surface syntax errors ourselves: because the plugin maps specs to a
72
+ // dedicated language id, the editor's built-in JSON/YAML service may not run.
73
+ const diagnostics = model.syntaxErrors.map((e) => ({
74
+ severity: DiagnosticSeverity.Error,
75
+ range: offsetRangeToLspRange(doc, e.range),
76
+ message: e.message,
77
+ source: DIAGNOSTIC_SOURCE,
78
+ }));
79
+ // A syntactically broken buffer can't be meaningfully schema-checked — the
80
+ // partial value produces misleading "must be object" noise. Show the syntax
81
+ // errors; schema diagnostics reappear once the document parses cleanly.
82
+ if (diagnostics.length > 0)
83
+ return diagnostics;
84
+ const value = model.value;
85
+ if (!value || typeof value !== "object")
86
+ return diagnostics;
87
+ const actionKeyed = model.actionKeyedSteps();
88
+ const result = validate({
89
+ schemaKey: SCHEMA_FOR_CLASS[cls],
90
+ object: value,
91
+ addDefaults: false,
92
+ structuredErrors: true,
93
+ });
94
+ // `structuredErrors: true` guarantees `errorObjects` is populated (empty when
95
+ // valid), so it is never undefined here.
96
+ const errorObjects = result.errorObjects;
97
+ // Decide each action-keyed step's fate *per step*, by whether AJV flagged an
98
+ // error at/under that specific step's pointer:
99
+ // - own errors → the step is genuinely invalid v3 → flagship error, and its
100
+ // raw anyOf noise is suppressed in favor of that one message.
101
+ // - no errors → a valid legacy v2 step (it transforms to v3) → a
102
+ // non-blocking deprecation nudge, and we do NOT suppress
103
+ // unrelated errors on its behalf.
104
+ // This keeps a valid v2 step from being mislabeled just because some *other*
105
+ // part of the document failed to validate.
106
+ const invalidPointers = actionKeyed
107
+ .map((step) => step.pointer)
108
+ .filter((pointer) => errorObjects.some((error) => isUnderPointer(error.instancePath, pointer)));
109
+ for (const error of errorObjects) {
110
+ const instancePath = error.instancePath;
111
+ if (isSuppressedByActionKeyed(instancePath, error.keyword, invalidPointers)) {
112
+ continue;
113
+ }
114
+ const offsetRange = model.rangeForPath(instancePath);
115
+ /* c8 ignore next - rangeForPath always resolves to at least the root for a parsed tree */
116
+ if (!offsetRange)
117
+ continue;
118
+ diagnostics.push({
119
+ severity: DiagnosticSeverity.Error,
120
+ range: offsetRangeToLspRange(doc, offsetRange),
121
+ message: schemaMessage(error),
122
+ source: DIAGNOSTIC_SOURCE,
123
+ });
124
+ }
125
+ for (const step of actionKeyed) {
126
+ const invalid = invalidPointers.includes(step.pointer);
127
+ diagnostics.push({
128
+ severity: invalid ? DiagnosticSeverity.Error : DiagnosticSeverity.Warning,
129
+ range: offsetRangeToLspRange(doc, step.keyRange),
130
+ message: invalid ? ACTION_KEYED_MESSAGE : V2_DEPRECATION_MESSAGE,
131
+ source: DIAGNOSTIC_SOURCE,
132
+ code: invalid ? "action-keyed-step" : "legacy-v2-step",
133
+ });
134
+ }
135
+ return diagnostics;
136
+ }
137
+ //# sourceMappingURL=diagnostics.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"diagnostics.js","sourceRoot":"","sources":["../../src/lsp/diagnostics.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,30 @@
1
+ /**
2
+ * What kind of Doc Detective document a buffer is, or `null` when the buffer is
3
+ * not a Doc Detective document at all. The server stays completely silent
4
+ * (no diagnostics, no completion) for `null` — the detection gate is biased
5
+ * toward silence, because false noise on an unrelated `package.json` is far
6
+ * worse than a real spec that a user has to opt into with `$schema`.
7
+ */
8
+ export type DocClass = "spec" | "config" | null;
9
+ export interface ClassifyInput {
10
+ /** Document URI (e.g. `file:///abs/path/foo.spec.json`). */
11
+ uri: string;
12
+ /** Full document text. */
13
+ text: string;
14
+ }
15
+ /** Extract a lowercase basename from a URI or path, sans query/fragment. */
16
+ export declare function basenameFromUri(uri: string): string;
17
+ /**
18
+ * Classify a document by filename first, then by an explicit `$schema` opt-in,
19
+ * then by a shape sniff — in that order of confidence. Pure: no filesystem, no
20
+ * network, tolerant of syntactically broken buffers (an editor sends those
21
+ * constantly), so it never throws.
22
+ */
23
+ export declare function classifyDocument({ uri, text }: ClassifyInput): DocClass;
24
+ /** Convenience predicate: is this a document the server should touch at all? */
25
+ export declare function shouldHandleDocument(input: ClassifyInput): boolean;
26
+ /** Is this URI a JSON document (by extension)? */
27
+ export declare function isJsonUri(uri: string): boolean;
28
+ /** Is this URI a YAML document (by extension)? */
29
+ export declare function isYamlUri(uri: string): boolean;
30
+ //# sourceMappingURL=gate.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gate.d.ts","sourceRoot":"","sources":["../../src/lsp/gate.ts"],"names":[],"mappings":"AAGA;;;;;;GAMG;AACH,MAAM,MAAM,QAAQ,GAAG,MAAM,GAAG,QAAQ,GAAG,IAAI,CAAC;AAEhD,MAAM,WAAW,aAAa;IAC5B,4DAA4D;IAC5D,GAAG,EAAE,MAAM,CAAC;IACZ,0BAA0B;IAC1B,IAAI,EAAE,MAAM,CAAC;CACd;AAED,4EAA4E;AAC5E,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAKnD;AAKD;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,EAAE,GAAG,EAAE,IAAI,EAAE,EAAE,aAAa,GAAG,QAAQ,CA6CvE;AAED,gFAAgF;AAChF,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,aAAa,GAAG,OAAO,CAElE;AAED,kDAAkD;AAClD,wBAAgB,SAAS,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAE9C;AAED,kDAAkD;AAClD,wBAAgB,SAAS,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAG9C"}
@@ -0,0 +1,80 @@
1
+ import { parse as parseJsonc } from "jsonc-parser";
2
+ import YAML from "yaml";
3
+ /** Extract a lowercase basename from a URI or path, sans query/fragment. */
4
+ export function basenameFromUri(uri) {
5
+ // Strip scheme/query/fragment, then take the last path segment.
6
+ const withoutQuery = uri.split(/[?#]/)[0];
7
+ const segments = withoutQuery.split(/[\\/]/);
8
+ return (segments[segments.length - 1] || "").toLowerCase();
9
+ }
10
+ const SPEC_NAME = /\.spec\.(json|ya?ml)$/;
11
+ const CONFIG_NAME = /^\.doc-detective\.(json|ya?ml)$/;
12
+ /**
13
+ * Classify a document by filename first, then by an explicit `$schema` opt-in,
14
+ * then by a shape sniff — in that order of confidence. Pure: no filesystem, no
15
+ * network, tolerant of syntactically broken buffers (an editor sends those
16
+ * constantly), so it never throws.
17
+ */
18
+ export function classifyDocument({ uri, text }) {
19
+ const name = basenameFromUri(uri);
20
+ // 1. Filename convention — highest confidence.
21
+ if (SPEC_NAME.test(name))
22
+ return "spec";
23
+ if (CONFIG_NAME.test(name))
24
+ return "config";
25
+ // For anything else we only engage on an explicit signal in the content.
26
+ // Parse leniently by extension: YAML files through the YAML parser, everything
27
+ // else through jsonc (tolerant of trailing commas / comments). Both are
28
+ // wrapped so a malformed buffer classifies as `null` rather than throwing.
29
+ const isYaml = name.endsWith(".yaml") || name.endsWith(".yml");
30
+ let parsed;
31
+ try {
32
+ if (isYaml) {
33
+ parsed = YAML.parse(text);
34
+ }
35
+ else {
36
+ // jsonc.parse returns a *partial* value for malformed input (`{"tests":[],`
37
+ // yields `{tests:[]}`), which would wrongly classify garbage as a spec.
38
+ // Collect its errors and refuse to classify when any remain.
39
+ const errors = [];
40
+ parsed = parseJsonc(text, errors, { allowTrailingComma: true });
41
+ if (errors.length > 0)
42
+ return null;
43
+ }
44
+ }
45
+ catch {
46
+ return null;
47
+ }
48
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
49
+ return null;
50
+ }
51
+ // 2. Explicit `$schema` opt-in — the documented escape hatch for
52
+ // non-conventionally-named files.
53
+ if (typeof parsed.$schema === "string") {
54
+ if (parsed.$schema.includes("spec_v3"))
55
+ return "spec";
56
+ if (parsed.$schema.includes("config_v3"))
57
+ return "config";
58
+ }
59
+ // 3. Shape sniff — only the spec shape is distinctive enough to trust
60
+ // without a name or `$schema`. A top-level `tests` array is the
61
+ // spec_v3 signature. Config is deliberately NOT sniffed: too many
62
+ // unrelated JSON files would match a permissive config shape.
63
+ if (Array.isArray(parsed.tests))
64
+ return "spec";
65
+ return null;
66
+ }
67
+ /** Convenience predicate: is this a document the server should touch at all? */
68
+ export function shouldHandleDocument(input) {
69
+ return classifyDocument(input) !== null;
70
+ }
71
+ /** Is this URI a JSON document (by extension)? */
72
+ export function isJsonUri(uri) {
73
+ return basenameFromUri(uri).endsWith(".json");
74
+ }
75
+ /** Is this URI a YAML document (by extension)? */
76
+ export function isYamlUri(uri) {
77
+ const name = basenameFromUri(uri);
78
+ return name.endsWith(".yaml") || name.endsWith(".yml");
79
+ }
80
+ //# sourceMappingURL=gate.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gate.js","sourceRoot":"","sources":["../../src/lsp/gate.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,9 @@
1
+ import { type Hover, type Position } from "vscode-languageserver";
2
+ import type { TextDocument } from "vscode-languageserver-textdocument";
3
+ /**
4
+ * Hover over an action key (a step's `"goTo"`, `"find"`, …) surfaces that
5
+ * action's description straight from the schema — one source of truth, no
6
+ * hand-written copies. Spec + JSON only.
7
+ */
8
+ export declare function computeHover(doc: TextDocument, position: Position): Hover | null;
9
+ //# sourceMappingURL=hover.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hover.d.ts","sourceRoot":"","sources":["../../src/lsp/hover.ts"],"names":[],"mappings":"AAAA,OAAO,EAAc,KAAK,KAAK,EAAE,KAAK,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AAC9E,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oCAAoC,CAAC;AA6BvE;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,GAAG,EAAE,YAAY,EAAE,QAAQ,EAAE,QAAQ,GAAG,KAAK,GAAG,IAAI,CAyBhF"}
@@ -0,0 +1,56 @@
1
+ import { MarkupKind } from "vscode-languageserver";
2
+ import { findNodeAtOffset, parseTree } from "jsonc-parser";
3
+ import { classifyDocument, isJsonUri } from "./gate.js";
4
+ import { getRegistry } from "./registry.js";
5
+ /**
6
+ * Is `keyNode` the key of a property that is a direct child of a `steps`
7
+ * array element? Walks: string(key) → property → object(step) → array →
8
+ * property("steps"). Returns true only for an action key on a step.
9
+ */
10
+ function isStepPropertyKey(keyNode) {
11
+ const property = keyNode.parent;
12
+ if (!property ||
13
+ property.type !== "property" ||
14
+ property.children?.[0] !== keyNode) {
15
+ return false;
16
+ }
17
+ const stepObject = property.parent;
18
+ const array = stepObject?.parent;
19
+ const arrayProperty = array?.parent;
20
+ return (array?.type === "array" &&
21
+ arrayProperty?.type === "property" &&
22
+ arrayProperty.children?.[0]?.value === "steps");
23
+ }
24
+ /**
25
+ * Hover over an action key (a step's `"goTo"`, `"find"`, …) surfaces that
26
+ * action's description straight from the schema — one source of truth, no
27
+ * hand-written copies. Spec + JSON only.
28
+ */
29
+ export function computeHover(doc, position) {
30
+ const text = doc.getText();
31
+ if (classifyDocument({ uri: doc.uri, text }) !== "spec")
32
+ return null;
33
+ if (!isJsonUri(doc.uri))
34
+ return null;
35
+ const root = parseTree(text);
36
+ if (!root)
37
+ return null;
38
+ const offset = doc.offsetAt(position);
39
+ const node = findNodeAtOffset(root, offset);
40
+ if (!node || node.type !== "string" || !isStepPropertyKey(node))
41
+ return null;
42
+ const action = getRegistry().byKey.get(node.value);
43
+ if (!action)
44
+ return null;
45
+ const parts = [`**${action.title}**`];
46
+ if (action.description)
47
+ parts.push("", action.description);
48
+ return {
49
+ contents: { kind: MarkupKind.Markdown, value: parts.join("\n") },
50
+ range: {
51
+ start: doc.positionAt(node.offset),
52
+ end: doc.positionAt(node.offset + node.length),
53
+ },
54
+ };
55
+ }
56
+ //# sourceMappingURL=hover.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hover.js","sourceRoot":"","sources":["../../src/lsp/hover.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAA6B,MAAM,uBAAuB,CAAC;AAE9E,OAAO,EAAE,gBAAgB,EAAE,SAAS,EAAa,MAAM,cAAc,CAAC;AACtE,OAAO,EAAE,gBAAgB,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AACxD,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAE5C;;;;GAIG;AACH,SAAS,iBAAiB,CAAC,OAAa;IACtC,MAAM,QAAQ,GAAG,OAAO,CAAC,MAAM,CAAC;IAChC,IACE,CAAC,QAAQ;QACT,QAAQ,CAAC,IAAI,KAAK,UAAU;QAC5B,QAAQ,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,KAAK,OAAO,EAClC,CAAC;QACD,OAAO,KAAK,CAAC;IACf,CAAC;IACD,MAAM,UAAU,GAAG,QAAQ,CAAC,MAAM,CAAC;IACnC,MAAM,KAAK,GAAG,UAAU,EAAE,MAAM,CAAC;IACjC,MAAM,aAAa,GAAG,KAAK,EAAE,MAAM,CAAC;IACpC,OAAO,CACL,KAAK,EAAE,IAAI,KAAK,OAAO;QACvB,aAAa,EAAE,IAAI,KAAK,UAAU;QAClC,aAAa,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,KAAK,OAAO,CAC/C,CAAC;AACJ,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,YAAY,CAAC,GAAiB,EAAE,QAAkB;IAChE,MAAM,IAAI,GAAG,GAAG,CAAC,OAAO,EAAE,CAAC;IAC3B,IAAI,gBAAgB,CAAC,EAAE,GAAG,EAAE,GAAG,CAAC,GAAG,EAAE,IAAI,EAAE,CAAC,KAAK,MAAM;QAAE,OAAO,IAAI,CAAC;IACrE,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,GAAG,CAAC;QAAE,OAAO,IAAI,CAAC;IAErC,MAAM,IAAI,GAAG,SAAS,CAAC,IAAI,CAAC,CAAC;IAC7B,IAAI,CAAC,IAAI;QAAE,OAAO,IAAI,CAAC;IAEvB,MAAM,MAAM,GAAG,GAAG,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;IACtC,MAAM,IAAI,GAAG,gBAAgB,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;IAC5C,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,IAAI,KAAK,QAAQ,IAAI,CAAC,iBAAiB,CAAC,IAAI,CAAC;QAAE,OAAO,IAAI,CAAC;IAE7E,MAAM,MAAM,GAAG,WAAW,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,KAAe,CAAC,CAAC;IAC7D,IAAI,CAAC,MAAM;QAAE,OAAO,IAAI,CAAC;IAEzB,MAAM,KAAK,GAAG,CAAC,KAAK,MAAM,CAAC,KAAK,IAAI,CAAC,CAAC;IACtC,IAAI,MAAM,CAAC,WAAW;QAAE,KAAK,CAAC,IAAI,CAAC,EAAE,EAAE,MAAM,CAAC,WAAW,CAAC,CAAC;IAE3D,OAAO;QACL,QAAQ,EAAE,EAAE,IAAI,EAAE,UAAU,CAAC,QAAQ,EAAE,KAAK,EAAE,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE;QAChE,KAAK,EAAE;YACL,KAAK,EAAE,GAAG,CAAC,UAAU,CAAC,IAAI,CAAC,MAAM,CAAC;YAClC,GAAG,EAAE,GAAG,CAAC,UAAU,CAAC,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC;SAC/C;KACF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,25 @@
1
+ import { Diagnostic } from "vscode-languageserver";
2
+ import type { TextDocument } from "vscode-languageserver-textdocument";
3
+ import { type FileType } from "../common/src/fileTypes.js";
4
+ import { type OffsetRange } from "./json/positions.js";
5
+ /** Find the fileType whose extensions match this URI, or null. */
6
+ export declare function fileTypeForUri(uri: string): FileType | null;
7
+ /** A raw inline statement: its parsed payload and the payload's source span. */
8
+ interface InlineStatement {
9
+ payload: any;
10
+ /** Whether parseObject could turn the captured text into an object. */
11
+ parsed: boolean;
12
+ range: OffsetRange;
13
+ }
14
+ export declare function collectMatchOffsets(text: string, patterns?: string[]): number[];
15
+ /** Extract statements of one kind (testStart / step) with payloads + spans. */
16
+ export declare function extractStatements(text: string, patterns: string[] | undefined, ignore: OffsetRange[]): InlineStatement[];
17
+ /**
18
+ * Compute diagnostics for inline Doc Detective test statements embedded in a
19
+ * markup document (markdown/asciidoc/html/dita, or a config fileType). Only the
20
+ * `test` open and `step` statement regions the runner recognizes get language
21
+ * features — the rest of the prose is left alone.
22
+ */
23
+ export declare function computeInlineDiagnostics(doc: TextDocument, fileType: FileType): Diagnostic[];
24
+ export {};
25
+ //# sourceMappingURL=inline.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"inline.d.ts","sourceRoot":"","sources":["../../src/lsp/inline.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,UAAU,EAGX,MAAM,uBAAuB,CAAC;AAC/B,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oCAAoC,CAAC;AAGvE,OAAO,EAAoB,KAAK,QAAQ,EAAE,MAAM,4BAA4B,CAAC;AAG7E,OAAO,EAAmB,KAAK,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAoBxE,kEAAkE;AAClE,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,QAAQ,GAAG,IAAI,CAS3D;AAED,gFAAgF;AAChF,UAAU,eAAe;IACvB,OAAO,EAAE,GAAG,CAAC;IACb,uEAAuE;IACvE,MAAM,EAAE,OAAO,CAAC;IAChB,KAAK,EAAE,WAAW,CAAC;CACpB;AA2BD,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,EAAE,GAAG,MAAM,EAAE,CAe/E;AAED,+EAA+E;AAC/E,wBAAgB,iBAAiB,CAC/B,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,MAAM,EAAE,GAAG,SAAS,EAC9B,MAAM,EAAE,WAAW,EAAE,GACpB,eAAe,EAAE,CAwBnB;AAkGD;;;;;GAKG;AACH,wBAAgB,wBAAwB,CACtC,GAAG,EAAE,YAAY,EACjB,QAAQ,EAAE,QAAQ,GACjB,UAAU,EAAE,CAad"}
@@ -0,0 +1,208 @@
1
+ import { DiagnosticSeverity, } from "vscode-languageserver";
2
+ import { validate } from "../common/src/validate.js";
3
+ import { parseObject } from "../common/src/detectTests.js";
4
+ import { defaultFileTypes } from "../common/src/fileTypes.js";
5
+ import { basenameFromUri } from "./gate.js";
6
+ import { getRegistry } from "./registry.js";
7
+ import { pointerFromPath } from "./json/positions.js";
8
+ import { DIAGNOSTIC_SOURCE, ACTION_KEYED_MESSAGE, V2_DEPRECATION_MESSAGE, schemaMessage, } from "./messages.js";
9
+ /**
10
+ * The fileTypes whose inline test statements we understand, keyed for lookup by
11
+ * extension. Derived from the runner's own `defaultFileTypes` so the detection
12
+ * patterns can never drift from what the runner parses.
13
+ */
14
+ const MARKUP_FILE_TYPES = [
15
+ defaultFileTypes.markdown,
16
+ defaultFileTypes.asciidoc,
17
+ defaultFileTypes.html,
18
+ defaultFileTypes.dita,
19
+ ].filter(Boolean);
20
+ /** Find the fileType whose extensions match this URI, or null. */
21
+ export function fileTypeForUri(uri) {
22
+ const name = basenameFromUri(uri);
23
+ for (const fileType of MARKUP_FILE_TYPES) {
24
+ /* c8 ignore next - every default fileType has extensions; `|| []` guards custom ones */
25
+ for (const ext of fileType.extensions || []) {
26
+ if (name.endsWith(`.${ext.toLowerCase()}`))
27
+ return fileType;
28
+ }
29
+ }
30
+ return null;
31
+ }
32
+ /** Compile a pattern with the global + hasIndices flags, or null if invalid. */
33
+ /* c8 ignore start - the runner's own patterns always compile; the catch guards malformed custom fileType patterns */
34
+ function compilePattern(pattern) {
35
+ try {
36
+ return new RegExp(pattern, "gd");
37
+ }
38
+ catch {
39
+ return null;
40
+ }
41
+ }
42
+ /* c8 ignore stop */
43
+ /** Offset spans of `ignoreStart`…`ignoreEnd` regions, to skip statements in. */
44
+ function ignoreRanges(text, fileType) {
45
+ const starts = collectMatchOffsets(text, fileType.inlineStatements?.ignoreStart);
46
+ const ends = collectMatchOffsets(text, fileType.inlineStatements?.ignoreEnd);
47
+ const ranges = [];
48
+ for (const start of starts) {
49
+ // Pair each ignoreStart with the next ignoreEnd after it; if none, ignore to
50
+ // end of document.
51
+ const end = ends.find((e) => e >= start);
52
+ ranges.push({ start, end: end ?? text.length });
53
+ }
54
+ return ranges;
55
+ }
56
+ export function collectMatchOffsets(text, patterns) {
57
+ const offsets = [];
58
+ for (const pattern of patterns || []) {
59
+ const re = compilePattern(pattern);
60
+ /* c8 ignore next - compilePattern only returns null for an invalid custom pattern */
61
+ if (!re)
62
+ continue;
63
+ let m;
64
+ while ((m = re.exec(text))) {
65
+ offsets.push(m.index);
66
+ // A zero-width match doesn't advance lastIndex; nudge it so a degenerate
67
+ // (empty-matching) custom pattern can't spin forever and hang the server.
68
+ if (m[0].length === 0)
69
+ re.lastIndex++;
70
+ }
71
+ }
72
+ return offsets.sort((a, b) => a - b);
73
+ }
74
+ /** Extract statements of one kind (testStart / step) with payloads + spans. */
75
+ export function extractStatements(text, patterns, ignore) {
76
+ const out = [];
77
+ for (const pattern of patterns || []) {
78
+ const re = compilePattern(pattern);
79
+ /* c8 ignore next - compilePattern only returns null for an invalid custom pattern */
80
+ if (!re)
81
+ continue;
82
+ let m;
83
+ while ((m = re.exec(text))) {
84
+ // A zero-width match doesn't advance lastIndex; nudge it so a degenerate
85
+ // (empty-matching) custom pattern can't spin forever and hang the server.
86
+ if (m[0].length === 0)
87
+ re.lastIndex++;
88
+ if (ignore.some((r) => m.index >= r.start && m.index < r.end))
89
+ continue;
90
+ const group = m.indices?.[1];
91
+ const range = group
92
+ ? { start: group[0], end: group[1] }
93
+ : /* c8 ignore next - hasIndices is always present under the 'd' flag */
94
+ { start: m.index, end: m.index + m[0].length };
95
+ /* c8 ignore next - capture group 1 is always present in the statement patterns */
96
+ const raw = m[1] ?? "";
97
+ const payload = parseObject({ stringifiedObject: raw });
98
+ out.push({ payload, parsed: payload !== null && typeof payload === "object", range });
99
+ }
100
+ }
101
+ return out;
102
+ }
103
+ /** Drop the "must have steps or contexts" errors so an open statement (which
104
+ * legitimately carries neither — later statements supply the steps) isn't
105
+ * falsely flagged. Everything else in the open statement is still validated. */
106
+ function isStepsOrContextsRequirement(error) {
107
+ if (error.instancePath)
108
+ return false; // only the top-level requirement
109
+ if (error.keyword === "anyOf")
110
+ return true;
111
+ if (error.keyword === "required" &&
112
+ ["steps", "contexts"].includes(error.params?.missingProperty)) {
113
+ return true;
114
+ }
115
+ return false;
116
+ }
117
+ function toLspRange(doc, range) {
118
+ return { start: doc.positionAt(range.start), end: doc.positionAt(range.end) };
119
+ }
120
+ /** Validate a `step` fragment and produce its diagnostics. */
121
+ function diagnoseStep(doc, stmt) {
122
+ if (!stmt.parsed)
123
+ return [];
124
+ const payload = stmt.payload;
125
+ const range = toLspRange(doc, stmt.range);
126
+ const isActionKeyed = "action" in payload;
127
+ const result = validate({
128
+ schemaKey: "step_v3",
129
+ object: payload,
130
+ addDefaults: false,
131
+ structuredErrors: true,
132
+ });
133
+ if (result.valid) {
134
+ // A valid v2 (action-keyed) step: deprecation warning, mirroring specs.
135
+ if (isActionKeyed)
136
+ return [warn(range, V2_DEPRECATION_MESSAGE, "legacy-v2-step")];
137
+ return [];
138
+ }
139
+ if (isActionKeyed) {
140
+ return [error(range, ACTION_KEYED_MESSAGE, "action-keyed-step")];
141
+ }
142
+ // A single invalid step matches no `anyOf` branch, so AJV reports a failure
143
+ // for EVERY action — dozens of "must have required property …". Collapse that
144
+ // wall: if the author clearly intended one action (a top-level key that is a
145
+ // known action), show only that action's own value errors; otherwise a single
146
+ // "not a recognized step" message.
147
+ const registry = getRegistry();
148
+ const actionKey = Object.keys(payload).find((k) => registry.byKey.has(k));
149
+ if (!actionKey) {
150
+ return [
151
+ error(range, 'Not a recognized Doc Detective step (expected `{"<action>": …}`).'),
152
+ ];
153
+ }
154
+ const prefix = pointerFromPath([actionKey]);
155
+ /* c8 ignore next - errorObjects is always set here (structuredErrors: true) */
156
+ const relevant = (result.errorObjects || []).filter((e) => (e.instancePath || "").startsWith(prefix));
157
+ // One concise diagnostic per step — the action's value can fail several
158
+ // internal branches (e.g. goTo accepts a URL string OR an object), and
159
+ // surfacing all of them is noise. Show the first, most specific message.
160
+ if (relevant.length === 0) {
161
+ return [error(range, `Invalid "${actionKey}" step.`)];
162
+ }
163
+ return [error(range, `${actionKey}: ${schemaMessage(relevant[0])}`)];
164
+ }
165
+ /** Validate a `test` open fragment (relaxed: steps/contexts not required). */
166
+ function diagnoseTestOpen(doc, stmt) {
167
+ if (!stmt.parsed)
168
+ return [];
169
+ const result = validate({
170
+ schemaKey: "test_v3",
171
+ object: stmt.payload,
172
+ addDefaults: false,
173
+ structuredErrors: true,
174
+ });
175
+ if (result.valid)
176
+ return [];
177
+ const range = toLspRange(doc, stmt.range);
178
+ /* c8 ignore next - errorObjects is always set here (structuredErrors: true) */
179
+ return (result.errorObjects || [])
180
+ .filter((e) => !isStepsOrContextsRequirement(e))
181
+ .map((e) => error(range, schemaMessage(e)));
182
+ }
183
+ function error(range, message, code) {
184
+ return { severity: DiagnosticSeverity.Error, range, message, source: DIAGNOSTIC_SOURCE, code };
185
+ }
186
+ function warn(range, message, code) {
187
+ return { severity: DiagnosticSeverity.Warning, range, message, source: DIAGNOSTIC_SOURCE, code };
188
+ }
189
+ /**
190
+ * Compute diagnostics for inline Doc Detective test statements embedded in a
191
+ * markup document (markdown/asciidoc/html/dita, or a config fileType). Only the
192
+ * `test` open and `step` statement regions the runner recognizes get language
193
+ * features — the rest of the prose is left alone.
194
+ */
195
+ export function computeInlineDiagnostics(doc, fileType) {
196
+ const text = doc.getText();
197
+ const ignore = ignoreRanges(text, fileType);
198
+ const statements = fileType.inlineStatements || {};
199
+ const diagnostics = [];
200
+ for (const stmt of extractStatements(text, statements.testStart, ignore)) {
201
+ diagnostics.push(...diagnoseTestOpen(doc, stmt));
202
+ }
203
+ for (const stmt of extractStatements(text, statements.step, ignore)) {
204
+ diagnostics.push(...diagnoseStep(doc, stmt));
205
+ }
206
+ return diagnostics;
207
+ }
208
+ //# sourceMappingURL=inline.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"inline.js","sourceRoot":"","sources":["../../src/lsp/inline.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,51 @@
1
+ import { Node, ParseError } from "jsonc-parser";
2
+ /** A character-offset span into the source text (end-exclusive). */
3
+ export interface OffsetRange {
4
+ start: number;
5
+ end: number;
6
+ }
7
+ /** Result of parsing: the CST root (if any) plus lenient syntax errors. */
8
+ export interface JsonParse {
9
+ root: Node | undefined;
10
+ errors: ParseError[];
11
+ }
12
+ /**
13
+ * Parse JSON(-with-comments) into a position-preserving CST. jsonc-parser is
14
+ * tolerant of the half-typed, comma-trailing states an editor streams, and
15
+ * collects syntax errors rather than throwing.
16
+ */
17
+ export declare function parseJsonTree(text: string): JsonParse;
18
+ /**
19
+ * Turn an AJV `instancePath` (`/tests/0/steps/1/goTo`) into path segments,
20
+ * decoding the JSON-Pointer escapes (`~1`→`/`, `~0`→`~`) and numeric indices.
21
+ */
22
+ export declare function instancePathToSegments(instancePath: string): Array<string | number>;
23
+ /**
24
+ * Resolve the source span for an AJV `instancePath`. When the exact node is
25
+ * absent (e.g. a `required` error names an object whose child doesn't exist),
26
+ * walks up to the nearest present ancestor so the diagnostic still anchors
27
+ * somewhere sensible instead of vanishing. Returns `null` only when even the
28
+ * root can't be located (unparseable input).
29
+ */
30
+ export declare function rangeForInstancePath(root: Node, instancePath: string): OffsetRange | null;
31
+ /** A step whose author used the `action`-as-value antipattern. */
32
+ export interface ActionKeyedStep {
33
+ /** Span of the offending `"action"` key, for the squiggle. */
34
+ keyRange: OffsetRange;
35
+ /** JSON-Pointer to the step object, used to suppress its `anyOf` noise. */
36
+ pointer: string;
37
+ }
38
+ /**
39
+ * Build a JSON-Pointer from path segments, escaping `~`→`~0` and `/`→`~1` per
40
+ * RFC 6901. Shared by the JSON and YAML action-keyed detectors.
41
+ */
42
+ export declare function pointerFromPath(segments: Array<string | number>): string;
43
+ /**
44
+ * Find every step written as an object carrying an `action` property inside a
45
+ * `steps` array — the classic Doc Detective mistake (action name as a *value*
46
+ * under `action`, instead of the compact action-as-key form). Returns the
47
+ * `"action"` key span (to squiggle) and the step's JSON-Pointer (to suppress
48
+ * the otherwise-overwhelming `anyOf` schema failures for that same step).
49
+ */
50
+ export declare function findActionKeyedSteps(root: Node): ActionKeyedStep[];
51
+ //# sourceMappingURL=positions.d.ts.map