@rohal12/spindle 0.53.0 → 0.55.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 (44) hide show
  1. package/README.md +3 -1
  2. package/dist/pkg/format.js +1 -1
  3. package/dist/pkg/headless.js +10058 -2075
  4. package/dist/pkg/macro-registry.json +4 -4
  5. package/dist/pkg/story-variables.js +10197 -1925
  6. package/dist/pkg/tooling.js +15 -1
  7. package/package.json +3 -1
  8. package/src/code-check.ts +314 -0
  9. package/src/components/Passage.tsx +3 -3
  10. package/src/components/PassageDialog.tsx +2 -4
  11. package/src/components/StoryInterface.tsx +2 -4
  12. package/src/components/macros/Do.tsx +1 -1
  13. package/src/components/macros/Goto.tsx +19 -8
  14. package/src/components/macros/Include.tsx +23 -11
  15. package/src/components/macros/Set.tsx +1 -1
  16. package/src/components/macros/VarDisplay.tsx +1 -1
  17. package/src/components/macros/Widget.tsx +5 -33
  18. package/src/components/macros/macro-args.ts +16 -7
  19. package/src/define-macro.ts +2 -0
  20. package/src/expression.ts +25 -20
  21. package/src/index.tsx +63 -37
  22. package/src/interpolation.ts +4 -5
  23. package/src/js-lexer.ts +981 -1342
  24. package/src/markup/ast.ts +11 -226
  25. package/src/markup/code-attributes.ts +3 -3
  26. package/src/markup/code-end.ts +121 -0
  27. package/src/markup/parse.ts +118 -0
  28. package/src/markup/render.tsx +1 -1
  29. package/src/markup/spindle.d.peggy.ts +24 -0
  30. package/src/markup/spindle.peggy +463 -0
  31. package/src/markup/tokens.ts +95 -0
  32. package/src/markup/validate.ts +281 -0
  33. package/src/parser.ts +19 -4
  34. package/src/registry.ts +49 -14
  35. package/src/runtime-errors.ts +10 -0
  36. package/src/store.ts +8 -2
  37. package/src/story-init.ts +3 -5
  38. package/src/story-variables.ts +49 -8
  39. package/src/tooling.ts +58 -0
  40. package/src/types-drift-check.ts +28 -1
  41. package/src/widgets/widget-def.ts +60 -0
  42. package/types/index.d.ts +7 -3
  43. package/types/tooling.d.ts +53 -3
  44. package/src/markup/tokenizer.ts +0 -1112
@@ -21,7 +21,19 @@ import type {
21
21
  MacroProps as PublishedMacroProps,
22
22
  ASTNode as PublishedASTNode,
23
23
  } from '../types/index';
24
- import type { parseStoryVariables as PublishedParse } from '../types/tooling';
24
+ import type {
25
+ parseStoryVariables as PublishedParse,
26
+ formatDiagnostic as PublishedFormatDiagnostic,
27
+ MarkupDiagnostic as PublishedMarkupDiagnostic,
28
+ MarkupPassage as PublishedMarkupPassage,
29
+ validateMarkup as PublishedValidateMarkup,
30
+ } from '../types/tooling';
31
+ import type {
32
+ formatDiagnostic as SourceFormatDiagnostic,
33
+ MarkupDiagnostic as SourceMarkupDiagnostic,
34
+ MarkupPassage as SourceMarkupPassage,
35
+ validateStoryMarkup as SourceValidateStoryMarkup,
36
+ } from './tooling';
25
37
  import type { bootStory as PublishedBootStory } from '../types/headless';
26
38
 
27
39
  /** What the source declares, by published name. */
