@reqlan/language 1.4.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.
- package/README.md +24 -0
- package/out/file-path-rewrite.d.ts +25 -0
- package/out/file-path-rewrite.js +56 -0
- package/out/file-path-rewrite.js.map +1 -0
- package/out/generated/ast.d.ts +1157 -0
- package/out/generated/ast.js +980 -0
- package/out/generated/ast.js.map +1 -0
- package/out/generated/grammar.d.ts +7 -0
- package/out/generated/grammar.js +3071 -0
- package/out/generated/grammar.js.map +1 -0
- package/out/generated/module.d.ts +20 -0
- package/out/generated/module.js +32 -0
- package/out/generated/module.js.map +1 -0
- package/out/index.d.ts +34 -0
- package/out/index.js +35 -0
- package/out/index.js.map +1 -0
- package/out/reqlan-attribute-catalog.d.ts +19 -0
- package/out/reqlan-attribute-catalog.js +87 -0
- package/out/reqlan-attribute-catalog.js.map +1 -0
- package/out/reqlan-code-action-provider.d.ts +55 -0
- package/out/reqlan-code-action-provider.js +269 -0
- package/out/reqlan-code-action-provider.js.map +1 -0
- package/out/reqlan-code-lens-provider.d.ts +13 -0
- package/out/reqlan-code-lens-provider.js +27 -0
- package/out/reqlan-code-lens-provider.js.map +1 -0
- package/out/reqlan-code-lens-settings.d.ts +34 -0
- package/out/reqlan-code-lens-settings.js +12 -0
- package/out/reqlan-code-lens-settings.js.map +1 -0
- package/out/reqlan-comment-resolver.d.ts +35 -0
- package/out/reqlan-comment-resolver.js +308 -0
- package/out/reqlan-comment-resolver.js.map +1 -0
- package/out/reqlan-completion-context.d.ts +46 -0
- package/out/reqlan-completion-context.js +244 -0
- package/out/reqlan-completion-context.js.map +1 -0
- package/out/reqlan-completion-provider.d.ts +41 -0
- package/out/reqlan-completion-provider.js +317 -0
- package/out/reqlan-completion-provider.js.map +1 -0
- package/out/reqlan-definition-provider.d.ts +31 -0
- package/out/reqlan-definition-provider.js +240 -0
- package/out/reqlan-definition-provider.js.map +1 -0
- package/out/reqlan-document-highlight-provider.d.ts +10 -0
- package/out/reqlan-document-highlight-provider.js +11 -0
- package/out/reqlan-document-highlight-provider.js.map +1 -0
- package/out/reqlan-document-link-provider.d.ts +14 -0
- package/out/reqlan-document-link-provider.js +24 -0
- package/out/reqlan-document-link-provider.js.map +1 -0
- package/out/reqlan-embedded-file-references.d.ts +11 -0
- package/out/reqlan-embedded-file-references.js +50 -0
- package/out/reqlan-embedded-file-references.js.map +1 -0
- package/out/reqlan-file-link-resolver.d.ts +44 -0
- package/out/reqlan-file-link-resolver.js +347 -0
- package/out/reqlan-file-link-resolver.js.map +1 -0
- package/out/reqlan-file-references.d.ts +15 -0
- package/out/reqlan-file-references.js +72 -0
- package/out/reqlan-file-references.js.map +1 -0
- package/out/reqlan-grammar-config.d.ts +5 -0
- package/out/reqlan-grammar-config.js +9 -0
- package/out/reqlan-grammar-config.js.map +1 -0
- package/out/reqlan-idea-reference-site.d.ts +14 -0
- package/out/reqlan-idea-reference-site.js +47 -0
- package/out/reqlan-idea-reference-site.js.map +1 -0
- package/out/reqlan-idea-summary.d.ts +6 -0
- package/out/reqlan-idea-summary.js +134 -0
- package/out/reqlan-idea-summary.js.map +1 -0
- package/out/reqlan-ignore-error.d.ts +10 -0
- package/out/reqlan-ignore-error.js +42 -0
- package/out/reqlan-ignore-error.js.map +1 -0
- package/out/reqlan-import-bindings.d.ts +12 -0
- package/out/reqlan-import-bindings.js +65 -0
- package/out/reqlan-import-bindings.js.map +1 -0
- package/out/reqlan-import-edits.d.ts +17 -0
- package/out/reqlan-import-edits.js +96 -0
- package/out/reqlan-import-edits.js.map +1 -0
- package/out/reqlan-imports.d.ts +22 -0
- package/out/reqlan-imports.js +61 -0
- package/out/reqlan-imports.js.map +1 -0
- package/out/reqlan-inbound-reference-inlay-label.d.ts +27 -0
- package/out/reqlan-inbound-reference-inlay-label.js +142 -0
- package/out/reqlan-inbound-reference-inlay-label.js.map +1 -0
- package/out/reqlan-inlay-hint-provider.d.ts +15 -0
- package/out/reqlan-inlay-hint-provider.js +54 -0
- package/out/reqlan-inlay-hint-provider.js.map +1 -0
- package/out/reqlan-inlay-hint-settings.d.ts +10 -0
- package/out/reqlan-inlay-hint-settings.js +10 -0
- package/out/reqlan-inlay-hint-settings.js.map +1 -0
- package/out/reqlan-linker.d.ts +26 -0
- package/out/reqlan-linker.js +83 -0
- package/out/reqlan-linker.js.map +1 -0
- package/out/reqlan-markdown-links.d.ts +28 -0
- package/out/reqlan-markdown-links.js +113 -0
- package/out/reqlan-markdown-links.js.map +1 -0
- package/out/reqlan-module.d.ts +41 -0
- package/out/reqlan-module.js +78 -0
- package/out/reqlan-module.js.map +1 -0
- package/out/reqlan-name-catalog.d.ts +23 -0
- package/out/reqlan-name-catalog.js +63 -0
- package/out/reqlan-name-catalog.js.map +1 -0
- package/out/reqlan-name-provider.d.ts +10 -0
- package/out/reqlan-name-provider.js +38 -0
- package/out/reqlan-name-provider.js.map +1 -0
- package/out/reqlan-namespace-import-links.d.ts +10 -0
- package/out/reqlan-namespace-import-links.js +69 -0
- package/out/reqlan-namespace-import-links.js.map +1 -0
- package/out/reqlan-path-completion.d.ts +50 -0
- package/out/reqlan-path-completion.js +304 -0
- package/out/reqlan-path-completion.js.map +1 -0
- package/out/reqlan-path-references.d.ts +5 -0
- package/out/reqlan-path-references.js +124 -0
- package/out/reqlan-path-references.js.map +1 -0
- package/out/reqlan-path-resolve.d.ts +81 -0
- package/out/reqlan-path-resolve.js +245 -0
- package/out/reqlan-path-resolve.js.map +1 -0
- package/out/reqlan-quoted-strings.d.ts +9 -0
- package/out/reqlan-quoted-strings.js +26 -0
- package/out/reqlan-quoted-strings.js.map +1 -0
- package/out/reqlan-reference-at-position.d.ts +20 -0
- package/out/reqlan-reference-at-position.js +89 -0
- package/out/reqlan-reference-at-position.js.map +1 -0
- package/out/reqlan-reference-code-lens.d.ts +32 -0
- package/out/reqlan-reference-code-lens.js +240 -0
- package/out/reqlan-reference-code-lens.js.map +1 -0
- package/out/reqlan-reference-search-site.d.ts +34 -0
- package/out/reqlan-reference-search-site.js +136 -0
- package/out/reqlan-reference-search-site.js.map +1 -0
- package/out/reqlan-references.d.ts +16 -0
- package/out/reqlan-references.js +51 -0
- package/out/reqlan-references.js.map +1 -0
- package/out/reqlan-scope.d.ts +25 -0
- package/out/reqlan-scope.js +259 -0
- package/out/reqlan-scope.js.map +1 -0
- package/out/reqlan-semantic-token-provider.d.ts +8 -0
- package/out/reqlan-semantic-token-provider.js +91 -0
- package/out/reqlan-semantic-token-provider.js.map +1 -0
- package/out/reqlan-token-builder.d.ts +12 -0
- package/out/reqlan-token-builder.js +459 -0
- package/out/reqlan-token-builder.js.map +1 -0
- package/out/reqlan-validator.d.ts +25 -0
- package/out/reqlan-validator.js +119 -0
- package/out/reqlan-validator.js.map +1 -0
- package/out/reqlan-workspace-attribute-catalog.d.ts +6 -0
- package/out/reqlan-workspace-attribute-catalog.js +54 -0
- package/out/reqlan-workspace-attribute-catalog.js.map +1 -0
- package/out/src/file-path-rewrite.d.ts +25 -0
- package/out/src/file-path-rewrite.js +56 -0
- package/out/src/file-path-rewrite.js.map +1 -0
- package/out/src/generated/ast.d.ts +1173 -0
- package/out/src/generated/ast.js +994 -0
- package/out/src/generated/ast.js.map +1 -0
- package/out/src/generated/grammar.d.ts +7 -0
- package/out/src/generated/grammar.js +3045 -0
- package/out/src/generated/grammar.js.map +1 -0
- package/out/src/generated/module.d.ts +20 -0
- package/out/src/generated/module.js +32 -0
- package/out/src/generated/module.js.map +1 -0
- package/out/src/index.d.ts +29 -0
- package/out/src/index.js +30 -0
- package/out/src/index.js.map +1 -0
- package/out/src/reqlan-attribute-catalog.d.ts +19 -0
- package/out/src/reqlan-attribute-catalog.js +87 -0
- package/out/src/reqlan-attribute-catalog.js.map +1 -0
- package/out/src/reqlan-code-action-provider.d.ts +34 -0
- package/out/src/reqlan-code-action-provider.js +206 -0
- package/out/src/reqlan-code-action-provider.js.map +1 -0
- package/out/src/reqlan-comment-resolver.d.ts +35 -0
- package/out/src/reqlan-comment-resolver.js +308 -0
- package/out/src/reqlan-comment-resolver.js.map +1 -0
- package/out/src/reqlan-completion-context.d.ts +46 -0
- package/out/src/reqlan-completion-context.js +244 -0
- package/out/src/reqlan-completion-context.js.map +1 -0
- package/out/src/reqlan-completion-provider.d.ts +41 -0
- package/out/src/reqlan-completion-provider.js +320 -0
- package/out/src/reqlan-completion-provider.js.map +1 -0
- package/out/src/reqlan-definition-provider.d.ts +31 -0
- package/out/src/reqlan-definition-provider.js +240 -0
- package/out/src/reqlan-definition-provider.js.map +1 -0
- package/out/src/reqlan-document-highlight-provider.d.ts +10 -0
- package/out/src/reqlan-document-highlight-provider.js +11 -0
- package/out/src/reqlan-document-highlight-provider.js.map +1 -0
- package/out/src/reqlan-document-link-provider.d.ts +14 -0
- package/out/src/reqlan-document-link-provider.js +24 -0
- package/out/src/reqlan-document-link-provider.js.map +1 -0
- package/out/src/reqlan-embedded-file-references.d.ts +11 -0
- package/out/src/reqlan-embedded-file-references.js +50 -0
- package/out/src/reqlan-embedded-file-references.js.map +1 -0
- package/out/src/reqlan-file-link-resolver.d.ts +44 -0
- package/out/src/reqlan-file-link-resolver.js +347 -0
- package/out/src/reqlan-file-link-resolver.js.map +1 -0
- package/out/src/reqlan-file-references.d.ts +15 -0
- package/out/src/reqlan-file-references.js +72 -0
- package/out/src/reqlan-file-references.js.map +1 -0
- package/out/src/reqlan-grammar-config.d.ts +5 -0
- package/out/src/reqlan-grammar-config.js +9 -0
- package/out/src/reqlan-grammar-config.js.map +1 -0
- package/out/src/reqlan-idea-summary.d.ts +6 -0
- package/out/src/reqlan-idea-summary.js +78 -0
- package/out/src/reqlan-idea-summary.js.map +1 -0
- package/out/src/reqlan-ignore-error.d.ts +10 -0
- package/out/src/reqlan-ignore-error.js +42 -0
- package/out/src/reqlan-ignore-error.js.map +1 -0
- package/out/src/reqlan-import-bindings.d.ts +12 -0
- package/out/src/reqlan-import-bindings.js +65 -0
- package/out/src/reqlan-import-bindings.js.map +1 -0
- package/out/src/reqlan-import-edits.d.ts +17 -0
- package/out/src/reqlan-import-edits.js +96 -0
- package/out/src/reqlan-import-edits.js.map +1 -0
- package/out/src/reqlan-imports.d.ts +22 -0
- package/out/src/reqlan-imports.js +61 -0
- package/out/src/reqlan-imports.js.map +1 -0
- package/out/src/reqlan-inbound-reference-inlay-label.d.ts +23 -0
- package/out/src/reqlan-inbound-reference-inlay-label.js +128 -0
- package/out/src/reqlan-inbound-reference-inlay-label.js.map +1 -0
- package/out/src/reqlan-inlay-hint-provider.d.ts +15 -0
- package/out/src/reqlan-inlay-hint-provider.js +54 -0
- package/out/src/reqlan-inlay-hint-provider.js.map +1 -0
- package/out/src/reqlan-inlay-hint-settings.d.ts +10 -0
- package/out/src/reqlan-inlay-hint-settings.js +10 -0
- package/out/src/reqlan-inlay-hint-settings.js.map +1 -0
- package/out/src/reqlan-linker.d.ts +26 -0
- package/out/src/reqlan-linker.js +86 -0
- package/out/src/reqlan-linker.js.map +1 -0
- package/out/src/reqlan-markdown-links.d.ts +28 -0
- package/out/src/reqlan-markdown-links.js +113 -0
- package/out/src/reqlan-markdown-links.js.map +1 -0
- package/out/src/reqlan-module.d.ts +41 -0
- package/out/src/reqlan-module.js +76 -0
- package/out/src/reqlan-module.js.map +1 -0
- package/out/src/reqlan-name-catalog.d.ts +23 -0
- package/out/src/reqlan-name-catalog.js +63 -0
- package/out/src/reqlan-name-catalog.js.map +1 -0
- package/out/src/reqlan-name-provider.d.ts +10 -0
- package/out/src/reqlan-name-provider.js +38 -0
- package/out/src/reqlan-name-provider.js.map +1 -0
- package/out/src/reqlan-namespace-import-links.d.ts +10 -0
- package/out/src/reqlan-namespace-import-links.js +81 -0
- package/out/src/reqlan-namespace-import-links.js.map +1 -0
- package/out/src/reqlan-path-completion.d.ts +50 -0
- package/out/src/reqlan-path-completion.js +304 -0
- package/out/src/reqlan-path-completion.js.map +1 -0
- package/out/src/reqlan-path-references.d.ts +5 -0
- package/out/src/reqlan-path-references.js +124 -0
- package/out/src/reqlan-path-references.js.map +1 -0
- package/out/src/reqlan-path-resolve.d.ts +59 -0
- package/out/src/reqlan-path-resolve.js +173 -0
- package/out/src/reqlan-path-resolve.js.map +1 -0
- package/out/src/reqlan-quoted-strings.d.ts +9 -0
- package/out/src/reqlan-quoted-strings.js +26 -0
- package/out/src/reqlan-quoted-strings.js.map +1 -0
- package/out/src/reqlan-reference-at-position.d.ts +20 -0
- package/out/src/reqlan-reference-at-position.js +89 -0
- package/out/src/reqlan-reference-at-position.js.map +1 -0
- package/out/src/reqlan-references.d.ts +16 -0
- package/out/src/reqlan-references.js +51 -0
- package/out/src/reqlan-references.js.map +1 -0
- package/out/src/reqlan-scope.d.ts +25 -0
- package/out/src/reqlan-scope.js +266 -0
- package/out/src/reqlan-scope.js.map +1 -0
- package/out/src/reqlan-semantic-token-provider.d.ts +8 -0
- package/out/src/reqlan-semantic-token-provider.js +97 -0
- package/out/src/reqlan-semantic-token-provider.js.map +1 -0
- package/out/src/reqlan-token-builder.d.ts +12 -0
- package/out/src/reqlan-token-builder.js +459 -0
- package/out/src/reqlan-token-builder.js.map +1 -0
- package/out/src/reqlan-validator.d.ts +25 -0
- package/out/src/reqlan-validator.js +119 -0
- package/out/src/reqlan-validator.js.map +1 -0
- package/out/src/reqlan-workspace-attribute-catalog.d.ts +6 -0
- package/out/src/reqlan-workspace-attribute-catalog.js +54 -0
- package/out/src/reqlan-workspace-attribute-catalog.js.map +1 -0
- package/out/test/code-actions.test.d.ts +1 -0
- package/out/test/code-actions.test.js +126 -0
- package/out/test/code-actions.test.js.map +1 -0
- package/out/test/comment-in-string.test.d.ts +1 -0
- package/out/test/comment-in-string.test.js +100 -0
- package/out/test/comment-in-string.test.js.map +1 -0
- package/out/test/completion.test.d.ts +1 -0
- package/out/test/completion.test.js +467 -0
- package/out/test/completion.test.js.map +1 -0
- package/out/test/file-path-rewrite.test.d.ts +1 -0
- package/out/test/file-path-rewrite.test.js +64 -0
- package/out/test/file-path-rewrite.test.js.map +1 -0
- package/out/test/ignore-error.test.d.ts +1 -0
- package/out/test/ignore-error.test.js +87 -0
- package/out/test/ignore-error.test.js.map +1 -0
- package/out/test/implicit-import-extension.test.d.ts +1 -0
- package/out/test/implicit-import-extension.test.js +203 -0
- package/out/test/implicit-import-extension.test.js.map +1 -0
- package/out/test/import-edits.test.d.ts +1 -0
- package/out/test/import-edits.test.js +20 -0
- package/out/test/import-edits.test.js.map +1 -0
- package/out/test/inlay-hints.test.d.ts +1 -0
- package/out/test/inlay-hints.test.js +238 -0
- package/out/test/inlay-hints.test.js.map +1 -0
- package/out/test/linking.test.d.ts +1 -0
- package/out/test/linking.test.js +754 -0
- package/out/test/linking.test.js.map +1 -0
- package/out/test/naked-strings.test.d.ts +1 -0
- package/out/test/naked-strings.test.js +50 -0
- package/out/test/naked-strings.test.js.map +1 -0
- package/out/test/parsing.test.d.ts +1 -0
- package/out/test/parsing.test.js +773 -0
- package/out/test/parsing.test.js.map +1 -0
- package/out/test/path-resolve.test.d.ts +1 -0
- package/out/test/path-resolve.test.js +88 -0
- package/out/test/path-resolve.test.js.map +1 -0
- package/out/test/references.test.d.ts +1 -0
- package/out/test/references.test.js +168 -0
- package/out/test/references.test.js.map +1 -0
- package/out/test/rqconfig.test.d.ts +1 -0
- package/out/test/rqconfig.test.js +275 -0
- package/out/test/rqconfig.test.js.map +1 -0
- package/out/test/test-requirement-refs.d.ts +8 -0
- package/out/test/test-requirement-refs.js +35 -0
- package/out/test/test-requirement-refs.js.map +1 -0
- package/out/test/testing.test.d.ts +1 -0
- package/out/test/testing.test.js +23 -0
- package/out/test/testing.test.js.map +1 -0
- package/out/test/validating.test.d.ts +1 -0
- package/out/test/validating.test.js +117 -0
- package/out/test/validating.test.js.map +1 -0
- package/out/tsconfig.tsbuildinfo +1 -0
- package/package.json +54 -0
- package/src/chevrotain.d.ts +8 -0
- package/src/file-path-rewrite.ts +96 -0
- package/src/generated/ast.ts +1481 -0
- package/src/generated/grammar.ts +3074 -0
- package/src/generated/module.ts +38 -0
- package/src/index.ts +34 -0
- package/src/reqlan-attribute-catalog.ts +100 -0
- package/src/reqlan-code-action-provider.ts +385 -0
- package/src/reqlan-code-lens-provider.ts +54 -0
- package/src/reqlan-code-lens-settings.ts +42 -0
- package/src/reqlan-comment-resolver.ts +372 -0
- package/src/reqlan-comment.langium +23 -0
- package/src/reqlan-completion-context.ts +330 -0
- package/src/reqlan-completion-provider.ts +400 -0
- package/src/reqlan-definition-provider.ts +319 -0
- package/src/reqlan-document-highlight-provider.ts +21 -0
- package/src/reqlan-document-link-provider.ts +45 -0
- package/src/reqlan-embedded-file-references.ts +72 -0
- package/src/reqlan-file-link-resolver.ts +485 -0
- package/src/reqlan-file-references.ts +84 -0
- package/src/reqlan-grammar-config.ts +14 -0
- package/src/reqlan-idea-reference-site.ts +74 -0
- package/src/reqlan-idea-summary.ts +162 -0
- package/src/reqlan-ignore-error.ts +50 -0
- package/src/reqlan-import-bindings.ts +87 -0
- package/src/reqlan-import-edits.ts +124 -0
- package/src/reqlan-imports.ts +115 -0
- package/src/reqlan-inbound-reference-inlay-label.ts +194 -0
- package/src/reqlan-inlay-hint-provider.ts +88 -0
- package/src/reqlan-inlay-hint-settings.ts +15 -0
- package/src/reqlan-linker.ts +108 -0
- package/src/reqlan-markdown-links.ts +136 -0
- package/src/reqlan-module.ts +104 -0
- package/src/reqlan-name-catalog.ts +81 -0
- package/src/reqlan-name-provider.ts +56 -0
- package/src/reqlan-namespace-import-links.ts +92 -0
- package/src/reqlan-path-completion.ts +420 -0
- package/src/reqlan-path-references.ts +140 -0
- package/src/reqlan-path-resolve.ts +346 -0
- package/src/reqlan-quoted-strings.ts +30 -0
- package/src/reqlan-reference-at-position.ts +154 -0
- package/src/reqlan-reference-code-lens.ts +323 -0
- package/src/reqlan-reference-search-site.ts +202 -0
- package/src/reqlan-references.ts +76 -0
- package/src/reqlan-scope.ts +312 -0
- package/src/reqlan-semantic-token-provider.ts +113 -0
- package/src/reqlan-token-builder.ts +492 -0
- package/src/reqlan-validator.ts +149 -0
- package/src/reqlan-workspace-attribute-catalog.ts +62 -0
- package/src/reqlan.langium +236 -0
|
@@ -0,0 +1,773 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
import { dirname, join } from 'node:path';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
4
|
+
import { beforeAll, describe, expect, test } from 'vitest';
|
|
5
|
+
import { EmptyFileSystem, AstUtils } from 'langium';
|
|
6
|
+
import { expandToString as s } from 'langium/generate';
|
|
7
|
+
import { parseHelper } from 'langium/test';
|
|
8
|
+
import { createReqlanServices, isAttribute, isBracketReference, isCodeSnippet, isFileReference, isFromImport, isBodyLine, isIdea, isModel, isOneLinerIdea, isScalarValue } from 'reqlan-language';
|
|
9
|
+
import { getReferencePrefixContext } from '../src/reqlan-completion-context.js';
|
|
10
|
+
import { isMarkdownLinkLabelPosition } from '../src/reqlan-markdown-links.js';
|
|
11
|
+
const repoDir = join(dirname(fileURLToPath(import.meta.url)), '../../..');
|
|
12
|
+
const exampleDir = join(repoDir, 'example_rq_project');
|
|
13
|
+
let services;
|
|
14
|
+
let parse;
|
|
15
|
+
beforeAll(async () => {
|
|
16
|
+
services = createReqlanServices(EmptyFileSystem);
|
|
17
|
+
parse = parseHelper(services.Reqlan);
|
|
18
|
+
});
|
|
19
|
+
function oneLinerText(idea) {
|
|
20
|
+
return idea.body.content
|
|
21
|
+
.filter((part) => typeof part === 'string')
|
|
22
|
+
.join(' ')
|
|
23
|
+
.replace(/\s+/g, ' ')
|
|
24
|
+
.trim();
|
|
25
|
+
}
|
|
26
|
+
function bodyLineContaining(document, needle) {
|
|
27
|
+
return bodyLinesContaining(document, needle)[0];
|
|
28
|
+
}
|
|
29
|
+
function bodyLinesContaining(document, needle) {
|
|
30
|
+
return [...AstUtils.streamAst(document.parseResult.value)]
|
|
31
|
+
.filter(isBodyLine)
|
|
32
|
+
.filter(line => !needle || line.$cstNode?.text?.includes(needle));
|
|
33
|
+
}
|
|
34
|
+
describe('Parsing tests', () => {
|
|
35
|
+
// rq:["../../../reqlan rq/language/syntax.rq".file_layout]
|
|
36
|
+
test('parse syntax.rq', async () => {
|
|
37
|
+
const document = await parse(readFileSync(join(repoDir, 'reqlan rq/language/syntax.rq'), 'utf8'));
|
|
38
|
+
expect(checkDocumentValid(document)).toBeUndefined();
|
|
39
|
+
});
|
|
40
|
+
// rq:["../../../reqlan rq/language/syntax.rq".lists]
|
|
41
|
+
test('keeps inline parentheses in body prose as text', async () => {
|
|
42
|
+
const document = await parse(`technology {
|
|
43
|
+
continuous ("live") physics.
|
|
44
|
+
the controller (pixelRatio 1, no WebGL) so webviews work.
|
|
45
|
+
sources (and contributions), not the whole model.
|
|
46
|
+
}`);
|
|
47
|
+
expect(checkDocumentValid(document)).toBeUndefined();
|
|
48
|
+
const idea = document.parseResult.value.elements
|
|
49
|
+
.filter(isIdea)
|
|
50
|
+
.find(candidate => candidate.name === 'technology');
|
|
51
|
+
expect(idea).toBeDefined();
|
|
52
|
+
expect(idea.elements.every(element => element.$type === 'BodyLine')).toBe(true);
|
|
53
|
+
const bodyText = idea.elements
|
|
54
|
+
.filter(isBodyLine)
|
|
55
|
+
.map(line => line.$cstNode?.text ?? '')
|
|
56
|
+
.join('\n');
|
|
57
|
+
expect(bodyText).toContain('("live")');
|
|
58
|
+
expect(bodyText).toContain('(pixelRatio 1, no WebGL)');
|
|
59
|
+
expect(bodyText).toContain('sources (and contributions)');
|
|
60
|
+
});
|
|
61
|
+
// rq:["../../../reqlan rq/language/syntax-edge-cases.rq".nested_curly_braces]
|
|
62
|
+
test('keeps inline prose brace pairs as text', async () => {
|
|
63
|
+
const document = await parse(`nested_curly_braces {
|
|
64
|
+
if there is a prose block containing curly braces, {such as this one} they should be treated as part of the prose.
|
|
65
|
+
}`);
|
|
66
|
+
expect(checkDocumentValid(document)).toBeUndefined();
|
|
67
|
+
const idea = document.parseResult.value.elements
|
|
68
|
+
.filter(isIdea)
|
|
69
|
+
.find(candidate => candidate.name === 'nested_curly_braces');
|
|
70
|
+
expect(idea).toBeDefined();
|
|
71
|
+
const bodyText = idea.elements
|
|
72
|
+
.filter(isBodyLine)
|
|
73
|
+
.map(line => line.$cstNode?.text ?? '')
|
|
74
|
+
.join('\n');
|
|
75
|
+
expect(bodyText).toContain('{such as this one}');
|
|
76
|
+
});
|
|
77
|
+
// rq:["../../../reqlan rq/language/syntax-edge-cases.rq".nested_curly_braces]
|
|
78
|
+
test('keeps end-of-line matching prose braces as text', async () => {
|
|
79
|
+
const document = await parse(`resources {
|
|
80
|
+
- Project site: {{SITE_URL}}
|
|
81
|
+
- Docs: {{QUICKSTART_URL}}
|
|
82
|
+
}
|
|
83
|
+
next_idea still here`);
|
|
84
|
+
expect(checkDocumentValid(document)).toBeUndefined();
|
|
85
|
+
const resources = document.parseResult.value.elements
|
|
86
|
+
.filter(isIdea)
|
|
87
|
+
.find(candidate => candidate.name === 'resources');
|
|
88
|
+
expect(resources).toBeDefined();
|
|
89
|
+
const bodyText = resources.elements
|
|
90
|
+
.filter(isBodyLine)
|
|
91
|
+
.map(line => line.$cstNode?.text ?? '')
|
|
92
|
+
.join('\n');
|
|
93
|
+
expect(bodyText).toContain('{{SITE_URL}}');
|
|
94
|
+
expect(bodyText).toContain('{{QUICKSTART_URL}}');
|
|
95
|
+
expect(document.parseResult.value.elements.some(element => isOneLinerIdea(element) && element.name === 'next_idea')).toBe(true);
|
|
96
|
+
});
|
|
97
|
+
// rq:["../../../reqlan rq/language/syntax-edge-cases.rq".nested_curly_braces]
|
|
98
|
+
// rq:["../../../reqlan rq/extension/onboarding/page-thanks-for-installing.rq".installation_event]
|
|
99
|
+
test('parse thanks-for-installing template', async () => {
|
|
100
|
+
const templatePath = join(repoDir, 'packages/extension/templates/thanks-for-installing.template.rq');
|
|
101
|
+
const document = await parse(readFileSync(templatePath, 'utf8'));
|
|
102
|
+
expect(checkDocumentValid(document)).toBeUndefined();
|
|
103
|
+
const names = document.parseResult.value.elements
|
|
104
|
+
.filter(isIdea)
|
|
105
|
+
.map(idea => idea.name);
|
|
106
|
+
expect(names).toEqual([
|
|
107
|
+
'welcome',
|
|
108
|
+
'resources',
|
|
109
|
+
'extension_overview',
|
|
110
|
+
'language_overview',
|
|
111
|
+
'acknowledgements'
|
|
112
|
+
]);
|
|
113
|
+
});
|
|
114
|
+
// rq:["../../../reqlan rq/language/syntax-edge-cases.rq".one_liner_curly_brace_context]
|
|
115
|
+
test('keeps inline braces in one-liner body as text', async () => {
|
|
116
|
+
const document = await parse('one_liner_curly_brace_context this should {be acceptable} as well');
|
|
117
|
+
expect(checkDocumentValid(document)).toBeUndefined();
|
|
118
|
+
const oneLiner = document.parseResult.value.elements.find(isOneLinerIdea);
|
|
119
|
+
expect(oneLiner?.name).toBe('one_liner_curly_brace_context');
|
|
120
|
+
expect(oneLiner?.$cstNode?.text).toBe('one_liner_curly_brace_context this should {be acceptable} as well');
|
|
121
|
+
});
|
|
122
|
+
// rq:["../../../reqlan rq/language/syntax-edge-cases.rq".closing_nested_curly_braces]
|
|
123
|
+
test('allows escaped closing brace in body prose', async () => {
|
|
124
|
+
const document = await parse(`closing_nested_curly_braces {
|
|
125
|
+
if, for some reason, there is a prose block containing only closing curly braces, they should be escaped and allowed \\} like this.
|
|
126
|
+
}`);
|
|
127
|
+
expect(checkDocumentValid(document)).toBeUndefined();
|
|
128
|
+
const idea = document.parseResult.value.elements
|
|
129
|
+
.filter(isIdea)
|
|
130
|
+
.find(candidate => candidate.name === 'closing_nested_curly_braces');
|
|
131
|
+
expect(idea).toBeDefined();
|
|
132
|
+
const bodyText = idea.elements
|
|
133
|
+
.filter(isBodyLine)
|
|
134
|
+
.map(line => line.$cstNode?.text ?? '')
|
|
135
|
+
.join('\n');
|
|
136
|
+
expect(bodyText).toContain('\\}');
|
|
137
|
+
});
|
|
138
|
+
// rq:["../../../reqlan rq/language/syntax-edge-cases.rq".nested_curly_braces]
|
|
139
|
+
test('parse syntax-edge-cases.rq', async () => {
|
|
140
|
+
const document = await parse(readFileSync(join(repoDir, 'reqlan rq/language/syntax-edge-cases.rq'), 'utf8'));
|
|
141
|
+
expect(checkDocumentValid(document)).toBeUndefined();
|
|
142
|
+
});
|
|
143
|
+
// rq:["../../../reqlan rq/language/syntax.rq".round_brackets]
|
|
144
|
+
test('keeps inline braces in body prose as text', async () => {
|
|
145
|
+
const document = await parse(`reframe_animation {
|
|
146
|
+
Every subsequent reframe animates using cytoscape.animate({ fit, easing: 'ease-out-cubic' }).
|
|
147
|
+
Callers may pass \`reframeToViewport({ animate: false })\` to force an instant reframe.
|
|
148
|
+
}`);
|
|
149
|
+
expect(checkDocumentValid(document)).toBeUndefined();
|
|
150
|
+
const idea = document.parseResult.value.elements
|
|
151
|
+
.filter(isIdea)
|
|
152
|
+
.find(candidate => candidate.name === 'reframe_animation');
|
|
153
|
+
expect(idea).toBeDefined();
|
|
154
|
+
const bodyText = idea.elements
|
|
155
|
+
.filter(isBodyLine)
|
|
156
|
+
.map(line => line.$cstNode?.text ?? '')
|
|
157
|
+
.join('\n');
|
|
158
|
+
expect(bodyText).toContain("animate({ fit, easing: 'ease-out-cubic' })");
|
|
159
|
+
expect(bodyText).toContain('reframeToViewport({ animate: false })');
|
|
160
|
+
});
|
|
161
|
+
// rq:["../../../reqlan rq/language/syntax.rq".round_brackets]
|
|
162
|
+
test('parse extension graph.rq library', async () => {
|
|
163
|
+
const document = await parse(readFileSync(join(repoDir, 'reqlan rq/extension/library/graph.rq'), 'utf8'));
|
|
164
|
+
expect(checkDocumentValid(document)).toBeUndefined();
|
|
165
|
+
expect(document.parseResult.value.elements.some(element => isIdea(element) && element.name === 'graph_library')).toBe(true);
|
|
166
|
+
});
|
|
167
|
+
// rq:["../../../reqlan rq/language/syntax.rq".reference_wikilink]
|
|
168
|
+
test('keeps pipe characters in body prose as text', async () => {
|
|
169
|
+
const document = await parse(`context_focus {
|
|
170
|
+
kind: idea | file | selection | none.
|
|
171
|
+
}`);
|
|
172
|
+
expect(checkDocumentValid(document)).toBeUndefined();
|
|
173
|
+
const idea = document.parseResult.value.elements
|
|
174
|
+
.filter(isIdea)
|
|
175
|
+
.find(candidate => candidate.name === 'context_focus');
|
|
176
|
+
expect(idea).toBeDefined();
|
|
177
|
+
const bodyText = idea.elements
|
|
178
|
+
.filter(isBodyLine)
|
|
179
|
+
.map(line => line.$cstNode?.text ?? '')
|
|
180
|
+
.join('\n');
|
|
181
|
+
expect(bodyText).toContain('idea | file | selection | none');
|
|
182
|
+
});
|
|
183
|
+
// rq:["../../../reqlan rq/language/syntax.rq".reference_wikilink]
|
|
184
|
+
test('parse extension context-scope.rq module', async () => {
|
|
185
|
+
const document = await parse(readFileSync(join(repoDir, 'reqlan rq/extension/module/context-scope.rq'), 'utf8'));
|
|
186
|
+
expect(checkDocumentValid(document)).toBeUndefined();
|
|
187
|
+
expect(document.parseResult.value.elements.some(element => isIdea(element) && element.name === 'context_scope')).toBe(true);
|
|
188
|
+
});
|
|
189
|
+
// rq:["../../../reqlan rq/language/syntax.rq".simple_idea]
|
|
190
|
+
test('parse example main.rq', async () => {
|
|
191
|
+
const document = await parse(readFileSync(join(exampleDir, 'main.rq'), 'utf8'));
|
|
192
|
+
expect(checkDocumentValid(document)).toBeUndefined();
|
|
193
|
+
const ideas = document.parseResult.value.elements.filter(isIdea);
|
|
194
|
+
expect(ideas.map(idea => idea.name)).toEqual(['myidea', 'my idea2']);
|
|
195
|
+
const oneLiners = document.parseResult.value.elements.filter(isOneLinerIdea);
|
|
196
|
+
expect(oneLiners.map(idea => idea.name)).toEqual([
|
|
197
|
+
'my_unbracketed_one_liner_idea',
|
|
198
|
+
'my_second_idea',
|
|
199
|
+
'a_simple_idea'
|
|
200
|
+
]);
|
|
201
|
+
expect(oneLinerText(oneLiners[0])).toBe('ideas should support one liners');
|
|
202
|
+
expect(oneLinerText(oneLiners[1])).toBe('this is a blob of text');
|
|
203
|
+
});
|
|
204
|
+
// rq:["../../../reqlan rq/extension/features-imports.rq".from_import_syntax]
|
|
205
|
+
test('parse from import with multiple symbols', async () => {
|
|
206
|
+
const document = await parse('from "./example.rq" import symbol1, symbol2, symbol3');
|
|
207
|
+
expect(checkDocumentValid(document)).toBeUndefined();
|
|
208
|
+
const fromImport = document.parseResult.value.imports.find(isFromImport);
|
|
209
|
+
expect(fromImport?.path).toBe('./example.rq');
|
|
210
|
+
expect(fromImport?.specifiers.map(specifier => specifier.idea.$refText)).toEqual([
|
|
211
|
+
'symbol1',
|
|
212
|
+
'symbol2',
|
|
213
|
+
'symbol3'
|
|
214
|
+
]);
|
|
215
|
+
});
|
|
216
|
+
// rq:["../../../reqlan rq/language/syntax.rq".string_and_reference_apostrophes]
|
|
217
|
+
test('parse single-quoted import paths and idea names', async () => {
|
|
218
|
+
const document = await parse(`from './example.rq' import symbol1
|
|
219
|
+
import './ontology.rq' as ontology
|
|
220
|
+
'my spaced idea' body text
|
|
221
|
+
demo {
|
|
222
|
+
see ['./other.rq'] and ['../file.py']
|
|
223
|
+
}`);
|
|
224
|
+
expect(checkDocumentValid(document)).toBeUndefined();
|
|
225
|
+
const fromImport = document.parseResult.value.imports.find(isFromImport);
|
|
226
|
+
expect(fromImport?.path).toBe('./example.rq');
|
|
227
|
+
const oneLiner = document.parseResult.value.elements.find(isOneLinerIdea);
|
|
228
|
+
expect(oneLiner?.name).toBe('my spaced idea');
|
|
229
|
+
const bracketReferences = [...AstUtils.streamAst(document.parseResult.value)].filter(isBracketReference);
|
|
230
|
+
expect(bracketReferences).toHaveLength(2);
|
|
231
|
+
});
|
|
232
|
+
// rq:["../../../reqlan rq/language/imports.rq".import_from]
|
|
233
|
+
test('parse example sub idea.rq', async () => {
|
|
234
|
+
const document = await parse(readFileSync(join(exampleDir, 'sub idea.rq'), 'utf8'));
|
|
235
|
+
expect(checkDocumentValid(document)).toBeUndefined();
|
|
236
|
+
expect(document.parseResult.value.imports).toHaveLength(5);
|
|
237
|
+
});
|
|
238
|
+
// rq:["../../../reqlan rq/language/syntax.rq".simple_idea]
|
|
239
|
+
test('parse ontology.rq one-liner ideas', async () => {
|
|
240
|
+
const document = await parse(readFileSync(join(repoDir, 'reqlan rq/language/ontology.rq'), 'utf8'));
|
|
241
|
+
expect(checkDocumentValid(document)).toBeUndefined();
|
|
242
|
+
const oneLiners = document.parseResult.value.elements.filter(isOneLinerIdea);
|
|
243
|
+
expect(oneLiners.map(idea => idea.name)).toEqual([
|
|
244
|
+
'idea',
|
|
245
|
+
'ideaset',
|
|
246
|
+
'file',
|
|
247
|
+
'keyword',
|
|
248
|
+
'import_statement',
|
|
249
|
+
'reference',
|
|
250
|
+
'referenced_files',
|
|
251
|
+
'cartographic_map',
|
|
252
|
+
'extension',
|
|
253
|
+
'grammar_rule',
|
|
254
|
+
'attribute',
|
|
255
|
+
'attribute_body',
|
|
256
|
+
'idea_name'
|
|
257
|
+
]);
|
|
258
|
+
});
|
|
259
|
+
// rq:["../../../reqlan rq/language/syntax.rq".simple_idea]
|
|
260
|
+
test('parse one-liner ideas', async () => {
|
|
261
|
+
const document = await parse(`my_unbracketed_one_liner_idea ideas should support one liners
|
|
262
|
+
my_second_idea this is a blob of text`);
|
|
263
|
+
expect(checkDocumentValid(document)).toBeUndefined();
|
|
264
|
+
const oneLiners = document.parseResult.value.elements.filter(isOneLinerIdea);
|
|
265
|
+
expect(oneLiners.map(idea => idea.name)).toEqual([
|
|
266
|
+
'my_unbracketed_one_liner_idea',
|
|
267
|
+
'my_second_idea'
|
|
268
|
+
]);
|
|
269
|
+
expect(oneLinerText(oneLiners[0])).toBe('ideas should support one liners');
|
|
270
|
+
expect(oneLinerText(oneLiners[1])).toBe('this is a blob of text');
|
|
271
|
+
});
|
|
272
|
+
// rq:["../../../reqlan rq/language/syntax.rq".simple_idea]
|
|
273
|
+
test('one-liner body does not continue on the next line', async () => {
|
|
274
|
+
const document = await parse(`first_idea line one
|
|
275
|
+
continued prose belongs to a new declaration
|
|
276
|
+
second_idea line two`);
|
|
277
|
+
expect(checkDocumentValid(document)).toBeUndefined();
|
|
278
|
+
const oneLiners = document.parseResult.value.elements.filter(isOneLinerIdea);
|
|
279
|
+
expect(oneLiners.map(idea => idea.name)).toEqual([
|
|
280
|
+
'first_idea',
|
|
281
|
+
'continued',
|
|
282
|
+
'second_idea'
|
|
283
|
+
]);
|
|
284
|
+
expect(oneLinerText(oneLiners[0])).toBe('line one');
|
|
285
|
+
expect(oneLinerText(oneLiners[2])).toBe('line two');
|
|
286
|
+
});
|
|
287
|
+
// rq:["../../../reqlan rq/language/syntax.rq".block_idea]
|
|
288
|
+
test('parse inline idea', async () => {
|
|
289
|
+
const document = await parse(`myidea {
|
|
290
|
+
It should be a good thing.
|
|
291
|
+
}`);
|
|
292
|
+
expect(checkDocumentValid(document)).toBeUndefined();
|
|
293
|
+
const idea = document.parseResult.value.elements[0];
|
|
294
|
+
expect(isIdea(idea) && idea.name).toBe('myidea');
|
|
295
|
+
});
|
|
296
|
+
// rq:["../../../reqlan rq/language/imports.rq".import_from]
|
|
297
|
+
test('parses from-import example line inside an attribute block value', async () => {
|
|
298
|
+
const document = await parse(`import_from {
|
|
299
|
+
From-import syntax uses from, a quoted path, import, and an idea name.
|
|
300
|
+
From-import may include an alias using as.
|
|
301
|
+
|
|
302
|
+
@example {
|
|
303
|
+
from "example.rq" import idea
|
|
304
|
+
}
|
|
305
|
+
}`);
|
|
306
|
+
expect(checkDocumentValid(document)).toBeUndefined();
|
|
307
|
+
});
|
|
308
|
+
// rq:["../../../reqlan rq/language/imports.rq".import_keywords]
|
|
309
|
+
test('parse import keywords in idea body text', async () => {
|
|
310
|
+
const document = await parse(readFileSync(join(exampleDir, 'exampleimport2.rq'), 'utf8'));
|
|
311
|
+
expect(checkDocumentValid(document)).toBeUndefined();
|
|
312
|
+
});
|
|
313
|
+
// rq:["../../../reqlan rq/language/imports.rq".import_keywords]
|
|
314
|
+
test('parse body text containing from import as words', async () => {
|
|
315
|
+
const document = await parse(`demo {
|
|
316
|
+
copy from import as needed.
|
|
317
|
+
}`);
|
|
318
|
+
expect(checkDocumentValid(document)).toBeUndefined();
|
|
319
|
+
});
|
|
320
|
+
// rq:["../../../reqlan rq/language/syntax.rq".reference_file]
|
|
321
|
+
test('parse file references in a reqfile.rq', async () => {
|
|
322
|
+
const document = await parse(readFileSync(join(exampleDir, 'a source folder/a reqfile.rq'), 'utf8'));
|
|
323
|
+
expect(checkDocumentValid(document)).toBeUndefined();
|
|
324
|
+
});
|
|
325
|
+
// rq:["../../../reqlan rq/language/syntax.rq".code_snippets]
|
|
326
|
+
test('parse code snippet in block idea', async () => {
|
|
327
|
+
const document = await parse(`demo {
|
|
328
|
+
Example usage:
|
|
329
|
+
\`\`\`python
|
|
330
|
+
print("hello")
|
|
331
|
+
\`\`\`
|
|
332
|
+
}`);
|
|
333
|
+
expect(checkDocumentValid(document)).toBeUndefined();
|
|
334
|
+
});
|
|
335
|
+
// rq:["../../../reqlan rq/language/syntax.rq".code_snippets]
|
|
336
|
+
test('parse fenced code block in attribute value', async () => {
|
|
337
|
+
const document = await parse(`demo {
|
|
338
|
+
@example {
|
|
339
|
+
\`\`\`python
|
|
340
|
+
print("hello")
|
|
341
|
+
\`\`\`
|
|
342
|
+
}
|
|
343
|
+
}`);
|
|
344
|
+
expect(checkDocumentValid(document)).toBeUndefined();
|
|
345
|
+
const attribute = [...AstUtils.streamAst(document.parseResult.value)]
|
|
346
|
+
.filter(isAttribute)
|
|
347
|
+
.find(candidate => candidate.name === 'example');
|
|
348
|
+
const snippet = attribute?.value
|
|
349
|
+
&& [...AstUtils.streamAst(attribute.value)].find(isCodeSnippet);
|
|
350
|
+
expect(snippet && 'raw' in snippet && snippet.raw).toContain('print("hello")');
|
|
351
|
+
});
|
|
352
|
+
// rq:["../../../reqlan rq/language/syntax.rq".code_snippets]
|
|
353
|
+
test('treats fenced rq example as opaque text', async () => {
|
|
354
|
+
const document = await parse(`demo {
|
|
355
|
+
@example {
|
|
356
|
+
\`\`\`rq
|
|
357
|
+
from "example.rq" import idea
|
|
358
|
+
@status pending
|
|
359
|
+
\`\`\`
|
|
360
|
+
}
|
|
361
|
+
}`);
|
|
362
|
+
expect(checkDocumentValid(document)).toBeUndefined();
|
|
363
|
+
});
|
|
364
|
+
// rq:["../../../reqlan rq/language/syntax.rq".lists]
|
|
365
|
+
test('parse list items as one-liners and anonymous blocks', async () => {
|
|
366
|
+
const document = await parse(`demo {
|
|
367
|
+
@tags (
|
|
368
|
+
todo
|
|
369
|
+
highpriority
|
|
370
|
+
)
|
|
371
|
+
@plan {
|
|
372
|
+
steps (
|
|
373
|
+
- do the thing
|
|
374
|
+
- do the other thing
|
|
375
|
+
)
|
|
376
|
+
}
|
|
377
|
+
@refs (
|
|
378
|
+
{ see [[beta]] }
|
|
379
|
+
)
|
|
380
|
+
}
|
|
381
|
+
beta {
|
|
382
|
+
target
|
|
383
|
+
}`);
|
|
384
|
+
expect(checkDocumentValid(document)).toBeUndefined();
|
|
385
|
+
});
|
|
386
|
+
// rq:["../../../reqlan rq/development/core.rq".testing]
|
|
387
|
+
test('parse file references in list items', async () => {
|
|
388
|
+
const document = await parse(`demo {
|
|
389
|
+
@tests (
|
|
390
|
+
["../../packages/language/test/validating.test.ts:reports duplicate when local idea shares imported idea name"]
|
|
391
|
+
)
|
|
392
|
+
}`);
|
|
393
|
+
expect(checkDocumentValid(document)).toBeUndefined();
|
|
394
|
+
const listItem = [...AstUtils.streamAst(document.parseResult.value)]
|
|
395
|
+
.find(node => node.$type === 'OneLinerListItem');
|
|
396
|
+
expect(listItem).toBeDefined();
|
|
397
|
+
const fileReferences = [...AstUtils.streamAst(document.parseResult.value)]
|
|
398
|
+
.filter(isFileReference);
|
|
399
|
+
const bracketReferences = [...AstUtils.streamAst(document.parseResult.value)]
|
|
400
|
+
.filter(isBracketReference);
|
|
401
|
+
expect(bracketReferences).toHaveLength(1);
|
|
402
|
+
const target = bracketReferences[0]?.target;
|
|
403
|
+
expect((target?.$type === 'FileReference' && target.file.includes('validating.test.ts'))
|
|
404
|
+
|| (target?.$type === 'QualifiedReference' && target.path?.$refText?.includes('validating.test.ts'))).toBe(true);
|
|
405
|
+
expect(fileReferences[0]?.file?.includes('validating.test.ts')
|
|
406
|
+
?? target?.$type === 'QualifiedReference').toBe(true);
|
|
407
|
+
});
|
|
408
|
+
// rq:["../../../reqlan rq/language/syntax.rq".reference_brackets]
|
|
409
|
+
test('parse idea.attribute and ideaset references', async () => {
|
|
410
|
+
const document = await parse(`demo {
|
|
411
|
+
see [my_ideaset] and [myidea.status]
|
|
412
|
+
}
|
|
413
|
+
my_ideaset (
|
|
414
|
+
myidea
|
|
415
|
+
)
|
|
416
|
+
myidea {
|
|
417
|
+
body text
|
|
418
|
+
}`);
|
|
419
|
+
expect(checkDocumentValid(document)).toBeUndefined();
|
|
420
|
+
});
|
|
421
|
+
// rq:["../../../reqlan rq/language/syntax.rq".lists]
|
|
422
|
+
test('parse named block list items and nested lists in idea bodies', async () => {
|
|
423
|
+
const document = await parse(`demo {
|
|
424
|
+
@subreqs (
|
|
425
|
+
duplicate_error {
|
|
426
|
+
should be raised if the idea has the same name.
|
|
427
|
+
@tests (
|
|
428
|
+
["../../packages/language/test/validating.test.ts:reports duplicate"]
|
|
429
|
+
)
|
|
430
|
+
}
|
|
431
|
+
)
|
|
432
|
+
}`);
|
|
433
|
+
expect(checkDocumentValid(document)).toBeUndefined();
|
|
434
|
+
});
|
|
435
|
+
// rq:["../../../reqlan rq/language/syntax.rq".naked_strings_in_body]
|
|
436
|
+
test('keeps naked quoted phrases in block body text', async () => {
|
|
437
|
+
const document = await parse(`manual_reframe {
|
|
438
|
+
- "Fit to view" control in ["../../../packages/extension/webviews/ideas-summary/components/GraphControls.svelte"] dispatches.
|
|
439
|
+
}`);
|
|
440
|
+
expect(checkDocumentValid(document)).toBeUndefined();
|
|
441
|
+
const bodyLine = [...AstUtils.streamAst(document.parseResult.value)].find(isBodyLine);
|
|
442
|
+
expect(bodyLine?.$cstNode?.text).toContain('"Fit to view"');
|
|
443
|
+
const bracketReferences = [...AstUtils.streamAst(document.parseResult.value)].filter(isBracketReference);
|
|
444
|
+
expect(bracketReferences).toHaveLength(1);
|
|
445
|
+
const target = bracketReferences[0]?.target;
|
|
446
|
+
expect(target?.$type === 'FileReference' || target?.$type === 'QualifiedReference').toBe(true);
|
|
447
|
+
});
|
|
448
|
+
// rq:["../../../reqlan rq/language/syntax.rq".naked_strings_in_body]
|
|
449
|
+
test('keeps naked quoted phrases in attribute scalar values', async () => {
|
|
450
|
+
const document = await parse(`demo {
|
|
451
|
+
@plan use "Fit to view" when the graph is off-screen
|
|
452
|
+
}`);
|
|
453
|
+
expect(checkDocumentValid(document)).toBeUndefined();
|
|
454
|
+
const attribute = [...AstUtils.streamAst(document.parseResult.value)].find(isAttribute);
|
|
455
|
+
expect(attribute?.name).toBe('plan');
|
|
456
|
+
expect(isScalarValue(attribute?.value)).toBe(true);
|
|
457
|
+
expect(attribute?.value?.$cstNode?.text).toContain('"Fit to view"');
|
|
458
|
+
});
|
|
459
|
+
// rq:["../../../reqlan rq/language/syntax.rq".naked_strings_in_body]
|
|
460
|
+
test('keeps single-quoted naked phrases in block body text', async () => {
|
|
461
|
+
const document = await parse(`demo {
|
|
462
|
+
click 'Fit to view' to reframe.
|
|
463
|
+
}`);
|
|
464
|
+
expect(checkDocumentValid(document)).toBeUndefined();
|
|
465
|
+
const bodyLine = bodyLineContaining(document, 'Fit to view');
|
|
466
|
+
expect(bodyLine?.$cstNode?.text).toContain("'Fit to view'");
|
|
467
|
+
});
|
|
468
|
+
// rq:["../../../reqlan rq/language/syntax.rq".naked_strings_in_body]
|
|
469
|
+
test('keeps naked quoted phrases in one-liner body after the idea name', async () => {
|
|
470
|
+
const document = await parse(`demo mention "Fit to view" in passing`);
|
|
471
|
+
expect(checkDocumentValid(document)).toBeUndefined();
|
|
472
|
+
const oneLiner = document.parseResult.value.elements.find(isOneLinerIdea);
|
|
473
|
+
expect(oneLiner?.name).toBe('demo');
|
|
474
|
+
expect(oneLiner?.$cstNode?.text).toContain('"Fit to view"');
|
|
475
|
+
});
|
|
476
|
+
// rq:["../../../reqlan rq/language/syntax.rq".naked_strings_in_body]
|
|
477
|
+
test('keeps naked quoted phrases in list items', async () => {
|
|
478
|
+
const document = await parse(`demo {
|
|
479
|
+
@notes (
|
|
480
|
+
"Fit to view" is the manual control label
|
|
481
|
+
)
|
|
482
|
+
}`);
|
|
483
|
+
expect(checkDocumentValid(document)).toBeUndefined();
|
|
484
|
+
const listItemLine = [...AstUtils.streamAst(document.parseResult.value)]
|
|
485
|
+
.find(node => node.$type === 'OneLinerListItem');
|
|
486
|
+
expect(listItemLine?.$cstNode?.text).toContain('"Fit to view"');
|
|
487
|
+
});
|
|
488
|
+
// rq:["../../../reqlan rq/language/syntax.rq".naked_strings_in_body]
|
|
489
|
+
test('keeps naked quotes alongside bracket references on one line', async () => {
|
|
490
|
+
const document = await parse(`auto_reframe {
|
|
491
|
+
without requiring the user to click "Fit to view".
|
|
492
|
+
- "Fit to view" in ["../../../packages/extension/webviews/activity-bar/components/MiniGraphCanvas.svelte"] calls reframe.
|
|
493
|
+
}`);
|
|
494
|
+
expect(checkDocumentValid(document)).toBeUndefined();
|
|
495
|
+
const lines = bodyLinesContaining(document, 'Fit to view');
|
|
496
|
+
expect(lines).toHaveLength(2);
|
|
497
|
+
for (const line of lines) {
|
|
498
|
+
expect(line.$cstNode?.text).toMatch(/"Fit to view"/);
|
|
499
|
+
}
|
|
500
|
+
expect([...AstUtils.streamAst(document.parseResult.value)].filter(isBracketReference)).toHaveLength(1);
|
|
501
|
+
});
|
|
502
|
+
// rq:["../../../reqlan rq/language/syntax.rq".naked_strings_in_body]
|
|
503
|
+
test('does not treat naked quoted path-like prose as an import reference', async () => {
|
|
504
|
+
const document = await parse(`demo {
|
|
505
|
+
see "./not-an-import.rq" mentioned in prose only
|
|
506
|
+
}`, { validation: true });
|
|
507
|
+
expect(checkDocumentValid(document)).toBeUndefined();
|
|
508
|
+
const importErrors = (document.diagnostics ?? []).filter(diagnostic => typeof diagnostic.message === 'string'
|
|
509
|
+
&& diagnostic.message.includes('Could not resolve reference to Import'));
|
|
510
|
+
expect(importErrors).toHaveLength(0);
|
|
511
|
+
});
|
|
512
|
+
// rq:["../../../reqlan rq/language/syntax.rq".naked_strings_in_body]
|
|
513
|
+
test('still parses top-level quoted idea names as string literals', async () => {
|
|
514
|
+
const document = await parse(`"my spaced idea" body mentions "Fit to view"`);
|
|
515
|
+
expect(checkDocumentValid(document)).toBeUndefined();
|
|
516
|
+
const oneLiner = document.parseResult.value.elements.find(isOneLinerIdea);
|
|
517
|
+
expect(oneLiner?.name).toBe('my spaced idea');
|
|
518
|
+
expect(oneLiner?.$cstNode?.text).toContain('"Fit to view"');
|
|
519
|
+
});
|
|
520
|
+
// rq:["../../../reqlan rq/language/syntax.rq".naked_strings_in_body]
|
|
521
|
+
test('parses reframe requirement snippets with naked quoted UI labels', async () => {
|
|
522
|
+
const document = await parse(`manual_reframe {
|
|
523
|
+
Users can explicitly reframe the viewport when pan/zoom has moved the graph off-screen or they want to see the whole neighbourhood again.
|
|
524
|
+
- "Fit to view" control in ["../../../packages/extension/webviews/ideas-summary/components/GraphControls.svelte"] dispatches to GraphView → GraphCyController.reframeToViewport().
|
|
525
|
+
- "Fit to view" control in ["../../../packages/extension/webviews/activity-bar/components/MiniGraphCanvas.svelte"] calls the same API on the mini-graph controller.
|
|
526
|
+
Every graph surface that mounts GraphCyController should expose this control.
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
auto_reframe {
|
|
530
|
+
The graph should automatically fit to the viewport when the loaded node set changes — without requiring the user to click "Fit to view".
|
|
531
|
+
Does not trigger for metadata-only updates on the same node ids (label, colour, isCenter flag, etc.) — those keep the user's current pan/zoom.
|
|
532
|
+
}`, { validation: true });
|
|
533
|
+
expect(checkDocumentValid(document)).toBeUndefined();
|
|
534
|
+
const manual = document.parseResult.value.elements.find(element => isIdea(element) && element.name === 'manual_reframe');
|
|
535
|
+
const auto = document.parseResult.value.elements.find(element => isIdea(element) && element.name === 'auto_reframe');
|
|
536
|
+
expect(isIdea(manual) && manual.elements.some(element => element.$type === 'BodyLine' && element.$cstNode?.text?.includes('"Fit to view"'))).toBe(true);
|
|
537
|
+
expect(isIdea(auto) && auto.elements.some(element => element.$type === 'BodyLine' && element.$cstNode?.text?.includes('"Fit to view"'))).toBe(true);
|
|
538
|
+
const nakedImportErrors = (document.diagnostics ?? []).filter(diagnostic => typeof diagnostic.message === 'string'
|
|
539
|
+
&& diagnostic.message.includes('Could not resolve reference to Import')
|
|
540
|
+
&& diagnostic.message.includes('Fit to view'));
|
|
541
|
+
expect(nakedImportErrors).toHaveLength(0);
|
|
542
|
+
});
|
|
543
|
+
// rq:["../../../reqlan rq/language/syntax.rq".naked_strings_in_body]
|
|
544
|
+
test('parses self-contained ideas with apostrophe contractions in body prose', async () => {
|
|
545
|
+
const document = await parse(`my_one_line_idea_with_appostrophe there's nothing wrong with this idea
|
|
546
|
+
|
|
547
|
+
my_multi_line_idea_with_appostrophe {
|
|
548
|
+
there's nothing wrong with this idea either.
|
|
549
|
+
}`);
|
|
550
|
+
expect(checkDocumentValid(document)).toBeUndefined();
|
|
551
|
+
expect(document.parseResult.value.elements).toHaveLength(2);
|
|
552
|
+
const oneLiner = document.parseResult.value.elements.find(isOneLinerIdea);
|
|
553
|
+
expect(oneLiner?.name).toBe('my_one_line_idea_with_appostrophe');
|
|
554
|
+
expect(oneLinerText(oneLiner)).toBe("there's nothing wrong with this idea");
|
|
555
|
+
const block = document.parseResult.value.elements.find(element => isIdea(element) && element.name === 'my_multi_line_idea_with_appostrophe');
|
|
556
|
+
expect(isIdea(block)).toBe(true);
|
|
557
|
+
const bodyLine = bodyLineContaining(document, "there's");
|
|
558
|
+
expect(bodyLine?.$cstNode?.text).toBe("there's nothing wrong with this idea either.");
|
|
559
|
+
});
|
|
560
|
+
// rq:["../../../reqlan rq/extension/features-syntax.rq".syntax_features]
|
|
561
|
+
test('parse features-syntax.rq', async () => {
|
|
562
|
+
const document = await parse(readFileSync(join(repoDir, 'reqlan rq/extension/syntax/features-syntax.rq'), 'utf8'));
|
|
563
|
+
expect(checkDocumentValid(document)).toBeUndefined();
|
|
564
|
+
expect(document.parseResult.value.elements.map(element => element.name)).toContain('sensible_alias_support');
|
|
565
|
+
});
|
|
566
|
+
// rq:["../../../reqlan rq/language/syntax.rq".markdown_links]
|
|
567
|
+
test('parse markdown links without treating label as a reference', async () => {
|
|
568
|
+
const document = await parse(`demo see [the label](path/here) and [myidea] refs
|
|
569
|
+
myidea {
|
|
570
|
+
body
|
|
571
|
+
}`);
|
|
572
|
+
expect(checkDocumentValid(document)).toBeUndefined();
|
|
573
|
+
const oneLiner = document.parseResult.value.elements.find(isOneLinerIdea);
|
|
574
|
+
const markdownLink = oneLiner?.body.content.find(part => typeof part !== 'string' && part.$type === 'MarkdownLink');
|
|
575
|
+
expect(markdownLink && 'raw' in markdownLink && markdownLink.raw).toBe('[the label](path/here)');
|
|
576
|
+
const bracketReferences = [...AstUtils.streamAst(document.parseResult.value)]
|
|
577
|
+
.filter(isBracketReference);
|
|
578
|
+
expect(bracketReferences).toHaveLength(1);
|
|
579
|
+
const target = bracketReferences[0]?.target;
|
|
580
|
+
expect(target?.$type === 'LocalReference'
|
|
581
|
+
|| (target?.$type === 'QualifiedReference' && target.qualifier?.$refText === 'myidea')).toBe(true);
|
|
582
|
+
});
|
|
583
|
+
// rq:["../../../reqlan rq/language/syntax.rq".markdown_links]
|
|
584
|
+
test('parse docs.rq markdown folder link', async () => {
|
|
585
|
+
const document = await parse(readFileSync(join(repoDir, 'reqlan rq/docs/docs.rq'), 'utf8'));
|
|
586
|
+
expect(checkDocumentValid(document)).toBeUndefined();
|
|
587
|
+
const oneLiner = document.parseResult.value.elements.find(isOneLinerIdea);
|
|
588
|
+
const markdownLink = oneLiner?.body.content.find(part => typeof part !== 'string' && part.$type === 'MarkdownLink');
|
|
589
|
+
expect(markdownLink && 'raw' in markdownLink && markdownLink.raw).toBe('[the reqlan rq folder of this repo](../../reqlan rq)');
|
|
590
|
+
});
|
|
591
|
+
// rq:["../../../reqlan rq/language/syntax.rq".markdown_links]
|
|
592
|
+
test('markdown link labels are not treated as reference prefix context', async () => {
|
|
593
|
+
const services = createReqlanServices(EmptyFileSystem);
|
|
594
|
+
const parse = parseHelper(services.Reqlan);
|
|
595
|
+
const document = await parse(`demo { see [label](target) }
|
|
596
|
+
label { body }`);
|
|
597
|
+
const labelOffset = document.textDocument.getText().indexOf('label');
|
|
598
|
+
const position = document.textDocument.positionAt(labelOffset);
|
|
599
|
+
expect(getReferencePrefixContext(document, position)).toBeUndefined();
|
|
600
|
+
expect(isMarkdownLinkLabelPosition(document, position)).toBe(true);
|
|
601
|
+
});
|
|
602
|
+
// rq:["../../../reqlan rq/extension/module/graphical_graph.rq".graphical_graph]
|
|
603
|
+
test('parse bare numbers in body text', async () => {
|
|
604
|
+
const document = await parse(s `
|
|
605
|
+
graphical_graph {
|
|
606
|
+
An "Indirect references" toggle expands neighbourhood depth from 1 to 2 hops.
|
|
607
|
+
}
|
|
608
|
+
`);
|
|
609
|
+
expect(document.parseResult.lexerErrors).toHaveLength(0);
|
|
610
|
+
expect(checkDocumentValid(document)).toBeUndefined();
|
|
611
|
+
});
|
|
612
|
+
// rq:["../../../reqlan rq/language/syntax.rq".attribute_forms]
|
|
613
|
+
test('parses valued attributes without a colon', async () => {
|
|
614
|
+
const document = await parse(`demo {
|
|
615
|
+
@status pending
|
|
616
|
+
@plan use "Fit to view" when needed
|
|
617
|
+
@tags (
|
|
618
|
+
todo
|
|
619
|
+
highpriority
|
|
620
|
+
)
|
|
621
|
+
@notes {
|
|
622
|
+
step one
|
|
623
|
+
step two
|
|
624
|
+
}
|
|
625
|
+
}`);
|
|
626
|
+
expect(checkDocumentValid(document)).toBeUndefined();
|
|
627
|
+
const attributes = [...AstUtils.streamAst(document.parseResult.value)]
|
|
628
|
+
.filter(isAttribute);
|
|
629
|
+
expect(attributes.map(attribute => attribute.name)).toEqual([
|
|
630
|
+
'status',
|
|
631
|
+
'plan',
|
|
632
|
+
'tags',
|
|
633
|
+
'notes'
|
|
634
|
+
]);
|
|
635
|
+
const status = attributes.find(attribute => attribute.name === 'status');
|
|
636
|
+
expect(isScalarValue(status?.value)).toBe(true);
|
|
637
|
+
expect(status?.value?.$cstNode?.text).toBe('pending');
|
|
638
|
+
const plan = attributes.find(attribute => attribute.name === 'plan');
|
|
639
|
+
expect(plan?.value?.$cstNode?.text).toContain('"Fit to view"');
|
|
640
|
+
expect(attributes.find(attribute => attribute.name === 'tags')?.value?.$type).toBe('ListValue');
|
|
641
|
+
expect(attributes.find(attribute => attribute.name === 'notes')?.value?.$type).toBe('BlockValue');
|
|
642
|
+
});
|
|
643
|
+
// rq:["../../../reqlan rq/language/syntax.rq".round_brackets]
|
|
644
|
+
test('parses inline comma-separated attribute list values', async () => {
|
|
645
|
+
const document = await parse(`context_scope_v2 {
|
|
646
|
+
@status in-progress
|
|
647
|
+
@tags (context, ui, knowledge, v2)
|
|
648
|
+
@tests (
|
|
649
|
+
["../../../packages/analytical/test/context-signals.test.ts"]
|
|
650
|
+
["../../../packages/extension/test/context-model.test.ts"]
|
|
651
|
+
)
|
|
652
|
+
}`);
|
|
653
|
+
expect(checkDocumentValid(document)).toBeUndefined();
|
|
654
|
+
const attributes = [...AstUtils.streamAst(document.parseResult.value)].filter(isAttribute);
|
|
655
|
+
expect(attributes.map(attribute => attribute.name)).toEqual([
|
|
656
|
+
'status',
|
|
657
|
+
'tags',
|
|
658
|
+
'tests'
|
|
659
|
+
]);
|
|
660
|
+
const tags = attributes.find(attribute => attribute.name === 'tags');
|
|
661
|
+
expect(tags?.value?.$type).toBe('ListValue');
|
|
662
|
+
if (tags?.value?.$type === 'ListValue') {
|
|
663
|
+
expect(tags.value.items).toHaveLength(4);
|
|
664
|
+
expect(tags.value.items.map(item => item.$cstNode?.text?.trim())).toEqual([
|
|
665
|
+
'context',
|
|
666
|
+
'ui',
|
|
667
|
+
'knowledge',
|
|
668
|
+
'v2'
|
|
669
|
+
]);
|
|
670
|
+
}
|
|
671
|
+
});
|
|
672
|
+
// rq:["../../../reqlan rq/language/syntax.rq".attribute_forms]
|
|
673
|
+
test('still accepts optional colon before attribute values', async () => {
|
|
674
|
+
const document = await parse(`demo {
|
|
675
|
+
@status: pending
|
|
676
|
+
@tags: (
|
|
677
|
+
todo
|
|
678
|
+
)
|
|
679
|
+
}`);
|
|
680
|
+
expect(checkDocumentValid(document)).toBeUndefined();
|
|
681
|
+
const attributes = [...AstUtils.streamAst(document.parseResult.value)].filter(isAttribute);
|
|
682
|
+
expect(attributes.map(attribute => attribute.name)).toEqual(['status', 'tags']);
|
|
683
|
+
expect(attributes[0]?.value?.$cstNode?.text).toBe('pending');
|
|
684
|
+
expect(attributes[1]?.value?.$type).toBe('ListValue');
|
|
685
|
+
});
|
|
686
|
+
// rq:["../../../reqlan rq/language/syntax.rq".attribute_values]
|
|
687
|
+
test('parse syntax_whitespace.rq body lines and insignificant attribute whitespace', async () => {
|
|
688
|
+
const document = await parse(readFileSync(join(repoDir, 'reqlan rq/language/syntax_whitespace.rq'), 'utf8'));
|
|
689
|
+
expect(checkDocumentValid(document)).toBeUndefined();
|
|
690
|
+
const idea = document.parseResult.value.elements.find(isIdea);
|
|
691
|
+
expect(idea?.name).toBe('syntax_whitespace');
|
|
692
|
+
expect(idea?.elements.map(element => element.$type)).toEqual([
|
|
693
|
+
'BodyLine',
|
|
694
|
+
'BodyLine',
|
|
695
|
+
'Attribute',
|
|
696
|
+
'Attribute'
|
|
697
|
+
]);
|
|
698
|
+
const attributes = idea?.elements.filter(element => element.$type === 'Attribute');
|
|
699
|
+
expect(attributes?.map(attribute => 'name' in attribute && attribute.name)).toEqual([
|
|
700
|
+
'exampleattribute',
|
|
701
|
+
'perfectly_acceptable_attribute'
|
|
702
|
+
]);
|
|
703
|
+
expect(attributes?.map(attribute => 'value' in attribute && attribute.value?.$cstNode?.text?.trim())).toEqual([
|
|
704
|
+
'the same goes for attributes in an idea',
|
|
705
|
+
'to labour the point'
|
|
706
|
+
]);
|
|
707
|
+
});
|
|
708
|
+
// rq:["../../../reqlan rq/extension/features-syntax.rq".at_sign_in_idea_text_block]
|
|
709
|
+
// rq:["../../../reqlan rq/language/syntax.rq".attribute_location]
|
|
710
|
+
test('treats mid-line @ as body text and line-start @ as attribute', async () => {
|
|
711
|
+
const document = await parse(s `
|
|
712
|
+
demo {
|
|
713
|
+
so @this_is_not_an_attribute
|
|
714
|
+
@this_is_an_attribute
|
|
715
|
+
}
|
|
716
|
+
`);
|
|
717
|
+
expect(checkDocumentValid(document)).toBeUndefined();
|
|
718
|
+
const idea = document.parseResult.value.elements.find(isIdea);
|
|
719
|
+
expect(idea?.elements.map(element => element.$type)).toEqual([
|
|
720
|
+
'BodyLine',
|
|
721
|
+
'Attribute'
|
|
722
|
+
]);
|
|
723
|
+
const body = idea?.elements[0];
|
|
724
|
+
expect(body?.$type).toBe('BodyLine');
|
|
725
|
+
if (body?.$type === 'BodyLine') {
|
|
726
|
+
const text = body.parts.map(part => part.text ?? part.punct ?? '').join('');
|
|
727
|
+
expect(text).toContain('@');
|
|
728
|
+
expect(text).toContain('this_is_not_an_attribute');
|
|
729
|
+
}
|
|
730
|
+
const attribute = idea?.elements[1];
|
|
731
|
+
expect(attribute?.$type).toBe('Attribute');
|
|
732
|
+
if (attribute?.$type === 'Attribute') {
|
|
733
|
+
expect(attribute.name).toBe('this_is_an_attribute');
|
|
734
|
+
}
|
|
735
|
+
});
|
|
736
|
+
// rq:["../../../reqlan rq/extension/features-syntax.rq".at_sign_in_idea_text_block]
|
|
737
|
+
test('backslash before @ at line start keeps it as body text', async () => {
|
|
738
|
+
const document = await parse(s `
|
|
739
|
+
demo {
|
|
740
|
+
\\@escaped_attribute
|
|
741
|
+
@real_attribute
|
|
742
|
+
}
|
|
743
|
+
`);
|
|
744
|
+
expect(checkDocumentValid(document)).toBeUndefined();
|
|
745
|
+
const idea = document.parseResult.value.elements.find(isIdea);
|
|
746
|
+
expect(idea?.elements.map(element => element.$type)).toEqual([
|
|
747
|
+
'BodyLine',
|
|
748
|
+
'Attribute'
|
|
749
|
+
]);
|
|
750
|
+
const body = idea?.elements[0];
|
|
751
|
+
expect(body?.$type).toBe('BodyLine');
|
|
752
|
+
if (body?.$type === 'BodyLine') {
|
|
753
|
+
const text = body.parts.map(part => part.text ?? part.punct ?? '').join('');
|
|
754
|
+
expect(text).toContain('@');
|
|
755
|
+
expect(text).toContain('escaped_attribute');
|
|
756
|
+
}
|
|
757
|
+
const attribute = idea?.elements[1];
|
|
758
|
+
expect(attribute?.$type).toBe('Attribute');
|
|
759
|
+
if (attribute?.$type === 'Attribute') {
|
|
760
|
+
expect(attribute.name).toBe('real_attribute');
|
|
761
|
+
}
|
|
762
|
+
});
|
|
763
|
+
});
|
|
764
|
+
function checkDocumentValid(document) {
|
|
765
|
+
return document.parseResult.parserErrors.length && s `
|
|
766
|
+
Parser errors:
|
|
767
|
+
${document.parseResult.parserErrors.map(e => e.message).join('\n ')}
|
|
768
|
+
`
|
|
769
|
+
|| document.parseResult.value === undefined && `ParseResult is 'undefined'.`
|
|
770
|
+
|| !isModel(document.parseResult.value) && `Root AST object is a ${document.parseResult.value.$type}, expected a 'Model'.`
|
|
771
|
+
|| undefined;
|
|
772
|
+
}
|
|
773
|
+
//# sourceMappingURL=parsing.test.js.map
|