@@ -30,6 +42,11 @@ interface Source {
30
42
  // Tooling entry point (`@rohal12/spindle/tooling`): the parser that
31
43
  // dist/pkg/tooling.js re-exports.
32
44
  parseStoryVariables: typeof SourceParse;
45
+ // Its markup validator (pkg/tooling.js binds validateStoryMarkup to the
46
+ // macro metadata, so validateMarkup itself takes only the passages).
47
+ MarkupPassage: SourceMarkupPassage;
48
+ MarkupDiagnostic: SourceMarkupDiagnostic;
49
+ formatDiagnostic: typeof SourceFormatDiagnostic;
33
50
  // Custom macro API: the MacroContext/MacroDefinition defineMacro()
34
51
  // actually passes and accepts.
35
52
  MacroContext: SourceMacroContext;
@@ -44,6 +61,9 @@ interface Source {
44
61
  interface Published {
45
62
  StoryAPI: PublishedAPI;
46
63
  parseStoryVariables: typeof PublishedParse;
64
+ MarkupPassage: PublishedMarkupPassage;
65
+ MarkupDiagnostic: PublishedMarkupDiagnostic;
66
+ formatDiagnostic: typeof PublishedFormatDiagnostic;
47
67
  MacroContext: PublishedMacroContext;
48
68
  MacroDefinition: PublishedMacroDefinition;
49
69
  MacroProps: PublishedMacroProps;
@@ -90,3 +110,10 @@ const _exampleMacro: PublishedMacroDefinition = {
90
110
  );
91
111
  },
92
112
  };
113
+
114
+ // pkg/tooling.js's validateMarkup is validateStoryMarkup bound to the macro
115
+ // metadata.
116
+ declare const validateStoryMarkup: typeof SourceValidateStoryMarkup;
117
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
118
+ const _validateMarkup: typeof PublishedValidateMarkup = (passages) =>
119
+ validateStoryMarkup(passages, []);
@@ -0,0 +1,60 @@
1
+ /**
2
+ * Reading widget definitions without rendering anything: the `{widget}`
3
+ * macro, startup and markup validation (also in tooling) share these.
4
+ */
5
+ import { parseMacroArgs } from '../components/macros/macro-args';
6
+ import type { MacroArgs } from '../registry';
7
+
8
+ /** A {widget} definition's name, then its `@` parameters. */
9
+ export const WIDGET_PARAMETERS = [
10
+ { name: 'name', type: 'text', required: true },
11
+ { name: 'parameters', type: 'text' },
12
+ ] as const;
13
+
14
+ export interface WidgetDef {
15
+ name: string;
16
+ params: string[];
17
+ }
18
+
19
+ /**
20
+ * The widget a {widget} definition's arguments declare: its name and its
21
+ * parameters, the words after it that start with `@` (docs/widgets.md).
22
+ * Other words, such as `$name`, are not parameters.
23
+ */
24
+ export function widgetDef({
25
+ name = '',
26
+ parameters = '',
27
+ }: MacroArgs<typeof WIDGET_PARAMETERS>): WidgetDef {
28
+ return {
29
+ name,
30
+ params: parameters.split(/\s+/).filter((word) => word.startsWith('@')),
31
+ };
32
+ }
33
+
34
+ /** Read the arguments of a {widget} definition (see widgetDef). */
35
+ export function parseWidgetDef(rawArgs: string): WidgetDef {
36
+ return widgetDef(parseMacroArgs(rawArgs, WIDGET_PARAMETERS));
37
+ }
38
+
39
+ const BLOCK_WIDGET = /\{widget\s+["']?(\w+)["']?[^}]*\}([\s\S]*?)\{\/widget\}/g;
40
+
41
+ /**
42
+ * The names of the block widgets (those whose body renders `{@children}`)
43
+ * that StoryInit and the passages tagged `widget` define. They must be
44
+ * known as block macros before any passage is parsed, so that passages
45
+ * invoking them nest their content whatever the passage order.
46
+ */
47
+ export function blockWidgetNames(
48
+ passages: Iterable<{ name: string; tags?: string[]; content: string }>,
49
+ ): string[] {
50
+ const names: string[] = [];
51
+ for (const passage of passages) {
52
+ if (passage.name !== 'StoryInit' && !passage.tags?.includes('widget')) {
53
+ continue;
54
+ }
55
+ for (const match of passage.content.matchAll(BLOCK_WIDGET)) {
56
+ if (/\{@children\}/.test(match[2]!)) names.push(match[1]!);
57
+ }
58
+ }
59
+ return names;
60
+ }
package/types/index.d.ts CHANGED
@@ -242,7 +242,9 @@ export interface StorageQuota {
242
242
  /**
243
243
  * How a macro argument is read into `ctx.args`. Quoted strings accept `\"`,
244
244
  * `\'` and `\\` escapes.
245
- * - `expression`: code, as written (the default).
245
+ * - `expression`: code, as written.
246
+ * - `statements`: code run as statements (`{set}`), as written.
247
+ * - `passage`: a passage name: a quoted string or an expression, as written.
246
248
  * - `variable`: a variable reference such as `$name` or `"$name"`, as written.
247
249
  * - `string`: one quoted string; anything else leaves the argument unset.
248
250
  * - `text`: one quoted string, or text with any loose quotes stripped.
@@ -258,6 +260,8 @@ export interface StorageQuota {
258
260
  */
259
261
  export type ParameterType =
260
262
  | 'expression'
263
+ | 'statements'
264
+ | 'passage'
261
265
  | 'variable'
262
266
  | 'string'
263
267
  | 'text'
@@ -276,8 +280,8 @@ export interface ParameterDef {
276
280
  name: string;
277
281
  required?: boolean;
278
282
  description?: string;
279
- /** How the argument is read (default `expression`). */
280
- type?: ParameterType;
283
+ /** How the argument is read: required, there is no default. */
284
+ type: ParameterType;
281
285
  /** The options of an `options` parameter. */
282
286
  parameters?: readonly ParameterDef[];
283
287
  }
@@ -1,7 +1,9 @@
1
1
  /**
2
2
  * How a macro argument is read into `ctx.args`. Quoted strings accept `\"`,
3
3
  * `\'` and `\\` escapes.
4
- * - `expression`: code, as written (the default).
4
+ * - `expression`: code, as written.
5
+ * - `statements`: code run as statements (`{set}`), as written.
6
+ * - `passage`: a passage name: a quoted string or an expression, as written.
5
7
  * - `variable`: a variable reference such as `$name` or `"$name"`, as written.
6
8
  * - `string`: one quoted string; anything else leaves the argument unset.
7
9
  * - `text`: one quoted string, or text with any loose quotes stripped.
@@ -16,6 +18,8 @@
16
18
  */
17
19
  export type ParameterType =
18
20
  | 'expression'
21
+ | 'statements'
22
+ | 'passage'
19
23
  | 'variable'
20
24
  | 'string'
21
25
  | 'text'
@@ -30,8 +34,8 @@ export interface ParameterDef {
30
34
  name: string;
31
35
  required?: boolean;
32
36
  description?: string;
33
- /** How the argument is read (default `expression`). */
34
- type?: ParameterType;
37
+ /** How the argument is read: required, there is no default. */
38
+ type: ParameterType;
35
39
  /** The options of an `options` parameter. */
36
40
  parameters?: readonly ParameterDef[];
37
41
  }
@@ -99,3 +103,49 @@ export declare function parseStoryVariables(
99
103
  content: string,
100
104
  sigil?: '$' | '%',
101
105
  ): Map<string, VariableSchema>;
106
+
107
+ /** A passage to validate. */
108
+ export interface MarkupPassage {
109
+ name: string;
110
+ content: string;
111
+ tags?: string[];
112
+ /**
113
+ * Passage attributes; `data-source-file` and `data-source-line` (the
114
+ * line of its `::` header) place errors in the source file.
115
+ */
116
+ metadata?: Record<string, string>;
117
+ }
118
+
119
+ /** An error in a passage's markup. */
120
+ export interface MarkupDiagnostic {
121
+ passage: string;
122
+ /** 1-based line within the passage's content. */
123
+ line: number;
124
+ /** 1-based column (UTF-16 code units). */
125
+ column: number;
126
+ message: string;
127
+ /** The source file and line, when the passage says where it came from. */
128
+ file?: string;
129
+ fileLine?: number;
130
+ }
131
+
132
+ /**
133
+ * Validate the markup of a story's passages as Spindle does when the story
134
+ * starts: malformed markup (unclosed or mismatched macros, tags, links,
135
+ * braces and attribute values), unknown macros and syntax errors in the
136
+ * code passages run (`{$…}` expressions, `{do}` bodies, conditions and the
137
+ * `expression`/`statements`/`passage` arguments of macros), checked against
138
+ * the built-in macros, those registered with `defineMacro` and the widgets
139
+ * the passages define, and passage names that name none of `passages`
140
+ * (links, quoted `passage` arguments, `{link}`, `{dialog}`, `{watch}`).
141
+ * Spindle refuses to start a story with any of these.
142
+ */
143
+ export declare function validateMarkup(
144
+ passages: Iterable<MarkupPassage>,
145
+ ): MarkupDiagnostic[];
146
+
147
+ /**
148
+ * A diagnostic as one line of text, as Spindle shows it:
149
+ * `Passage "Start", line 3, column 5 (story.twee:12): Unclosed {if}: …`.
150
+ */
151
+ export declare function formatDiagnostic(diagnostic: MarkupDiagnostic): string;