From 01a1be9bdea13e73fd38096ddde0feaed581ab0c Mon Sep 17 00:00:00 2001 From: badrislamovrolan Date: Mon, 21 Sep 2026 02:36:06 +0200 Subject: [PATCH 1/8] chore(xml-tools): move sources into projects/xml-tools Raw relocation of the 10 SAP/xml-tools packages into projects/xml-tools/packages/ as-is (no logic changes). Toolchain wiring, tsconfig/eslint alignment, workspace deps and changeset config follow in subsequent commits. Standalone-repo root cruft (lerna.json, .husky, .github, .circleci, yarn.lock, root package.json, root .reuse/LICENSES/LICENSE/CONTRIBUTING, scripts, webpack.config.base.js, .eslintrc.js) is intentionally not migrated; the monorepo root is a superset. Signed-off-by: badrislamovrolan --- projects/xml-tools/README.md | 57 ++ .../packages/ast-position/CHANGELOG.md | 54 ++ .../packages/ast-position/CONTRIBUTING.md | 4 + .../xml-tools/packages/ast-position/README.md | 32 + .../xml-tools/packages/ast-position/api.d.ts | 36 + .../packages/ast-position/lib/api.js | 17 + .../packages/ast-position/lib/ast-position.js | 39 + .../packages/ast-position/package.json | 44 ++ .../ast-position/test/ast-position-spec.js | 143 ++++ projects/xml-tools/packages/ast/.npmignore | 10 + projects/xml-tools/packages/ast/CHANGELOG.md | 165 +++++ .../xml-tools/packages/ast/CONTRIBUTING.md | 4 + projects/xml-tools/packages/ast/README.md | 70 ++ projects/xml-tools/packages/ast/api.d.ts | 216 ++++++ projects/xml-tools/packages/ast/lib/api.js | 9 + .../xml-tools/packages/ast/lib/build-ast.js | 457 ++++++++++++ .../xml-tools/packages/ast/lib/constants.js | 3 + projects/xml-tools/packages/ast/lib/utils.js | 25 + .../xml-tools/packages/ast/lib/visit-ast.js | 61 ++ projects/xml-tools/packages/ast/package.json | 48 ++ .../packages/ast/scripts/update-snapshots.js | 32 + .../packages/ast/test/sample-test.js | 41 ++ .../attributes-no-value-last/input.xml | 1 + .../attributes-no-value-last/output.js | 51 ++ .../attributes-no-value-last/sample.spec.js | 7 + .../attributes-no-value-middle/input.xml | 1 + .../attributes-no-value-middle/output.js | 51 ++ .../attributes-no-value-middle/sample.spec.js | 7 + .../attributes-no-value-no-eql-last/input.xml | 1 + .../attributes-no-value-no-eql-last/output.js | 51 ++ .../sample.spec.js | 7 + .../input.xml | 1 + .../output.js | 51 ++ .../sample.spec.js | 9 + .../input.xml | 5 + .../output.js | 130 ++++ .../sample.spec.js | 7 + .../attributes-no-value-not-closed/input.xml | 1 + .../attributes-no-value-not-closed/output.js | 34 + .../sample.spec.js | 7 + .../snapshots/invalid/bad-namespace/input.xml | 6 + .../snapshots/invalid/bad-namespace/output.js | 126 ++++ .../invalid/bad-namespace/sample.spec.js | 7 + .../invalid/element-name-empty-last/input.xml | 5 + .../invalid/element-name-empty-last/output.js | 96 +++ .../element-name-empty-last/sample.spec.js | 7 + .../input.xml | 6 + .../output.js | 96 +++ .../sample.spec.js | 7 + .../element-name-empty-middle/input.xml | 5 + .../element-name-empty-middle/output.js | 96 +++ .../element-name-empty-middle/sample.spec.js | 7 + .../invalid/element-name-last/input.xml | 5 + .../invalid/element-name-last/output.js | 99 +++ .../invalid/element-name-last/sample.spec.js | 7 + .../invalid/element-name-middle/input.xml | 6 + .../invalid/element-name-middle/output.js | 127 ++++ .../element-name-middle/sample.spec.js | 7 + .../missing-top-level-element/input.xml | 2 + .../missing-top-level-element/output.js | 33 + .../missing-top-level-element/sample.spec.js | 7 + .../test/snapshots/valid/attributes/input.xml | 1 + .../test/snapshots/valid/attributes/output.js | 44 ++ .../snapshots/valid/attributes/sample.spec.js | 7 + .../valid/complex-contents/input.xml | 2 + .../valid/complex-contents/output.js | 28 + .../valid/complex-contents/sample.spec.js | 7 + .../valid/elements-short-form/input.xml | 4 + .../valid/elements-short-form/output.js | 89 +++ .../valid/elements-short-form/sample.spec.js | 7 + .../test/snapshots/valid/elements/input.xml | 4 + .../test/snapshots/valid/elements/output.js | 86 +++ .../snapshots/valid/elements/sample.spec.js | 7 + .../test/snapshots/valid/namespaces/input.xml | 7 + .../test/snapshots/valid/namespaces/output.js | 198 ++++++ .../snapshots/valid/namespaces/sample.spec.js | 7 + .../valid/prolog-no-attriibs/input.xml | 7 + .../valid/prolog-no-attriibs/output.js | 101 +++ .../valid/prolog-no-attriibs/sample.spec.js | 7 + .../ast/test/snapshots/valid/prolog/input.xml | 2 + .../ast/test/snapshots/valid/prolog/output.js | 49 ++ .../snapshots/valid/prolog/sample.spec.js | 7 + projects/xml-tools/packages/ast/test/utils.js | 112 +++ .../packages/ast/test/visitor/visitor-spec.js | 137 ++++ projects/xml-tools/packages/common/.npmignore | 10 + .../xml-tools/packages/common/CHANGELOG.md | 68 ++ .../xml-tools/packages/common/CONTRIBUTING.md | 4 + projects/xml-tools/packages/common/README.md | 35 + projects/xml-tools/packages/common/api.d.ts | 33 + projects/xml-tools/packages/common/lib/api.js | 11 + .../common/lib/find-next-textual-token.js | 33 + .../packages/common/lib/xml-ns-key.js | 54 ++ .../xml-tools/packages/common/package.json | 45 ++ .../test/find-next-textual-token-spec.js | 31 + .../packages/common/test/xml-ns-key-spec.js | 106 +++ .../xml-tools/packages/constraints/.npmignore | 10 + .../packages/constraints/CHANGELOG.md | 20 + .../packages/constraints/CONTRIBUTING.md | 4 + .../xml-tools/packages/constraints/README.md | 51 ++ .../xml-tools/packages/constraints/api.d.ts | 8 + .../xml-tools/packages/constraints/lib/api.js | 26 + .../lib/constraints/tag-closing-name-match.js | 44 ++ .../lib/constraints/unique-attribute-keys.js | 37 + .../packages/constraints/package.json | 46 ++ .../packages/constraints/test/api-spec.js | 40 ++ .../tag-closing-name-match-spec.js | 109 +++ .../constraints/unique-attribute-keys-spec.js | 66 ++ .../packages/content-assist/.npmignore | 10 + .../packages/content-assist/CHANGELOG.md | 163 +++++ .../packages/content-assist/CONTRIBUTING.md | 4 + .../packages/content-assist/README.md | 94 +++ .../packages/content-assist/api.d.ts | 194 +++++ .../packages/content-assist/lib/api.js | 40 ++ .../content-assist/lib/content-assist.js | 399 +++++++++++ .../packages/content-assist/package.json | 45 ++ .../content-assist/test/scenarios-spec.js | 555 +++++++++++++++ .../packages/language-server/.gitignore | 1 + .../packages/language-server/.npmignore | 10 + .../packages/language-server/CHANGELOG.md | 81 +++ .../packages/language-server/README.md | 41 ++ .../packages/language-server/api.d.ts | 13 + .../packages/language-server/lib/api.js | 26 + .../packages/language-server/lib/constants.js | 5 + .../language-server/lib/language-services.js | 109 +++ .../packages/language-server/lib/server.js | 37 + .../packages/language-server/nyc.config.js | 13 + .../packages/language-server/package.json | 49 ++ .../test/langauge-services.spec.js | 153 ++++ .../language-server/webpack.config.js | 14 + projects/xml-tools/packages/parser/.npmignore | 10 + .../xml-tools/packages/parser/CHANGELOG.md | 109 +++ .../xml-tools/packages/parser/CONTRIBUTING.md | 4 + projects/xml-tools/packages/parser/README.md | 54 ++ projects/xml-tools/packages/parser/api.d.ts | 186 +++++ projects/xml-tools/packages/parser/lib/api.js | 21 + .../xml-tools/packages/parser/lib/lexer.js | 206 ++++++ .../xml-tools/packages/parser/lib/parser.js | 211 ++++++ .../xml-tools/packages/parser/package.json | 46 ++ .../parser/scripts/update-snapshots.js | 31 + .../packages/parser/test/parser-smoke-spec.js | 132 ++++ .../packages/parser/test/sample-test.js | 33 + .../snapshots/invalid/attributes/input.xml | 1 + .../snapshots/invalid/attributes/output.js | 129 ++++ .../invalid/attributes/sample.spec.js | 7 + .../invalid/element-closing-name/input.xml | 1 + .../invalid/element-closing-name/output.js | 77 ++ .../element-closing-name/sample.spec.js | 7 + .../test/snapshots/valid/attributes/input.xml | 1 + .../test/snapshots/valid/attributes/output.js | 144 ++++ .../snapshots/valid/attributes/sample.spec.js | 7 + .../test/snapshots/valid/cdata/input.xml | 3 + .../test/snapshots/valid/cdata/output.js | 116 +++ .../test/snapshots/valid/cdata/sample.spec.js | 7 + .../test/snapshots/valid/comments/input.xml | 6 + .../test/snapshots/valid/comments/output.js | 271 +++++++ .../snapshots/valid/comments/sample.spec.js | 7 + .../valid/complex-contents/input.xml | 2 + .../valid/complex-contents/output.js | 120 ++++ .../valid/complex-contents/sample.spec.js | 7 + .../snapshots/valid/doc-type-public/input.xml | 2 + .../snapshots/valid/doc-type-public/output.js | 160 +++++ .../valid/doc-type-public/sample.spec.js | 7 + .../snapshots/valid/doc-type-system/input.xml | 2 + .../snapshots/valid/doc-type-system/output.js | 152 ++++ .../valid/doc-type-system/sample.spec.js | 7 + .../valid/elements-short-form/input.xml | 4 + .../valid/elements-short-form/output.js | 264 +++++++ .../valid/elements-short-form/sample.spec.js | 7 + .../test/snapshots/valid/elements/input.xml | 4 + .../test/snapshots/valid/elements/output.js | 280 ++++++++ .../snapshots/valid/elements/sample.spec.js | 7 + .../snapshots/valid/entity-char-ref/input.xml | 5 + .../snapshots/valid/entity-char-ref/output.js | 246 +++++++ .../valid/entity-char-ref/sample.spec.js | 7 + .../valid/multi-line-comments/input.xml | 13 + .../valid/multi-line-comments/output.js | 271 +++++++ .../valid/multi-line-comments/sample.spec.js | 7 + .../test/snapshots/valid/namespaces/input.xml | 6 + .../test/snapshots/valid/namespaces/output.js | 426 +++++++++++ .../snapshots/valid/namespaces/sample.spec.js | 7 + .../valid/processing-instruction/input.xml | 4 + .../valid/processing-instruction/output.js | 151 ++++ .../processing-instruction/sample.spec.js | 7 + .../test/snapshots/valid/prolog/input.xml | 2 + .../test/snapshots/valid/prolog/output.js | 182 +++++ .../snapshots/valid/prolog/sample.spec.js | 7 + .../xml-tools/packages/parser/test/utils.js | 47 ++ .../packages/simple-schema/.npmignore | 10 + .../packages/simple-schema/CHANGELOG.md | 155 ++++ .../packages/simple-schema/CONTRIBUTING.md | 4 + .../packages/simple-schema/README.md | 119 ++++ .../xml-tools/packages/simple-schema/api.d.ts | 77 ++ .../packages/simple-schema/lib/api.js | 7 + .../lib/content-assist/attribute-name.js | 30 + .../lib/content-assist/attribute-value.js | 40 ++ .../lib/content-assist/element-name.js | 96 +++ .../simple-schema/lib/get-content-assist.js | 62 ++ .../simple-schema/lib/get-validators.js | 99 +++ .../packages/simple-schema/lib/path.js | 69 ++ .../lib/validators/attribute-value.js | 57 ++ .../lib/validators/duplicate-sub-elements.js | 42 ++ .../lib/validators/required-attributes.js | 39 + .../lib/validators/required-sub-elements.js | 39 + .../lib/validators/unknown-attributes.js | 46 ++ .../lib/validators/unknown-sub-elements.js | 40 ++ .../simple-schema/lib/validators/utils.js | 9 + .../packages/simple-schema/package.json | 48 ++ .../content-assist/atrribute-name-spec.js | 189 +++++ .../content-assist/atrribute-value-spec.js | 181 +++++ .../test/content-assist/element-name-spec.js | 667 ++++++++++++++++++ .../test/content-assist/utils.js | 36 + .../test/validators/attribute-value-spec.js | 250 +++++++ .../element-duplicate-sub-elements-spec.js | 189 +++++ .../element-required-attributes-spec.js | 126 ++++ .../element-required-elements-spec.js | 140 ++++ .../element-unknown-attributes-spec.js | 195 +++++ .../element-unknown-sub-elements-spec.js | 193 +++++ .../simple-schema/test/validators/utils.js | 28 + .../xml-tools/packages/validation/.npmignore | 10 + .../packages/validation/CHANGELOG.md | 130 ++++ .../packages/validation/CONTRIBUTING.md | 4 + .../xml-tools/packages/validation/README.md | 70 ++ .../xml-tools/packages/validation/api.d.ts | 36 + .../xml-tools/packages/validation/lib/api.js | 5 + .../packages/validation/lib/validate.js | 37 + .../packages/validation/package.json | 46 ++ .../validation/test/validate-apis-spec.js | 62 ++ .../xml-tools/packages/xml-toolkit/.gitignore | 4 + .../packages/xml-toolkit/.vscodeignore | 20 + .../packages/xml-toolkit/CHANGELOG.md | 96 +++ .../xml-tools/packages/xml-toolkit/README.md | 48 ++ .../packages/xml-toolkit/lib/extension.js | 64 ++ .../packages/xml-toolkit/package.json | 58 ++ .../readme/preview-syntax-validation.png | Bin 0 -> 105106 bytes .../xml-toolkit/resources/xml-toolkit.png | Bin 0 -> 64713 bytes .../xml-toolkit/scripts/package-vsix.js | 106 +++ .../packages/xml-toolkit/test/runTest.js | 42 ++ .../xml-toolkit/test/suite/extension.test.js | 129 ++++ .../packages/xml-toolkit/test/suite/index.js | 43 ++ .../xml-toolkit/test/testFixure/test.xml | 0 .../packages/xml-toolkit/webpack.config.js | 23 + 241 files changed, 15168 insertions(+) create mode 100644 projects/xml-tools/README.md create mode 100644 projects/xml-tools/packages/ast-position/CHANGELOG.md create mode 100644 projects/xml-tools/packages/ast-position/CONTRIBUTING.md create mode 100644 projects/xml-tools/packages/ast-position/README.md create mode 100644 projects/xml-tools/packages/ast-position/api.d.ts create mode 100644 projects/xml-tools/packages/ast-position/lib/api.js create mode 100644 projects/xml-tools/packages/ast-position/lib/ast-position.js create mode 100644 projects/xml-tools/packages/ast-position/package.json create mode 100644 projects/xml-tools/packages/ast-position/test/ast-position-spec.js create mode 100644 projects/xml-tools/packages/ast/.npmignore create mode 100644 projects/xml-tools/packages/ast/CHANGELOG.md create mode 100644 projects/xml-tools/packages/ast/CONTRIBUTING.md create mode 100644 projects/xml-tools/packages/ast/README.md create mode 100644 projects/xml-tools/packages/ast/api.d.ts create mode 100644 projects/xml-tools/packages/ast/lib/api.js create mode 100644 projects/xml-tools/packages/ast/lib/build-ast.js create mode 100644 projects/xml-tools/packages/ast/lib/constants.js create mode 100644 projects/xml-tools/packages/ast/lib/utils.js create mode 100644 projects/xml-tools/packages/ast/lib/visit-ast.js create mode 100644 projects/xml-tools/packages/ast/package.json create mode 100644 projects/xml-tools/packages/ast/scripts/update-snapshots.js create mode 100644 projects/xml-tools/packages/ast/test/sample-test.js create mode 100644 projects/xml-tools/packages/ast/test/snapshots/invalid/attributes-no-value-last/input.xml create mode 100644 projects/xml-tools/packages/ast/test/snapshots/invalid/attributes-no-value-last/output.js create mode 100644 projects/xml-tools/packages/ast/test/snapshots/invalid/attributes-no-value-last/sample.spec.js create mode 100644 projects/xml-tools/packages/ast/test/snapshots/invalid/attributes-no-value-middle/input.xml create mode 100644 projects/xml-tools/packages/ast/test/snapshots/invalid/attributes-no-value-middle/output.js create mode 100644 projects/xml-tools/packages/ast/test/snapshots/invalid/attributes-no-value-middle/sample.spec.js create mode 100644 projects/xml-tools/packages/ast/test/snapshots/invalid/attributes-no-value-no-eql-last/input.xml create mode 100644 projects/xml-tools/packages/ast/test/snapshots/invalid/attributes-no-value-no-eql-last/output.js create mode 100644 projects/xml-tools/packages/ast/test/snapshots/invalid/attributes-no-value-no-eql-last/sample.spec.js create mode 100644 projects/xml-tools/packages/ast/test/snapshots/invalid/attributes-no-value-no-eql-middle/input.xml create mode 100644 projects/xml-tools/packages/ast/test/snapshots/invalid/attributes-no-value-no-eql-middle/output.js create mode 100644 projects/xml-tools/packages/ast/test/snapshots/invalid/attributes-no-value-no-eql-middle/sample.spec.js create mode 100644 projects/xml-tools/packages/ast/test/snapshots/invalid/attributes-no-value-not-closed-nested/input.xml create mode 100644 projects/xml-tools/packages/ast/test/snapshots/invalid/attributes-no-value-not-closed-nested/output.js create mode 100644 projects/xml-tools/packages/ast/test/snapshots/invalid/attributes-no-value-not-closed-nested/sample.spec.js create mode 100644 projects/xml-tools/packages/ast/test/snapshots/invalid/attributes-no-value-not-closed/input.xml create mode 100644 projects/xml-tools/packages/ast/test/snapshots/invalid/attributes-no-value-not-closed/output.js create mode 100644 projects/xml-tools/packages/ast/test/snapshots/invalid/attributes-no-value-not-closed/sample.spec.js create mode 100644 projects/xml-tools/packages/ast/test/snapshots/invalid/bad-namespace/input.xml create mode 100644 projects/xml-tools/packages/ast/test/snapshots/invalid/bad-namespace/output.js create mode 100644 projects/xml-tools/packages/ast/test/snapshots/invalid/bad-namespace/sample.spec.js create mode 100644 projects/xml-tools/packages/ast/test/snapshots/invalid/element-name-empty-last/input.xml create mode 100644 projects/xml-tools/packages/ast/test/snapshots/invalid/element-name-empty-last/output.js create mode 100644 projects/xml-tools/packages/ast/test/snapshots/invalid/element-name-empty-last/sample.spec.js create mode 100644 projects/xml-tools/packages/ast/test/snapshots/invalid/element-name-empty-middle-comment/input.xml create mode 100644 projects/xml-tools/packages/ast/test/snapshots/invalid/element-name-empty-middle-comment/output.js create mode 100644 projects/xml-tools/packages/ast/test/snapshots/invalid/element-name-empty-middle-comment/sample.spec.js create mode 100644 projects/xml-tools/packages/ast/test/snapshots/invalid/element-name-empty-middle/input.xml create mode 100644 projects/xml-tools/packages/ast/test/snapshots/invalid/element-name-empty-middle/output.js create mode 100644 projects/xml-tools/packages/ast/test/snapshots/invalid/element-name-empty-middle/sample.spec.js create mode 100644 projects/xml-tools/packages/ast/test/snapshots/invalid/element-name-last/input.xml create mode 100644 projects/xml-tools/packages/ast/test/snapshots/invalid/element-name-last/output.js create mode 100644 projects/xml-tools/packages/ast/test/snapshots/invalid/element-name-last/sample.spec.js create mode 100644 projects/xml-tools/packages/ast/test/snapshots/invalid/element-name-middle/input.xml create mode 100644 projects/xml-tools/packages/ast/test/snapshots/invalid/element-name-middle/output.js create mode 100644 projects/xml-tools/packages/ast/test/snapshots/invalid/element-name-middle/sample.spec.js create mode 100644 projects/xml-tools/packages/ast/test/snapshots/invalid/missing-top-level-element/input.xml create mode 100644 projects/xml-tools/packages/ast/test/snapshots/invalid/missing-top-level-element/output.js create mode 100644 projects/xml-tools/packages/ast/test/snapshots/invalid/missing-top-level-element/sample.spec.js create mode 100644 projects/xml-tools/packages/ast/test/snapshots/valid/attributes/input.xml create mode 100644 projects/xml-tools/packages/ast/test/snapshots/valid/attributes/output.js create mode 100644 projects/xml-tools/packages/ast/test/snapshots/valid/attributes/sample.spec.js create mode 100644 projects/xml-tools/packages/ast/test/snapshots/valid/complex-contents/input.xml create mode 100644 projects/xml-tools/packages/ast/test/snapshots/valid/complex-contents/output.js create mode 100644 projects/xml-tools/packages/ast/test/snapshots/valid/complex-contents/sample.spec.js create mode 100644 projects/xml-tools/packages/ast/test/snapshots/valid/elements-short-form/input.xml create mode 100644 projects/xml-tools/packages/ast/test/snapshots/valid/elements-short-form/output.js create mode 100644 projects/xml-tools/packages/ast/test/snapshots/valid/elements-short-form/sample.spec.js create mode 100644 projects/xml-tools/packages/ast/test/snapshots/valid/elements/input.xml create mode 100644 projects/xml-tools/packages/ast/test/snapshots/valid/elements/output.js create mode 100644 projects/xml-tools/packages/ast/test/snapshots/valid/elements/sample.spec.js create mode 100644 projects/xml-tools/packages/ast/test/snapshots/valid/namespaces/input.xml create mode 100644 projects/xml-tools/packages/ast/test/snapshots/valid/namespaces/output.js create mode 100644 projects/xml-tools/packages/ast/test/snapshots/valid/namespaces/sample.spec.js create mode 100644 projects/xml-tools/packages/ast/test/snapshots/valid/prolog-no-attriibs/input.xml create mode 100644 projects/xml-tools/packages/ast/test/snapshots/valid/prolog-no-attriibs/output.js create mode 100644 projects/xml-tools/packages/ast/test/snapshots/valid/prolog-no-attriibs/sample.spec.js create mode 100644 projects/xml-tools/packages/ast/test/snapshots/valid/prolog/input.xml create mode 100644 projects/xml-tools/packages/ast/test/snapshots/valid/prolog/output.js create mode 100644 projects/xml-tools/packages/ast/test/snapshots/valid/prolog/sample.spec.js create mode 100644 projects/xml-tools/packages/ast/test/utils.js create mode 100644 projects/xml-tools/packages/ast/test/visitor/visitor-spec.js create mode 100644 projects/xml-tools/packages/common/.npmignore create mode 100644 projects/xml-tools/packages/common/CHANGELOG.md create mode 100644 projects/xml-tools/packages/common/CONTRIBUTING.md create mode 100644 projects/xml-tools/packages/common/README.md create mode 100644 projects/xml-tools/packages/common/api.d.ts create mode 100644 projects/xml-tools/packages/common/lib/api.js create mode 100644 projects/xml-tools/packages/common/lib/find-next-textual-token.js create mode 100644 projects/xml-tools/packages/common/lib/xml-ns-key.js create mode 100644 projects/xml-tools/packages/common/package.json create mode 100644 projects/xml-tools/packages/common/test/find-next-textual-token-spec.js create mode 100644 projects/xml-tools/packages/common/test/xml-ns-key-spec.js create mode 100644 projects/xml-tools/packages/constraints/.npmignore create mode 100644 projects/xml-tools/packages/constraints/CHANGELOG.md create mode 100644 projects/xml-tools/packages/constraints/CONTRIBUTING.md create mode 100644 projects/xml-tools/packages/constraints/README.md create mode 100644 projects/xml-tools/packages/constraints/api.d.ts create mode 100644 projects/xml-tools/packages/constraints/lib/api.js create mode 100644 projects/xml-tools/packages/constraints/lib/constraints/tag-closing-name-match.js create mode 100644 projects/xml-tools/packages/constraints/lib/constraints/unique-attribute-keys.js create mode 100644 projects/xml-tools/packages/constraints/package.json create mode 100644 projects/xml-tools/packages/constraints/test/api-spec.js create mode 100644 projects/xml-tools/packages/constraints/test/constraints/tag-closing-name-match-spec.js create mode 100644 projects/xml-tools/packages/constraints/test/constraints/unique-attribute-keys-spec.js create mode 100644 projects/xml-tools/packages/content-assist/.npmignore create mode 100644 projects/xml-tools/packages/content-assist/CHANGELOG.md create mode 100644 projects/xml-tools/packages/content-assist/CONTRIBUTING.md create mode 100644 projects/xml-tools/packages/content-assist/README.md create mode 100644 projects/xml-tools/packages/content-assist/api.d.ts create mode 100644 projects/xml-tools/packages/content-assist/lib/api.js create mode 100644 projects/xml-tools/packages/content-assist/lib/content-assist.js create mode 100644 projects/xml-tools/packages/content-assist/package.json create mode 100644 projects/xml-tools/packages/content-assist/test/scenarios-spec.js create mode 100644 projects/xml-tools/packages/language-server/.gitignore create mode 100644 projects/xml-tools/packages/language-server/.npmignore create mode 100644 projects/xml-tools/packages/language-server/CHANGELOG.md create mode 100644 projects/xml-tools/packages/language-server/README.md create mode 100644 projects/xml-tools/packages/language-server/api.d.ts create mode 100644 projects/xml-tools/packages/language-server/lib/api.js create mode 100644 projects/xml-tools/packages/language-server/lib/constants.js create mode 100644 projects/xml-tools/packages/language-server/lib/language-services.js create mode 100644 projects/xml-tools/packages/language-server/lib/server.js create mode 100644 projects/xml-tools/packages/language-server/nyc.config.js create mode 100644 projects/xml-tools/packages/language-server/package.json create mode 100644 projects/xml-tools/packages/language-server/test/langauge-services.spec.js create mode 100644 projects/xml-tools/packages/language-server/webpack.config.js create mode 100644 projects/xml-tools/packages/parser/.npmignore create mode 100644 projects/xml-tools/packages/parser/CHANGELOG.md create mode 100644 projects/xml-tools/packages/parser/CONTRIBUTING.md create mode 100644 projects/xml-tools/packages/parser/README.md create mode 100644 projects/xml-tools/packages/parser/api.d.ts create mode 100644 projects/xml-tools/packages/parser/lib/api.js create mode 100644 projects/xml-tools/packages/parser/lib/lexer.js create mode 100644 projects/xml-tools/packages/parser/lib/parser.js create mode 100644 projects/xml-tools/packages/parser/package.json create mode 100644 projects/xml-tools/packages/parser/scripts/update-snapshots.js create mode 100644 projects/xml-tools/packages/parser/test/parser-smoke-spec.js create mode 100644 projects/xml-tools/packages/parser/test/sample-test.js create mode 100644 projects/xml-tools/packages/parser/test/snapshots/invalid/attributes/input.xml create mode 100644 projects/xml-tools/packages/parser/test/snapshots/invalid/attributes/output.js create mode 100644 projects/xml-tools/packages/parser/test/snapshots/invalid/attributes/sample.spec.js create mode 100644 projects/xml-tools/packages/parser/test/snapshots/invalid/element-closing-name/input.xml create mode 100644 projects/xml-tools/packages/parser/test/snapshots/invalid/element-closing-name/output.js create mode 100644 projects/xml-tools/packages/parser/test/snapshots/invalid/element-closing-name/sample.spec.js create mode 100644 projects/xml-tools/packages/parser/test/snapshots/valid/attributes/input.xml create mode 100644 projects/xml-tools/packages/parser/test/snapshots/valid/attributes/output.js create mode 100644 projects/xml-tools/packages/parser/test/snapshots/valid/attributes/sample.spec.js create mode 100644 projects/xml-tools/packages/parser/test/snapshots/valid/cdata/input.xml create mode 100644 projects/xml-tools/packages/parser/test/snapshots/valid/cdata/output.js create mode 100644 projects/xml-tools/packages/parser/test/snapshots/valid/cdata/sample.spec.js create mode 100644 projects/xml-tools/packages/parser/test/snapshots/valid/comments/input.xml create mode 100644 projects/xml-tools/packages/parser/test/snapshots/valid/comments/output.js create mode 100644 projects/xml-tools/packages/parser/test/snapshots/valid/comments/sample.spec.js create mode 100644 projects/xml-tools/packages/parser/test/snapshots/valid/complex-contents/input.xml create mode 100644 projects/xml-tools/packages/parser/test/snapshots/valid/complex-contents/output.js create mode 100644 projects/xml-tools/packages/parser/test/snapshots/valid/complex-contents/sample.spec.js create mode 100644 projects/xml-tools/packages/parser/test/snapshots/valid/doc-type-public/input.xml create mode 100644 projects/xml-tools/packages/parser/test/snapshots/valid/doc-type-public/output.js create mode 100644 projects/xml-tools/packages/parser/test/snapshots/valid/doc-type-public/sample.spec.js create mode 100644 projects/xml-tools/packages/parser/test/snapshots/valid/doc-type-system/input.xml create mode 100644 projects/xml-tools/packages/parser/test/snapshots/valid/doc-type-system/output.js create mode 100644 projects/xml-tools/packages/parser/test/snapshots/valid/doc-type-system/sample.spec.js create mode 100644 projects/xml-tools/packages/parser/test/snapshots/valid/elements-short-form/input.xml create mode 100644 projects/xml-tools/packages/parser/test/snapshots/valid/elements-short-form/output.js create mode 100644 projects/xml-tools/packages/parser/test/snapshots/valid/elements-short-form/sample.spec.js create mode 100644 projects/xml-tools/packages/parser/test/snapshots/valid/elements/input.xml create mode 100644 projects/xml-tools/packages/parser/test/snapshots/valid/elements/output.js create mode 100644 projects/xml-tools/packages/parser/test/snapshots/valid/elements/sample.spec.js create mode 100644 projects/xml-tools/packages/parser/test/snapshots/valid/entity-char-ref/input.xml create mode 100644 projects/xml-tools/packages/parser/test/snapshots/valid/entity-char-ref/output.js create mode 100644 projects/xml-tools/packages/parser/test/snapshots/valid/entity-char-ref/sample.spec.js create mode 100644 projects/xml-tools/packages/parser/test/snapshots/valid/multi-line-comments/input.xml create mode 100644 projects/xml-tools/packages/parser/test/snapshots/valid/multi-line-comments/output.js create mode 100644 projects/xml-tools/packages/parser/test/snapshots/valid/multi-line-comments/sample.spec.js create mode 100644 projects/xml-tools/packages/parser/test/snapshots/valid/namespaces/input.xml create mode 100644 projects/xml-tools/packages/parser/test/snapshots/valid/namespaces/output.js create mode 100644 projects/xml-tools/packages/parser/test/snapshots/valid/namespaces/sample.spec.js create mode 100644 projects/xml-tools/packages/parser/test/snapshots/valid/processing-instruction/input.xml create mode 100644 projects/xml-tools/packages/parser/test/snapshots/valid/processing-instruction/output.js create mode 100644 projects/xml-tools/packages/parser/test/snapshots/valid/processing-instruction/sample.spec.js create mode 100644 projects/xml-tools/packages/parser/test/snapshots/valid/prolog/input.xml create mode 100644 projects/xml-tools/packages/parser/test/snapshots/valid/prolog/output.js create mode 100644 projects/xml-tools/packages/parser/test/snapshots/valid/prolog/sample.spec.js create mode 100644 projects/xml-tools/packages/parser/test/utils.js create mode 100644 projects/xml-tools/packages/simple-schema/.npmignore create mode 100644 projects/xml-tools/packages/simple-schema/CHANGELOG.md create mode 100644 projects/xml-tools/packages/simple-schema/CONTRIBUTING.md create mode 100644 projects/xml-tools/packages/simple-schema/README.md create mode 100644 projects/xml-tools/packages/simple-schema/api.d.ts create mode 100644 projects/xml-tools/packages/simple-schema/lib/api.js create mode 100644 projects/xml-tools/packages/simple-schema/lib/content-assist/attribute-name.js create mode 100644 projects/xml-tools/packages/simple-schema/lib/content-assist/attribute-value.js create mode 100644 projects/xml-tools/packages/simple-schema/lib/content-assist/element-name.js create mode 100644 projects/xml-tools/packages/simple-schema/lib/get-content-assist.js create mode 100644 projects/xml-tools/packages/simple-schema/lib/get-validators.js create mode 100644 projects/xml-tools/packages/simple-schema/lib/path.js create mode 100644 projects/xml-tools/packages/simple-schema/lib/validators/attribute-value.js create mode 100644 projects/xml-tools/packages/simple-schema/lib/validators/duplicate-sub-elements.js create mode 100644 projects/xml-tools/packages/simple-schema/lib/validators/required-attributes.js create mode 100644 projects/xml-tools/packages/simple-schema/lib/validators/required-sub-elements.js create mode 100644 projects/xml-tools/packages/simple-schema/lib/validators/unknown-attributes.js create mode 100644 projects/xml-tools/packages/simple-schema/lib/validators/unknown-sub-elements.js create mode 100644 projects/xml-tools/packages/simple-schema/lib/validators/utils.js create mode 100644 projects/xml-tools/packages/simple-schema/package.json create mode 100644 projects/xml-tools/packages/simple-schema/test/content-assist/atrribute-name-spec.js create mode 100644 projects/xml-tools/packages/simple-schema/test/content-assist/atrribute-value-spec.js create mode 100644 projects/xml-tools/packages/simple-schema/test/content-assist/element-name-spec.js create mode 100644 projects/xml-tools/packages/simple-schema/test/content-assist/utils.js create mode 100644 projects/xml-tools/packages/simple-schema/test/validators/attribute-value-spec.js create mode 100644 projects/xml-tools/packages/simple-schema/test/validators/element-duplicate-sub-elements-spec.js create mode 100644 projects/xml-tools/packages/simple-schema/test/validators/element-required-attributes-spec.js create mode 100644 projects/xml-tools/packages/simple-schema/test/validators/element-required-elements-spec.js create mode 100644 projects/xml-tools/packages/simple-schema/test/validators/element-unknown-attributes-spec.js create mode 100644 projects/xml-tools/packages/simple-schema/test/validators/element-unknown-sub-elements-spec.js create mode 100644 projects/xml-tools/packages/simple-schema/test/validators/utils.js create mode 100644 projects/xml-tools/packages/validation/.npmignore create mode 100644 projects/xml-tools/packages/validation/CHANGELOG.md create mode 100644 projects/xml-tools/packages/validation/CONTRIBUTING.md create mode 100644 projects/xml-tools/packages/validation/README.md create mode 100644 projects/xml-tools/packages/validation/api.d.ts create mode 100644 projects/xml-tools/packages/validation/lib/api.js create mode 100644 projects/xml-tools/packages/validation/lib/validate.js create mode 100644 projects/xml-tools/packages/validation/package.json create mode 100644 projects/xml-tools/packages/validation/test/validate-apis-spec.js create mode 100644 projects/xml-tools/packages/xml-toolkit/.gitignore create mode 100644 projects/xml-tools/packages/xml-toolkit/.vscodeignore create mode 100644 projects/xml-tools/packages/xml-toolkit/CHANGELOG.md create mode 100644 projects/xml-tools/packages/xml-toolkit/README.md create mode 100644 projects/xml-tools/packages/xml-toolkit/lib/extension.js create mode 100644 projects/xml-tools/packages/xml-toolkit/package.json create mode 100644 projects/xml-tools/packages/xml-toolkit/resources/readme/preview-syntax-validation.png create mode 100644 projects/xml-tools/packages/xml-toolkit/resources/xml-toolkit.png create mode 100644 projects/xml-tools/packages/xml-toolkit/scripts/package-vsix.js create mode 100644 projects/xml-tools/packages/xml-toolkit/test/runTest.js create mode 100644 projects/xml-tools/packages/xml-toolkit/test/suite/extension.test.js create mode 100644 projects/xml-tools/packages/xml-toolkit/test/suite/index.js create mode 100644 projects/xml-tools/packages/xml-toolkit/test/testFixure/test.xml create mode 100644 projects/xml-tools/packages/xml-toolkit/webpack.config.js diff --git a/projects/xml-tools/README.md b/projects/xml-tools/README.md new file mode 100644 index 00000000..9f2688a6 --- /dev/null +++ b/projects/xml-tools/README.md @@ -0,0 +1,57 @@ +[![CircleCI](https://circleci.com/gh/SAP/xml-tools.svg?style=svg)](https://circleci.com/gh/SAP/xml-tools) +[![Coverage Status](https://coveralls.io/repos/github/SAP/xml-tools/badge.svg?branch=master)](https://coveralls.io/github/SAP/xml-tools?branch=master) +[![Language grade: JavaScript](https://img.shields.io/lgtm/grade/javascript/g/SAP/xml-tools.svg?logo=lgtm&logoWidth=18)](https://lgtm.com/projects/g/SAP/xml-tools/context:javascript) +[![styled with prettier](https://img.shields.io/badge/styled_with-prettier-ff69b4.svg)](https://github.com/prettier/prettier) +[![Commitizen friendly](https://img.shields.io/badge/commitizen-friendly-brightgreen.svg)](http://commitizen.github.io/cz-cli/) +[![dependentbot](https://api.dependabot.com/badges/status?host=github&repo=SAP/xml-tools)](https://dependabot.com/) +[![REUSE status](https://api.reuse.software/badge/github.com/SAP/xml-tools)](https://api.reuse.software/info/github.com/SAP/xml-tools) + +# XML-Tools + +XML-Tools is an npm [mono-repo][mono-repo] that contains tools & libraries for [Extensible Markup Language][xml] (XML). +These tools & libraries are mainly focused on capabilities for implementing Editor Services flows in IDEs, for example: +Content Assist and Diagnostics. + +It currently contains the following packages: + +- [![npm-xml-tools-parser][npm-xml-tools-parser-image]][npm-xml-tools-parser-url] [@xml-tools/parser](./packages/parser) A Fault Tolerant XML Parser which outputs a [Concrete Syntax Tree][cst]. +- [![npm-xml-tools-ast][npm-xml-tools-ast-image]][npm-xml-tools-ast-url] [@xml-tools/ast](./packages/ast) XML [Abstract Syntax Tree][ast]. +- [![npm-xml-tools-ast-position][npm-xml-tools-ast-position-image]][npm-xml-tools-ast-position-url] [@xml-tools/ast-position](./packages/ast-position) XML Abstract Syntax Tree position utilities. +- [![npm-xml-tools-content-assist][npm-xml-tools-content-assist-image]][npm-xml-tools-content-assist-url] [@xml-tools/content-assist](./packages/content-assist) Extensible Content Assist APIs for XML. +- [![npm-xml-tools-validation][npm-xml-tools-validation-image]][npm-xml-tools-validation-url] [@xml-tools/validation](./packages/validation) Extensible Validation APIs for XML. +- [![npm-xml-tools-constraints][npm-xml-tools-constraints-image]][npm-xml-tools-constraints-url] [@xml-tools/constraints](./packages/constraints) Validations for XML constraints. +- [![npm-xml-tools-simple-schema][npm-xml-tools-simple-schema-image]][npm-xml-tools-simple-schema-url] [@xml-tools/simple-schema](./packages/simple-schema) Simple XML Schema represented as a JavaScript object literal. +- [![npm-xml-tools-language-server][npm-xml-tools-language-server-image]][npm-xml-tools-language-server-url] [@xml-tools/language-server](./packages/language-server) XML Language Server. +- [![npm-xml-tools-common][npm-xml-tools-common-image]][npm-xml-tools-common-url] [@xml-tools/common](./packages/common) Shared Utilities for xml-tools packages. + +[npm-xml-tools-parser-image]: https://img.shields.io/npm/v/@xml-tools/parser.svg +[npm-xml-tools-parser-url]: https://www.npmjs.com/package/@xml-tools/parser +[npm-xml-tools-ast-image]: https://img.shields.io/npm/v/@xml-tools/ast.svg +[npm-xml-tools-ast-url]: https://www.npmjs.com/package/@xml-tools/ast +[npm-xml-tools-ast-position-image]: https://img.shields.io/npm/v/@xml-tools/ast-position.svg +[npm-xml-tools-ast-position-url]: https://www.npmjs.com/package/@xml-tools/ast-position +[npm-xml-tools-content-assist-image]: https://img.shields.io/npm/v/@xml-tools/content-assist.svg +[npm-xml-tools-content-assist-url]: https://www.npmjs.com/package/@xml-tools/content-assist +[npm-xml-tools-validation-image]: https://img.shields.io/npm/v/@xml-tools/validation.svg +[npm-xml-tools-validation-url]: https://www.npmjs.com/package/@xml-tools/validation +[npm-xml-tools-constraints-url]: https://www.npmjs.com/package/@xml-tools/constraints +[npm-xml-tools-constraints-image]: https://img.shields.io/npm/v/@xml-tools/constraints.svg +[npm-xml-tools-simple-schema-image]: https://img.shields.io/npm/v/@xml-tools/simple-schema.svg +[npm-xml-tools-simple-schema-url]: https://www.npmjs.com/package/@xml-tools/simple-schema +[npm-xml-tools-language-server-image]: https://img.shields.io/npm/v/@xml-tools/language-server.svg +[npm-xml-tools-language-server-url]: https://www.npmjs.com/package/@xml-tools/language-server +[npm-xml-tools-common-image]: https://img.shields.io/npm/v/@xml-tools/common.svg +[npm-xml-tools-common-url]: https://www.npmjs.com/package/@xml-tools/common + +## Support + +Please open [issues](https://github.com/SAP/xml-tools/issues) on github. + +## Contributing + +See [CONTRIBUTING.md](./CONTRIBUTING.md). + +[mono-repo]: https://github.com/babel/babel/blob/master/doc/design/monorepo.md +[xml]: https://github.com/babel/babel/blob/master/doc/design/monorepo.md +[ast]: https://en.wikipedia.org/wiki/Abstract_syntax_tree +[cst]: https://en.wikipedia.org/wiki/Parse_tree diff --git a/projects/xml-tools/packages/ast-position/CHANGELOG.md b/projects/xml-tools/packages/ast-position/CHANGELOG.md new file mode 100644 index 00000000..9d1c5929 --- /dev/null +++ b/projects/xml-tools/packages/ast-position/CHANGELOG.md @@ -0,0 +1,54 @@ +# Change Log + +All notable changes to this project will be documented in this file. +See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + +## [2.0.8](https://github.com/sap/xml-tools/compare/@xml-tools/ast-position@2.0.7...@xml-tools/ast-position@2.0.8) (2026-02-11) + +**Note:** Version bump only for package @xml-tools/ast-position + +## [2.0.7](https://github.com/sap/xml-tools/compare/@xml-tools/ast-position@2.0.6...@xml-tools/ast-position@2.0.7) (2021-06-03) + +**Note:** Version bump only for package @xml-tools/ast-position + +## [2.0.6](https://github.com/sap/xml-tools/compare/@xml-tools/ast-position@2.0.5...@xml-tools/ast-position@2.0.6) (2021-02-09) + +**Note:** Version bump only for package @xml-tools/ast-position + +## [2.0.5](https://github.com/sap/xml-tools/compare/@xml-tools/ast-position@2.0.4...@xml-tools/ast-position@2.0.5) (2021-01-21) + +**Note:** Version bump only for package @xml-tools/ast-position + +## [2.0.4](https://github.com/sap/xml-tools/compare/@xml-tools/ast-position@2.0.3...@xml-tools/ast-position@2.0.4) (2021-01-20) + +### Bug Fixes + +- missing lib folder in npm packages ([ffed3c2](https://github.com/sap/xml-tools/commit/ffed3c2c54c70aea8b9ded0d53786382bc190cc5)) + +## [2.0.3](https://github.com/sap/xml-tools/compare/@xml-tools/ast-position@2.0.2...@xml-tools/ast-position@2.0.3) (2021-01-20) + +**Note:** Version bump only for package @xml-tools/ast-position + +## [2.0.2](https://github.com/sap/xml-tools/compare/@xml-tools/ast-position@2.0.1...@xml-tools/ast-position@2.0.2) (2020-06-29) + +**Note:** Version bump only for package @xml-tools/ast-position + +## [2.0.1](https://github.com/sap/xml-tools/compare/@xml-tools/ast-position@2.0.0...@xml-tools/ast-position@2.0.1) (2020-06-18) + +**Note:** Version bump only for package @xml-tools/ast-position + +# [2.0.0](https://github.com/sap/xml-tools/compare/@xml-tools/ast-position@1.1.0...@xml-tools/ast-position@2.0.0) (2020-05-21) + +### Features + +- **ast-position:** rename `getAstNodeInPosition` -> astPositionAtOffset ([585253f](https://github.com/sap/xml-tools/commit/585253f)) + +### BREAKING CHANGES + +- **ast-position:** rename `getAstNodeInPosition` -> astPositionAtOffset + +# 1.1.0 (2020-05-18) + +### Features + +- **ast-position:** add AST position visitor ([#175](https://github.com/sap/xml-tools/issues/175)) ([9eb1253](https://github.com/sap/xml-tools/commit/9eb1253)) diff --git a/projects/xml-tools/packages/ast-position/CONTRIBUTING.md b/projects/xml-tools/packages/ast-position/CONTRIBUTING.md new file mode 100644 index 00000000..84eb9447 --- /dev/null +++ b/projects/xml-tools/packages/ast-position/CONTRIBUTING.md @@ -0,0 +1,4 @@ +# Contribution Guide + +This package does not have any unique development flows. +Please see the top level [Contribution Guide](../../CONTRIBUTING.md). diff --git a/projects/xml-tools/packages/ast-position/README.md b/projects/xml-tools/packages/ast-position/README.md new file mode 100644 index 00000000..720484b7 --- /dev/null +++ b/projects/xml-tools/packages/ast-position/README.md @@ -0,0 +1,32 @@ +[![npm (scoped)](https://img.shields.io/npm/v/@xml-tools/ast-position.svg)](https://www.npmjs.com/package/@xml-tools/ast-position) + +# @xml-tools/ast-position + +Utilities related to textual positions in the XML-Tools **A**bstract **S**yntax **T**ree. + +## Installation + +With npm: + +- `npm install @xml-tools/ast-position` + +With Yarn + +- `yarn add @xml-tools/ast-position` + +## Usage + +Please see the [TypeScript Definitions](./api.d.ts) for full API details. + +Simply import/require the @xml-tools/ast-position package and use the utilities +as declared in the TypeScript signatures above. + +## Support + +Please open [issues](https://github.com/SAP/xml-tols/issues) on github. + +## Contributing + +See [CONTRIBUTING.md](./CONTRIBUTING.md). + +[ast]: https://en.wikipedia.org/wiki/Abstract_syntax_tree diff --git a/projects/xml-tools/packages/ast-position/api.d.ts b/projects/xml-tools/packages/ast-position/api.d.ts new file mode 100644 index 00000000..dae97dba --- /dev/null +++ b/projects/xml-tools/packages/ast-position/api.d.ts @@ -0,0 +1,36 @@ +import { XMLAttribute, XMLElement, XMLDocument } from "@xml-tools/ast"; + +/** + * Not all possible kinds of positions are currently supported. + * More may be added in the future. + */ +type AstPosition = + | XMLElementOpenName + | XMLElementCloseName + | XMLAttributeKey + | XMLAttributeValue; + +declare function astPositionAtOffset( + ast: XMLDocument, + offset: number +): AstPosition | undefined; + +interface XMLElementOpenName { + kind: "XMLElementOpenName"; + astNode: XMLElement; +} + +interface XMLElementCloseName { + kind: "XMLElementCloseName"; + astNode: XMLElement; +} + +interface XMLAttributeKey { + kind: "XMLAttributeKey"; + astNode: XMLAttribute; +} + +interface XMLAttributeValue { + kind: "XMLAttributeValue"; + astNode: XMLAttribute; +} diff --git a/projects/xml-tools/packages/ast-position/lib/api.js b/projects/xml-tools/packages/ast-position/lib/api.js new file mode 100644 index 00000000..2e1c3be5 --- /dev/null +++ b/projects/xml-tools/packages/ast-position/lib/api.js @@ -0,0 +1,17 @@ +const { AstPositionVisitor } = require("./ast-position"); +const { accept } = require("@xml-tools/ast"); + +/** + * @param {XMLDocument} xmlDoc + * @param {number} offset + * @returns {AstPosition | undefined} + */ +function astPositionAtOffset(xmlDoc, offset) { + const positionVisitor = new AstPositionVisitor(offset); + accept(xmlDoc, positionVisitor); + return positionVisitor.position; +} + +module.exports = { + astPositionAtOffset, +}; diff --git a/projects/xml-tools/packages/ast-position/lib/ast-position.js b/projects/xml-tools/packages/ast-position/lib/ast-position.js new file mode 100644 index 00000000..49818ee3 --- /dev/null +++ b/projects/xml-tools/packages/ast-position/lib/ast-position.js @@ -0,0 +1,39 @@ +class AstPositionVisitor { + constructor(offset) { + this.offset = offset; + this.position = undefined; + } + + visitXMLElement(node) { + const openName = node.syntax.openName; + const closeName = node.syntax.closeName; + if (this.isOffsetInRange(openName)) { + this.position = { kind: "XMLElementOpenName", astNode: node }; + } else if (this.isOffsetInRange(closeName)) { + this.position = { kind: "XMLElementCloseName", astNode: node }; + } + } + + visitXMLAttribute(node) { + const key = node.syntax.key; + const value = node.syntax.value; + + if (this.isOffsetInRange(key)) { + this.position = { kind: "XMLAttributeKey", astNode: node }; + } else if (this.isOffsetInRange(value)) { + this.position = { kind: "XMLAttributeValue", astNode: node }; + } + } + + isOffsetInRange(position) { + return ( + position !== undefined && + position.startOffset <= this.offset && + position.endOffset >= this.offset + ); + } +} + +module.exports = { + AstPositionVisitor: AstPositionVisitor, +}; diff --git a/projects/xml-tools/packages/ast-position/package.json b/projects/xml-tools/packages/ast-position/package.json new file mode 100644 index 00000000..59e8dcc0 --- /dev/null +++ b/projects/xml-tools/packages/ast-position/package.json @@ -0,0 +1,44 @@ +{ + "name": "@xml-tools/ast-position", + "version": "2.0.8", + "description": "XML Ast Position APIs", + "keywords": [ + "xml", + "position" + ], + "main": "lib/api.js", + "repository": "https://github.com/sap/xml-tools/", + "license": "Apache-2.0", + "typings": "./api.d.ts", + "files": [ + "lib", + ".reuse", + "LICENSES", + "api.d.ts" + ], + "dependencies": { + "@xml-tools/ast": "^5.1.0" + }, + "devDependencies": { + "@xml-tools/parser": "^1.0.11" + }, + "scripts": { + "ci": "npm-run-all type-check coverage:*", + "test": "mocha \"./test/**/*spec.js\"", + "coverage:run": "nyc mocha \"./test/**/*spec.js\"", + "coverage:check": "nyc check-coverage --lines 100 --branches 100 --statements 100 --functions 100", + "type-check": "tsc api.d.ts" + }, + "publishConfig": { + "access": "public" + }, + "nyc": { + "include": [ + "lib/**/*.js" + ], + "reporter": [ + "text", + "lcov" + ] + } +} diff --git a/projects/xml-tools/packages/ast-position/test/ast-position-spec.js b/projects/xml-tools/packages/ast-position/test/ast-position-spec.js new file mode 100644 index 00000000..b04e9bb6 --- /dev/null +++ b/projects/xml-tools/packages/ast-position/test/ast-position-spec.js @@ -0,0 +1,143 @@ +const { expect } = require("chai"); +const { buildAst } = require("@xml-tools/ast"); +const { parse } = require("@xml-tools/parser"); +// Testing the npm package `main` exports by requiring the folder where the `package.json` resides. +const { astPositionAtOffset } = require("../"); + +describe("AST Position visitor", () => { + it("will get xml attribute key", () => { + const xmlSnippet = ` + + + `; + const astNodeInPosition = getXMLPositionFromSnippet(xmlSnippet); + + expect(astNodeInPosition).to.exist; + expect(astNodeInPosition.kind).to.equal("XMLAttributeKey"); + expect(astNodeInPosition.astNode.key).to.equal("showSeparators"); + }); + + it("will get xml attribute value", () => { + const xmlSnippet = ` + + + `; + const astNodeInPosition = getXMLPositionFromSnippet(xmlSnippet); + + expect(astNodeInPosition).to.exist; + expect(astNodeInPosition.kind).to.equal("XMLAttributeValue"); + expect(astNodeInPosition.astNode.value).to.equal("All"); + }); + + it("will get xml element open name", () => { + const xmlSnippet = ` + + + `; + const astNodeInPosition = getXMLPositionFromSnippet(xmlSnippet); + + expect(astNodeInPosition).to.exist; + expect(astNodeInPosition.kind).to.equal("XMLElementOpenName"); + expect(astNodeInPosition.astNode.name).to.equal("List"); + }); + + it("will get xml element close name", () => { + const xmlSnippet = ` + + + `; + const astNodeInPosition = getXMLPositionFromSnippet(xmlSnippet); + + expect(astNodeInPosition).to.exist; + expect(astNodeInPosition.kind).to.equal("XMLElementCloseName"); + expect(astNodeInPosition.astNode.name).to.equal("customData"); + }); + + it("Out of element - before open tag", () => { + const xmlSnippet = ` + ⇶ + + `; + + const astNodeInPosition = getXMLPositionFromSnippet(xmlSnippet); + expect(astNodeInPosition).to.be.undefined; + }); + + it("Out of element - after close tag", () => { + const xmlSnippet = ` + + + ⇶`; + + const astNodeInPosition = getXMLPositionFromSnippet(xmlSnippet); + expect(astNodeInPosition).to.be.undefined; + }); + + it("Out of element - between elements", () => { + const xmlSnippet = ` + + ⇶ + `; + + const astNodeInPosition = getXMLPositionFromSnippet(xmlSnippet); + expect(astNodeInPosition).to.be.undefined; + }); + + it("Out of attribute - before attribute", () => { + const xmlSnippet = ` + + + `; + + const astNodeInPosition = getXMLPositionFromSnippet(xmlSnippet); + expect(astNodeInPosition).to.be.undefined; + }); + + it("Out of attribute - between attributes", () => { + const xmlSnippet = ` + + + `; + + const astNodeInPosition = getXMLPositionFromSnippet(xmlSnippet); + expect(astNodeInPosition).to.be.undefined; + }); + + it("Out of attribute - between key to vale", () => { + const xmlSnippet = ` + + + `; + + const astNodeInPosition = getXMLPositionFromSnippet(xmlSnippet); + expect(astNodeInPosition).to.be.undefined; + }); + + function getXMLPositionFromSnippet(xmlSnippet) { + const xmlText = xmlSnippet.replace("⇶", ""); + const offset = xmlSnippet.indexOf("⇶"); + const { cst, tokenVector } = parse(xmlText); + const xmlDoc = buildAst(cst, tokenVector); + return astPositionAtOffset(xmlDoc, offset); + } +}); diff --git a/projects/xml-tools/packages/ast/.npmignore b/projects/xml-tools/packages/ast/.npmignore new file mode 100644 index 00000000..ae12335e --- /dev/null +++ b/projects/xml-tools/packages/ast/.npmignore @@ -0,0 +1,10 @@ +# NYC +.nyc_output +nyc.config.js +coverage + +# tests +test + +# contributing +CONTRIBUTING.md \ No newline at end of file diff --git a/projects/xml-tools/packages/ast/CHANGELOG.md b/projects/xml-tools/packages/ast/CHANGELOG.md new file mode 100644 index 00000000..37d84e3d --- /dev/null +++ b/projects/xml-tools/packages/ast/CHANGELOG.md @@ -0,0 +1,165 @@ +# Change Log + +All notable changes to this project will be documented in this file. +See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + +# [5.1.0](https://github.com/sap/xml-tools/compare/@xml-tools/ast@5.0.5...@xml-tools/ast@5.1.0) (2026-02-11) + +### Features + +- update lodash dependency to version 4.17.23 ([#461](https://github.com/sap/xml-tools/issues/461)) ([23a319d](https://github.com/sap/xml-tools/commit/23a319d6ed42a4a3885ec5748eb127147a8960bc)) + +## [5.0.5](https://github.com/sap/xml-tools/compare/@xml-tools/ast@5.0.4...@xml-tools/ast@5.0.5) (2021-06-03) + +**Note:** Version bump only for package @xml-tools/ast + +## [5.0.4](https://github.com/sap/xml-tools/compare/@xml-tools/ast@5.0.3...@xml-tools/ast@5.0.4) (2021-02-09) + +**Note:** Version bump only for package @xml-tools/ast + +## [5.0.3](https://github.com/sap/xml-tools/compare/@xml-tools/ast@5.0.2...@xml-tools/ast@5.0.3) (2021-01-21) + +### Bug Fixes + +- missing lib folder in ast package ([c61d703](https://github.com/sap/xml-tools/commit/c61d70320bdc13800402082aeaac42a1fd92cc68)) + +## [5.0.2](https://github.com/sap/xml-tools/compare/@xml-tools/ast@5.0.1...@xml-tools/ast@5.0.2) (2021-01-20) + +**Note:** Version bump only for package @xml-tools/ast + +## [5.0.1](https://github.com/sap/xml-tools/compare/@xml-tools/ast@5.0.0...@xml-tools/ast@5.0.1) (2021-01-20) + +**Note:** Version bump only for package @xml-tools/ast + +# [5.0.0](https://github.com/sap/xml-tools/compare/@xml-tools/ast@4.2.1...@xml-tools/ast@5.0.0) (2020-06-29) + +### Features + +- **ast:** attribute inside a prolog are now of XMLPrologAttribute type ([#208](https://github.com/sap/xml-tools/issues/208)) ([a303db5](https://github.com/sap/xml-tools/commit/a303db5)) + +### BREAKING CHANGES + +- **ast:** Vistors that need to handle attributes inside prolog must now implement the `visitPrologAttribute` method. + +## [4.2.1](https://github.com/sap/xml-tools/compare/@xml-tools/ast@4.2.0...@xml-tools/ast@4.2.1) (2020-06-18) + +**Note:** Version bump only for package @xml-tools/ast + +# [4.2.0](https://github.com/sap/xml-tools/compare/@xml-tools/ast@4.1.0...@xml-tools/ast@4.2.0) (2020-05-17) + +### Features + +- **common:** add utility functions for xmlns attributes ([#173](https://github.com/sap/xml-tools/issues/173)) ([20d6c09](https://github.com/sap/xml-tools/commit/20d6c09)) + +# [4.1.0](https://github.com/sap/xml-tools/compare/@xml-tools/ast@4.0.3...@xml-tools/ast@4.1.0) (2020-05-05) + +### Features + +- **ast:** added `.syntax.isSelfClosing` property on an XMLElement node ([#164](https://github.com/sap/xml-tools/issues/164)) ([bfc448b](https://github.com/sap/xml-tools/commit/bfc448b)) + +## [4.0.3](https://github.com/sap/xml-tools/compare/@xml-tools/ast@4.0.2...@xml-tools/ast@4.0.3) (2020-04-02) + +**Note:** Version bump only for package @xml-tools/ast + +## [4.0.2](https://github.com/sap/xml-tools/compare/@xml-tools/ast@4.0.1...@xml-tools/ast@4.0.2) (2020-02-19) + +### Bug Fixes + +- add npmignore file to each package ([5bbf209](https://github.com/sap/xml-tools/commit/5bbf209)) + +## [4.0.1](https://github.com/sap/xml-tools/compare/@xml-tools/ast@4.0.0...@xml-tools/ast@4.0.1) (2020-02-11) + +**Note:** Version bump only for package @xml-tools/ast + +# [4.0.0](https://github.com/sap/xml-tools/compare/@xml-tools/ast@2.0.1...@xml-tools/ast@4.0.0) (2020-02-10) + +### Features + +- **ast:** element Namespaces are now represented as Records in a Map ([f9f6fdc](https://github.com/sap/xml-tools/commit/f9f6fdc)) + +### BREAKING CHANGES + +- **ast:** The `namespaces` property of an XMLElement is now a Map/Dictionary not an Array. + +# [3.0.0](https://github.com/sap/xml-tools/compare/@xml-tools/ast@2.0.1...@xml-tools/ast@3.0.0) (2020-02-07) + +### Features + +- **ast:** element Namespaces are now represented as Records in a Map ([f9f6fdc](https://github.com/sap/xml-tools/commit/f9f6fdc)) + +### BREAKING CHANGES + +- **ast:** The `namespaces` property of an XMLElement is now a Map/Dictionary not an Array. + +## [2.0.1](https://github.com/sap/xml-tools/compare/@xml-tools/ast@2.0.0...@xml-tools/ast@2.0.1) (2020-01-28) + +**Note:** Version bump only for package @xml-tools/ast + +# [2.0.0](https://github.com/sap/xml-tools/compare/@xml-tools/ast@1.0.0...@xml-tools/ast@2.0.0) (2020-01-20) + +### Features + +- **parser:** support Basic DocType Declarations ([5b4db21](https://github.com/sap/xml-tools/commit/5b4db21)) + +### BREAKING CHANGES + +- **parser:** Implementing XmlCstVisitor now requires implementing two additional methods: + (docTypeDecl and externalID) + +# [1.0.0](https://github.com/sap/xml-tools/compare/@xml-tools/ast@0.5.0...@xml-tools/ast@1.0.0) (2019-12-08) + +### Features + +- **ast:** attributesRange and guessedAttributeRange support ([8ce840d](https://github.com/sap/xml-tools/commit/8ce840d)), closes [#51](https://github.com/sap/xml-tools/issues/51) + +### BREAKING CHANGES + +- **ast:** buildAst now requires a tokenVector argument + +# [0.5.0](https://github.com/sap/xml-tools/compare/@xml-tools/ast@0.4.6...@xml-tools/ast@0.5.0) (2019-12-05) + +### Features + +- better Handling of Open/Close Body ranges ([0751a7e](https://github.com/sap/xml-tools/commit/0751a7e)) + +## [0.4.6](https://github.com/sap/xml-tools/compare/@xml-tools/ast@0.4.5...@xml-tools/ast@0.4.6) (2019-12-03) + +**Note:** Version bump only for package @xml-tools/ast + +## [0.4.5](https://github.com/sap/xml-tools/compare/@xml-tools/ast@0.4.4...@xml-tools/ast@0.4.5) (2019-11-20) + +**Note:** Version bump only for package @xml-tools/ast + +## [0.4.4](https://github.com/sap/xml-tools/compare/@xml-tools/ast@0.4.3...@xml-tools/ast@0.4.4) (2019-11-12) + +**Note:** Version bump only for package @xml-tools/ast + +## [0.4.3](https://github.com/sap/xml-tools/compare/@xml-tools/ast@0.4.2...@xml-tools/ast@0.4.3) (2019-11-11) + +**Note:** Version bump only for package @xml-tools/ast + +## [0.4.2](https://github.com/sap/xml-tools/compare/@xml-tools/ast@0.4.1...@xml-tools/ast@0.4.2) (2019-11-11) + +**Note:** Version bump only for package @xml-tools/ast + +## [0.4.1](https://github.com/sap/xml-tools/compare/@xml-tools/ast@0.4.0...@xml-tools/ast@0.4.1) (2019-11-06) + +### Bug Fixes + +- new property added to ast interface ([#35](https://github.com/sap/xml-tools/issues/35)) ([d7fd672](https://github.com/sap/xml-tools/commit/d7fd672)) + +# [0.4.0](https://github.com/sap/xml-tools/compare/@xml-tools/ast@0.3.0...@xml-tools/ast@0.4.0) (2019-11-06) + +### Features + +- open tag body range added ([#34](https://github.com/sap/xml-tools/issues/34)) ([5414111](https://github.com/sap/xml-tools/commit/5414111)) + +# 0.3.0 (2019-10-28) + +### Features + +- move to lerna independent mode ([6c347a8](https://github.com/sap/xml-tools/commit/6c347a8)), closes [#18](https://github.com/sap/xml-tools/issues/18) + +# 0.2.0 (2019-10-28) + +# 0.1.0 (2019-10-28) diff --git a/projects/xml-tools/packages/ast/CONTRIBUTING.md b/projects/xml-tools/packages/ast/CONTRIBUTING.md new file mode 100644 index 00000000..84eb9447 --- /dev/null +++ b/projects/xml-tools/packages/ast/CONTRIBUTING.md @@ -0,0 +1,4 @@ +# Contribution Guide + +This package does not have any unique development flows. +Please see the top level [Contribution Guide](../../CONTRIBUTING.md). diff --git a/projects/xml-tools/packages/ast/README.md b/projects/xml-tools/packages/ast/README.md new file mode 100644 index 00000000..b0b177be --- /dev/null +++ b/projects/xml-tools/packages/ast/README.md @@ -0,0 +1,70 @@ +[![npm (scoped)](https://img.shields.io/npm/v/@xml-tools/ast.svg)](https://www.npmjs.com/package/@xml-tools/ast) + +# @xml-tools/ast + +Utilities for building and traversing an XML **A**bstract **S**yntax **T**ree ([AST][ast]). + +There are two things which distinguish this AST from most others ASTs: + +1. This AST can represent a **partially valid** XML, in practice this means virtually all properties on + the AST are optional and may have undefined values. +2. This AST contains **additional syntactic information** to enable additional linting & formatting flows, for example: + - the original position and value of an attribute's value (including the quotes). + - The original positions and values of an XMLElement's open/close names. + +The input for constructing the AST is a CST which is created by the [@xml-tools/parser](../parser) package. + +The AST structure is used as part of the input for the [@xml-tools/content-assist](../content-assist) APIs. + +## Installation + +With npm: + +- `npm install @xml-tools/ast` + +With Yarn + +- `yarn add @xml-tools/ast` + +## Usage + +Please see the [TypeScript Definitions](./api.d.ts) for full API details. + +A simple usage example: + +```javascript +const { parse } = require("@xml-tools/parser"); +const { buildAst, accept } = require("@xml-tools/ast"); + +const xmlText = ` + Bill + Tim + +`; + +const { cst, tokenVector } = parse(xmlText); +const xmlDocAst = buildAst(cst, tokenVector); +console.log(xmlDocAst.rootElement.name); // -> note + +// A Visitor allows us to invoke actions on the XML ASTNodes without worrying about +// The XML AST structure / traversal method. +const printVisitor = { + // Will be invoked once for each Element node in the AST. + visitXMLElement: function (node) { + console.log(node.name); + }, + + // An XML AST Visitor may have other methods as well, see the api.d.ts file/ +}; + +// Invoking the Visitor +accept(xmlDocAst, printVisitor); // -> note, Bill, Tim +``` + +## Support + +Please open [issues](https://github.com/SAP/xml-tols/issues) on github. + +## Contributing + +See [CONTRIBUTING.md](./CONTRIBUTING.md). diff --git a/projects/xml-tools/packages/ast/api.d.ts b/projects/xml-tools/packages/ast/api.d.ts new file mode 100644 index 00000000..2fc95e55 --- /dev/null +++ b/projects/xml-tools/packages/ast/api.d.ts @@ -0,0 +1,216 @@ +import { IToken } from "chevrotain"; +import { DocumentCstNode } from "@xml-tools/parser"; + +/** + * Builds an XML Ast from an XML Cst. + * Can process even partial CSTs... + */ +export function buildAst( + docCst: DocumentCstNode, + tokenVector: IToken[] +): XMLDocument; + +/** + * An Abstract Syntax Tree structure + * Which contains partial position information relevant + * for validation and content assist scenarios. + * + * Note that: + * + * - This data structure is immutable. + * - The AST is is a "pure data structure" + * there are no methods directly on the nodes, instead utilities are provided + * as external functions. + * - The AST does not contain the full syntactic information, which means + * implementing certain editor services scenarios may require first augmenting + * this data structure (e.g formatting). + * - Some properties are optional (marked with '?' syntax) + * - Some properties are mandatory but may be `InvalidSyntax` (null) because + * this structure also represents partially valid XML. + */ +declare interface XMLDocument { + readonly type: "XMLDocument"; + readonly prolog?: XMLProlog; + readonly rootElement: XMLElement | InvalidSyntax; + readonly position: SourcePosition; +} + +declare interface XMLProlog { + readonly type: "XMLProlog"; + readonly parent: XMLDocument; + + readonly attributes: XMLPrologAttribute[]; + + readonly position: SourcePosition; +} + +// A Prefix cannot include a colon ":" so the below string will never conflict +// with a valid namespace prefix. +declare type DefaultNS = "::DEFAULT"; +declare const DEFAULT_NS: DefaultNS; +declare type Prefix = string | DefaultNS; +declare type Uri = string; + +declare interface XMLElement { + readonly type: "XMLElement"; + readonly parent: XMLElement | XMLDocument; + + readonly namespaces: Record; + + // namespace prefix used by this XML Element. + // - Note that this is an optional syntax. + readonly ns?: string; + // Actual name part (without the namespace prefix) used by this XML Element. + readonly name: string | InvalidSyntax; + + readonly attributes: XMLAttribute[]; + readonly subElements: XMLElement[]; + readonly textContents: XMLTextContent[]; + + readonly syntax: { + // Is the XML Element self closing or with a distinct closing tag. + // - e.g: `` vs `` + // This value depends on the existence of the self closing "/>" token + // versus the regular closing ">" token of the opening tag. + // If none of these tokens exist than this property would not be defined. + // + // - Note that the a missing `closeName` token is not sufficient to conclude + // the element is self closing, e.g when dealing with invalid syntax: + // - `` + readonly isSelfClosing?: boolean; + + // Will only exist if there is a valid "Start Name" for the XML Element + // - Note this would always exist in a valid XML. + readonly openName?: XMLToken; + + // Will only exist if there is a valid "Closing Name" for the XML Element + // - Note that this is an **optional** syntax in a valid XML. + readonly closeName?: XMLToken; + + // Describes the range of the opening element name + attributes area: + // Examples: + // Some content + // <===== openBody range =====> + // + // <======= openBody range ===========> + // + // Will not exist if there is no open '<' or closing '>' | "/>" (self closing) tokens. + readonly openBody?: XMLToken; + + // Describes the range of the **closing** element name area: + // Example: + // Some content + // < ------ > + // + // Will not exist if any of the closing "brackets" are missing + // - e.g in a self closing element. + readonly closeBody?: XMLToken; + + // Describes the range of the attributes section: + // This starts one character **after** the opening name token + // and ends one character before the open body closing '>' or self closing '/> + // Examples: + // Some content + // <===== attributesRange =====> + // + // <===== attributesRange =====> + readonly attributesRange?: SourceRange; + + // Same as attributesRange except this property will be used for partially valid + // XMLElements when the AstBuilder cannot be certain what is the exact attributes range. + // - Only one of the attributeRanges properties may exist at the same time. + // - It is possible that in some cases it won't be possible to even guess the attributes range. + // In that scenario neither of the properties will exist. + readonly guessedAttributesRange?: SourceRange; + }; + readonly position: SourcePosition; +} + +declare interface XMLTextContent { + readonly type: "XMLTextContent"; + readonly parent: XMLElement; + + readonly text: string | InvalidSyntax; + + readonly position: SourcePosition; +} + +declare interface XMLAttributeBase { + readonly type: string; + + readonly key: string | InvalidSyntax; + // Semantic Value: Would not include the quotes! + readonly value: string | InvalidSyntax; + + readonly syntax: { + readonly key?: XMLToken; + // Original Literal value, would include the quotes + readonly value?: XMLToken; + }; + readonly position: SourcePosition; +} + +declare interface XMLAttribute extends XMLAttributeBase { + readonly type: "XMLAttribute"; + readonly parent: XMLElement; +} + +/** + * XMLPrologAttribute is virtually identical to a regular `XMLAttribute`. + * However it was moved to a separate interface to allow consistent handling of the `parent` + * property in `XMLAttribute`, as 99.9% of the time the edge case of a parent being an XMLProlog + * is not relevant. + */ +declare interface XMLPrologAttribute extends XMLAttributeBase { + readonly type: "XMLPrologAttribute"; + readonly parent: XMLProlog; +} + +declare interface SourceRange { + readonly startOffset: number; + readonly endOffset: number; +} + +declare interface SourcePosition { + readonly startOffset: number; + readonly endOffset: number; + readonly startLine: number; + readonly endLine: number; + readonly startColumn: number; + readonly endColumn: number; +} + +declare interface XMLToken extends SourcePosition { + readonly image: string; +} + +declare type XMLAstNode = + | XMLDocument + | XMLProlog + | XMLPrologAttribute + | XMLElement + | XMLAttribute + | XMLTextContent; + +/** + * Will Traverse as XML AST and activate the relevant visitor method on each node. + * There is no guarantee on the order in which the nodes will be visited. + * For greater control of the traversal, one should evaluate implementing a recursive tree walker. + */ +declare function accept(node: XMLAstNode, visitor: XMLAstVisitor): void; + +/** + * Very Simple Visitor API. + * Currently No support for IN/OUT params on the visit methods + * Which + */ +declare interface XMLAstVisitor { + visitXMLDocument?(node: XMLDocument): void; + visitXMLProlog?(node: XMLProlog): void; + visitXMLPrologAttribute?(node: XMLPrologAttribute): void; + visitXMLElement?(node: XMLElement): void; + visitXMLAttribute?(node: XMLAttribute): void; + visitXMLTextContent?(node: XMLTextContent): void; +} + +export type InvalidSyntax = null; diff --git a/projects/xml-tools/packages/ast/lib/api.js b/projects/xml-tools/packages/ast/lib/api.js new file mode 100644 index 00000000..3ef2b0bc --- /dev/null +++ b/projects/xml-tools/packages/ast/lib/api.js @@ -0,0 +1,9 @@ +const { buildAst } = require("./build-ast"); +const { accept } = require("./visit-ast"); +const { DEFAULT_NS } = require("./constants"); + +module.exports = { + buildAst: buildAst, + accept: accept, + DEFAULT_NS: DEFAULT_NS, +}; diff --git a/projects/xml-tools/packages/ast/lib/build-ast.js b/projects/xml-tools/packages/ast/lib/build-ast.js new file mode 100644 index 00000000..6346ae0b --- /dev/null +++ b/projects/xml-tools/packages/ast/lib/build-ast.js @@ -0,0 +1,457 @@ +const { BaseXmlCstVisitor } = require("@xml-tools/parser"); +const { + last, + forEach, + reduce, + map, + pick, + sortBy, + isEmpty, + isArray, + assign, +} = require("lodash"); +const { + findNextTextualToken, + isXMLNamespaceKey, + getXMLNamespaceKeyPrefix, +} = require("@xml-tools/common"); + +const { getAstChildrenReflective } = require("./utils"); +const { DEFAULT_NS } = require("./constants"); + +/** + * @param {DocumentCstNode} docCst + * @param {IToken[]} tokenVector + * @returns {XMLDocument} + */ +function buildAst(docCst, tokenVector) { + AstBuilder.setState({ tokenVector }); + const xmlDocAst = AstBuilder.visit(docCst); + + if (xmlDocAst.rootElement !== invalidSyntax) { + updateNamespaces(xmlDocAst.rootElement); + } + return xmlDocAst; +} + +/* eslint-disable no-unused-vars -- consistent signatures in visitor methods even if they are empty placeholders */ +class CstToAstVisitor extends BaseXmlCstVisitor { + constructor() { + super(); + } + + setState({ tokenVector }) { + this.tokenVector = tokenVector; + } + + visit(cstNode, params = {}) { + return super.visit(cstNode, { location: cstNode.location, ...params }); + } + + /** + * @param ctx {DocumentCtx} + * @param opts {Object} + * @param opts.location {SourcePosition} + * + * @returns {XMLDocument} + */ + document(ctx, { location }) { + const astNode = { + type: "XMLDocument", + rootElement: invalidSyntax, + position: location, + }; + + if (ctx.prolog !== undefined) { + astNode.prolog = this.visit(ctx.prolog[0]); + } + + if ( + ctx.element !== undefined && + isEmpty(ctx.element[0].children) === false + ) { + astNode.rootElement = this.visit(ctx.element[0]); + } + + setChildrenParent(astNode); + + return astNode; + } + + /** + * @param ctx {PrologCtx} + * @param opts {Object} + * @param opts.location {SourcePosition} + */ + prolog(ctx, { location }) { + const astNode = { + type: "XMLProlog", + attributes: [], + position: location, + }; + + if (ctx.attribute !== undefined) { + astNode.attributes = map(ctx.attribute, (_) => + this.visit(_, { isPrologParent: true }) + ); + } + + setChildrenParent(astNode); + + return astNode; + } + + /** + * @param {docTypeDeclCtx} ctx + */ + /* istanbul ignore next - place holder*/ + docTypeDecl(ctx, astNode) {} + + /** + * @param {ExternalIDCtx} ctx + */ + /* istanbul ignore next - place holder*/ + externalID(ctx, astNode) {} + + /** + * @param ctx {ContentCtx} + * @param opts {Object} + * @param opts.location {SourcePosition} + * + * @return {{elements, textContents}} + */ + content(ctx, { location }) { + let elements = []; + let textContents = []; + + if (ctx.element !== undefined) { + elements = map(ctx.element, this.visit.bind(this)); + } + + if (ctx.chardata !== undefined) { + textContents = map(ctx.chardata, this.visit.bind(this)); + } + + return { elements, textContents }; + } + + /** + * @param ctx {ElementCtx} + * @param opts {Object} + * @param opts.location {SourcePosition} + */ + element(ctx, { location }) { + const astNode = { + type: "XMLElement", + // Avoid Accidental Keys in this map + namespaces: Object.create(null), + name: invalidSyntax, + attributes: [], + subElements: [], + textContents: [], + position: location, + syntax: {}, + }; + + if (ctx.attribute !== undefined) { + astNode.attributes = map(ctx.attribute, this.visit.bind(this)); + } + + if (ctx.content !== undefined) { + const { elements, textContents } = this.visit(ctx.content[0]); + astNode.subElements = elements; + astNode.textContents = textContents; + } + + handleElementOpenCloseNameRanges(astNode, ctx); + handleElementOpenCloseBodyRanges(astNode, ctx); + handleElementAttributeRanges(astNode, ctx, this.tokenVector); + + setChildrenParent(astNode); + + return astNode; + } + + /** + * @param ctx {ReferenceCtx} + * @param opts {Object} + * @param opts.location {SourcePosition} + */ + /* istanbul ignore next - place holder*/ + reference(ctx, { location }) { + // Irrelevant for the AST at this time + } + + /** + * @param ctx {AttributeCtx} + * @param opts {Object} + * @param opts.location {SourcePosition} + * @param opts.isPrologParent {boolean} + */ + attribute(ctx, { location, isPrologParent }) { + const astNode = { + type: isPrologParent ? "XMLPrologAttribute" : "XMLAttribute", + position: location, + key: invalidSyntax, + value: invalidSyntax, + syntax: {}, + }; + + /* istanbul ignore else - Defensive Coding, not actually possible else branch */ + if (ctx.Name !== undefined && ctx.Name[0].isInsertedInRecovery !== true) { + const keyToken = ctx.Name[0]; + astNode.key = keyToken.image; + astNode.syntax.key = toXMLToken(keyToken); + } + + if ( + ctx.STRING !== undefined && + ctx.STRING[0].isInsertedInRecovery !== true + ) { + const valueToken = ctx.STRING[0]; + astNode.value = stripQuotes(valueToken.image); + astNode.syntax.value = toXMLToken(valueToken); + } + + setChildrenParent(astNode); + + return astNode; + } + + /** + * @param ctx {ChardataCtx} + * @param opts {Object} + * @param opts.location {SourcePosition} + */ + chardata(ctx, { location }) { + const astNode = { + type: "XMLTextContent", + position: location, + text: invalidSyntax, + }; + + let allTokens = []; + if (ctx.SEA_WS !== undefined) { + allTokens = allTokens.concat(ctx.SEA_WS); + } + if (ctx.TEXT !== undefined) { + allTokens = allTokens.concat(ctx.TEXT); + } + const sortedTokens = sortBy(allTokens, ["startOffset"]); + const fullText = map(sortedTokens, "image").join(""); + astNode.text = fullText; + + return astNode; + } + + /** + * @param ctx {MiscCtx} + * @param opts {Object} + * @param opts.location {SourcePosition} + */ + /* istanbul ignore next - place holder*/ + misc(ctx, { location }) { + // Irrelevant for the AST at this time + } +} +/* eslint-enable no-unused-vars -- see matching disable comment */ + +const AstBuilder = new CstToAstVisitor(); + +function setChildrenParent(astParent) { + const astChildren = getAstChildrenReflective(astParent); + forEach(astChildren, (child) => (child.parent = astParent)); +} + +/** + * @param {XMLElement} element + * @param {Record} prevNamespaces + */ +function updateNamespaces(element, prevNamespaces = []) { + const currElemNamespaces = reduce( + element.attributes, + (result, attrib) => { + /* istanbul ignore else - Defensive Coding, not actually possible branch */ + if (attrib.key !== invalidSyntax) { + if ( + isXMLNamespaceKey({ key: attrib.key, includeEmptyPrefix: false }) === + true + ) { + const prefix = getXMLNamespaceKeyPrefix(attrib.key); + // TODO: Support un-defining namespaces (including the default one) + if (attrib.value) { + const uri = attrib.value; + if (prefix !== "") { + result[prefix] = uri; + } else { + // default namespace + result[DEFAULT_NS] = uri; + } + } + } + } + + return result; + }, + {} + ); + + const emptyMap = Object.create(null); + // "newer" (closer scope) namespaces definitions will overwrite "older" ones. + element.namespaces = assign(emptyMap, prevNamespaces, currElemNamespaces); + + forEach(element.subElements, (subElem) => + updateNamespaces(subElem, element.namespaces) + ); +} + +/** + * @param {chevrotain.IToken} token + */ +function toXMLToken(token) { + return pick(token, [ + "image", + "startOffset", + "endOffset", + "startLine", + "endLine", + "startColumn", + "endColumn", + ]); +} + +function startOfXMLToken(token) { + return pick(token, ["startOffset", "startLine", "startColumn"]); +} + +function endOfXMLToken(token) { + return pick(token, ["endOffset", "endLine", "endColumn"]); +} + +function exists(tokArr) { + return ( + isArray(tokArr) && + tokArr.length === 1 && + tokArr[0].isInsertedInRecovery !== true + ); +} + +function stripQuotes(quotedText) { + return quotedText.substring(1, quotedText.length - 1); +} + +/** + * @param {string} text + */ +function nsToParts(text) { + const matchResult = /^([^:]+):([^:]+)$/.exec(text); + if (matchResult === null) { + return null; + } + const ns = matchResult[1]; + const name = matchResult[2]; + return { ns, name }; +} + +/** + * @type {InvalidSyntax} + */ +const invalidSyntax = null; + +/** + * @param {XMLElement} astNode + * @param {ElementCtx} ctx + */ +function handleElementOpenCloseNameRanges(astNode, ctx) { + if (ctx.Name !== undefined && ctx.Name[0].isInsertedInRecovery !== true) { + const openNameToken = ctx.Name[0]; + astNode.syntax.openName = toXMLToken(openNameToken); + const nsParts = nsToParts(openNameToken.image); + if (nsParts !== null) { + astNode.ns = nsParts.ns; + astNode.name = nsParts.name; + } else { + astNode.name = openNameToken.image; + } + } + + if ( + ctx.END_NAME !== undefined && + ctx.END_NAME[0].isInsertedInRecovery !== true + ) { + astNode.syntax.closeName = toXMLToken(ctx.END_NAME[0]); + } +} + +/** + * @param {XMLElement} astNode + * @param {ElementCtx} ctx + */ +function handleElementOpenCloseBodyRanges(astNode, ctx) { + /* istanbul ignore else - Defensive Coding */ + if (exists(ctx.OPEN)) { + let openBodyCloseTok = undefined; + /* istanbul ignore else - Defensive Coding */ + if (exists(ctx.START_CLOSE)) { + openBodyCloseTok = ctx.START_CLOSE[0]; + astNode.syntax.isSelfClosing = false; + } else if (exists(ctx.SLASH_CLOSE)) { + openBodyCloseTok = ctx.SLASH_CLOSE[0]; + astNode.syntax.isSelfClosing = true; + } + + if (openBodyCloseTok !== undefined) { + astNode.syntax.openBody = { + ...startOfXMLToken(ctx.OPEN[0]), + ...endOfXMLToken(openBodyCloseTok), + }; + } + + if (exists(ctx.SLASH_OPEN) && exists(ctx.END)) { + astNode.syntax.closeBody = { + ...startOfXMLToken(ctx.SLASH_OPEN[0]), + ...endOfXMLToken(ctx.END[0]), + }; + } + } +} + +/** + * @param {XMLElement} astNode + * @param {ElementCtx} ctx + * @param {IToken[]} tokenVector + */ +function handleElementAttributeRanges(astNode, ctx, tokenVector) { + if (exists(ctx.Name)) { + const startOffset = ctx.Name[0].endOffset + 2; + // Valid `attributesRange` exists + if (exists(ctx.START_CLOSE) || exists(ctx.SLASH_CLOSE)) { + const endOffset = + (exists(ctx.START_CLOSE) + ? ctx.START_CLOSE[0].startOffset + : ctx.SLASH_CLOSE[0].startOffset) - 1; + astNode.syntax.attributesRange = { startOffset, endOffset }; + } + // Have to scan-ahead and guess where the attributes range ends + else { + const hasAttributes = isArray(ctx.attribute); + const lastKnownAttribRangeTokenEnd = hasAttributes + ? last(ctx.attribute).location.endOffset + : ctx.Name[0].endOffset; + const nextTextualToken = findNextTextualToken( + tokenVector, + lastKnownAttribRangeTokenEnd + ); + if (nextTextualToken !== null) { + astNode.syntax.guessedAttributesRange = { + startOffset, + endOffset: nextTextualToken.endOffset - 1, + }; + } + } + } +} + +module.exports = { + buildAst: buildAst, +}; diff --git a/projects/xml-tools/packages/ast/lib/constants.js b/projects/xml-tools/packages/ast/lib/constants.js new file mode 100644 index 00000000..7fb0bae3 --- /dev/null +++ b/projects/xml-tools/packages/ast/lib/constants.js @@ -0,0 +1,3 @@ +module.exports = { + DEFAULT_NS: "::DEFAULT", +}; diff --git a/projects/xml-tools/packages/ast/lib/utils.js b/projects/xml-tools/packages/ast/lib/utils.js new file mode 100644 index 00000000..27c8ee20 --- /dev/null +++ b/projects/xml-tools/packages/ast/lib/utils.js @@ -0,0 +1,25 @@ +const { reduce, has, isArray } = require("lodash"); + +function getAstChildrenReflective(astParent) { + const astChildren = reduce( + astParent, + (result, prop, name) => { + if (name === "parent") { + // parent property is never a child... + } else if (has(prop, "type")) { + result.push(prop); + } else if (isArray(prop) && prop.length > 0 && has(prop[0], "type")) { + result = result.concat(prop); + } + + return result; + }, + [] + ); + + return astChildren; +} + +module.exports = { + getAstChildrenReflective: getAstChildrenReflective, +}; diff --git a/projects/xml-tools/packages/ast/lib/visit-ast.js b/projects/xml-tools/packages/ast/lib/visit-ast.js new file mode 100644 index 00000000..2ec6b0f7 --- /dev/null +++ b/projects/xml-tools/packages/ast/lib/visit-ast.js @@ -0,0 +1,61 @@ +const { forEach, isFunction } = require("lodash"); +const { getAstChildrenReflective } = require("./utils"); + +/** + * @param {XMLAstNode} node + * @param {XMLAstVisitor} visitor + * + * @returns {void} + */ +function accept(node, visitor) { + switch (node.type) { + case "XMLDocument": { + if (isFunction(visitor.visitXMLDocument)) { + visitor.visitXMLDocument(node); + } + break; + } + case "XMLProlog": { + if (isFunction(visitor.visitXMLProlog)) { + visitor.visitXMLProlog(node); + } + break; + } + case "XMLPrologAttribute": { + if (isFunction(visitor.visitXMLPrologAttribute)) { + visitor.visitXMLPrologAttribute(node); + } + break; + } + case "XMLElement": { + if (isFunction(visitor.visitXMLElement)) { + visitor.visitXMLElement(node); + } + break; + } + case "XMLAttribute": { + if (isFunction(visitor.visitXMLAttribute)) { + visitor.visitXMLAttribute(node); + } + break; + } + case "XMLTextContent": { + if (isFunction(visitor.visitXMLTextContent)) { + visitor.visitXMLTextContent(node); + } + break; + } + /* istanbul ignore next defensive programming */ + default: + throw Error("None Exhaustive Match"); + } + + const astChildren = getAstChildrenReflective(node); + forEach(astChildren, (childNode) => { + accept(childNode, visitor); + }); +} + +module.exports = { + accept: accept, +}; diff --git a/projects/xml-tools/packages/ast/package.json b/projects/xml-tools/packages/ast/package.json new file mode 100644 index 00000000..2843a1e5 --- /dev/null +++ b/projects/xml-tools/packages/ast/package.json @@ -0,0 +1,48 @@ +{ + "name": "@xml-tools/ast", + "version": "5.1.0", + "description": "XML Ast and Utilities", + "keywords": [ + "xml", + "ast" + ], + "main": "lib/api.js", + "repository": "https://github.com/sap/xml-tools/", + "license": "Apache-2.0", + "typings": "./api.d.ts", + "files": [ + "lib", + ".reuse", + "LICENSES", + "api.d.ts" + ], + "dependencies": { + "@xml-tools/common": "^0.2.0", + "@xml-tools/parser": "^1.0.11", + "lodash": "4.17.23" + }, + "devDependencies": { + "klaw-sync": "6.0.0" + }, + "scripts": { + "ci": "npm-run-all clean type-check coverage:*", + "clean": "rimraf ./coverage ./nyc_output", + "test": "mocha \"./test/**/*spec.js\"", + "coverage:run": "nyc mocha \"./test/**/*spec.js\"", + "coverage:check": "nyc check-coverage --lines 100 --branches 100 --statements 100 --functions 100", + "snapshots:update": "node ./scripts/update-snapshots.js", + "type-check": "tsc api.d.ts" + }, + "publishConfig": { + "access": "public" + }, + "nyc": { + "include": [ + "lib/**/*.js" + ], + "reporter": [ + "text", + "lcov" + ] + } +} diff --git a/projects/xml-tools/packages/ast/scripts/update-snapshots.js b/projects/xml-tools/packages/ast/scripts/update-snapshots.js new file mode 100644 index 00000000..81349ce9 --- /dev/null +++ b/projects/xml-tools/packages/ast/scripts/update-snapshots.js @@ -0,0 +1,32 @@ +"use strict"; +const { parse } = require("@xml-tools/parser"); +const { modifyAstForAssertions } = require("../test/utils"); +const klawSync = require("klaw-sync"); +const { resolve } = require("path"); +const { readFileSync, writeFileSync } = require("fs"); +const { format } = require("prettier"); + +const testsDir = resolve(__dirname, "..", "test"); +const { buildAst } = require("../"); + +const sampleFiles = klawSync(testsDir, { nodir: true }); +const xmlSampleFiles = sampleFiles.filter((fileDesc) => { + if (fileDesc.path.includes("node_modules")) { + return false; + } + return fileDesc.path.endsWith("input.xml"); +}); + +xmlSampleFiles.forEach((fileDesc) => { + const xmlInput = readFileSync(fileDesc.path, "utf8"); + const simpleNewLinesInput = xmlInput.replace(/\r\n/g, "\n"); + console.log(`Reading <${fileDesc.path}>`); + const { cst, tokenVector } = parse(simpleNewLinesInput); + const ast = buildAst(cst, tokenVector); + modifyAstForAssertions(ast); + const snapshotOutput = `module.exports = { ast : ${JSON.stringify(ast)}}`; + const formattedSnapshotOutput = format(snapshotOutput, { parser: "babel" }); + const outputFilePath = fileDesc.path.replace(/input.xml$/, "output.js"); + console.log(`writing <${outputFilePath}>`); + writeFileSync(outputFilePath, formattedSnapshotOutput); +}); diff --git a/projects/xml-tools/packages/ast/test/sample-test.js b/projects/xml-tools/packages/ast/test/sample-test.js new file mode 100644 index 00000000..a4ad9007 --- /dev/null +++ b/projects/xml-tools/packages/ast/test/sample-test.js @@ -0,0 +1,41 @@ +const { expect } = require("chai"); +const { readFileSync } = require("fs"); +const { resolve, basename } = require("path"); +const { parse } = require("@xml-tools/parser"); +const { partialRight } = require("lodash"); + +const { + modifyAstForAssertions, + assertParentPropsAreValid, +} = require("./utils"); +const { buildAst } = require("../"); + +function executeSampleTest(dirPath, assertNoErrors) { + it("Can build an AST for a valid XML", () => { + const inputPath = resolve(dirPath, "input.xml"); + const inputText = readFileSync(inputPath).toString("utf8"); + const simpleNewLinesInput = inputText.replace(/\r\n/g, "\n"); + const { cst, tokenVector, lexErrors, parseErrors } = parse( + simpleNewLinesInput + ); + if (assertNoErrors === true) { + expect(lexErrors).to.be.empty; + expect(parseErrors).to.be.empty; + } + const ast = buildAst(cst, tokenVector); + assertParentPropsAreValid(ast); + modifyAstForAssertions(ast); + const expectedOutput = require(resolve(dirPath, "output.js")).ast; + expect(ast).to.deep.eql(expectedOutput); + }); +} + +function testNameFromDir(dirPath) { + return basename(dirPath); +} + +module.exports = { + executeValidSampleTest: partialRight(executeSampleTest, true), + executeInValidSampleTest: partialRight(executeSampleTest, false), + testNameFromDir: testNameFromDir, +}; diff --git a/projects/xml-tools/packages/ast/test/snapshots/invalid/attributes-no-value-last/input.xml b/projects/xml-tools/packages/ast/test/snapshots/invalid/attributes-no-value-last/input.xml new file mode 100644 index 00000000..9b28ba6a --- /dev/null +++ b/projects/xml-tools/packages/ast/test/snapshots/invalid/attributes-no-value-last/input.xml @@ -0,0 +1 @@ + diff --git a/projects/xml-tools/packages/ast/test/snapshots/invalid/attributes-no-value-last/output.js b/projects/xml-tools/packages/ast/test/snapshots/invalid/attributes-no-value-last/output.js new file mode 100644 index 00000000..80abddc4 --- /dev/null +++ b/projects/xml-tools/packages/ast/test/snapshots/invalid/attributes-no-value-last/output.js @@ -0,0 +1,51 @@ +module.exports = { + ast: { + type: "XMLDocument", + rootElement: { + type: "XMLElement", + namespaces: {}, + name: "note", + attributes: [ + { + type: "XMLAttribute", + position: { startOffset: 6, endOffset: 16 }, + key: "attr1", + value: "666", + syntax: { + key: { image: "attr1", startOffset: 6, endOffset: 10 }, + value: { image: '"666"', startOffset: 12, endOffset: 16 }, + }, + }, + { + type: "XMLAttribute", + position: { startOffset: 18, endOffset: 28 }, + key: "attr2", + value: "333", + syntax: { + key: { image: "attr2", startOffset: 18, endOffset: 22 }, + value: { image: '"333"', startOffset: 24, endOffset: 28 }, + }, + }, + { + type: "XMLAttribute", + position: { startOffset: 30, endOffset: 35 }, + key: "attr3", + value: null, + syntax: { key: { image: "attr3", startOffset: 30, endOffset: 34 } }, + }, + ], + subElements: [], + textContents: [], + position: { startOffset: 0, endOffset: 44 }, + syntax: { + openName: { image: "note", startOffset: 1, endOffset: 4 }, + closeName: { image: "note", startOffset: 40, endOffset: 43 }, + isSelfClosing: false, + openBody: { startOffset: 0, endOffset: 37 }, + closeBody: { startOffset: 38, endOffset: 44 }, + attributesRange: { startOffset: 6, endOffset: 36 }, + }, + }, + position: { startOffset: 0, endOffset: 45 }, + }, +}; diff --git a/projects/xml-tools/packages/ast/test/snapshots/invalid/attributes-no-value-last/sample.spec.js b/projects/xml-tools/packages/ast/test/snapshots/invalid/attributes-no-value-last/sample.spec.js new file mode 100644 index 00000000..7e6e9a0b --- /dev/null +++ b/projects/xml-tools/packages/ast/test/snapshots/invalid/attributes-no-value-last/sample.spec.js @@ -0,0 +1,7 @@ +const { + executeInValidSampleTest, + testNameFromDir, +} = require("../../../sample-test"); +describe(`${testNameFromDir(__dirname)}`, () => { + executeInValidSampleTest(__dirname); +}); diff --git a/projects/xml-tools/packages/ast/test/snapshots/invalid/attributes-no-value-middle/input.xml b/projects/xml-tools/packages/ast/test/snapshots/invalid/attributes-no-value-middle/input.xml new file mode 100644 index 00000000..e8b46b55 --- /dev/null +++ b/projects/xml-tools/packages/ast/test/snapshots/invalid/attributes-no-value-middle/input.xml @@ -0,0 +1 @@ + diff --git a/projects/xml-tools/packages/ast/test/snapshots/invalid/attributes-no-value-middle/output.js b/projects/xml-tools/packages/ast/test/snapshots/invalid/attributes-no-value-middle/output.js new file mode 100644 index 00000000..20f6d585 --- /dev/null +++ b/projects/xml-tools/packages/ast/test/snapshots/invalid/attributes-no-value-middle/output.js @@ -0,0 +1,51 @@ +module.exports = { + ast: { + type: "XMLDocument", + rootElement: { + type: "XMLElement", + namespaces: {}, + name: "note", + attributes: [ + { + type: "XMLAttribute", + position: { startOffset: 6, endOffset: 16 }, + key: "attr1", + value: "666", + syntax: { + key: { image: "attr1", startOffset: 6, endOffset: 10 }, + value: { image: '"666"', startOffset: 12, endOffset: 16 }, + }, + }, + { + type: "XMLAttribute", + position: { startOffset: 18, endOffset: 23 }, + key: "attr2", + value: null, + syntax: { key: { image: "attr2", startOffset: 18, endOffset: 22 } }, + }, + { + type: "XMLAttribute", + position: { startOffset: 25, endOffset: 35 }, + key: "attr3", + value: "111", + syntax: { + key: { image: "attr3", startOffset: 25, endOffset: 29 }, + value: { image: '"111"', startOffset: 31, endOffset: 35 }, + }, + }, + ], + subElements: [], + textContents: [], + position: { startOffset: 0, endOffset: 44 }, + syntax: { + openName: { image: "note", startOffset: 1, endOffset: 4 }, + closeName: { image: "note", startOffset: 40, endOffset: 43 }, + isSelfClosing: false, + openBody: { startOffset: 0, endOffset: 37 }, + closeBody: { startOffset: 38, endOffset: 44 }, + attributesRange: { startOffset: 6, endOffset: 36 }, + }, + }, + position: { startOffset: 0, endOffset: 45 }, + }, +}; diff --git a/projects/xml-tools/packages/ast/test/snapshots/invalid/attributes-no-value-middle/sample.spec.js b/projects/xml-tools/packages/ast/test/snapshots/invalid/attributes-no-value-middle/sample.spec.js new file mode 100644 index 00000000..7e6e9a0b --- /dev/null +++ b/projects/xml-tools/packages/ast/test/snapshots/invalid/attributes-no-value-middle/sample.spec.js @@ -0,0 +1,7 @@ +const { + executeInValidSampleTest, + testNameFromDir, +} = require("../../../sample-test"); +describe(`${testNameFromDir(__dirname)}`, () => { + executeInValidSampleTest(__dirname); +}); diff --git a/projects/xml-tools/packages/ast/test/snapshots/invalid/attributes-no-value-no-eql-last/input.xml b/projects/xml-tools/packages/ast/test/snapshots/invalid/attributes-no-value-no-eql-last/input.xml new file mode 100644 index 00000000..48e91654 --- /dev/null +++ b/projects/xml-tools/packages/ast/test/snapshots/invalid/attributes-no-value-no-eql-last/input.xml @@ -0,0 +1 @@ + diff --git a/projects/xml-tools/packages/ast/test/snapshots/invalid/attributes-no-value-no-eql-last/output.js b/projects/xml-tools/packages/ast/test/snapshots/invalid/attributes-no-value-no-eql-last/output.js new file mode 100644 index 00000000..f17d2ec2 --- /dev/null +++ b/projects/xml-tools/packages/ast/test/snapshots/invalid/attributes-no-value-no-eql-last/output.js @@ -0,0 +1,51 @@ +module.exports = { + ast: { + type: "XMLDocument", + rootElement: { + type: "XMLElement", + namespaces: {}, + name: "note", + attributes: [ + { + type: "XMLAttribute", + position: { startOffset: 6, endOffset: 16 }, + key: "attr1", + value: "666", + syntax: { + key: { image: "attr1", startOffset: 6, endOffset: 10 }, + value: { image: '"666"', startOffset: 12, endOffset: 16 }, + }, + }, + { + type: "XMLAttribute", + position: { startOffset: 18, endOffset: 28 }, + key: "attr2", + value: "333", + syntax: { + key: { image: "attr2", startOffset: 18, endOffset: 22 }, + value: { image: '"333"', startOffset: 24, endOffset: 28 }, + }, + }, + { + type: "XMLAttribute", + position: { startOffset: 30, endOffset: 34 }, + key: "attr3", + value: null, + syntax: { key: { image: "attr3", startOffset: 30, endOffset: 34 } }, + }, + ], + subElements: [], + textContents: [], + position: { startOffset: 0, endOffset: 43 }, + syntax: { + openName: { image: "note", startOffset: 1, endOffset: 4 }, + closeName: { image: "note", startOffset: 39, endOffset: 42 }, + isSelfClosing: false, + openBody: { startOffset: 0, endOffset: 36 }, + closeBody: { startOffset: 37, endOffset: 43 }, + attributesRange: { startOffset: 6, endOffset: 35 }, + }, + }, + position: { startOffset: 0, endOffset: 44 }, + }, +}; diff --git a/projects/xml-tools/packages/ast/test/snapshots/invalid/attributes-no-value-no-eql-last/sample.spec.js b/projects/xml-tools/packages/ast/test/snapshots/invalid/attributes-no-value-no-eql-last/sample.spec.js new file mode 100644 index 00000000..7e6e9a0b --- /dev/null +++ b/projects/xml-tools/packages/ast/test/snapshots/invalid/attributes-no-value-no-eql-last/sample.spec.js @@ -0,0 +1,7 @@ +const { + executeInValidSampleTest, + testNameFromDir, +} = require("../../../sample-test"); +describe(`${testNameFromDir(__dirname)}`, () => { + executeInValidSampleTest(__dirname); +}); diff --git a/projects/xml-tools/packages/ast/test/snapshots/invalid/attributes-no-value-no-eql-middle/input.xml b/projects/xml-tools/packages/ast/test/snapshots/invalid/attributes-no-value-no-eql-middle/input.xml new file mode 100644 index 00000000..a1a9f34d --- /dev/null +++ b/projects/xml-tools/packages/ast/test/snapshots/invalid/attributes-no-value-no-eql-middle/input.xml @@ -0,0 +1 @@ + diff --git a/projects/xml-tools/packages/ast/test/snapshots/invalid/attributes-no-value-no-eql-middle/output.js b/projects/xml-tools/packages/ast/test/snapshots/invalid/attributes-no-value-no-eql-middle/output.js new file mode 100644 index 00000000..0ea3997c --- /dev/null +++ b/projects/xml-tools/packages/ast/test/snapshots/invalid/attributes-no-value-no-eql-middle/output.js @@ -0,0 +1,51 @@ +module.exports = { + ast: { + type: "XMLDocument", + rootElement: { + type: "XMLElement", + namespaces: {}, + name: "note", + attributes: [ + { + type: "XMLAttribute", + position: { startOffset: 6, endOffset: 16 }, + key: "attr1", + value: "666", + syntax: { + key: { image: "attr1", startOffset: 6, endOffset: 10 }, + value: { image: '"666"', startOffset: 12, endOffset: 16 }, + }, + }, + { + type: "XMLAttribute", + position: { startOffset: 18, endOffset: 22 }, + key: "attr2", + value: null, + syntax: { key: { image: "attr2", startOffset: 18, endOffset: 22 } }, + }, + { + type: "XMLAttribute", + position: { startOffset: 24, endOffset: 34 }, + key: "attr3", + value: "111", + syntax: { + key: { image: "attr3", startOffset: 24, endOffset: 28 }, + value: { image: '"111"', startOffset: 30, endOffset: 34 }, + }, + }, + ], + subElements: [], + textContents: [], + position: { startOffset: 0, endOffset: 43 }, + syntax: { + openName: { image: "note", startOffset: 1, endOffset: 4 }, + closeName: { image: "note", startOffset: 39, endOffset: 42 }, + isSelfClosing: false, + openBody: { startOffset: 0, endOffset: 36 }, + closeBody: { startOffset: 37, endOffset: 43 }, + attributesRange: { startOffset: 6, endOffset: 35 }, + }, + }, + position: { startOffset: 0, endOffset: 44 }, + }, +}; diff --git a/projects/xml-tools/packages/ast/test/snapshots/invalid/attributes-no-value-no-eql-middle/sample.spec.js b/projects/xml-tools/packages/ast/test/snapshots/invalid/attributes-no-value-no-eql-middle/sample.spec.js new file mode 100644 index 00000000..8d7c77f8 --- /dev/null +++ b/projects/xml-tools/packages/ast/test/snapshots/invalid/attributes-no-value-no-eql-middle/sample.spec.js @@ -0,0 +1,9 @@ +const { + executeInValidSampleTest, + testNameFromDir, +} = require("../../../sample-test"); + +// TODO: should we disable single token deletion in this case? +describe(`${testNameFromDir(__dirname)}`, () => { + executeInValidSampleTest(__dirname); +}); diff --git a/projects/xml-tools/packages/ast/test/snapshots/invalid/attributes-no-value-not-closed-nested/input.xml b/projects/xml-tools/packages/ast/test/snapshots/invalid/attributes-no-value-not-closed-nested/input.xml new file mode 100644 index 00000000..d8b56c02 --- /dev/null +++ b/projects/xml-tools/packages/ast/test/snapshots/invalid/attributes-no-value-not-closed-nested/input.xml @@ -0,0 +1,5 @@ + + hello world + + diff --git a/projects/xml-tools/packages/ast/test/snapshots/invalid/attributes-no-value-not-closed-nested/output.js b/projects/xml-tools/packages/ast/test/snapshots/invalid/attributes-no-value-not-closed-nested/output.js new file mode 100644 index 00000000..2c4b8874 --- /dev/null +++ b/projects/xml-tools/packages/ast/test/snapshots/invalid/attributes-no-value-not-closed-nested/output.js @@ -0,0 +1,130 @@ +module.exports = { + ast: { + type: "XMLDocument", + rootElement: { + type: "XMLElement", + namespaces: {}, + name: "top", + attributes: [], + subElements: [ + { + type: "XMLElement", + namespaces: {}, + name: "nested", + attributes: [ + { + type: "XMLAttribute", + position: { startOffset: 18, endOffset: 28 }, + key: "attr1", + value: "666", + syntax: { + key: { image: "attr1", startOffset: 18, endOffset: 22 }, + value: { image: '"666"', startOffset: 24, endOffset: 28 }, + }, + }, + { + type: "XMLAttribute", + position: { startOffset: 30, endOffset: 35 }, + key: "attr2", + value: null, + syntax: { + key: { image: "attr2", startOffset: 30, endOffset: 34 }, + }, + }, + ], + subElements: [], + textContents: [], + position: { startOffset: 10, endOffset: 35 }, + syntax: { + openName: { image: "nested", startOffset: 11, endOffset: 16 }, + guessedAttributesRange: { startOffset: 18, endOffset: 40 }, + }, + }, + { + type: "XMLElement", + namespaces: {}, + name: "nest2", + attributes: [], + subElements: [], + textContents: [ + { + type: "XMLTextContent", + position: { startOffset: 48, endOffset: 58 }, + text: "hello world", + }, + ], + position: { startOffset: 41, endOffset: 66 }, + syntax: { + openName: { image: "nest2", startOffset: 42, endOffset: 46 }, + closeName: { image: "nest2", startOffset: 61, endOffset: 65 }, + isSelfClosing: false, + openBody: { startOffset: 41, endOffset: 47 }, + closeBody: { startOffset: 59, endOffset: 66 }, + attributesRange: { startOffset: 48, endOffset: 46 }, + }, + }, + { + type: "XMLElement", + namespaces: {}, + name: "nested3", + attributes: [ + { + type: "XMLAttribute", + position: { startOffset: 81, endOffset: 89 }, + key: "attr5", + value: "1", + syntax: { + key: { image: "attr5", startOffset: 81, endOffset: 85 }, + value: { image: '"1"', startOffset: 87, endOffset: 89 }, + }, + }, + ], + subElements: [], + textContents: [ + { + type: "XMLTextContent", + position: { startOffset: 91, endOffset: 91 }, + text: " ", + }, + ], + position: { startOffset: 72, endOffset: 101 }, + syntax: { + openName: { image: "nested3", startOffset: 73, endOffset: 79 }, + closeName: { image: "nested3", startOffset: 94, endOffset: 100 }, + isSelfClosing: false, + openBody: { startOffset: 72, endOffset: 90 }, + closeBody: { startOffset: 92, endOffset: 101 }, + attributesRange: { startOffset: 81, endOffset: 89 }, + }, + }, + ], + textContents: [ + { + type: "XMLTextContent", + position: { startOffset: 5, endOffset: 9 }, + text: "\n ", + }, + { + type: "XMLTextContent", + position: { startOffset: 67, endOffset: 71 }, + text: "\n ", + }, + { + type: "XMLTextContent", + position: { startOffset: 102, endOffset: 102 }, + text: "\n", + }, + ], + position: { startOffset: 0, endOffset: 108 }, + syntax: { + openName: { image: "top", startOffset: 1, endOffset: 3 }, + closeName: { image: "top", startOffset: 105, endOffset: 107 }, + isSelfClosing: false, + openBody: { startOffset: 0, endOffset: 4 }, + closeBody: { startOffset: 103, endOffset: 108 }, + attributesRange: { startOffset: 5, endOffset: 3 }, + }, + }, + position: { startOffset: 0, endOffset: 109 }, + }, +}; diff --git a/projects/xml-tools/packages/ast/test/snapshots/invalid/attributes-no-value-not-closed-nested/sample.spec.js b/projects/xml-tools/packages/ast/test/snapshots/invalid/attributes-no-value-not-closed-nested/sample.spec.js new file mode 100644 index 00000000..7e6e9a0b --- /dev/null +++ b/projects/xml-tools/packages/ast/test/snapshots/invalid/attributes-no-value-not-closed-nested/sample.spec.js @@ -0,0 +1,7 @@ +const { + executeInValidSampleTest, + testNameFromDir, +} = require("../../../sample-test"); +describe(`${testNameFromDir(__dirname)}`, () => { + executeInValidSampleTest(__dirname); +}); diff --git a/projects/xml-tools/packages/ast/test/snapshots/invalid/attributes-no-value-not-closed/input.xml b/projects/xml-tools/packages/ast/test/snapshots/invalid/attributes-no-value-not-closed/input.xml new file mode 100644 index 00000000..caf98d53 --- /dev/null +++ b/projects/xml-tools/packages/ast/test/snapshots/invalid/attributes-no-value-not-closed/input.xml @@ -0,0 +1 @@ + { + executeInValidSampleTest(__dirname); +}); diff --git a/projects/xml-tools/packages/ast/test/snapshots/invalid/bad-namespace/input.xml b/projects/xml-tools/packages/ast/test/snapshots/invalid/bad-namespace/input.xml new file mode 100644 index 00000000..96a3921e --- /dev/null +++ b/projects/xml-tools/packages/ast/test/snapshots/invalid/bad-namespace/input.xml @@ -0,0 +1,6 @@ + + + Some Chair + 50 + 67 + diff --git a/projects/xml-tools/packages/ast/test/snapshots/invalid/bad-namespace/output.js b/projects/xml-tools/packages/ast/test/snapshots/invalid/bad-namespace/output.js new file mode 100644 index 00000000..9804b264 --- /dev/null +++ b/projects/xml-tools/packages/ast/test/snapshots/invalid/bad-namespace/output.js @@ -0,0 +1,126 @@ +module.exports = { + ast: { + type: "XMLDocument", + rootElement: { + type: "XMLElement", + namespaces: {}, + name: "table", + attributes: [ + { + type: "XMLAttribute", + position: { startOffset: 10, endOffset: 17 }, + key: "xmlns:f", + value: null, + syntax: { key: { image: "xmlns:f", startOffset: 10, endOffset: 16 } }, + }, + ], + subElements: [ + { + type: "XMLElement", + namespaces: {}, + name: "name", + attributes: [], + subElements: [], + textContents: [ + { + type: "XMLTextContent", + position: { startOffset: 32, endOffset: 41 }, + text: "Some Chair", + }, + ], + position: { startOffset: 24, endOffset: 50 }, + syntax: { + openName: { image: "f:name", startOffset: 25, endOffset: 30 }, + closeName: { image: "f:name", startOffset: 44, endOffset: 49 }, + isSelfClosing: false, + openBody: { startOffset: 24, endOffset: 31 }, + closeBody: { startOffset: 42, endOffset: 50 }, + attributesRange: { startOffset: 32, endOffset: 30 }, + }, + ns: "f", + }, + { + type: "XMLElement", + namespaces: {}, + name: "width", + attributes: [], + subElements: [], + textContents: [ + { + type: "XMLTextContent", + position: { startOffset: 65, endOffset: 66 }, + text: "50", + }, + ], + position: { startOffset: 56, endOffset: 76 }, + syntax: { + openName: { image: "f:width", startOffset: 57, endOffset: 63 }, + closeName: { image: "f:width", startOffset: 69, endOffset: 75 }, + isSelfClosing: false, + openBody: { startOffset: 56, endOffset: 64 }, + closeBody: { startOffset: 67, endOffset: 76 }, + attributesRange: { startOffset: 65, endOffset: 63 }, + }, + ns: "f", + }, + { + type: "XMLElement", + namespaces: {}, + name: "length", + attributes: [], + subElements: [], + textContents: [ + { + type: "XMLTextContent", + position: { startOffset: 92, endOffset: 93 }, + text: "67", + }, + ], + position: { startOffset: 82, endOffset: 104 }, + syntax: { + openName: { image: "f:length", startOffset: 83, endOffset: 90 }, + closeName: { image: "f:length", startOffset: 96, endOffset: 103 }, + isSelfClosing: false, + openBody: { startOffset: 82, endOffset: 91 }, + closeBody: { startOffset: 94, endOffset: 104 }, + attributesRange: { startOffset: 92, endOffset: 90 }, + }, + ns: "f", + }, + ], + textContents: [ + { + type: "XMLTextContent", + position: { startOffset: 19, endOffset: 23 }, + text: "\n ", + }, + { + type: "XMLTextContent", + position: { startOffset: 51, endOffset: 55 }, + text: "\n ", + }, + { + type: "XMLTextContent", + position: { startOffset: 77, endOffset: 81 }, + text: "\n ", + }, + { + type: "XMLTextContent", + position: { startOffset: 105, endOffset: 105 }, + text: "\n", + }, + ], + position: { startOffset: 1, endOffset: 115 }, + syntax: { + openName: { image: "f:table", startOffset: 2, endOffset: 8 }, + closeName: { image: "f:table", startOffset: 108, endOffset: 114 }, + isSelfClosing: false, + openBody: { startOffset: 1, endOffset: 18 }, + closeBody: { startOffset: 106, endOffset: 115 }, + attributesRange: { startOffset: 10, endOffset: 17 }, + }, + ns: "f", + }, + position: { startOffset: 0, endOffset: 116 }, + }, +}; diff --git a/projects/xml-tools/packages/ast/test/snapshots/invalid/bad-namespace/sample.spec.js b/projects/xml-tools/packages/ast/test/snapshots/invalid/bad-namespace/sample.spec.js new file mode 100644 index 00000000..7e6e9a0b --- /dev/null +++ b/projects/xml-tools/packages/ast/test/snapshots/invalid/bad-namespace/sample.spec.js @@ -0,0 +1,7 @@ +const { + executeInValidSampleTest, + testNameFromDir, +} = require("../../../sample-test"); +describe(`${testNameFromDir(__dirname)}`, () => { + executeInValidSampleTest(__dirname); +}); diff --git a/projects/xml-tools/packages/ast/test/snapshots/invalid/element-name-empty-last/input.xml b/projects/xml-tools/packages/ast/test/snapshots/invalid/element-name-empty-last/input.xml new file mode 100644 index 00000000..92dceccd --- /dev/null +++ b/projects/xml-tools/packages/ast/test/snapshots/invalid/element-name-empty-last/input.xml @@ -0,0 +1,5 @@ + + bobi + john + < + diff --git a/projects/xml-tools/packages/ast/test/snapshots/invalid/element-name-empty-last/output.js b/projects/xml-tools/packages/ast/test/snapshots/invalid/element-name-empty-last/output.js new file mode 100644 index 00000000..fef24c50 --- /dev/null +++ b/projects/xml-tools/packages/ast/test/snapshots/invalid/element-name-empty-last/output.js @@ -0,0 +1,96 @@ +module.exports = { + ast: { + type: "XMLDocument", + rootElement: { + type: "XMLElement", + namespaces: {}, + name: "note", + attributes: [], + subElements: [ + { + type: "XMLElement", + namespaces: {}, + name: "to", + attributes: [], + subElements: [], + textContents: [ + { + type: "XMLTextContent", + position: { startOffset: 15, endOffset: 18 }, + text: "bobi", + }, + ], + position: { startOffset: 11, endOffset: 23 }, + syntax: { + openName: { image: "to", startOffset: 12, endOffset: 13 }, + closeName: { image: "to", startOffset: 21, endOffset: 22 }, + isSelfClosing: false, + openBody: { startOffset: 11, endOffset: 14 }, + closeBody: { startOffset: 19, endOffset: 23 }, + attributesRange: { startOffset: 15, endOffset: 13 }, + }, + }, + { + type: "XMLElement", + namespaces: {}, + name: "from", + attributes: [], + subElements: [], + textContents: [ + { + type: "XMLTextContent", + position: { startOffset: 35, endOffset: 38 }, + text: "john", + }, + ], + position: { startOffset: 29, endOffset: 45 }, + syntax: { + openName: { image: "from", startOffset: 30, endOffset: 33 }, + closeName: { image: "from", startOffset: 41, endOffset: 44 }, + isSelfClosing: false, + openBody: { startOffset: 29, endOffset: 34 }, + closeBody: { startOffset: 39, endOffset: 45 }, + attributesRange: { startOffset: 35, endOffset: 33 }, + }, + }, + { + type: "XMLElement", + namespaces: {}, + name: null, + attributes: [], + subElements: [], + textContents: [], + position: { startOffset: 51, endOffset: 51 }, + syntax: {}, + }, + ], + textContents: [ + { + type: "XMLTextContent", + position: { startOffset: 6, endOffset: 10 }, + text: "\n ", + }, + { + type: "XMLTextContent", + position: { startOffset: 24, endOffset: 28 }, + text: "\n ", + }, + { + type: "XMLTextContent", + position: { startOffset: 46, endOffset: 50 }, + text: "\n ", + }, + ], + position: { startOffset: 0, endOffset: 59 }, + syntax: { + openName: { image: "note", startOffset: 1, endOffset: 4 }, + closeName: { image: "note", startOffset: 55, endOffset: 58 }, + isSelfClosing: false, + openBody: { startOffset: 0, endOffset: 5 }, + closeBody: { startOffset: 53, endOffset: 59 }, + attributesRange: { startOffset: 6, endOffset: 4 }, + }, + }, + position: { startOffset: 0, endOffset: 60 }, + }, +}; diff --git a/projects/xml-tools/packages/ast/test/snapshots/invalid/element-name-empty-last/sample.spec.js b/projects/xml-tools/packages/ast/test/snapshots/invalid/element-name-empty-last/sample.spec.js new file mode 100644 index 00000000..7e6e9a0b --- /dev/null +++ b/projects/xml-tools/packages/ast/test/snapshots/invalid/element-name-empty-last/sample.spec.js @@ -0,0 +1,7 @@ +const { + executeInValidSampleTest, + testNameFromDir, +} = require("../../../sample-test"); +describe(`${testNameFromDir(__dirname)}`, () => { + executeInValidSampleTest(__dirname); +}); diff --git a/projects/xml-tools/packages/ast/test/snapshots/invalid/element-name-empty-middle-comment/input.xml b/projects/xml-tools/packages/ast/test/snapshots/invalid/element-name-empty-middle-comment/input.xml new file mode 100644 index 00000000..925ade84 --- /dev/null +++ b/projects/xml-tools/packages/ast/test/snapshots/invalid/element-name-empty-middle-comment/input.xml @@ -0,0 +1,6 @@ + + bobi + < + + john + diff --git a/projects/xml-tools/packages/ast/test/snapshots/invalid/element-name-empty-middle-comment/output.js b/projects/xml-tools/packages/ast/test/snapshots/invalid/element-name-empty-middle-comment/output.js new file mode 100644 index 00000000..6c8431e3 --- /dev/null +++ b/projects/xml-tools/packages/ast/test/snapshots/invalid/element-name-empty-middle-comment/output.js @@ -0,0 +1,96 @@ +module.exports = { + ast: { + type: "XMLDocument", + rootElement: { + type: "XMLElement", + namespaces: {}, + name: "note", + attributes: [], + subElements: [ + { + type: "XMLElement", + namespaces: {}, + name: "to", + attributes: [], + subElements: [], + textContents: [ + { + type: "XMLTextContent", + position: { startOffset: 15, endOffset: 18 }, + text: "bobi", + }, + ], + position: { startOffset: 11, endOffset: 23 }, + syntax: { + openName: { image: "to", startOffset: 12, endOffset: 13 }, + closeName: { image: "to", startOffset: 21, endOffset: 22 }, + isSelfClosing: false, + openBody: { startOffset: 11, endOffset: 14 }, + closeBody: { startOffset: 19, endOffset: 23 }, + attributesRange: { startOffset: 15, endOffset: 13 }, + }, + }, + { + type: "XMLElement", + namespaces: {}, + name: null, + attributes: [], + subElements: [], + textContents: [], + position: { startOffset: 29, endOffset: 29 }, + syntax: {}, + }, + { + type: "XMLElement", + namespaces: {}, + name: "from", + attributes: [], + subElements: [], + textContents: [ + { + type: "XMLTextContent", + position: { startOffset: 70, endOffset: 73 }, + text: "john", + }, + ], + position: { startOffset: 64, endOffset: 80 }, + syntax: { + openName: { image: "from", startOffset: 65, endOffset: 68 }, + closeName: { image: "from", startOffset: 76, endOffset: 79 }, + isSelfClosing: false, + openBody: { startOffset: 64, endOffset: 69 }, + closeBody: { startOffset: 74, endOffset: 80 }, + attributesRange: { startOffset: 70, endOffset: 68 }, + }, + }, + ], + textContents: [ + { + type: "XMLTextContent", + position: { startOffset: 6, endOffset: 10 }, + text: "\n ", + }, + { + type: "XMLTextContent", + position: { startOffset: 24, endOffset: 28 }, + text: "\n ", + }, + { + type: "XMLTextContent", + position: { startOffset: 81, endOffset: 81 }, + text: "\n", + }, + ], + position: { startOffset: 0, endOffset: 88 }, + syntax: { + openName: { image: "note", startOffset: 1, endOffset: 4 }, + closeName: { image: "note", startOffset: 84, endOffset: 87 }, + isSelfClosing: false, + openBody: { startOffset: 0, endOffset: 5 }, + closeBody: { startOffset: 82, endOffset: 88 }, + attributesRange: { startOffset: 6, endOffset: 4 }, + }, + }, + position: { startOffset: 0, endOffset: 89 }, + }, +}; diff --git a/projects/xml-tools/packages/ast/test/snapshots/invalid/element-name-empty-middle-comment/sample.spec.js b/projects/xml-tools/packages/ast/test/snapshots/invalid/element-name-empty-middle-comment/sample.spec.js new file mode 100644 index 00000000..7e6e9a0b --- /dev/null +++ b/projects/xml-tools/packages/ast/test/snapshots/invalid/element-name-empty-middle-comment/sample.spec.js @@ -0,0 +1,7 @@ +const { + executeInValidSampleTest, + testNameFromDir, +} = require("../../../sample-test"); +describe(`${testNameFromDir(__dirname)}`, () => { + executeInValidSampleTest(__dirname); +}); diff --git a/projects/xml-tools/packages/ast/test/snapshots/invalid/element-name-empty-middle/input.xml b/projects/xml-tools/packages/ast/test/snapshots/invalid/element-name-empty-middle/input.xml new file mode 100644 index 00000000..9d4efe38 --- /dev/null +++ b/projects/xml-tools/packages/ast/test/snapshots/invalid/element-name-empty-middle/input.xml @@ -0,0 +1,5 @@ + + bobi + < + john + diff --git a/projects/xml-tools/packages/ast/test/snapshots/invalid/element-name-empty-middle/output.js b/projects/xml-tools/packages/ast/test/snapshots/invalid/element-name-empty-middle/output.js new file mode 100644 index 00000000..e43d6870 --- /dev/null +++ b/projects/xml-tools/packages/ast/test/snapshots/invalid/element-name-empty-middle/output.js @@ -0,0 +1,96 @@ +module.exports = { + ast: { + type: "XMLDocument", + rootElement: { + type: "XMLElement", + namespaces: {}, + name: "note", + attributes: [], + subElements: [ + { + type: "XMLElement", + namespaces: {}, + name: "to", + attributes: [], + subElements: [], + textContents: [ + { + type: "XMLTextContent", + position: { startOffset: 15, endOffset: 18 }, + text: "bobi", + }, + ], + position: { startOffset: 11, endOffset: 23 }, + syntax: { + openName: { image: "to", startOffset: 12, endOffset: 13 }, + closeName: { image: "to", startOffset: 21, endOffset: 22 }, + isSelfClosing: false, + openBody: { startOffset: 11, endOffset: 14 }, + closeBody: { startOffset: 19, endOffset: 23 }, + attributesRange: { startOffset: 15, endOffset: 13 }, + }, + }, + { + type: "XMLElement", + namespaces: {}, + name: null, + attributes: [], + subElements: [], + textContents: [], + position: { startOffset: 29, endOffset: 29 }, + syntax: {}, + }, + { + type: "XMLElement", + namespaces: {}, + name: "from", + attributes: [], + subElements: [], + textContents: [ + { + type: "XMLTextContent", + position: { startOffset: 41, endOffset: 44 }, + text: "john", + }, + ], + position: { startOffset: 35, endOffset: 51 }, + syntax: { + openName: { image: "from", startOffset: 36, endOffset: 39 }, + closeName: { image: "from", startOffset: 47, endOffset: 50 }, + isSelfClosing: false, + openBody: { startOffset: 35, endOffset: 40 }, + closeBody: { startOffset: 45, endOffset: 51 }, + attributesRange: { startOffset: 41, endOffset: 39 }, + }, + }, + ], + textContents: [ + { + type: "XMLTextContent", + position: { startOffset: 6, endOffset: 10 }, + text: "\n ", + }, + { + type: "XMLTextContent", + position: { startOffset: 24, endOffset: 28 }, + text: "\n ", + }, + { + type: "XMLTextContent", + position: { startOffset: 52, endOffset: 52 }, + text: "\n", + }, + ], + position: { startOffset: 0, endOffset: 59 }, + syntax: { + openName: { image: "note", startOffset: 1, endOffset: 4 }, + closeName: { image: "note", startOffset: 55, endOffset: 58 }, + isSelfClosing: false, + openBody: { startOffset: 0, endOffset: 5 }, + closeBody: { startOffset: 53, endOffset: 59 }, + attributesRange: { startOffset: 6, endOffset: 4 }, + }, + }, + position: { startOffset: 0, endOffset: 60 }, + }, +}; diff --git a/projects/xml-tools/packages/ast/test/snapshots/invalid/element-name-empty-middle/sample.spec.js b/projects/xml-tools/packages/ast/test/snapshots/invalid/element-name-empty-middle/sample.spec.js new file mode 100644 index 00000000..7e6e9a0b --- /dev/null +++ b/projects/xml-tools/packages/ast/test/snapshots/invalid/element-name-empty-middle/sample.spec.js @@ -0,0 +1,7 @@ +const { + executeInValidSampleTest, + testNameFromDir, +} = require("../../../sample-test"); +describe(`${testNameFromDir(__dirname)}`, () => { + executeInValidSampleTest(__dirname); +}); diff --git a/projects/xml-tools/packages/ast/test/snapshots/invalid/element-name-last/input.xml b/projects/xml-tools/packages/ast/test/snapshots/invalid/element-name-last/input.xml new file mode 100644 index 00000000..b0036ccc --- /dev/null +++ b/projects/xml-tools/packages/ast/test/snapshots/invalid/element-name-last/input.xml @@ -0,0 +1,5 @@ + + bobi + john + diff --git a/projects/xml-tools/packages/ast/test/snapshots/invalid/element-name-last/output.js b/projects/xml-tools/packages/ast/test/snapshots/invalid/element-name-last/output.js new file mode 100644 index 00000000..dd465c41 --- /dev/null +++ b/projects/xml-tools/packages/ast/test/snapshots/invalid/element-name-last/output.js @@ -0,0 +1,99 @@ +module.exports = { + ast: { + type: "XMLDocument", + rootElement: { + type: "XMLElement", + namespaces: {}, + name: "note", + attributes: [], + subElements: [ + { + type: "XMLElement", + namespaces: {}, + name: "to", + attributes: [], + subElements: [], + textContents: [ + { + type: "XMLTextContent", + position: { startOffset: 15, endOffset: 18 }, + text: "bobi", + }, + ], + position: { startOffset: 11, endOffset: 23 }, + syntax: { + openName: { image: "to", startOffset: 12, endOffset: 13 }, + closeName: { image: "to", startOffset: 21, endOffset: 22 }, + isSelfClosing: false, + openBody: { startOffset: 11, endOffset: 14 }, + closeBody: { startOffset: 19, endOffset: 23 }, + attributesRange: { startOffset: 15, endOffset: 13 }, + }, + }, + { + type: "XMLElement", + namespaces: {}, + name: "from", + attributes: [], + subElements: [], + textContents: [ + { + type: "XMLTextContent", + position: { startOffset: 35, endOffset: 38 }, + text: "john", + }, + ], + position: { startOffset: 29, endOffset: 45 }, + syntax: { + openName: { image: "from", startOffset: 30, endOffset: 33 }, + closeName: { image: "from", startOffset: 41, endOffset: 44 }, + isSelfClosing: false, + openBody: { startOffset: 29, endOffset: 34 }, + closeBody: { startOffset: 39, endOffset: 45 }, + attributesRange: { startOffset: 35, endOffset: 33 }, + }, + }, + { + type: "XMLElement", + namespaces: {}, + name: "ad", + attributes: [], + subElements: [], + textContents: [], + position: { startOffset: 51, endOffset: 53 }, + syntax: { + openName: { image: "ad", startOffset: 52, endOffset: 53 }, + guessedAttributesRange: { startOffset: 55, endOffset: 55 }, + }, + }, + ], + textContents: [ + { + type: "XMLTextContent", + position: { startOffset: 6, endOffset: 10 }, + text: "\n ", + }, + { + type: "XMLTextContent", + position: { startOffset: 24, endOffset: 28 }, + text: "\n ", + }, + { + type: "XMLTextContent", + position: { startOffset: 46, endOffset: 50 }, + text: "\n ", + }, + ], + position: { startOffset: 0, endOffset: 61 }, + syntax: { + openName: { image: "note", startOffset: 1, endOffset: 4 }, + closeName: { image: "note", startOffset: 57, endOffset: 60 }, + isSelfClosing: false, + openBody: { startOffset: 0, endOffset: 5 }, + closeBody: { startOffset: 55, endOffset: 61 }, + attributesRange: { startOffset: 6, endOffset: 4 }, + }, + }, + position: { startOffset: 0, endOffset: 62 }, + }, +}; diff --git a/projects/xml-tools/packages/ast/test/snapshots/invalid/element-name-last/sample.spec.js b/projects/xml-tools/packages/ast/test/snapshots/invalid/element-name-last/sample.spec.js new file mode 100644 index 00000000..7e6e9a0b --- /dev/null +++ b/projects/xml-tools/packages/ast/test/snapshots/invalid/element-name-last/sample.spec.js @@ -0,0 +1,7 @@ +const { + executeInValidSampleTest, + testNameFromDir, +} = require("../../../sample-test"); +describe(`${testNameFromDir(__dirname)}`, () => { + executeInValidSampleTest(__dirname); +}); diff --git a/projects/xml-tools/packages/ast/test/snapshots/invalid/element-name-middle/input.xml b/projects/xml-tools/packages/ast/test/snapshots/invalid/element-name-middle/input.xml new file mode 100644 index 00000000..1955ad5e --- /dev/null +++ b/projects/xml-tools/packages/ast/test/snapshots/invalid/element-name-middle/input.xml @@ -0,0 +1,6 @@ + + bobi + john + ny + diff --git a/projects/xml-tools/packages/ast/test/snapshots/invalid/element-name-middle/output.js b/projects/xml-tools/packages/ast/test/snapshots/invalid/element-name-middle/output.js new file mode 100644 index 00000000..38522076 --- /dev/null +++ b/projects/xml-tools/packages/ast/test/snapshots/invalid/element-name-middle/output.js @@ -0,0 +1,127 @@ +module.exports = { + ast: { + type: "XMLDocument", + rootElement: { + type: "XMLElement", + namespaces: {}, + name: "note", + attributes: [], + subElements: [ + { + type: "XMLElement", + namespaces: {}, + name: "to", + attributes: [], + subElements: [], + textContents: [ + { + type: "XMLTextContent", + position: { startOffset: 15, endOffset: 18 }, + text: "bobi", + }, + ], + position: { startOffset: 11, endOffset: 23 }, + syntax: { + openName: { image: "to", startOffset: 12, endOffset: 13 }, + closeName: { image: "to", startOffset: 21, endOffset: 22 }, + isSelfClosing: false, + openBody: { startOffset: 11, endOffset: 14 }, + closeBody: { startOffset: 19, endOffset: 23 }, + attributesRange: { startOffset: 15, endOffset: 13 }, + }, + }, + { + type: "XMLElement", + namespaces: {}, + name: "ad", + attributes: [], + subElements: [], + textContents: [], + position: { startOffset: 29, endOffset: 31 }, + syntax: { + openName: { image: "ad", startOffset: 30, endOffset: 31 }, + guessedAttributesRange: { startOffset: 33, endOffset: 36 }, + }, + }, + { + type: "XMLElement", + namespaces: {}, + name: "from", + attributes: [], + subElements: [], + textContents: [ + { + type: "XMLTextContent", + position: { startOffset: 43, endOffset: 46 }, + text: "john", + }, + ], + position: { startOffset: 37, endOffset: 53 }, + syntax: { + openName: { image: "from", startOffset: 38, endOffset: 41 }, + closeName: { image: "from", startOffset: 49, endOffset: 52 }, + isSelfClosing: false, + openBody: { startOffset: 37, endOffset: 42 }, + closeBody: { startOffset: 47, endOffset: 53 }, + attributesRange: { startOffset: 43, endOffset: 41 }, + }, + }, + { + type: "XMLElement", + namespaces: {}, + name: "city", + attributes: [], + subElements: [], + textContents: [ + { + type: "XMLTextContent", + position: { startOffset: 65, endOffset: 66 }, + text: "ny", + }, + ], + position: { startOffset: 59, endOffset: 73 }, + syntax: { + openName: { image: "city", startOffset: 60, endOffset: 63 }, + closeName: { image: "city", startOffset: 69, endOffset: 72 }, + isSelfClosing: false, + openBody: { startOffset: 59, endOffset: 64 }, + closeBody: { startOffset: 67, endOffset: 73 }, + attributesRange: { startOffset: 65, endOffset: 63 }, + }, + }, + ], + textContents: [ + { + type: "XMLTextContent", + position: { startOffset: 6, endOffset: 10 }, + text: "\n ", + }, + { + type: "XMLTextContent", + position: { startOffset: 24, endOffset: 28 }, + text: "\n ", + }, + { + type: "XMLTextContent", + position: { startOffset: 54, endOffset: 58 }, + text: "\n ", + }, + { + type: "XMLTextContent", + position: { startOffset: 74, endOffset: 74 }, + text: "\n", + }, + ], + position: { startOffset: 0, endOffset: 81 }, + syntax: { + openName: { image: "note", startOffset: 1, endOffset: 4 }, + closeName: { image: "note", startOffset: 77, endOffset: 80 }, + isSelfClosing: false, + openBody: { startOffset: 0, endOffset: 5 }, + closeBody: { startOffset: 75, endOffset: 81 }, + attributesRange: { startOffset: 6, endOffset: 4 }, + }, + }, + position: { startOffset: 0, endOffset: 82 }, + }, +}; diff --git a/projects/xml-tools/packages/ast/test/snapshots/invalid/element-name-middle/sample.spec.js b/projects/xml-tools/packages/ast/test/snapshots/invalid/element-name-middle/sample.spec.js new file mode 100644 index 00000000..7e6e9a0b --- /dev/null +++ b/projects/xml-tools/packages/ast/test/snapshots/invalid/element-name-middle/sample.spec.js @@ -0,0 +1,7 @@ +const { + executeInValidSampleTest, + testNameFromDir, +} = require("../../../sample-test"); +describe(`${testNameFromDir(__dirname)}`, () => { + executeInValidSampleTest(__dirname); +}); diff --git a/projects/xml-tools/packages/ast/test/snapshots/invalid/missing-top-level-element/input.xml b/projects/xml-tools/packages/ast/test/snapshots/invalid/missing-top-level-element/input.xml new file mode 100644 index 00000000..fb7c4aba --- /dev/null +++ b/projects/xml-tools/packages/ast/test/snapshots/invalid/missing-top-level-element/input.xml @@ -0,0 +1,2 @@ + + diff --git a/projects/xml-tools/packages/ast/test/snapshots/invalid/missing-top-level-element/output.js b/projects/xml-tools/packages/ast/test/snapshots/invalid/missing-top-level-element/output.js new file mode 100644 index 00000000..4a972ec2 --- /dev/null +++ b/projects/xml-tools/packages/ast/test/snapshots/invalid/missing-top-level-element/output.js @@ -0,0 +1,33 @@ +module.exports = { + ast: { + type: "XMLDocument", + rootElement: null, + position: { startOffset: 0, endOffset: 39 }, + prolog: { + type: "XMLProlog", + attributes: [ + { + type: "XMLPrologAttribute", + position: { startOffset: 6, endOffset: 18 }, + key: "version", + value: "1.0", + syntax: { + key: { image: "version", startOffset: 6, endOffset: 12 }, + value: { image: '"1.0"', startOffset: 14, endOffset: 18 }, + }, + }, + { + type: "XMLPrologAttribute", + position: { startOffset: 20, endOffset: 35 }, + key: "encoding", + value: "UTF-8", + syntax: { + key: { image: "encoding", startOffset: 20, endOffset: 27 }, + value: { image: '"UTF-8"', startOffset: 29, endOffset: 35 }, + }, + }, + ], + position: { startOffset: 0, endOffset: 37 }, + }, + }, +}; diff --git a/projects/xml-tools/packages/ast/test/snapshots/invalid/missing-top-level-element/sample.spec.js b/projects/xml-tools/packages/ast/test/snapshots/invalid/missing-top-level-element/sample.spec.js new file mode 100644 index 00000000..7e6e9a0b --- /dev/null +++ b/projects/xml-tools/packages/ast/test/snapshots/invalid/missing-top-level-element/sample.spec.js @@ -0,0 +1,7 @@ +const { + executeInValidSampleTest, + testNameFromDir, +} = require("../../../sample-test"); +describe(`${testNameFromDir(__dirname)}`, () => { + executeInValidSampleTest(__dirname); +}); diff --git a/projects/xml-tools/packages/ast/test/snapshots/valid/attributes/input.xml b/projects/xml-tools/packages/ast/test/snapshots/valid/attributes/input.xml new file mode 100644 index 00000000..8a9a5902 --- /dev/null +++ b/projects/xml-tools/packages/ast/test/snapshots/valid/attributes/input.xml @@ -0,0 +1 @@ + diff --git a/projects/xml-tools/packages/ast/test/snapshots/valid/attributes/output.js b/projects/xml-tools/packages/ast/test/snapshots/valid/attributes/output.js new file mode 100644 index 00000000..f538a5cc --- /dev/null +++ b/projects/xml-tools/packages/ast/test/snapshots/valid/attributes/output.js @@ -0,0 +1,44 @@ +module.exports = { + ast: { + type: "XMLDocument", + rootElement: { + type: "XMLElement", + namespaces: {}, + name: "note", + attributes: [ + { + type: "XMLAttribute", + position: { startOffset: 6, endOffset: 16 }, + key: "attr1", + value: "666", + syntax: { + key: { image: "attr1", startOffset: 6, endOffset: 10 }, + value: { image: '"666"', startOffset: 12, endOffset: 16 }, + }, + }, + { + type: "XMLAttribute", + position: { startOffset: 18, endOffset: 28 }, + key: "attr2", + value: "333", + syntax: { + key: { image: "attr2", startOffset: 18, endOffset: 22 }, + value: { image: '"333"', startOffset: 24, endOffset: 28 }, + }, + }, + ], + subElements: [], + textContents: [], + position: { startOffset: 0, endOffset: 36 }, + syntax: { + openName: { image: "note", startOffset: 1, endOffset: 4 }, + closeName: { image: "note", startOffset: 32, endOffset: 35 }, + isSelfClosing: false, + openBody: { startOffset: 0, endOffset: 29 }, + closeBody: { startOffset: 30, endOffset: 36 }, + attributesRange: { startOffset: 6, endOffset: 28 }, + }, + }, + position: { startOffset: 0, endOffset: 37 }, + }, +}; diff --git a/projects/xml-tools/packages/ast/test/snapshots/valid/attributes/sample.spec.js b/projects/xml-tools/packages/ast/test/snapshots/valid/attributes/sample.spec.js new file mode 100644 index 00000000..afb88de9 --- /dev/null +++ b/projects/xml-tools/packages/ast/test/snapshots/valid/attributes/sample.spec.js @@ -0,0 +1,7 @@ +const { + executeValidSampleTest, + testNameFromDir, +} = require("../../../sample-test"); +describe(`${testNameFromDir(__dirname)}`, () => { + executeValidSampleTest(__dirname); +}); diff --git a/projects/xml-tools/packages/ast/test/snapshots/valid/complex-contents/input.xml b/projects/xml-tools/packages/ast/test/snapshots/valid/complex-contents/input.xml new file mode 100644 index 00000000..8aca3184 --- /dev/null +++ b/projects/xml-tools/packages/ast/test/snapshots/valid/complex-contents/input.xml @@ -0,0 +1,2 @@ + + diff --git a/projects/xml-tools/packages/ast/test/snapshots/valid/complex-contents/output.js b/projects/xml-tools/packages/ast/test/snapshots/valid/complex-contents/output.js new file mode 100644 index 00000000..937979f7 --- /dev/null +++ b/projects/xml-tools/packages/ast/test/snapshots/valid/complex-contents/output.js @@ -0,0 +1,28 @@ +module.exports = { + ast: { + type: "XMLDocument", + rootElement: { + type: "XMLElement", + namespaces: {}, + name: "note", + attributes: [], + subElements: [], + textContents: [], + position: { startOffset: 9, endOffset: 21 }, + syntax: { + openName: { image: "note", startOffset: 10, endOffset: 13 }, + closeName: { image: "note", startOffset: 17, endOffset: 20 }, + isSelfClosing: false, + openBody: { startOffset: 9, endOffset: 14 }, + closeBody: { startOffset: 15, endOffset: 21 }, + attributesRange: { startOffset: 15, endOffset: 13 }, + }, + }, + position: { startOffset: 0, endOffset: 22 }, + prolog: { + type: "XMLProlog", + attributes: [], + position: { startOffset: 0, endOffset: 7 }, + }, + }, +}; diff --git a/projects/xml-tools/packages/ast/test/snapshots/valid/complex-contents/sample.spec.js b/projects/xml-tools/packages/ast/test/snapshots/valid/complex-contents/sample.spec.js new file mode 100644 index 00000000..afb88de9 --- /dev/null +++ b/projects/xml-tools/packages/ast/test/snapshots/valid/complex-contents/sample.spec.js @@ -0,0 +1,7 @@ +const { + executeValidSampleTest, + testNameFromDir, +} = require("../../../sample-test"); +describe(`${testNameFromDir(__dirname)}`, () => { + executeValidSampleTest(__dirname); +}); diff --git a/projects/xml-tools/packages/ast/test/snapshots/valid/elements-short-form/input.xml b/projects/xml-tools/packages/ast/test/snapshots/valid/elements-short-form/input.xml new file mode 100644 index 00000000..f9d7191f --- /dev/null +++ b/projects/xml-tools/packages/ast/test/snapshots/valid/elements-short-form/input.xml @@ -0,0 +1,4 @@ + + Bill + + diff --git a/projects/xml-tools/packages/ast/test/snapshots/valid/elements-short-form/output.js b/projects/xml-tools/packages/ast/test/snapshots/valid/elements-short-form/output.js new file mode 100644 index 00000000..307fb63c --- /dev/null +++ b/projects/xml-tools/packages/ast/test/snapshots/valid/elements-short-form/output.js @@ -0,0 +1,89 @@ +module.exports = { + ast: { + type: "XMLDocument", + rootElement: { + type: "XMLElement", + namespaces: {}, + name: "note", + attributes: [], + subElements: [ + { + type: "XMLElement", + namespaces: {}, + name: "to", + attributes: [], + subElements: [], + textContents: [ + { + type: "XMLTextContent", + position: { startOffset: 15, endOffset: 18 }, + text: "Bill", + }, + ], + position: { startOffset: 11, endOffset: 23 }, + syntax: { + openName: { image: "to", startOffset: 12, endOffset: 13 }, + closeName: { image: "to", startOffset: 21, endOffset: 22 }, + isSelfClosing: false, + openBody: { startOffset: 11, endOffset: 14 }, + closeBody: { startOffset: 19, endOffset: 23 }, + attributesRange: { startOffset: 15, endOffset: 13 }, + }, + }, + { + type: "XMLElement", + namespaces: {}, + name: "from", + attributes: [ + { + type: "XMLAttribute", + position: { startOffset: 35, endOffset: 44 }, + key: "name", + value: "tim", + syntax: { + key: { image: "name", startOffset: 35, endOffset: 38 }, + value: { image: '"tim"', startOffset: 40, endOffset: 44 }, + }, + }, + ], + subElements: [], + textContents: [], + position: { startOffset: 29, endOffset: 46 }, + syntax: { + openName: { image: "from", startOffset: 30, endOffset: 33 }, + isSelfClosing: true, + openBody: { startOffset: 29, endOffset: 46 }, + attributesRange: { startOffset: 35, endOffset: 44 }, + }, + }, + ], + textContents: [ + { + type: "XMLTextContent", + position: { startOffset: 6, endOffset: 10 }, + text: "\n ", + }, + { + type: "XMLTextContent", + position: { startOffset: 24, endOffset: 28 }, + text: "\n ", + }, + { + type: "XMLTextContent", + position: { startOffset: 47, endOffset: 47 }, + text: "\n", + }, + ], + position: { startOffset: 0, endOffset: 54 }, + syntax: { + openName: { image: "note", startOffset: 1, endOffset: 4 }, + closeName: { image: "note", startOffset: 50, endOffset: 53 }, + isSelfClosing: false, + openBody: { startOffset: 0, endOffset: 5 }, + closeBody: { startOffset: 48, endOffset: 54 }, + attributesRange: { startOffset: 6, endOffset: 4 }, + }, + }, + position: { startOffset: 0, endOffset: 55 }, + }, +}; diff --git a/projects/xml-tools/packages/ast/test/snapshots/valid/elements-short-form/sample.spec.js b/projects/xml-tools/packages/ast/test/snapshots/valid/elements-short-form/sample.spec.js new file mode 100644 index 00000000..afb88de9 --- /dev/null +++ b/projects/xml-tools/packages/ast/test/snapshots/valid/elements-short-form/sample.spec.js @@ -0,0 +1,7 @@ +const { + executeValidSampleTest, + testNameFromDir, +} = require("../../../sample-test"); +describe(`${testNameFromDir(__dirname)}`, () => { + executeValidSampleTest(__dirname); +}); diff --git a/projects/xml-tools/packages/ast/test/snapshots/valid/elements/input.xml b/projects/xml-tools/packages/ast/test/snapshots/valid/elements/input.xml new file mode 100644 index 00000000..77dfac07 --- /dev/null +++ b/projects/xml-tools/packages/ast/test/snapshots/valid/elements/input.xml @@ -0,0 +1,4 @@ + + Bill + Tim + diff --git a/projects/xml-tools/packages/ast/test/snapshots/valid/elements/output.js b/projects/xml-tools/packages/ast/test/snapshots/valid/elements/output.js new file mode 100644 index 00000000..d1789754 --- /dev/null +++ b/projects/xml-tools/packages/ast/test/snapshots/valid/elements/output.js @@ -0,0 +1,86 @@ +module.exports = { + ast: { + type: "XMLDocument", + rootElement: { + type: "XMLElement", + namespaces: {}, + name: "note", + attributes: [], + subElements: [ + { + type: "XMLElement", + namespaces: {}, + name: "to", + attributes: [], + subElements: [], + textContents: [ + { + type: "XMLTextContent", + position: { startOffset: 15, endOffset: 18 }, + text: "Bill", + }, + ], + position: { startOffset: 11, endOffset: 23 }, + syntax: { + openName: { image: "to", startOffset: 12, endOffset: 13 }, + closeName: { image: "to", startOffset: 21, endOffset: 22 }, + isSelfClosing: false, + openBody: { startOffset: 11, endOffset: 14 }, + closeBody: { startOffset: 19, endOffset: 23 }, + attributesRange: { startOffset: 15, endOffset: 13 }, + }, + }, + { + type: "XMLElement", + namespaces: {}, + name: "from", + attributes: [], + subElements: [], + textContents: [ + { + type: "XMLTextContent", + position: { startOffset: 35, endOffset: 37 }, + text: "Tim", + }, + ], + position: { startOffset: 29, endOffset: 44 }, + syntax: { + openName: { image: "from", startOffset: 30, endOffset: 33 }, + closeName: { image: "from", startOffset: 40, endOffset: 43 }, + isSelfClosing: false, + openBody: { startOffset: 29, endOffset: 34 }, + closeBody: { startOffset: 38, endOffset: 44 }, + attributesRange: { startOffset: 35, endOffset: 33 }, + }, + }, + ], + textContents: [ + { + type: "XMLTextContent", + position: { startOffset: 6, endOffset: 10 }, + text: "\n ", + }, + { + type: "XMLTextContent", + position: { startOffset: 24, endOffset: 28 }, + text: "\n ", + }, + { + type: "XMLTextContent", + position: { startOffset: 45, endOffset: 45 }, + text: "\n", + }, + ], + position: { startOffset: 0, endOffset: 52 }, + syntax: { + openName: { image: "note", startOffset: 1, endOffset: 4 }, + closeName: { image: "note", startOffset: 48, endOffset: 51 }, + isSelfClosing: false, + openBody: { startOffset: 0, endOffset: 5 }, + closeBody: { startOffset: 46, endOffset: 52 }, + attributesRange: { startOffset: 6, endOffset: 4 }, + }, + }, + position: { startOffset: 0, endOffset: 53 }, + }, +}; diff --git a/projects/xml-tools/packages/ast/test/snapshots/valid/elements/sample.spec.js b/projects/xml-tools/packages/ast/test/snapshots/valid/elements/sample.spec.js new file mode 100644 index 00000000..afb88de9 --- /dev/null +++ b/projects/xml-tools/packages/ast/test/snapshots/valid/elements/sample.spec.js @@ -0,0 +1,7 @@ +const { + executeValidSampleTest, + testNameFromDir, +} = require("../../../sample-test"); +describe(`${testNameFromDir(__dirname)}`, () => { + executeValidSampleTest(__dirname); +}); diff --git a/projects/xml-tools/packages/ast/test/snapshots/valid/namespaces/input.xml b/projects/xml-tools/packages/ast/test/snapshots/valid/namespaces/input.xml new file mode 100644 index 00000000..c54a334c --- /dev/null +++ b/projects/xml-tools/packages/ast/test/snapshots/valid/namespaces/input.xml @@ -0,0 +1,7 @@ + + + Some Chair + 50 + 67 + Awsome Chair + diff --git a/projects/xml-tools/packages/ast/test/snapshots/valid/namespaces/output.js b/projects/xml-tools/packages/ast/test/snapshots/valid/namespaces/output.js new file mode 100644 index 00000000..20c33f89 --- /dev/null +++ b/projects/xml-tools/packages/ast/test/snapshots/valid/namespaces/output.js @@ -0,0 +1,198 @@ +module.exports = { + ast: { + type: "XMLDocument", + rootElement: { + type: "XMLElement", + namespaces: { + f: "https://blah.com/furniture", + "::DEFAULT": "https://blah.com/default", + }, + name: "table", + attributes: [ + { + type: "XMLAttribute", + position: { startOffset: 10, endOffset: 45 }, + key: "xmlns:f", + value: "https://blah.com/furniture", + syntax: { + key: { image: "xmlns:f", startOffset: 10, endOffset: 16 }, + value: { + image: '"https://blah.com/furniture"', + startOffset: 18, + endOffset: 45, + }, + }, + }, + { + type: "XMLAttribute", + position: { startOffset: 47, endOffset: 78 }, + key: "xmlns", + value: "https://blah.com/default", + syntax: { + key: { image: "xmlns", startOffset: 47, endOffset: 51 }, + value: { + image: '"https://blah.com/default"', + startOffset: 53, + endOffset: 78, + }, + }, + }, + ], + subElements: [ + { + type: "XMLElement", + namespaces: { + f: "https://blah.com/furniture", + "::DEFAULT": "https://blah.com/default", + }, + name: "name", + attributes: [], + subElements: [], + textContents: [ + { + type: "XMLTextContent", + position: { startOffset: 93, endOffset: 102 }, + text: "Some Chair", + }, + ], + position: { startOffset: 85, endOffset: 111 }, + syntax: { + openName: { image: "f:name", startOffset: 86, endOffset: 91 }, + closeName: { image: "f:name", startOffset: 105, endOffset: 110 }, + isSelfClosing: false, + openBody: { startOffset: 85, endOffset: 92 }, + closeBody: { startOffset: 103, endOffset: 111 }, + attributesRange: { startOffset: 93, endOffset: 91 }, + }, + ns: "f", + }, + { + type: "XMLElement", + namespaces: { + f: "https://blah.com/furniture", + "::DEFAULT": "https://blah.com/default", + }, + name: "width", + attributes: [], + subElements: [], + textContents: [ + { + type: "XMLTextContent", + position: { startOffset: 126, endOffset: 127 }, + text: "50", + }, + ], + position: { startOffset: 117, endOffset: 137 }, + syntax: { + openName: { image: "f:width", startOffset: 118, endOffset: 124 }, + closeName: { image: "f:width", startOffset: 130, endOffset: 136 }, + isSelfClosing: false, + openBody: { startOffset: 117, endOffset: 125 }, + closeBody: { startOffset: 128, endOffset: 137 }, + attributesRange: { startOffset: 126, endOffset: 124 }, + }, + ns: "f", + }, + { + type: "XMLElement", + namespaces: { + f: "https://blah.com/furniture", + "::DEFAULT": "https://blah.com/default", + }, + name: "length", + attributes: [], + subElements: [], + textContents: [ + { + type: "XMLTextContent", + position: { startOffset: 153, endOffset: 154 }, + text: "67", + }, + ], + position: { startOffset: 143, endOffset: 165 }, + syntax: { + openName: { image: "f:length", startOffset: 144, endOffset: 151 }, + closeName: { image: "f:length", startOffset: 157, endOffset: 164 }, + isSelfClosing: false, + openBody: { startOffset: 143, endOffset: 152 }, + closeBody: { startOffset: 155, endOffset: 165 }, + attributesRange: { startOffset: 153, endOffset: 151 }, + }, + ns: "f", + }, + { + type: "XMLElement", + namespaces: { + f: "https://blah.com/furniture", + "::DEFAULT": "https://blah.com/default", + }, + name: "description", + attributes: [], + subElements: [], + textContents: [ + { + type: "XMLTextContent", + position: { startOffset: 184, endOffset: 195 }, + text: "Awsome Chair", + }, + ], + position: { startOffset: 171, endOffset: 209 }, + syntax: { + openName: { + image: "description", + startOffset: 172, + endOffset: 182, + }, + closeName: { + image: "description", + startOffset: 198, + endOffset: 208, + }, + isSelfClosing: false, + openBody: { startOffset: 171, endOffset: 183 }, + closeBody: { startOffset: 196, endOffset: 209 }, + attributesRange: { startOffset: 184, endOffset: 182 }, + }, + }, + ], + textContents: [ + { + type: "XMLTextContent", + position: { startOffset: 80, endOffset: 84 }, + text: "\n ", + }, + { + type: "XMLTextContent", + position: { startOffset: 112, endOffset: 116 }, + text: "\n ", + }, + { + type: "XMLTextContent", + position: { startOffset: 138, endOffset: 142 }, + text: "\n ", + }, + { + type: "XMLTextContent", + position: { startOffset: 166, endOffset: 170 }, + text: "\n ", + }, + { + type: "XMLTextContent", + position: { startOffset: 210, endOffset: 210 }, + text: "\n", + }, + ], + position: { startOffset: 1, endOffset: 220 }, + syntax: { + openName: { image: "f:table", startOffset: 2, endOffset: 8 }, + closeName: { image: "f:table", startOffset: 213, endOffset: 219 }, + isSelfClosing: false, + openBody: { startOffset: 1, endOffset: 79 }, + closeBody: { startOffset: 211, endOffset: 220 }, + attributesRange: { startOffset: 10, endOffset: 78 }, + }, + ns: "f", + }, + position: { startOffset: 0, endOffset: 221 }, + }, +}; diff --git a/projects/xml-tools/packages/ast/test/snapshots/valid/namespaces/sample.spec.js b/projects/xml-tools/packages/ast/test/snapshots/valid/namespaces/sample.spec.js new file mode 100644 index 00000000..afb88de9 --- /dev/null +++ b/projects/xml-tools/packages/ast/test/snapshots/valid/namespaces/sample.spec.js @@ -0,0 +1,7 @@ +const { + executeValidSampleTest, + testNameFromDir, +} = require("../../../sample-test"); +describe(`${testNameFromDir(__dirname)}`, () => { + executeValidSampleTest(__dirname); +}); diff --git a/projects/xml-tools/packages/ast/test/snapshots/valid/prolog-no-attriibs/input.xml b/projects/xml-tools/packages/ast/test/snapshots/valid/prolog-no-attriibs/input.xml new file mode 100644 index 00000000..6d98cea1 --- /dev/null +++ b/projects/xml-tools/packages/ast/test/snapshots/valid/prolog-no-attriibs/input.xml @@ -0,0 +1,7 @@ + + + Bill + hello + Tim + world + diff --git a/projects/xml-tools/packages/ast/test/snapshots/valid/prolog-no-attriibs/output.js b/projects/xml-tools/packages/ast/test/snapshots/valid/prolog-no-attriibs/output.js new file mode 100644 index 00000000..9984db53 --- /dev/null +++ b/projects/xml-tools/packages/ast/test/snapshots/valid/prolog-no-attriibs/output.js @@ -0,0 +1,101 @@ +module.exports = { + ast: { + type: "XMLDocument", + rootElement: { + type: "XMLElement", + namespaces: {}, + name: "note", + attributes: [], + subElements: [ + { + type: "XMLElement", + namespaces: {}, + name: "to", + attributes: [], + subElements: [], + textContents: [ + { + type: "XMLTextContent", + position: { startOffset: 39, endOffset: 42 }, + text: "Bill", + }, + ], + position: { startOffset: 35, endOffset: 47 }, + syntax: { + openName: { image: "to", startOffset: 36, endOffset: 37 }, + closeName: { image: "to", startOffset: 45, endOffset: 46 }, + isSelfClosing: false, + openBody: { startOffset: 35, endOffset: 38 }, + closeBody: { startOffset: 43, endOffset: 47 }, + attributesRange: { startOffset: 39, endOffset: 37 }, + }, + }, + { + type: "XMLElement", + namespaces: {}, + name: "from", + attributes: [], + subElements: [], + textContents: [ + { + type: "XMLTextContent", + position: { startOffset: 69, endOffset: 71 }, + text: "Tim", + }, + ], + position: { startOffset: 63, endOffset: 78 }, + syntax: { + openName: { image: "from", startOffset: 64, endOffset: 67 }, + closeName: { image: "from", startOffset: 74, endOffset: 77 }, + isSelfClosing: false, + openBody: { startOffset: 63, endOffset: 68 }, + closeBody: { startOffset: 72, endOffset: 78 }, + attributesRange: { startOffset: 69, endOffset: 67 }, + }, + }, + ], + textContents: [ + { + type: "XMLTextContent", + position: { startOffset: 6, endOffset: 10 }, + text: "\n ", + }, + { + type: "XMLTextContent", + position: { startOffset: 30, endOffset: 34 }, + text: "\n ", + }, + { + type: "XMLTextContent", + position: { startOffset: 48, endOffset: 52 }, + text: "\n ", + }, + { + type: "XMLTextContent", + position: { startOffset: 53, endOffset: 62 }, + text: "hello\n ", + }, + { + type: "XMLTextContent", + position: { startOffset: 79, endOffset: 83 }, + text: "\n ", + }, + { + type: "XMLTextContent", + position: { startOffset: 84, endOffset: 89 }, + text: "world\n", + }, + ], + position: { startOffset: 0, endOffset: 96 }, + syntax: { + openName: { image: "note", startOffset: 1, endOffset: 4 }, + closeName: { image: "note", startOffset: 92, endOffset: 95 }, + isSelfClosing: false, + openBody: { startOffset: 0, endOffset: 5 }, + closeBody: { startOffset: 90, endOffset: 96 }, + attributesRange: { startOffset: 6, endOffset: 4 }, + }, + }, + position: { startOffset: 0, endOffset: 97 }, + }, +}; diff --git a/projects/xml-tools/packages/ast/test/snapshots/valid/prolog-no-attriibs/sample.spec.js b/projects/xml-tools/packages/ast/test/snapshots/valid/prolog-no-attriibs/sample.spec.js new file mode 100644 index 00000000..afb88de9 --- /dev/null +++ b/projects/xml-tools/packages/ast/test/snapshots/valid/prolog-no-attriibs/sample.spec.js @@ -0,0 +1,7 @@ +const { + executeValidSampleTest, + testNameFromDir, +} = require("../../../sample-test"); +describe(`${testNameFromDir(__dirname)}`, () => { + executeValidSampleTest(__dirname); +}); diff --git a/projects/xml-tools/packages/ast/test/snapshots/valid/prolog/input.xml b/projects/xml-tools/packages/ast/test/snapshots/valid/prolog/input.xml new file mode 100644 index 00000000..30f696f1 --- /dev/null +++ b/projects/xml-tools/packages/ast/test/snapshots/valid/prolog/input.xml @@ -0,0 +1,2 @@ + + diff --git a/projects/xml-tools/packages/ast/test/snapshots/valid/prolog/output.js b/projects/xml-tools/packages/ast/test/snapshots/valid/prolog/output.js new file mode 100644 index 00000000..7fe19cf7 --- /dev/null +++ b/projects/xml-tools/packages/ast/test/snapshots/valid/prolog/output.js @@ -0,0 +1,49 @@ +module.exports = { + ast: { + type: "XMLDocument", + rootElement: { + type: "XMLElement", + namespaces: {}, + name: "note", + attributes: [], + subElements: [], + textContents: [], + position: { startOffset: 39, endOffset: 51 }, + syntax: { + openName: { image: "note", startOffset: 40, endOffset: 43 }, + closeName: { image: "note", startOffset: 47, endOffset: 50 }, + isSelfClosing: false, + openBody: { startOffset: 39, endOffset: 44 }, + closeBody: { startOffset: 45, endOffset: 51 }, + attributesRange: { startOffset: 45, endOffset: 43 }, + }, + }, + position: { startOffset: 0, endOffset: 52 }, + prolog: { + type: "XMLProlog", + attributes: [ + { + type: "XMLPrologAttribute", + position: { startOffset: 6, endOffset: 18 }, + key: "version", + value: "1.0", + syntax: { + key: { image: "version", startOffset: 6, endOffset: 12 }, + value: { image: '"1.0"', startOffset: 14, endOffset: 18 }, + }, + }, + { + type: "XMLPrologAttribute", + position: { startOffset: 20, endOffset: 35 }, + key: "encoding", + value: "UTF-8", + syntax: { + key: { image: "encoding", startOffset: 20, endOffset: 27 }, + value: { image: '"UTF-8"', startOffset: 29, endOffset: 35 }, + }, + }, + ], + position: { startOffset: 0, endOffset: 37 }, + }, + }, +}; diff --git a/projects/xml-tools/packages/ast/test/snapshots/valid/prolog/sample.spec.js b/projects/xml-tools/packages/ast/test/snapshots/valid/prolog/sample.spec.js new file mode 100644 index 00000000..afb88de9 --- /dev/null +++ b/projects/xml-tools/packages/ast/test/snapshots/valid/prolog/sample.spec.js @@ -0,0 +1,7 @@ +const { + executeValidSampleTest, + testNameFromDir, +} = require("../../../sample-test"); +describe(`${testNameFromDir(__dirname)}`, () => { + executeValidSampleTest(__dirname); +}); diff --git a/projects/xml-tools/packages/ast/test/utils.js b/projects/xml-tools/packages/ast/test/utils.js new file mode 100644 index 00000000..914b9dae --- /dev/null +++ b/projects/xml-tools/packages/ast/test/utils.js @@ -0,0 +1,112 @@ +const { forEach } = require("lodash"); +const { expect } = require("chai"); + +const { accept } = require("../"); + +function modifyAstForAssertions(astNode) { + // Avoid comparing cyclic structures + accept(astNode, parentRemoverVisitor); + // Reduce verbosity of assertions + accept(astNode, positionReducerVisitor); +} + +/** + * @type {XMLAstVisitor} + */ +const parentRemoverVisitor = { + /* eslint-disable-next-line no-unused-vars -- consistent signatures in visitor methods even if they are empty placeholders */ + visitXMLDocument: (node) => { + // top level XML Doc does not have a parent... + }, + visitXMLProlog: (node) => { + delete node.parent; + }, + visitXMLPrologAttribute: (node) => { + delete node.parent; + }, + visitXMLElement: (node) => { + delete node.parent; + }, + visitXMLAttribute: (node) => { + delete node.parent; + }, + visitXMLTextContent: (node) => { + delete node.parent; + }, +}; + +/** + * @type {XMLAstVisitor} + */ +const positionReducerVisitor = { + visitXMLDocument: (node) => { + reduceNodePoseInfo(node); + }, + visitXMLProlog: (node) => { + reduceNodePoseInfo(node); + }, + visitXMLPrologAttribute: (node) => { + reduceNodePoseInfo(node); + }, + visitXMLElement: (node) => { + reduceNodePoseInfo(node); + }, + visitXMLAttribute: (node) => { + reduceNodePoseInfo(node); + }, + visitXMLTextContent: (node) => { + reduceNodePoseInfo(node); + }, +}; + +function reduceNodePoseInfo(node) { + reducePositionInfo(node.position); + + forEach(node.syntax, (tok) => { + reducePositionInfo(tok); + }); +} + +function reducePositionInfo(pos) { + delete pos.startColumn; + delete pos.endColumn; + delete pos.startLine; + delete pos.endLine; +} + +function assertParentPropsAreValid(astNode) { + accept(astNode, parentPropsValidatorVisitor); +} + +/** + * @type {XMLAstVisitor} + */ +const parentPropsValidatorVisitor = { + /* eslint-disable-next-line no-unused-vars -- consistent signatures in visitor methods even if they are empty placeholders */ + visitXMLDocument: (node) => { + // top level XML Doc does not have a parent... + }, + visitXMLProlog: (node) => { + expect(node.parent.prolog).to.eql(node); + }, + visitXMLElement: (node) => { + const parent = node.parent; + if (parent.type === "XMLDocument") { + expect(parent.rootElement).to.eql(node); + } else { + expect(parent.subElements).to.include(node); + } + }, + visitXMLAttribute: (node) => { + expect(node.parent.attributes).to.include(node); + }, + visitXMLTextContent: (node) => { + expect(node.parent.textContents).to.include(node); + }, +}; + +module.exports = { + modifyAstForAssertions: modifyAstForAssertions, + positionReducerVisitor: positionReducerVisitor, + assertParentPropsAreValid: assertParentPropsAreValid, +}; diff --git a/projects/xml-tools/packages/ast/test/visitor/visitor-spec.js b/projects/xml-tools/packages/ast/test/visitor/visitor-spec.js new file mode 100644 index 00000000..7430a0a3 --- /dev/null +++ b/projects/xml-tools/packages/ast/test/visitor/visitor-spec.js @@ -0,0 +1,137 @@ +const { expect } = require("chai"); +const { parse } = require("@xml-tools/parser"); +const { buildAst, accept } = require("../../"); + +describe("The XML AST Visitor", () => { + it("can traverse AST Elements", () => { + const inputText = ` + Bill + Tim + + `; + + let visitedCounter = 0; + const visitor = { + visitXMLElement: function (node) { + expect(["to", "from", "note"]).to.include(node.name); + visitedCounter++; + }, + }; + + const astNode = getAst(inputText); + accept(astNode, visitor); + expect(visitedCounter).to.eql(3); + }); + + it("can traverse AST TextContents", () => { + const inputText = ` + Bill + Tim + + `; + + let visitedCounter = 0; + const visitor = { + visitXMLTextContent: function (node) { + // ignore whitespace TextNodes + if (/^\s+$/.test(node.text) === false) { + expect(["Bill", "Tim", "note"]).to.include(node.text); + visitedCounter++; + } + }, + }; + + const astNode = getAst(inputText); + accept(astNode, visitor); + expect(visitedCounter).to.eql(2); + }); + + it("can traverse AST Attributes", () => { + const inputText = ` + + Bill + Tim + + `; + + let visitedCounter = 0; + const visitor = { + visitXMLAttribute: function (node) { + expect(["foo", "bar"]).to.include(node.key); + visitedCounter++; + }, + }; + + const astNode = getAst(inputText); + accept(astNode, visitor); + expect(visitedCounter).to.eql(2); + }); + + it("can traverse AST Prolog Attributes", () => { + const inputText = ` + + Bill + Tim + + `; + + let visitedCounter = 0; + const visitor = { + visitXMLPrologAttribute: function (node) { + expect(["version", "encoding"]).to.include(node.key); + visitedCounter++; + }, + }; + + const astNode = getAst(inputText); + accept(astNode, visitor); + expect(visitedCounter).to.eql(2); + }); + + it("can traverse AST Prolog", () => { + const inputText = ` + + + `; + + let visitedCounter = 0; + const visitor = { + visitXMLProlog: function (node) { + expect(node.attributes).to.have.lengthOf(2); + expect(node.attributes[0].key).to.eql("version"); + expect(node.attributes[1].key).to.eql("encoding"); + visitedCounter++; + }, + }; + + const astNode = getAst(inputText); + accept(astNode, visitor); + expect(visitedCounter).to.eql(1); + }); + + it("can traverse XML Document AST", () => { + const inputText = ` + foo + bar + + `; + + let visitedCounter = 0; + const visitor = { + visitXMLDocument: function (node) { + expect(node.rootElement.name).to.eql("note"); + visitedCounter++; + }, + }; + + const astNode = getAst(inputText); + accept(astNode, visitor); + expect(visitedCounter).to.eql(1); + }); +}); + +function getAst(text) { + const { cst, tokenVector } = parse(text); + const ast = buildAst(cst, tokenVector); + return ast; +} diff --git a/projects/xml-tools/packages/common/.npmignore b/projects/xml-tools/packages/common/.npmignore new file mode 100644 index 00000000..ae12335e --- /dev/null +++ b/projects/xml-tools/packages/common/.npmignore @@ -0,0 +1,10 @@ +# NYC +.nyc_output +nyc.config.js +coverage + +# tests +test + +# contributing +CONTRIBUTING.md \ No newline at end of file diff --git a/projects/xml-tools/packages/common/CHANGELOG.md b/projects/xml-tools/packages/common/CHANGELOG.md new file mode 100644 index 00000000..f58154de --- /dev/null +++ b/projects/xml-tools/packages/common/CHANGELOG.md @@ -0,0 +1,68 @@ +# Change Log + +All notable changes to this project will be documented in this file. +See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + +# [0.2.0](https://github.com/sap/xml-tools/compare/@xml-tools/common@0.1.6...@xml-tools/common@0.2.0) (2026-02-11) + +### Features + +- update lodash dependency to version 4.17.23 ([#461](https://github.com/sap/xml-tools/issues/461)) ([23a319d](https://github.com/sap/xml-tools/commit/23a319d6ed42a4a3885ec5748eb127147a8960bc)) + +## [0.1.6](https://github.com/sap/xml-tools/compare/@xml-tools/common@0.1.5...@xml-tools/common@0.1.6) (2021-06-03) + +**Note:** Version bump only for package @xml-tools/common + +## [0.1.5](https://github.com/sap/xml-tools/compare/@xml-tools/common@0.1.4...@xml-tools/common@0.1.5) (2021-02-09) + +**Note:** Version bump only for package @xml-tools/common + +## [0.1.4](https://github.com/sap/xml-tools/compare/@xml-tools/common@0.1.3...@xml-tools/common@0.1.4) (2021-01-20) + +### Bug Fixes + +- missing lib folder in npm packages ([ffed3c2](https://github.com/sap/xml-tools/commit/ffed3c2c54c70aea8b9ded0d53786382bc190cc5)) + +## [0.1.3](https://github.com/sap/xml-tools/compare/@xml-tools/common@0.1.2...@xml-tools/common@0.1.3) (2021-01-20) + +**Note:** Version bump only for package @xml-tools/common + +## [0.1.2](https://github.com/sap/xml-tools/compare/@xml-tools/common@0.1.1...@xml-tools/common@0.1.2) (2020-06-29) + +**Note:** Version bump only for package @xml-tools/common + +## [0.1.1](https://github.com/sap/xml-tools/compare/@xml-tools/common@0.1.0...@xml-tools/common@0.1.1) (2020-06-18) + +**Note:** Version bump only for package @xml-tools/common + +# [0.1.0](https://github.com/sap/xml-tools/compare/@xml-tools/common@0.0.6...@xml-tools/common@0.1.0) (2020-05-17) + +### Features + +- **common:** add utility functions for xmlns attributes ([#173](https://github.com/sap/xml-tools/issues/173)) ([20d6c09](https://github.com/sap/xml-tools/commit/20d6c09)) + +## [0.0.6](https://github.com/sap/xml-tools/compare/@xml-tools/common@0.0.5...@xml-tools/common@0.0.6) (2020-04-02) + +**Note:** Version bump only for package @xml-tools/common + +## [0.0.5](https://github.com/sap/xml-tools/compare/@xml-tools/common@0.0.4...@xml-tools/common@0.0.5) (2020-02-19) + +### Bug Fixes + +- add npmignore file to each package ([5bbf209](https://github.com/sap/xml-tools/commit/5bbf209)) + +## [0.0.4](https://github.com/sap/xml-tools/compare/@xml-tools/common@0.0.3...@xml-tools/common@0.0.4) (2020-02-11) + +**Note:** Version bump only for package @xml-tools/common + +## [0.0.3](https://github.com/sap/xml-tools/compare/@xml-tools/common@0.0.2...@xml-tools/common@0.0.3) (2020-01-28) + +**Note:** Version bump only for package @xml-tools/common + +## [0.0.2](https://github.com/sap/xml-tools/compare/@xml-tools/common@0.0.1...@xml-tools/common@0.0.2) (2020-01-20) + +**Note:** Version bump only for package @xml-tools/common + +## 0.0.1 (2019-12-05) + +**Note:** Version bump only for package @xml-tools/common diff --git a/projects/xml-tools/packages/common/CONTRIBUTING.md b/projects/xml-tools/packages/common/CONTRIBUTING.md new file mode 100644 index 00000000..84eb9447 --- /dev/null +++ b/projects/xml-tools/packages/common/CONTRIBUTING.md @@ -0,0 +1,4 @@ +# Contribution Guide + +This package does not have any unique development flows. +Please see the top level [Contribution Guide](../../CONTRIBUTING.md). diff --git a/projects/xml-tools/packages/common/README.md b/projects/xml-tools/packages/common/README.md new file mode 100644 index 00000000..8c7f4950 --- /dev/null +++ b/projects/xml-tools/packages/common/README.md @@ -0,0 +1,35 @@ +[![npm (scoped)](https://img.shields.io/npm/v/@xml-tools/common.svg)](https://www.npmjs.com/package/@xml-tools/common) + +# @xml-tools/common + +Shared Utilities for XML-Tools + +## Installation + +With npm: + +- `npm install @xml-tools/Common` + +With Yarn + +- `yarn add @xml-tools/Common` + +## Usage + +Please see the [TypeScript Definitions](./api.d.ts) for full API details. + +Simply import/require the @xml-tools/common package and use the utilities +as defined in the APIs above. + +```javascript +const xmlToolsCommon = require("@xml-tools/common"); +xmlToolsCommon.findNextTextualToken(/*... */); +``` + +## Support + +Please open [issues](https://github.com/SAP/xml-tols/issues) on github. + +## Contributing + +See [CONTRIBUTING.md](./CONTRIBUTING.md). diff --git a/projects/xml-tools/packages/common/api.d.ts b/projects/xml-tools/packages/common/api.d.ts new file mode 100644 index 00000000..882fec45 --- /dev/null +++ b/projects/xml-tools/packages/common/api.d.ts @@ -0,0 +1,33 @@ +import { IToken } from "chevrotain"; + +/** + * Finds the next none "SEA_WS" Token after a specific offset. + * + * @param tokenVector - The TokenVector returned from @xml-tools/parser parse method. + * @param prevTokEndOffset - This must be the endOffset of an existing token. + */ +export function findNextTextualToken( + tokenVector: IToken[], + prevTokEndOffset: number +): IToken | null; + +/** + * Check if an xml attribute key is an xmlns key. + * Attribute keys which are xmlns keys: "xmlns", "xmlns:core". + * Attribute keys which are not xmlns keys: "myattr", "xmlns:with:extra:colon". + * "xmlns:" is considered an xmlns key if opts.includeEmptyPrefix is true. + * + * @param opts.key - the attribute key + * @param opts.includeEmptyPrefix - should true be returned when there is no prefix (key === "xmlns:") + */ +export function isXMLNamespaceKey(opts: { + key: string; + includeEmptyPrefix: boolean; +}): boolean; + +/** + * Get the attribute name, without its "xmlns:" prefix, from an xmlns attribute key. + * If the attribute key is not an xmlns key, undefined is returned. + * @param key - the attribute key + */ +export function getXMLNamespaceKeyPrefix(key: string): string | undefined; diff --git a/projects/xml-tools/packages/common/lib/api.js b/projects/xml-tools/packages/common/lib/api.js new file mode 100644 index 00000000..d35bce6b --- /dev/null +++ b/projects/xml-tools/packages/common/lib/api.js @@ -0,0 +1,11 @@ +const { findNextTextualToken } = require("./find-next-textual-token"); +const { + isXMLNamespaceKey, + getXMLNamespaceKeyPrefix, +} = require("./xml-ns-key.js"); + +module.exports = { + findNextTextualToken: findNextTextualToken, + isXMLNamespaceKey: isXMLNamespaceKey, + getXMLNamespaceKeyPrefix: getXMLNamespaceKeyPrefix, +}; diff --git a/projects/xml-tools/packages/common/lib/find-next-textual-token.js b/projects/xml-tools/packages/common/lib/find-next-textual-token.js new file mode 100644 index 00000000..8752c759 --- /dev/null +++ b/projects/xml-tools/packages/common/lib/find-next-textual-token.js @@ -0,0 +1,33 @@ +const { findIndex } = require("lodash"); + +function findNextTextualToken(tokenVector, prevTokenEndOffset) { + // The TokenVector is sorted, so we could use a BinarySearch to optimize performance + const prevTokenIdx = findIndex( + tokenVector, + (tok) => tok.endOffset === prevTokenEndOffset + ); + let nextTokenIdx = prevTokenIdx; + let found = false; + while (found === false) { + nextTokenIdx++; + const nextPossibleToken = tokenVector[nextTokenIdx]; + // No Next textualToken + if (nextPossibleToken === undefined) { + return null; + } + /* istanbul ignore next + * I don't think this scenario can be created, however defensive coding never killed anyone... + * Basically SEA_WS can only only appear in "OUTSIDE" mode, and we need a CLOSE/SLASH_CLOSE to get back to outside + * mode, however if we had those this function would never have been called... + */ + if (nextPossibleToken.tokenType.name === "SEA_WS") { + // skip pure WS tokens as they do not contain any actual text + } else { + return nextPossibleToken; + } + } +} + +module.exports = { + findNextTextualToken: findNextTextualToken, +}; diff --git a/projects/xml-tools/packages/common/lib/xml-ns-key.js b/projects/xml-tools/packages/common/lib/xml-ns-key.js new file mode 100644 index 00000000..cc3f960d --- /dev/null +++ b/projects/xml-tools/packages/common/lib/xml-ns-key.js @@ -0,0 +1,54 @@ +// The xml parser takes care of validating the attribute name. +// If the user started the attribute name with "xmlns:" we can assume that +// they meant for it to be an xml namespace attribute. +// xmlns attributes explicitly can't contain ":" after the "xmlns:" part. +const namespaceRegex = /^xmlns(?:(?[^:]*))?$/; + +/** + * See comment in api.d.ts. + * + * @param {string} key + * @param {boolean} includeEmptyPrefix + * @returns {boolean} + */ +function isXMLNamespaceKey({ key, includeEmptyPrefix }) { + if (typeof key !== "string") { + return false; + } + const matchArr = key.match(namespaceRegex); + + // No match - this is not an xmlns key + if (matchArr === null) { + return false; + } + + return !!( + includeEmptyPrefix === true || + // "xmlns" case + !matchArr.groups.prefixWithColon || + // "xmlns:" case + matchArr.groups.prefix + ); +} + +/** + * See comment in api.d.ts. + * + * @param {string} key + * @returns {string|undefined} + */ +function getXMLNamespaceKeyPrefix(key) { + if (typeof key !== "string") { + return undefined; + } + const matchArr = key.match(namespaceRegex); + if (matchArr === null) { + return undefined; + } + return (matchArr.groups && matchArr.groups.prefix) || ""; +} + +module.exports = { + isXMLNamespaceKey: isXMLNamespaceKey, + getXMLNamespaceKeyPrefix: getXMLNamespaceKeyPrefix, +}; diff --git a/projects/xml-tools/packages/common/package.json b/projects/xml-tools/packages/common/package.json new file mode 100644 index 00000000..56cab68d --- /dev/null +++ b/projects/xml-tools/packages/common/package.json @@ -0,0 +1,45 @@ +{ + "name": "@xml-tools/common", + "version": "0.2.0", + "description": "XML-Tools Common Utilities", + "keywords": [ + "xml", + "validation" + ], + "main": "lib/api.js", + "repository": "https://github.com/sap/xml-tools/", + "license": "Apache-2.0", + "typings": "./api.d.ts", + "files": [ + "lib", + ".reuse", + "LICENSES", + "api.d.ts" + ], + "dependencies": { + "lodash": "4.17.23" + }, + "devDependencies": { + "@xml-tools/parser": "^1.0.11" + }, + "scripts": { + "ci": "npm-run-all clean type-check coverage:*", + "clean": "rimraf ./coverage ./nyc_output", + "test": "mocha \"./test/**/*spec.js\"", + "coverage:run": "nyc mocha \"./test/**/*spec.js\"", + "coverage:check": "nyc check-coverage --lines 100 --branches 100 --statements 100 --functions 100", + "type-check": "tsc api.d.ts" + }, + "publishConfig": { + "access": "public" + }, + "nyc": { + "include": [ + "lib/**/*.js" + ], + "reporter": [ + "text", + "lcov" + ] + } +} diff --git a/projects/xml-tools/packages/common/test/find-next-textual-token-spec.js b/projects/xml-tools/packages/common/test/find-next-textual-token-spec.js new file mode 100644 index 00000000..40a415af --- /dev/null +++ b/projects/xml-tools/packages/common/test/find-next-textual-token-spec.js @@ -0,0 +1,31 @@ +const { expect } = require("chai"); +const { parse } = require("@xml-tools/parser"); +const { findNextTextualToken } = require("../"); + +describe("The XML-Tools Common Utils", () => { + context("findNextTextualToken", () => { + it("can find the next textual token in a valid XML", () => { + const xmlText = ` + Bill + Tim + `; + + const { tokenVector } = parse(xmlText); + // "28" is the endOffset of `Bill` + const nextTextualToken = findNextTextualToken(tokenVector, 27); + expect(nextTextualToken.image).to.eql("<"); + expect(nextTextualToken.startOffset).to.eql(37); + }); + + it("will return null when there is no following textual token", () => { + const xmlText = ` + Bill + `; + + const { tokenVector } = parse(xmlText); + // "28" is the endOffset of `Bill` + const nextTextualToken = findNextTextualToken(tokenVector, 27); + expect(nextTextualToken).to.be.null; + }); + }); +}); diff --git a/projects/xml-tools/packages/common/test/xml-ns-key-spec.js b/projects/xml-tools/packages/common/test/xml-ns-key-spec.js new file mode 100644 index 00000000..a7fefcef --- /dev/null +++ b/projects/xml-tools/packages/common/test/xml-ns-key-spec.js @@ -0,0 +1,106 @@ +const { expect } = require("chai"); +const { isXMLNamespaceKey, getXMLNamespaceKeyPrefix } = require("../"); + +describe("The XML-Tools Common Utils", () => { + context("isXMLNamespaceKey", () => { + it("will return true for attribute name starting with xmlns:", () => { + expect(isXMLNamespaceKey({ key: "xmlns:a", includeEmptyPrefix: true })).to + .be.true; + expect(isXMLNamespaceKey({ key: "xmlns:a", includeEmptyPrefix: false })) + .to.be.true; + }); + + it("will return true for attribute name starting with xmlns: that contains dots", () => { + expect( + isXMLNamespaceKey({ key: "xmlns:a.b.c", includeEmptyPrefix: true }) + ).to.be.true; + expect( + isXMLNamespaceKey({ key: "xmlns:a.b.c", includeEmptyPrefix: false }) + ).to.be.true; + }); + + it("will return true for the default namespace attribute", () => { + expect(isXMLNamespaceKey({ key: "xmlns", includeEmptyPrefix: true })).to + .be.true; + expect(isXMLNamespaceKey({ key: "xmlns", includeEmptyPrefix: false })).to + .be.true; + }); + + it("will return true for xmlns attribute without a name when includeEmptyPrefix is true", () => { + expect(isXMLNamespaceKey({ key: "xmlns:", includeEmptyPrefix: true })).to + .be.true; + }); + + it("will return false for xmlns attribute without a name when includeEmptyPrefix is false", () => { + expect(isXMLNamespaceKey({ key: "xmlns:", includeEmptyPrefix: false })).to + .be.false; + }); + + it("will return false for the non-xmlns attribute", () => { + expect(isXMLNamespaceKey({ key: "abc", includeEmptyPrefix: true })).to.be + .false; + expect(isXMLNamespaceKey({ key: "abc", includeEmptyPrefix: false })).to.be + .false; + }); + + it("will return false for non-xmlns attribute that starts with xmlns", () => { + expect(isXMLNamespaceKey({ key: "xmlnst", includeEmptyPrefix: true })).to + .be.false; + expect(isXMLNamespaceKey({ key: "xmlnst", includeEmptyPrefix: false })).to + .be.false; + }); + + it("will return false for attribute name starting with xmlns: that contains additional colons", () => { + expect( + isXMLNamespaceKey({ key: "xmlns:a.b:c", includeEmptyPrefix: true }) + ).to.be.false; + expect( + isXMLNamespaceKey({ key: "xmlns:a.b:c", includeEmptyPrefix: false }) + ).to.be.false; + }); + + it("will return false for undefined", () => { + expect(isXMLNamespaceKey({ key: undefined, includeEmptyPrefix: true })).to + .be.false; + expect(isXMLNamespaceKey({ key: undefined, includeEmptyPrefix: false })) + .to.be.false; + }); + + it("will return false for null", () => { + expect(isXMLNamespaceKey({ key: null, includeEmptyPrefix: true })).to.be + .false; + expect(isXMLNamespaceKey({ key: null, includeEmptyPrefix: false })).to.be + .false; + }); + }); + + context("getNamespaceKeyPrefix", () => { + it("will return the name without xmlns prefix for xmlns attribute with name", () => { + expect(getXMLNamespaceKeyPrefix("xmlns:abc")).to.eql("abc"); + }); + + it("will return the name without xmlns prefix for xmlns attribute with name that contains dots", () => { + expect(getXMLNamespaceKeyPrefix("xmlns:abc.efg")).to.eql("abc.efg"); + }); + + it("will return empty string when prefix is empty", () => { + expect(getXMLNamespaceKeyPrefix("xmlns:")).to.be.empty; + }); + + it("will return undefined when key does not start with xmlns", () => { + expect(getXMLNamespaceKeyPrefix("abc")).to.be.undefined; + }); + + it("will return undefined when key starts with xmlns but is not an xmlns key", () => { + expect(getXMLNamespaceKeyPrefix("xmlns*")).to.be.undefined; + }); + + it("will return undefined for undefined", () => { + expect(getXMLNamespaceKeyPrefix(undefined)).to.be.undefined; + }); + + it("will return undefined for null", () => { + expect(getXMLNamespaceKeyPrefix(null)).to.be.undefined; + }); + }); +}); diff --git a/projects/xml-tools/packages/constraints/.npmignore b/projects/xml-tools/packages/constraints/.npmignore new file mode 100644 index 00000000..ae12335e --- /dev/null +++ b/projects/xml-tools/packages/constraints/.npmignore @@ -0,0 +1,10 @@ +# NYC +.nyc_output +nyc.config.js +coverage + +# tests +test + +# contributing +CONTRIBUTING.md \ No newline at end of file diff --git a/projects/xml-tools/packages/constraints/CHANGELOG.md b/projects/xml-tools/packages/constraints/CHANGELOG.md new file mode 100644 index 00000000..863b3e60 --- /dev/null +++ b/projects/xml-tools/packages/constraints/CHANGELOG.md @@ -0,0 +1,20 @@ +# Change Log + +All notable changes to this project will be documented in this file. +See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + +# [1.2.0](https://github.com/sap/xml-tools/compare/@xml-tools/constraints@1.1.1...@xml-tools/constraints@1.2.0) (2026-02-11) + +### Features + +- update lodash dependency to version 4.17.23 ([#461](https://github.com/sap/xml-tools/issues/461)) ([23a319d](https://github.com/sap/xml-tools/commit/23a319d6ed42a4a3885ec5748eb127147a8960bc)) + +## [1.1.1](https://github.com/sap/xml-tools/compare/@xml-tools/constraints@1.1.0...@xml-tools/constraints@1.1.1) (2021-06-03) + +**Note:** Version bump only for package @xml-tools/constraints + +# 1.1.0 (2021-02-09) + +### Features + +- well formed constraints validations ([#293](https://github.com/sap/xml-tools/issues/293)) ([8a9d535](https://github.com/sap/xml-tools/commit/8a9d535e95a52b9dfc1068d2e203b09bd08e1066)), closes [#13](https://github.com/sap/xml-tools/issues/13) [#14](https://github.com/sap/xml-tools/issues/14) diff --git a/projects/xml-tools/packages/constraints/CONTRIBUTING.md b/projects/xml-tools/packages/constraints/CONTRIBUTING.md new file mode 100644 index 00000000..84eb9447 --- /dev/null +++ b/projects/xml-tools/packages/constraints/CONTRIBUTING.md @@ -0,0 +1,4 @@ +# Contribution Guide + +This package does not have any unique development flows. +Please see the top level [Contribution Guide](../../CONTRIBUTING.md). diff --git a/projects/xml-tools/packages/constraints/README.md b/projects/xml-tools/packages/constraints/README.md new file mode 100644 index 00000000..9a9c2c11 --- /dev/null +++ b/projects/xml-tools/packages/constraints/README.md @@ -0,0 +1,51 @@ +[![npm (scoped)](https://img.shields.io/npm/v/@xml-tools/constraints.svg)](https://www.npmjs.com/package/@xml-tools/constraints) + +# @xml-tools/constraints + +Validations for XML constraints. + +The following constraints are currently implemented: + +- Uniqueness of attribute keys inside the same element. +- Element opening tag name must be identical to the element closing tag name. + +## Installation + +With npm: + +- `npm install @xml-tools/constraints` + +With Yarn + +- `yarn add @xml-tools/constraints` + +## Usage + +Please see the [TypeScript Definitions](./api.d.ts) for full API details. + +A simple usage example: + +```javascript +const { parse } = require("@xml-tools/parser"); +const { buildAst } = require("@xml-tools/ast"); +const { checkConstraints } = require("@xml-tools/constraints"); + +const xmlText = ` + + Bill + Tim + `; + +const { cst, tokenVector } = parse(xmlText); +const document = buildAst(cst, tokenVector); +const validationIssues = checkConstraints(document); +console.log(validationIssues[0].msg); // --> 'opening tag: "note" must match closing tag: "note-typo" +``` + +## Support + +Please open [issues](https://github.com/SAP/xml-tols/issues) on github. + +## Contributing + +See [CONTRIBUTING.md](./CONTRIBUTING.md). diff --git a/projects/xml-tools/packages/constraints/api.d.ts b/projects/xml-tools/packages/constraints/api.d.ts new file mode 100644 index 00000000..622104fc --- /dev/null +++ b/projects/xml-tools/packages/constraints/api.d.ts @@ -0,0 +1,8 @@ +import { ValidationIssue } from "@xml-tools/validation"; + +/** + * detects XML constraints issues (e.g non well-formed XML) + * - https://www.w3.org/TR/xml/#sec-starttags + * - see: "Well-formedness constraint: Unique Att Spec". + */ +export function checkConstraints(ast: XMLDocument): ValidationIssue[]; diff --git a/projects/xml-tools/packages/constraints/lib/api.js b/projects/xml-tools/packages/constraints/lib/api.js new file mode 100644 index 00000000..f39210de --- /dev/null +++ b/projects/xml-tools/packages/constraints/lib/api.js @@ -0,0 +1,26 @@ +const { validate } = require("@xml-tools/validation"); +const { + validateUniqueAttributeKeys, +} = require("./constraints/unique-attribute-keys"); +const { + validateTagClosingNameMatch, +} = require("./constraints/tag-closing-name-match"); + +/** + * @param {XMLDocument} ast + * @returns {ValidationIssue[]} + */ +function checkConstraints(ast) { + const constraintIssues = validate({ + doc: ast, + validators: { + element: [validateTagClosingNameMatch, validateUniqueAttributeKeys], + }, + }); + + return constraintIssues; +} + +module.exports = { + checkConstraints, +}; diff --git a/projects/xml-tools/packages/constraints/lib/constraints/tag-closing-name-match.js b/projects/xml-tools/packages/constraints/lib/constraints/tag-closing-name-match.js new file mode 100644 index 00000000..7d02744a --- /dev/null +++ b/projects/xml-tools/packages/constraints/lib/constraints/tag-closing-name-match.js @@ -0,0 +1,44 @@ +/** + * @param {XMLElement} elem + * @returns {ValidationIssue[]} + */ +function validateTagClosingNameMatch(elem) { + const openTagToken = elem.syntax.openName; + const closeTagToken = elem.syntax.closeName; + + // The element tag must have **both** the opening and closing tokens + // to be able to validate. + if (!openTagToken || !closeTagToken) { + return []; + } + + // alles gut + if (openTagToken.image === closeTagToken.image) { + return []; + } else { + return [ + { + msg: `tags mismatch: "${openTagToken.image}" must match closing tag: "${closeTagToken.image}"`, + node: elem, + severity: "error", + position: { + startOffset: openTagToken.startOffset, + endOffset: openTagToken.endOffset, + }, + }, + { + msg: `tags mismatch: "${closeTagToken.image}" must match opening tag: "${openTagToken.image}"`, + node: elem, + severity: "error", + position: { + startOffset: closeTagToken.startOffset, + endOffset: closeTagToken.endOffset, + }, + }, + ]; + } +} + +module.exports = { + validateTagClosingNameMatch, +}; diff --git a/projects/xml-tools/packages/constraints/lib/constraints/unique-attribute-keys.js b/projects/xml-tools/packages/constraints/lib/constraints/unique-attribute-keys.js new file mode 100644 index 00000000..6dccf039 --- /dev/null +++ b/projects/xml-tools/packages/constraints/lib/constraints/unique-attribute-keys.js @@ -0,0 +1,37 @@ +const { groupBy, pickBy, reduce, map, filter } = require("lodash"); + +/** + * @param {XMLElement} elem + * @returns {ValidationIssue[]} + */ +function validateUniqueAttributeKeys(elem) { + const attributesWithKeys = filter(elem.attributes, (_) => _.key !== null); + const attribByKey = groupBy(attributesWithKeys, "key"); + const nonUniqueAttribsGroups = pickBy(attribByKey, (_) => _.length > 1); + const nonUniqueAttribs = reduce( + nonUniqueAttribsGroups, + (result, attribsGroup) => result.concat(attribsGroup), + [] + ); + + const validationIssues = map(nonUniqueAttribs, (_) => { + // the `key` is guaranteed to exist because we have filtered above + // for attributes with valid keys + const keyToken = _.syntax.key; + return { + msg: `duplicate attribute: "${_.key}"`, + node: _, + severity: "error", + position: { + startOffset: keyToken.startOffset, + endOffset: keyToken.endOffset, + }, + }; + }); + + return validationIssues; +} + +module.exports = { + validateUniqueAttributeKeys, +}; diff --git a/projects/xml-tools/packages/constraints/package.json b/projects/xml-tools/packages/constraints/package.json new file mode 100644 index 00000000..28f42daa --- /dev/null +++ b/projects/xml-tools/packages/constraints/package.json @@ -0,0 +1,46 @@ +{ + "name": "@xml-tools/constraints", + "version": "1.2.0", + "description": "XML constraints validations", + "keywords": [ + "xml", + "validation" + ], + "main": "lib/api.js", + "repository": "https://github.com/sap/xml-tools/", + "license": "Apache-2.0", + "typings": "./api.d.ts", + "files": [ + "lib", + ".reuse", + "LICENSES", + "api.d.ts" + ], + "dependencies": { + "@xml-tools/validation": "^1.1.0", + "lodash": "4.17.23" + }, + "devDependencies": { + "@xml-tools/ast": "^5.1.0" + }, + "scripts": { + "ci": "npm-run-all clean type-check coverage:*", + "clean": "rimraf ./coverage ./nyc_output", + "test": "mocha \"./test/**/*spec.js\"", + "coverage:run": "nyc mocha \"./test/**/*spec.js\"", + "coverage:check": "nyc check-coverage --lines 100 --branches 100 --statements 100 --functions 100", + "type-check": "tsc api.d.ts" + }, + "publishConfig": { + "access": "public" + }, + "nyc": { + "include": [ + "lib/**/*.js" + ], + "reporter": [ + "text", + "lcov" + ] + } +} diff --git a/projects/xml-tools/packages/constraints/test/api-spec.js b/projects/xml-tools/packages/constraints/test/api-spec.js new file mode 100644 index 00000000..8f7404b9 --- /dev/null +++ b/projects/xml-tools/packages/constraints/test/api-spec.js @@ -0,0 +1,40 @@ +const { expect } = require("chai"); +const { parse } = require("@xml-tools/parser"); +const { buildAst } = require("@xml-tools/ast"); +const { checkConstraints } = require("../"); + +describe("the constraints sub-package apis", () => { + it("can detect validation issues using `checkConstraints` api", () => { + const xmlText = ` + + Bill + Tim + `; + + const { cst, tokenVector } = parse(xmlText); + const document = buildAst(cst, tokenVector); + const validationIssues = checkConstraints(document); + const element = document.rootElement; + expect(validationIssues).to.have.lengthOf(2); + expect(validationIssues).to.deep.include.members([ + { + msg: 'tags mismatch: "note" must match closing tag: "note-typo"', + severity: "error", + node: element, + position: { + startOffset: element.syntax.openName.startOffset, + endOffset: element.syntax.openName.endOffset, + }, + }, + { + msg: 'tags mismatch: "note-typo" must match opening tag: "note"', + severity: "error", + node: element, + position: { + startOffset: element.syntax.closeName.startOffset, + endOffset: element.syntax.closeName.endOffset, + }, + }, + ]); + }); +}); diff --git a/projects/xml-tools/packages/constraints/test/constraints/tag-closing-name-match-spec.js b/projects/xml-tools/packages/constraints/test/constraints/tag-closing-name-match-spec.js new file mode 100644 index 00000000..829eb242 --- /dev/null +++ b/projects/xml-tools/packages/constraints/test/constraints/tag-closing-name-match-spec.js @@ -0,0 +1,109 @@ +const { expect } = require("chai"); +const { parse } = require("@xml-tools/parser"); +const { buildAst } = require("@xml-tools/ast"); +const { + validateTagClosingNameMatch, +} = require("../../lib/constraints/tag-closing-name-match"); + +describe("tag closing name constraint", () => { + context("no issues", () => { + it("will not detect any issues for valid XML", () => { + const xmlText = ` + + + Tim + `; + + const { cst, tokenVector } = parse(xmlText); + const document = buildAst(cst, tokenVector); + const element = document.rootElement; + const result = validateTagClosingNameMatch(element); + + expect(result).to.be.empty; + }); + }); + + context("unable to validate", () => { + it("will not detect any issues when the opening tag name is missing", () => { + const xmlText = ` + <> + Bill + Tim + `; + + const { cst, tokenVector } = parse(xmlText); + const document = buildAst(cst, tokenVector); + const element = document.rootElement; + const result = validateTagClosingNameMatch(element); + + expect(result).to.be.empty; + }); + + it("will not detect any issues when the closing tag name is missing", () => { + const xmlText = ` + + Bill + Tim + `; + + const { cst, tokenVector } = parse(xmlText); + const document = buildAst(cst, tokenVector); + const element = document.rootElement; + const result = validateTagClosingNameMatch(element); + + expect(result).to.be.empty; + }); + + it("will not detect any issues when both tag names are missing", () => { + const xmlText = ` + <> + Bill + Tim + `; + + const { cst, tokenVector } = parse(xmlText); + const document = buildAst(cst, tokenVector); + const element = document.rootElement; + const result = validateTagClosingNameMatch(element); + + expect(result).to.be.empty; + }); + }); + + context("positive constraint checks", () => { + it("detects a mis-aligned closing tag", () => { + const xmlText = ` + + Bill + Tim + `; + + const { cst, tokenVector } = parse(xmlText); + const document = buildAst(cst, tokenVector); + const element = document.rootElement; + const result = validateTagClosingNameMatch(element); + + expect(result).to.have.lengthOf(2); + expect(result).to.deep.include.members([ + { + msg: 'tags mismatch: "note" must match closing tag: "note-typo"', + severity: "error", + node: element, + position: { + startOffset: element.syntax.openName.startOffset, + endOffset: element.syntax.openName.endOffset, + }, + }, + { + msg: 'tags mismatch: "note-typo" must match opening tag: "note"', + severity: "error", + node: element, + position: { + startOffset: element.syntax.closeName.startOffset, + endOffset: element.syntax.closeName.endOffset, + }, + }, + ]); + }); + }); +}); diff --git a/projects/xml-tools/packages/constraints/test/constraints/unique-attribute-keys-spec.js b/projects/xml-tools/packages/constraints/test/constraints/unique-attribute-keys-spec.js new file mode 100644 index 00000000..963a5e68 --- /dev/null +++ b/projects/xml-tools/packages/constraints/test/constraints/unique-attribute-keys-spec.js @@ -0,0 +1,66 @@ +const { expect } = require("chai"); +const { parse } = require("@xml-tools/parser"); +const { buildAst } = require("@xml-tools/ast"); +const { + validateUniqueAttributeKeys, +} = require("../../lib/constraints/unique-attribute-keys"); + +describe("unique attribute keys constraint", () => { + context("no issues", () => { + it("will not detect any issues for a valid XML with attributes", () => { + const xmlText = ``; + const { cst, tokenVector } = parse(xmlText); + const document = buildAst(cst, tokenVector); + const element = document.rootElement; + const result = validateUniqueAttributeKeys(element); + + expect(result).to.be.empty; + }); + + it("will not detect any issues for a valid XML without attributes", () => { + const xmlText = ``; + const { cst, tokenVector } = parse(xmlText); + const document = buildAst(cst, tokenVector); + const element = document.rootElement; + const result = validateUniqueAttributeKeys(element); + + expect(result).to.be.empty; + }); + }); + + context("positive constraint checks", () => { + it("detects a duplicate attribute keys", () => { + const xmlText = ``; + + const { cst, tokenVector } = parse(xmlText); + const document = buildAst(cst, tokenVector); + const element = document.rootElement; + const result = validateUniqueAttributeKeys(element); + + const donaldNameAttrib = element.attributes[0]; + const bambaNameAttrib = element.attributes[3]; + + expect(result).to.have.lengthOf(2); + expect(result).to.deep.include.members([ + { + msg: 'duplicate attribute: "name"', + severity: "error", + node: donaldNameAttrib, + position: { + startOffset: donaldNameAttrib.syntax.key.startOffset, + endOffset: donaldNameAttrib.syntax.key.endOffset, + }, + }, + { + msg: 'duplicate attribute: "name"', + severity: "error", + node: bambaNameAttrib, + position: { + startOffset: bambaNameAttrib.syntax.key.startOffset, + endOffset: bambaNameAttrib.syntax.key.endOffset, + }, + }, + ]); + }); + }); +}); diff --git a/projects/xml-tools/packages/content-assist/.npmignore b/projects/xml-tools/packages/content-assist/.npmignore new file mode 100644 index 00000000..ae12335e --- /dev/null +++ b/projects/xml-tools/packages/content-assist/.npmignore @@ -0,0 +1,10 @@ +# NYC +.nyc_output +nyc.config.js +coverage + +# tests +test + +# contributing +CONTRIBUTING.md \ No newline at end of file diff --git a/projects/xml-tools/packages/content-assist/CHANGELOG.md b/projects/xml-tools/packages/content-assist/CHANGELOG.md new file mode 100644 index 00000000..1a270864 --- /dev/null +++ b/projects/xml-tools/packages/content-assist/CHANGELOG.md @@ -0,0 +1,163 @@ +# Change Log + +All notable changes to this project will be documented in this file. +See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + +# [3.2.0](https://github.com/sap/xml-tools/compare/@xml-tools/content-assist@3.1.11...@xml-tools/content-assist@3.2.0) (2026-02-11) + +### Features + +- update lodash dependency to version 4.17.23 ([#461](https://github.com/sap/xml-tools/issues/461)) ([23a319d](https://github.com/sap/xml-tools/commit/23a319d6ed42a4a3885ec5748eb127147a8960bc)) + +## [3.1.11](https://github.com/sap/xml-tools/compare/@xml-tools/content-assist@3.1.10...@xml-tools/content-assist@3.1.11) (2021-06-03) + +**Note:** Version bump only for package @xml-tools/content-assist + +## [3.1.10](https://github.com/sap/xml-tools/compare/@xml-tools/content-assist@3.1.9...@xml-tools/content-assist@3.1.10) (2021-02-09) + +**Note:** Version bump only for package @xml-tools/content-assist + +## [3.1.9](https://github.com/sap/xml-tools/compare/@xml-tools/content-assist@3.1.8...@xml-tools/content-assist@3.1.9) (2021-01-20) + +### Bug Fixes + +- missing lib folder in npm packages ([ffed3c2](https://github.com/sap/xml-tools/commit/ffed3c2c54c70aea8b9ded0d53786382bc190cc5)) + +## [3.1.8](https://github.com/sap/xml-tools/compare/@xml-tools/content-assist@3.1.7...@xml-tools/content-assist@3.1.8) (2021-01-20) + +**Note:** Version bump only for package @xml-tools/content-assist + +## [3.1.7](https://github.com/sap/xml-tools/compare/@xml-tools/content-assist@3.1.6...@xml-tools/content-assist@3.1.7) (2020-06-29) + +**Note:** Version bump only for package @xml-tools/content-assist + +## [3.1.6](https://github.com/sap/xml-tools/compare/@xml-tools/content-assist@3.1.5...@xml-tools/content-assist@3.1.6) (2020-06-18) + +**Note:** Version bump only for package @xml-tools/content-assist + +## [3.1.5](https://github.com/sap/xml-tools/compare/@xml-tools/content-assist@3.1.4...@xml-tools/content-assist@3.1.5) (2020-05-17) + +**Note:** Version bump only for package @xml-tools/content-assist + +## [3.1.4](https://github.com/sap/xml-tools/compare/@xml-tools/content-assist@3.1.3...@xml-tools/content-assist@3.1.4) (2020-04-02) + +**Note:** Version bump only for package @xml-tools/content-assist + +## [3.1.3](https://github.com/sap/xml-tools/compare/@xml-tools/content-assist@3.1.2...@xml-tools/content-assist@3.1.3) (2020-02-19) + +### Bug Fixes + +- add npmignore file to each package ([5bbf209](https://github.com/sap/xml-tools/commit/5bbf209)) + +## [3.1.2](https://github.com/sap/xml-tools/compare/@xml-tools/content-assist@3.1.1...@xml-tools/content-assist@3.1.2) (2020-02-11) + +**Note:** Version bump only for package @xml-tools/content-assist + +## [3.1.1](https://github.com/sap/xml-tools/compare/@xml-tools/content-assist@3.1.0...@xml-tools/content-assist@3.1.1) (2020-02-10) + +**Note:** Version bump only for package @xml-tools/content-assist + +# [3.1.0](https://github.com/sap/xml-tools/compare/@xml-tools/content-assist@3.0.2...@xml-tools/content-assist@3.1.0) (2020-02-06) + +### Bug Fixes + +- **content-assist:** avoid breaking changes with new `context` arg ([c7bbdc2](https://github.com/sap/xml-tools/commit/c7bbdc2)) + +### Features + +- **content-assist:** support optional `context` arg in `getSuggestions` ([6e5a228](https://github.com/sap/xml-tools/commit/6e5a228)) + +## [3.0.2](https://github.com/sap/xml-tools/compare/@xml-tools/content-assist@3.0.1...@xml-tools/content-assist@3.0.2) (2020-02-06) + +**Note:** Version bump only for package @xml-tools/content-assist + +## [3.0.1](https://github.com/sap/xml-tools/compare/@xml-tools/content-assist@3.0.0...@xml-tools/content-assist@3.0.1) (2020-01-28) + +**Note:** Version bump only for package @xml-tools/content-assist + +# [3.0.0](https://github.com/sap/xml-tools/compare/@xml-tools/content-assist@2.0.0...@xml-tools/content-assist@3.0.0) (2020-01-20) + +### Features + +- **parser:** support Basic DocType Declarations ([5b4db21](https://github.com/sap/xml-tools/commit/5b4db21)) + +### BREAKING CHANGES + +- **parser:** Implementing XmlCstVisitor now requires implementing two additional methods: + (docTypeDecl and externalID) + +# [2.0.0](https://github.com/sap/xml-tools/compare/@xml-tools/content-assist@1.0.0...@xml-tools/content-assist@2.0.0) (2019-12-18) + +### Bug Fixes + +- **content-assist:** crash with invalid xml ([#57](https://github.com/sap/xml-tools/issues/57)) ([774c623](https://github.com/sap/xml-tools/commit/774c623)) + +### Features + +- **content-assist:** decouple content assist from parsing ([#58](https://github.com/sap/xml-tools/issues/58)) ([3688da8](https://github.com/sap/xml-tools/commit/3688da8)) + +### BREAKING CHANGES + +- **content-assist:** getSuggestions now uses ast, cst and tokenVector instead of text + +# [1.0.0](https://github.com/sap/xml-tools/compare/@xml-tools/content-assist@0.4.2...@xml-tools/content-assist@1.0.0) (2019-12-08) + +### Features + +- **ast:** attributesRange and guessedAttributeRange support ([8ce840d](https://github.com/sap/xml-tools/commit/8ce840d)), closes [#51](https://github.com/sap/xml-tools/issues/51) + +### BREAKING CHANGES + +- **ast:** buildAst now requires a tokenVector argument + +## [0.4.2](https://github.com/sap/xml-tools/compare/@xml-tools/content-assist@0.4.1...@xml-tools/content-assist@0.4.2) (2019-12-05) + +**Note:** Version bump only for package @xml-tools/content-assist + +## [0.4.1](https://github.com/sap/xml-tools/compare/@xml-tools/content-assist@0.4.0...@xml-tools/content-assist@0.4.1) (2019-12-03) + +### Bug Fixes + +- simple schema with xml namespaces ([#52](https://github.com/sap/xml-tools/issues/52)) ([bb0163e](https://github.com/sap/xml-tools/commit/bb0163e)) + +# [0.4.0](https://github.com/sap/xml-tools/compare/@xml-tools/content-assist@0.3.6...@xml-tools/content-assist@0.4.0) (2019-11-20) + +### Features + +- **content assist:** improved attributes keys content assist ([3d3b7c2](https://github.com/sap/xml-tools/commit/3d3b7c2)), closes [#42](https://github.com/sap/xml-tools/issues/42) + +## [0.3.6](https://github.com/sap/xml-tools/compare/@xml-tools/content-assist@0.3.5...@xml-tools/content-assist@0.3.6) (2019-11-12) + +**Note:** Version bump only for package @xml-tools/content-assist + +## [0.3.5](https://github.com/sap/xml-tools/compare/@xml-tools/content-assist@0.3.4...@xml-tools/content-assist@0.3.5) (2019-11-11) + +**Note:** Version bump only for package @xml-tools/content-assist + +## [0.3.4](https://github.com/sap/xml-tools/compare/@xml-tools/content-assist@0.3.3...@xml-tools/content-assist@0.3.4) (2019-11-11) + +**Note:** Version bump only for package @xml-tools/content-assist + +## [0.3.3](https://github.com/sap/xml-tools/compare/@xml-tools/content-assist@0.3.2...@xml-tools/content-assist@0.3.3) (2019-11-06) + +**Note:** Version bump only for package @xml-tools/content-assist + +## [0.3.2](https://github.com/sap/xml-tools/compare/@xml-tools/content-assist@0.3.1...@xml-tools/content-assist@0.3.2) (2019-11-06) + +**Note:** Version bump only for package @xml-tools/content-assist + +## [0.3.1](https://github.com/sap/xml-tools/compare/@xml-tools/content-assist@0.3.0...@xml-tools/content-assist@0.3.1) (2019-10-29) + +### Bug Fixes + +- content Assist - ElementContents bug fixes ([1eba20a](https://github.com/sap/xml-tools/commit/1eba20a)), closes [#15](https://github.com/sap/xml-tools/issues/15) [#16](https://github.com/sap/xml-tools/issues/16) + +# 0.3.0 (2019-10-28) + +### Features + +- move to lerna independent mode ([6c347a8](https://github.com/sap/xml-tools/commit/6c347a8)), closes [#18](https://github.com/sap/xml-tools/issues/18) + +# 0.2.0 (2019-10-28) + +# 0.1.0 (2019-10-28) diff --git a/projects/xml-tools/packages/content-assist/CONTRIBUTING.md b/projects/xml-tools/packages/content-assist/CONTRIBUTING.md new file mode 100644 index 00000000..84eb9447 --- /dev/null +++ b/projects/xml-tools/packages/content-assist/CONTRIBUTING.md @@ -0,0 +1,4 @@ +# Contribution Guide + +This package does not have any unique development flows. +Please see the top level [Contribution Guide](../../CONTRIBUTING.md). diff --git a/projects/xml-tools/packages/content-assist/README.md b/projects/xml-tools/packages/content-assist/README.md new file mode 100644 index 00000000..b595dbe4 --- /dev/null +++ b/projects/xml-tools/packages/content-assist/README.md @@ -0,0 +1,94 @@ +[![npm (scoped)](https://img.shields.io/npm/v/@xml-tools/content-assist.svg)](https://www.npmjs.com/package/@xml-tools/content-assist) + +# @xml-tools/content-assist + +APIs which assist in implementing content assist (auto-complete) logic for XML. +This package handles the identification of the specific content assist **scenario and context**. +Given this scenario & context information a user of this package can then implement +their custom content assist logic. + +For example: + +Given the following XML and a content assist request at the `⇶` sign: + +```xml + +
+ + + +``` + +A **Attribute Value** content assist scenario would be identified. +With a **prefix** `New` and references to the ASTNodes +of the relevant attribute(city) and the containing element (address). + +- Note that an ASTNode possesses a `parent` property, so the full structure is accessible. + +## Installation + +With npm: + +- `npm install @xml-tools/content-assist` + +With Yarn + +- `yarn add @xml-tools/content-assist` + +## Usage + +Please see the [TypeScript Definitions](./api.d.ts) for full API details. + +A simple usage example: + +```javascript +const { parse } = require("@xml-tools/parser"); +const { buildAst } = require("@xml-tools/ast"); +const { getSuggestions } = require("@xml-tools/content-assist"); + +const xmlText = ` + Bill + Bill + +`; + +// A-lot of contextual information is needed to compute the content assist context. +const { cst, tokenVector } = parse(xmlText); +const xmlDocAst = buildAst(cst, tokenVector); + +const suggestions = getSuggestions({ + ast: xmlDocAst, + cst: cst, + tokVector: tokenVector, + offset: 66, // Right after the ' { + const suggestions = []; + if (element.parent.name === "note" && "address".startsWith(prefix)) { + suggestions.push({ + text: "address".slice(prefix.length), + label: "address", + }); + } + return suggestions; + }, + ], + }, +}); + +console.log(suggestions[0].text); // -> dress +``` + +## Support + +Please open [issues](https://github.com/SAP/xml-tols/issues) on github. + +## Contributing + +See [CONTRIBUTING.md](./CONTRIBUTING.md). + +[ast]: https://en.wikipedia.org/wiki/Abstract_syntax_tree diff --git a/projects/xml-tools/packages/content-assist/api.d.ts b/projects/xml-tools/packages/content-assist/api.d.ts new file mode 100644 index 00000000..d8e4d6f3 --- /dev/null +++ b/projects/xml-tools/packages/content-assist/api.d.ts @@ -0,0 +1,194 @@ +import { IToken } from "chevrotain"; +import { DocumentCstNode } from "@xml-tools/parser"; +import { + XMLAttribute, + XMLElement, + XMLTextContent, + XMLDocument, +} from "@xml-tools/ast"; + +declare function getSuggestions(options: { + cst: DocumentCstNode; + ast: XMLDocument; + offset: number; + tokenVector: IToken[]; + providers: SuggestionProviders; +}): OUT[]; + +/** + * The `context` argument can be used to inject state/data + * to the be used in the implementation of the suggestionsProviders. + * For example a Symbol Table of known Attributes / Tags. + */ +declare function getSuggestions(options: { + cst: DocumentCstNode; + ast: XMLDocument; + offset: number; + tokenVector: IToken[]; + providers: SuggestionProviders; + context: CONTEXT; +}): OUT[]; + +declare type SuggestionProviders = { + elementContent?: ElementContentCompletion[]; + elementName?: ElementNameCompletion[]; + attributeName?: AttributeNameCompletion[]; + attributeValue?: AttributeValueCompletion[]; +}; + +declare type ProviderOptions = + | ElementContentCompletionOptions + | ElementNameCompletionOptions + | AttributeNameCompletionOptions + | AttributeValueCompletionOptions; + +declare type SuggestionProvider< + IN extends ProviderOptions, + OUT, + CONTEXT = undefined +> = (options: IN) => OUT[]; + +declare type ElementContentCompletionOptions = { + /** + * Element ASTNode for which the name content assist was requested. + * This ASTNode may be used to: + * - Inspect if there is any other content in the Element. + * - Inspect the available namespaces aliases in this scope. + * - ... + */ + element: XMLElement; + + /** + * The pre-existing part of the name at the content assist request offset. + * This would normally be used to filter out suggestions that do not match the prefix. + */ + prefix: string | undefined; + + /** + * TextContent ASTNode in which content assist was requested + * Note that this property may be undefined if no prefix was provided. + */ + textContent: XMLTextContent | undefined; + + context: CONTEXT; +}; +/** + * Suggestions provider for element's contents. + * This is triggered when content assist is requested: + * 1. **inside** an element's contents. + * 2. And **outside** any of the element's sub-parts + * + * For example: ('⇶' marks the content assist position): + * - + * bobi + * ⇶ + * + * + * + * - ⇶ + */ +declare type ElementContentCompletion< + OUT, + CONTEXT = undefined +> = SuggestionProvider, OUT, CONTEXT>; + +declare type ElementNameCompletionOptions = { + /** + * Element ASTNode for which the name content assist was requested + */ + element: XMLElement; + /** + * The pre-existing part of the name at the content assist request offset. + * This would normally be used to filter out suggestions that do not match the prefix. + */ + prefix: string | undefined; + + context: CONTEXT; +}; +/** + * Suggestions provider for element names. + * This is triggered when content assist is requested: + * 1. **inside** an element's contents. + * 2. Following the '<' character. + * 3. optionally also following a name prefix. + * + * For example: ('⇶' marks the content assist position): + * - = SuggestionProvider, OUT, CONTEXT>; + +declare type AttributeNameCompletionOptions = { + /** + * Element ASTNode in which content assist was requested + * This ASTNode may be used to: + * - Inspect pre-existing attribute names. + * - Identify namespaces aliases available in this "scope" + * - ... + */ + element: XMLElement; + /** + * Attribute ASTNode in which content assist was requested + * Note that this property may be undefined if no prefix was provided. + */ + attribute: XMLAttribute | undefined; + /** + * The pre-existing part of the name at the content assist request offset. + * This would normally be used to filter out suggestions that do not match the prefix. + */ + prefix: string | undefined; + + context: CONTEXT; +}; + +/** + * Suggestions provider for attribute names. + * This is triggered when content assist is requested **inside** an element opening name block + * in a position where an attribute is valid e.g ('⇶' marks the content assist position): + * - + * - + */ +declare type AttributeNameCompletion< + OUT, + CONTEXT = undefined +> = SuggestionProvider, OUT, CONTEXT>; + +declare type AttributeValueCompletionOptions = { + /** + * Element ASTNode in which content assist was requested + */ + element: XMLElement; + /** + * Attribute ASTNode in which content assist was requested + * Note that **unlike** `AttributeNameCompletion` this property **always** exists. + * This ASTNode may be used to: + * - Inspect the attribute key for which value suggestions are requested. + * - Inspect the **whole** pre-existing value of the attribute. + */ + attribute: XMLAttribute; + /** + * The pre-existing part of the value at the content assist request location. + * This would normally be used to filter out suggestions that do not match the prefix. + * + * Note that the prefix does not include any quotes. + */ + prefix: string | undefined; + + context: CONTEXT; +}; + +/** + * Suggestions provider for attribute values. + * This is triggered when content assist is requested **inside** the quotes + * d.g: ('⇶' marks the content assist position): + * - + * - + * - ... + */ +declare type AttributeValueCompletion< + OUT, + CONTEXT = undefined +> = SuggestionProvider, OUT, CONTEXT>; diff --git a/projects/xml-tools/packages/content-assist/lib/api.js b/projects/xml-tools/packages/content-assist/lib/api.js new file mode 100644 index 00000000..690d7176 --- /dev/null +++ b/projects/xml-tools/packages/content-assist/lib/api.js @@ -0,0 +1,40 @@ +const { defaultsDeep, flatMap } = require("lodash"); + +const { computeCompletionSyntacticContext } = require("./content-assist"); + +function getSuggestions(options) { + const actualOptions = defaultsDeep(options, { + providers: { + elementContent: [], + elementName: [], + attributeName: [], + attributeValue: [], + }, + context: undefined, + }); + + let { providerType, providerArgs } = computeCompletionSyntacticContext({ + cst: actualOptions.cst, + tokenVector: actualOptions.tokenVector, + ast: actualOptions.ast, + offset: actualOptions.offset, + }); + + // Inject Additional semantic context for the content assist providers. + providerArgs.context = actualOptions.context; + + if (providerType === null) { + return []; + } else { + const selectedProviders = actualOptions.providers[providerType]; + + const suggestions = flatMap(selectedProviders, (suggestionProvider) => + suggestionProvider(providerArgs) + ); + return suggestions; + } +} + +module.exports = { + getSuggestions: getSuggestions, +}; diff --git a/projects/xml-tools/packages/content-assist/lib/content-assist.js b/projects/xml-tools/packages/content-assist/lib/content-assist.js new file mode 100644 index 00000000..6a55a9a6 --- /dev/null +++ b/projects/xml-tools/packages/content-assist/lib/content-assist.js @@ -0,0 +1,399 @@ +const { + forEach, + isArray, + find, + findIndex, + flatMap, + identity, + last, + isEmpty, +} = require("lodash"); +const { BaseXmlCstVisitor } = require("@xml-tools/parser"); +const { findNextTextualToken } = require("@xml-tools/common"); + +/** + * + * @return {{providerType:string, providerArgs: {prefix?:string, element:XMLElement, attribute?:XMLAttribute}}} + */ +function computeCompletionSyntacticContext({ + cst, + ast: docAst, + offset, + tokenVector, +}) { + const contextVisitor = new SuggestionContextVisitor( + docAst, + offset, + tokenVector + ); + contextVisitor.visit(cst); + return contextVisitor.result; +} + +/* eslint-disable no-unused-vars -- consistent signatures in visitor methods even if they are empty placeholders */ +class SuggestionContextVisitor extends BaseXmlCstVisitor { + constructor(docAst, offset, tokenVector) { + super(); + this.docAst = docAst; + this.targetOffset = offset; + this.tokenVector = tokenVector; + this.result = { providerType: null, providerArgs: {} }; + this.found = false; + } + + /** + * @param {DocumentCtx} ctx + */ + document(ctx) { + this.visit(ctx.element, this.docAst.rootElement); + } + + /** + * @param {PrologCtx} ctx + */ + /* istanbul ignore next - place holder*/ + prolog(ctx, astNode) {} + + /** + * @param {docTypeDeclCtx} ctx + */ + /* istanbul ignore next - place holder*/ + docTypeDecl(ctx, astNode) {} + + /** + * @param {ExternalIDCtx} ctx + */ + /* istanbul ignore next - place holder*/ + externalID(ctx, astNode) {} + + /** + * @param {ContentCtx} ctx + * @param {XMLElement} astNode + * + */ + content(ctx, astNode) { + forEach(ctx.element, (elem, idx) => { + this.visit(elem, astNode.subElements[idx]); + }); + } + + /** + * @param {ElementCtx} ctx + * @param {XMLElement} astNode + */ + element(ctx, astNode) { + // Order of handlers can affect the result! + // They are ordered by priority, the more specific handlers are listed first. + handleElementNameWithoutPrefixScenario(ctx, astNode, this); + + if (this.found === false) { + handleElementNameWithPrefixScenario(ctx, astNode, this); + } + + // Traverse Deeper + if (this.found === false) { + forEach(ctx.attribute, (attrib, idx) => + this.visit(attrib, astNode.attributes[idx]) + ); + this.visit(ctx.content, astNode); + } + + if (this.found === false) { + handleNewAttributeKeyScenario(ctx, astNode, this.tokenVector, this); + } + + if (this.found === false) { + handleElementContentScenario(ctx, astNode, this); + } + } + + /** + * @param {ReferenceCtx} ctx + */ + /* istanbul ignore next - place holder*/ + reference(ctx, astNode) {} + + /** + * @param {AttributeCtx} ctx + * @param {XMLAttribute} astNode + */ + attribute(ctx, astNode) { + // potential Attribute Value scenarios + /* istanbul ignore else - Very difficult to reproduce specific partial CSTs */ + if (exists(ctx.STRING)) { + const valueTok = ctx.STRING[0]; + if ( + // The content assist point must be inside the string quotes + valueTok.startOffset < this.targetOffset && + valueTok.endOffset >= this.targetOffset + ) { + const prefixEnd = this.targetOffset - valueTok.startOffset; + const prefix = valueTok.image.substring(1, prefixEnd); + this.result.providerType = "attributeValue"; + this.result.providerArgs = { + element: astNode.parent, + attribute: astNode, + prefix: prefix !== "" ? prefix : undefined, + }; + this.found = true; + } + } + + /* istanbul ignore else - Very difficult to reproduce specific partial CSTs */ + if (exists(ctx.Name)) { + const keyTok = ctx.Name[0]; + + if ( + keyTok.startOffset <= this.targetOffset && + keyTok.endOffset + 1 >= this.targetOffset + ) { + const prefixEnd = this.targetOffset - keyTok.startOffset; + const prefix = keyTok.image.substring(0, prefixEnd); + this.result.providerType = "attributeName"; + this.result.providerArgs = { + element: astNode.parent, + attribute: astNode, + prefix: prefix !== "" ? prefix : undefined, + }; + this.found = true; + } + } + } + + /** + * @param {ChardataCtx} ctx + */ + /* istanbul ignore next - place holder*/ + chardata(ctx, astNode) {} + + /** + * @param {MiscCtx} ctx + */ + /* istanbul ignore next - place holder*/ + misc(ctx, astNode) {} +} +/* eslint-enable no-unused-vars -- see matching pair above */ + +function handleElementNameWithoutPrefixScenario(ctx, astNode, visitor) { + /* istanbul ignore else - Very difficult to reproduce specific partial CSTs */ + if (exists(ctx.OPEN)) { + const openTok = ctx.OPEN[0]; + if (openTok.endOffset + 1 === visitor.targetOffset) { + visitor.result.providerType = "elementName"; + visitor.result.providerArgs = { element: astNode, prefix: undefined }; + visitor.found = true; + } + } +} + +function handleElementNameWithPrefixScenario(ctx, astNode, visitor) { + /* istanbul ignore else - Very difficult to reproduce specific partial CSTs */ + if (exists(ctx.Name)) { + const nameTok = ctx.Name[0]; + if ( + nameTok.startOffset < visitor.targetOffset && + nameTok.endOffset + 1 >= visitor.targetOffset + ) { + visitor.result.providerType = "elementName"; + const prefixLength = visitor.targetOffset - nameTok.startOffset; + const prefix = nameTok.image.substring(0, prefixLength); + visitor.result.providerArgs = { element: astNode, prefix: prefix }; + visitor.found = true; + } + } +} + +function handleNewAttributeKeyScenario(ctx, astNode, tokenVector, visitor) { + // Potential AttributeKey scenario in a completely new attribute + // Note the guard condition (in caller) to avoid this branch if one of the attributes scenarios was already detected. + // This means the order of checking the scenarios is meaningful! + // Example of the problem: + // - `` should be matched as attributeName **with prefix** (In attribute handler code) + // - `` should be matched as a **new** attributeName **without prefix** (in the code below). + // But the logic below cannot distinguish these, so it must only be executed if the attribute handler failed. + const attributesRange = { from: undefined, to: undefined }; + let hasTerminatedAttribRange = false; + let hasAttributeCloseToken = false; + /* istanbul ignore else - Very difficult to reproduce specific partial CSTs */ + if (exists(ctx.Name)) { + attributesRange.from = ctx.Name[0].endOffset + 1; + // Figure where does the attributes area end + if (exists(ctx.START_CLOSE)) { + attributesRange.to = ctx.START_CLOSE[0].startOffset; + hasTerminatedAttribRange = true; + hasAttributeCloseToken = true; + } else if (exists(ctx.SLASH_CLOSE)) { + attributesRange.to = ctx.SLASH_CLOSE[0].startOffset; + hasTerminatedAttribRange = true; + hasAttributeCloseToken = true; + // If we do not have a proper Attribute Section closing mark we use the last attribute instead + } else if (isEmpty(ctx.attribute) === false) { + attributesRange.to = last(ctx.attribute).location.endOffset; + hasTerminatedAttribRange = true; + } + } + + if ( + hasTerminatedAttribRange && + visitor.targetOffset <= attributesRange.to && + attributesRange.from <= visitor.targetOffset + ) { + const isNotInExistingAttribute = + find(ctx.attribute, (attribCst) => { + const attribLoc = attribCst.location; + return ( + visitor.targetOffset >= attribLoc.startOffset && + visitor.targetOffset <= attribLoc.endOffset + ); + }) === undefined; + + // inside attribute area but not contained in any existing attribute + /* istanbul ignore else - else branch is handled inside the attributes themselves and can never occur here */ + if (isNotInExistingAttribute) { + visitor.result.providerType = "attributeName"; + visitor.result.providerArgs = { + element: astNode, + attribute: undefined, + prefix: undefined, + }; + visitor.found = true; + } + } else if ( + /** + * Heuristic when we some attributes but no proper attribute Range + * and we request the assist after the last attribute. + * + * + */ + hasTerminatedAttribRange && + visitor.targetOffset > attributesRange.to && + hasAttributeCloseToken === false + ) { + handleNewAttributeKeyForPartialElement( + // dummy pesudo token a we are searching the following token using the endOffset + { endOffset: attributesRange.to }, + tokenVector, + visitor, + astNode + ); + /** + * Heuristic when we have no attribute range, e.g: + * + * + */ + } else if (hasTerminatedAttribRange === false) { + handleNewAttributeKeyForPartialElement( + ctx.Name ? ctx.Name[0] : ctx.OPEN[0], + tokenVector, + visitor, + astNode + ); + } +} + +function handleNewAttributeKeyForPartialElement( + possibleAttribKeyRangeStartTok, + tokenVector, + visitor, + astNode +) { + const nextAfterElemNameTok = findNextTextualToken( + tokenVector, + possibleAttribKeyRangeStartTok.endOffset + ); + if ( + visitor.targetOffset > possibleAttribKeyRangeStartTok.endOffset && + (nextAfterElemNameTok === null || // `null`` means there are no more textual tokens in the input + visitor.targetOffset <= nextAfterElemNameTok.startOffset) + ) { + visitor.result.providerType = "attributeName"; + visitor.result.providerArgs = { + element: astNode, + attribute: undefined, + prefix: undefined, + }; + visitor.found = true; + } +} + +function handleElementContentScenario(ctx, astNode, visitor) { + const contentRange = { from: undefined, to: undefined }; + let hasContentRange = false; + /* istanbul ignore else - Very difficult to reproduce specific partial CSTs */ + if (exists(ctx.START_CLOSE)) { + contentRange.from = ctx.START_CLOSE[0].endOffset + 1; + // visitor logic only works when the attribute section is properly terminated + /* istanbul ignore else - Very difficult to reproduce specific partial CSTs */ + if (exists(ctx.SLASH_OPEN)) { + contentRange.to = ctx.SLASH_OPEN[0].startOffset; + hasContentRange = true; + } else if (exists(ctx.SLASH_CLOSE)) { + contentRange.to = ctx.SLASH_CLOSE[0].startOffset; + hasContentRange = true; + } + } + + if ( + hasContentRange && + visitor.targetOffset <= contentRange.to && + contentRange.from <= visitor.targetOffset + ) { + const allContentChildren = flatMap(ctx.content[0].children, identity); + const innerContentPart = find(allContentChildren, (subContent) => { + // Handling either CSTNodes or Tokens + const subContentLoc = subContent.location + ? subContent.location + : subContent; + + // Our offset ranges are Inclusive to Exclusive + // abc⇶ --> Not inside the CharData Location, but uses `abc` as prefix + // ⇶abc --> Inside the CharData Location, but does not have any prefix + const targetOffsetForPrefix = visitor.targetOffset - 1; + + return ( + targetOffsetForPrefix >= subContentLoc.startOffset && + targetOffsetForPrefix <= subContentLoc.endOffset + ); + }); + + // ElementContent without prefix + if (innerContentPart === undefined) { + visitor.result.providerType = "elementContent"; + visitor.result.providerArgs = { + element: astNode, + textContent: undefined, + prefix: undefined, + }; + visitor.found = true; + } else if (innerContentPart.name === "chardata") { + const textNodeIdx = findIndex( + ctx.content[0].children.chardata, + innerContentPart + ); + const textContentsAstNode = astNode.textContents[textNodeIdx]; + const prefixEnd = + visitor.targetOffset - textContentsAstNode.position.startOffset; + visitor.result.providerType = "elementContent"; + visitor.result.providerArgs = { + element: astNode, + textContent: textContentsAstNode, + prefix: textContentsAstNode.text.substring(0, prefixEnd), + }; + visitor.found = true; + } + } +} + +function exists(tokArr) { + return ( + isArray(tokArr) && + tokArr.length === 1 && + tokArr[0].isInsertedInRecovery !== true + ); +} + +module.exports = { + computeCompletionSyntacticContext: computeCompletionSyntacticContext, +}; diff --git a/projects/xml-tools/packages/content-assist/package.json b/projects/xml-tools/packages/content-assist/package.json new file mode 100644 index 00000000..16665e7c --- /dev/null +++ b/projects/xml-tools/packages/content-assist/package.json @@ -0,0 +1,45 @@ +{ + "name": "@xml-tools/content-assist", + "version": "3.2.0", + "description": "XML Content Assist APIs", + "keywords": [ + "xml", + "auto-complete", + "content-assist" + ], + "main": "lib/api.js", + "repository": "https://github.com/sap/xml-tools/", + "license": "Apache-2.0", + "typings": "./api.d.ts", + "files": [ + "lib", + ".reuse", + "LICENSES", + "api.d.ts" + ], + "dependencies": { + "@xml-tools/common": "^0.2.0", + "@xml-tools/parser": "^1.0.11", + "lodash": "4.17.23" + }, + "scripts": { + "ci": "npm-run-all clean type-check coverage:*", + "clean": "rimraf ./coverage ./nyc_output", + "test": "mocha \"./test/**/*spec.js\"", + "coverage:run": "nyc mocha \"./test/**/*spec.js\"", + "coverage:check": "nyc check-coverage --lines 100 --branches 100 --statements 100 --functions 100", + "type-check": "tsc api.d.ts" + }, + "publishConfig": { + "access": "public" + }, + "nyc": { + "include": [ + "lib/**/*.js" + ], + "reporter": [ + "text", + "lcov" + ] + } +} diff --git a/projects/xml-tools/packages/content-assist/test/scenarios-spec.js b/projects/xml-tools/packages/content-assist/test/scenarios-spec.js new file mode 100644 index 00000000..54bea315 --- /dev/null +++ b/projects/xml-tools/packages/content-assist/test/scenarios-spec.js @@ -0,0 +1,555 @@ +const { parse } = require("@xml-tools/parser"); +const { buildAst } = require("@xml-tools/ast"); +const { expect } = require("chai"); +const { getSuggestions } = require("../"); + +describe("The XML Content Assist Capabilities", () => { + describe("Can provide suggestion for", () => { + context("element Content", () => { + it("in middle of text content", () => { + const sample = `hello ⇶world`; + + let providerCalled = false; + getSampleSuggestions( + sample, + { + elementContent: [ + ({ element, prefix, textContent, context }) => { + expect(prefix).to.eql("hello "); + expect(textContent.text).to.eql("hello world"); + expect(element.name).to.eql("person"); + expect(context).to.eql(999); + providerCalled = true; + }, + ], + }, + 999 + ); + expect(providerCalled).to.be.true; + }); + + it("With other elements", () => { + const sample = + `\n` + + `\tAnna\n` + + `\t⇶\n` + + `\tSmith\n` + + ``; + + let providerCalled = false; + getSampleSuggestions(sample, { + elementContent: [ + ({ element, prefix, textContent, context }) => { + expect(prefix).to.eql("\n\t"); + expect(textContent.text).to.eql("\n\t\n\t"); + expect(element.name).to.eql("person"); + expect(context).to.be.undefined; + providerCalled = true; + }, + ], + }); + expect(providerCalled).to.be.true; + }); + + it("In empty element", () => { + const sample = `` + `⇶` + ``; + + let providerCalled = false; + getSampleSuggestions(sample, { + elementContent: [ + ({ element, prefix, textContent, context }) => { + expect(prefix).to.be.undefined; + expect(textContent).to.be.undefined; + expect(element.name).to.eql("person"); + expect(context).to.be.undefined; + providerCalled = true; + }, + ], + }); + expect(providerCalled).to.be.true; + }); + + it("Before invalid element", () => { + const sample = `` + `⇶` + `<\n`; + + let providerCalled = false; + getSampleSuggestions(sample, { + elementContent: [ + ({ element, prefix, textContent, context }) => { + expect(prefix).to.be.undefined; + expect(textContent).to.be.undefined; + expect(element.name).to.eql("person"); + expect(context).to.be.undefined; + providerCalled = true; + }, + ], + }); + expect(providerCalled).to.be.true; + }); + + it("before comment", () => { + const sample = + `\n` + + `\t⇶\n` + + ``; + + let providerCalled = false; + getSampleSuggestions(sample, { + elementContent: [ + ({ element, prefix, textContent, context }) => { + expect(prefix).to.eql("\n\t"); + expect(textContent.text).to.eql("\n\t"); + expect(element.name).to.eql("person"); + expect(context).to.be.undefined; + providerCalled = true; + }, + ], + }); + expect(providerCalled).to.be.true; + }); + + it("inside comment - will not trigger", () => { + const sample = + `\n` + + `\t\n` + + ``; + + let providerCalled = false; + getSampleSuggestions(sample, { + elementContent: [ + // eslint-disable-next-line no-unused-vars -- match the type of `ElementContentCompletion` for consistency + ({ element }) => { + providerCalled = true; + }, + ], + }); + expect(providerCalled).to.be.false; + }); + }); + + context("element Name", () => { + it("With Prefix and Context", () => { + const sample = + `\n` + + `\tAnna\n` + + `\tSmith\n` + + ``; + + let providerCalled = false; + getSampleSuggestions( + sample, + { + elementName: [ + ({ element, prefix, context }) => { + expect(prefix).to.eql("ag"); + expect(element.name).to.eql("age"); + expect(context).to.eql(666); + providerCalled = true; + }, + ], + }, + 666 + ); + expect(providerCalled).to.be.true; + }); + + it("With Prefix - end", () => { + const sample = + `\n` + + `\tAnna\n` + + `\tSmith\n` + + ``; + + let providerCalled = false; + getSampleSuggestions(sample, { + elementName: [ + ({ element, prefix, context }) => { + expect(prefix).to.eql("age"); + expect(element.name).to.eql("age"); + expect(context).to.be.undefined; + providerCalled = true; + }, + ], + }); + expect(providerCalled).to.be.true; + }); + + it("without Prefix", () => { + const sample = + `\n` + + `\tAnna\n` + + `\t<⇶\n` + + `\tSmith\n` + + ``; + + let providerCalled = false; + getSampleSuggestions(sample, { + elementName: [ + ({ element, prefix, context }) => { + expect(prefix).to.be.undefined; + expect(element.name).to.be.null; + expect(context).to.be.undefined; + providerCalled = true; + }, + ], + }); + expect(providerCalled).to.be.true; + }); + }); + + context("attribute Value", () => { + it("Without prefix and empty value and context", () => { + const sample = + `\n` + + `\tAnna\n` + + `\tSmith\n` + + ``; + + let providerCalled = false; + getSampleSuggestions( + sample, + { + attributeValue: [ + ({ element, attribute, prefix, context }) => { + expect(element.name).to.eql("person"); + expect(attribute.key).to.eql("gender"); + expect(prefix).to.be.undefined; + expect(context).to.eql(123); + providerCalled = true; + }, + ], + }, + 123 + ); + expect(providerCalled).to.be.true; + }); + + it("Without prefix but with value", () => { + const sample = + `\n` + + `\tAnna\n` + + `\tSmith\n` + + ``; + + let providerCalled = false; + getSampleSuggestions(sample, { + attributeValue: [ + ({ element, attribute, prefix, context }) => { + expect(element.name).to.eql("lastname"); + expect(attribute.key).to.eql("language"); + expect(attribute.value).to.eql("english"); + expect(prefix).to.be.undefined; + expect(context).to.be.undefined; + providerCalled = true; + }, + ], + }); + expect(providerCalled).to.be.true; + }); + + it("With prefix identical to value", () => { + const sample = + `\n` + + `\tAnna\n` + + `\tSmith\n` + + ``; + + let providerCalled = false; + getSampleSuggestions(sample, { + attributeValue: [ + ({ element, attribute, prefix, context }) => { + expect(element.name).to.eql("lastname"); + expect(attribute.key).to.eql("language"); + expect(attribute.value).to.eql("eng"); + expect(prefix).to.eql("eng"); + expect(context).to.be.undefined; + providerCalled = true; + }, + ], + }); + expect(providerCalled).to.be.true; + }); + + it("With prefix different from value", () => { + const sample = + `\n` + + `\tAnna\n` + + `\tSmith\n` + + ``; + + let providerCalled = false; + getSampleSuggestions(sample, { + attributeValue: [ + ({ element, attribute, prefix, context }) => { + expect(element.name).to.eql("lastname"); + expect(attribute.key).to.eql("language"); + expect(attribute.value).to.eql("english"); + expect(prefix).to.eql("eng"); + expect(context).to.be.undefined; + providerCalled = true; + }, + ], + }); + expect(providerCalled).to.be.true; + }); + }); + + context("attribute name", () => { + it("With prefix identical to existing key and context", () => { + const sample = + `\n` + + `\tAnna\n` + + `\tSmith\n` + + ``; + + let providerCalled = false; + getSampleSuggestions( + sample, + { + attributeName: [ + ({ element, attribute, prefix, context }) => { + expect(element.name).to.eql("person"); + expect(attribute.key).to.eql("gen"); + expect(prefix).to.eql("gen"); + expect(context).to.eql(333); + providerCalled = true; + }, + ], + }, + 333 + ); + expect(providerCalled).to.be.true; + }); + + it("With prefix which is a subset of an existing key", () => { + const sample = + `\n` + + `\tAnna\n` + + `\tSmith\n` + + ``; + + let providerCalled = false; + getSampleSuggestions(sample, { + attributeName: [ + ({ element, attribute, prefix, context }) => { + expect(element.name).to.eql("person"); + expect(attribute.key).to.eql("gender"); + expect(prefix).to.eql("gen"); + expect(context).to.be.undefined; + providerCalled = true; + }, + ], + }); + expect(providerCalled).to.be.true; + }); + + context("with empty prefix", () => { + it("With empty prefix which is a subset of an existing key", () => { + const sample = + `\n` + + `\tAnna\n` + + `\tSmith\n` + + ``; + + let providerCalled = false; + getSampleSuggestions(sample, { + attributeName: [ + ({ element, attribute, prefix, context }) => { + expect(element.name).to.eql("person"); + expect(attribute.key).to.eql("gen"); + expect(prefix).to.be.undefined; + expect(context).to.be.undefined; + providerCalled = true; + }, + ], + }); + expect(providerCalled).to.be.true; + }); + + it("without key", () => { + const sample = + `\n` + + `\tAnna\n` + + `\tSmith\n` + + ``; + + let providerCalled = false; + getSampleSuggestions(sample, { + attributeName: [ + ({ element, attribute, prefix, context }) => { + expect(element.name).to.eql("person"); + expect(attribute).to.be.undefined; + expect(prefix).to.be.undefined; + expect(context).to.be.undefined; + providerCalled = true; + }, + ], + }); + expect(providerCalled).to.be.true; + }); + + it("without key - short ElementForm", () => { + const sample = ``; + + let providerCalled = false; + getSampleSuggestions(sample, { + attributeName: [ + ({ element, attribute, prefix, context }) => { + expect(element.name).to.eql("person"); + expect(attribute).to.be.undefined; + expect(prefix).to.be.undefined; + expect(context).to.be.undefined; + providerCalled = true; + }, + ], + }); + expect(providerCalled).to.be.true; + }); + + it("without key - end", () => { + const sample = + `\n` + + `\tAnna\n` + + `\tSmith\n` + + ``; + + let providerCalled = false; + getSampleSuggestions(sample, { + attributeName: [ + ({ element, attribute, prefix, context }) => { + expect(element.name).to.eql("person"); + expect(attribute).to.be.undefined; + expect(prefix).to.be.undefined; + expect(context).to.be.undefined; + providerCalled = true; + }, + ], + }); + expect(providerCalled).to.be.true; + }); + + it("without attributes section close", () => { + const sample = ` + `; + + let providerCalled = false; + getSampleSuggestions(sample, { + attributeName: [ + ({ element, attribute, prefix, context }) => { + expect(element.name).to.eql("person"); + expect(attribute).to.be.undefined; + expect(prefix).to.be.undefined; + expect(context).to.be.undefined; + providerCalled = true; + }, + ], + }); + expect(providerCalled).to.be.true; + }); + + it("without attributes section close with prior attributes", () => { + const sample = ` + `; + + let providerCalled = false; + getSampleSuggestions(sample, { + attributeName: [ + ({ element, attribute, prefix, context }) => { + expect(element.name).to.eql("person"); + expect(attribute).to.be.undefined; + expect(prefix).to.be.undefined; + expect(context).to.be.undefined; + providerCalled = true; + }, + ], + }); + expect(providerCalled).to.be.true; + }); + + it("without attributes section close with following attributes", () => { + const sample = ` + `; + + let providerCalled = false; + getSampleSuggestions(sample, { + attributeName: [ + ({ element, attribute, prefix, context }) => { + expect(element.name).to.eql("person"); + expect(attribute).to.be.undefined; + expect(prefix).to.be.undefined; + expect(context).to.be.undefined; + providerCalled = true; + }, + ], + }); + expect(providerCalled).to.be.true; + }); + + it("without attributes section close with following and prior attributes", () => { + const sample = ` + `; + + let providerCalled = false; + getSampleSuggestions(sample, { + attributeName: [ + ({ element, attribute, prefix, context }) => { + expect(element.name).to.eql("person"); + expect(attribute).to.be.undefined; + expect(prefix).to.be.undefined; + expect(context).to.be.undefined; + providerCalled = true; + }, + ], + }); + expect(providerCalled).to.be.true; + }); + + it("without attributes section close - no following tokens", () => { + const sample = ` + { + expect(element.name).to.eql("person"); + expect(attribute).to.be.undefined; + expect(prefix).to.be.undefined; + expect(context).to.be.undefined; + providerCalled = true; + }, + ], + }); + expect(providerCalled).to.be.true; + }); + }); + }); + }); +}); + +/** + * Get Content Assist Suggestions from a sample which marks the offset + * with unicode triple arrow: " ⇶ " + */ +function getSampleSuggestions(sample, providers, context) { + const realSample = sample.replace("⇶", ""); + const offset = sample.indexOf("⇶"); + const { cst, tokenVector } = parse(realSample); + const ast = buildAst(cst, tokenVector); + + return getSuggestions({ + cst, + ast, + tokenVector, + offset: offset, + providers: providers, + context: context, + }); +} diff --git a/projects/xml-tools/packages/language-server/.gitignore b/projects/xml-tools/packages/language-server/.gitignore new file mode 100644 index 00000000..16d3c4db --- /dev/null +++ b/projects/xml-tools/packages/language-server/.gitignore @@ -0,0 +1 @@ +.cache diff --git a/projects/xml-tools/packages/language-server/.npmignore b/projects/xml-tools/packages/language-server/.npmignore new file mode 100644 index 00000000..ae12335e --- /dev/null +++ b/projects/xml-tools/packages/language-server/.npmignore @@ -0,0 +1,10 @@ +# NYC +.nyc_output +nyc.config.js +coverage + +# tests +test + +# contributing +CONTRIBUTING.md \ No newline at end of file diff --git a/projects/xml-tools/packages/language-server/CHANGELOG.md b/projects/xml-tools/packages/language-server/CHANGELOG.md new file mode 100644 index 00000000..b3947f99 --- /dev/null +++ b/projects/xml-tools/packages/language-server/CHANGELOG.md @@ -0,0 +1,81 @@ +# Change Log + +All notable changes to this project will be documented in this file. +See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + +# [1.2.0](https://github.com/SAP/xml-tools/compare/@xml-tools/language-server@1.1.1...@xml-tools/language-server@1.2.0) (2026-02-11) + +### Features + +- update lodash dependency to version 4.17.23 ([#461](https://github.com/SAP/xml-tools/issues/461)) ([23a319d](https://github.com/SAP/xml-tools/commit/23a319d6ed42a4a3885ec5748eb127147a8960bc)) + +## [1.1.1](https://github.com/SAP/xml-tools/compare/@xml-tools/language-server@1.1.0...@xml-tools/language-server@1.1.1) (2021-06-03) + +**Note:** Version bump only for package @xml-tools/language-server + +# [1.1.0](https://github.com/SAP/xml-tools/compare/@xml-tools/language-server@1.0.12...@xml-tools/language-server@1.1.0) (2021-02-09) + +### Bug Fixes + +- use extension name as diagnostic source ([#297](https://github.com/SAP/xml-tools/issues/297)) ([f6d7dc4](https://github.com/SAP/xml-tools/commit/f6d7dc42a05c4e1d7c9ab3f1fbe35ecb1eb820fa)) + +### Features + +- well formed constraints validations ([#293](https://github.com/SAP/xml-tools/issues/293)) ([8a9d535](https://github.com/SAP/xml-tools/commit/8a9d535e95a52b9dfc1068d2e203b09bd08e1066)), closes [#13](https://github.com/SAP/xml-tools/issues/13) [#14](https://github.com/SAP/xml-tools/issues/14) + +## [1.0.12](https://github.com/SAP/xml-tools/compare/@xml-tools/language-server@1.0.11...@xml-tools/language-server@1.0.12) (2021-01-20) + +### Bug Fixes + +- missing lib folder in npm packages ([ffed3c2](https://github.com/SAP/xml-tools/commit/ffed3c2c54c70aea8b9ded0d53786382bc190cc5)) + +## [1.0.11](https://github.com/SAP/xml-tools/compare/@xml-tools/language-server@1.0.10...@xml-tools/language-server@1.0.11) (2021-01-20) + +**Note:** Version bump only for package @xml-tools/language-server + +## [1.0.10](https://github.com/SAP/xml-tools/compare/@xml-tools/language-server@1.0.9...@xml-tools/language-server@1.0.10) (2020-06-29) + +**Note:** Version bump only for package @xml-tools/language-server + +## [1.0.9](https://github.com/SAP/xml-tools/compare/@xml-tools/language-server@1.0.8...@xml-tools/language-server@1.0.9) (2020-06-18) + +**Note:** Version bump only for package @xml-tools/language-server + +## [1.0.8](https://github.com/SAP/xml-tools/compare/@xml-tools/language-server@1.0.7...@xml-tools/language-server@1.0.8) (2020-05-21) + +**Note:** Version bump only for package @xml-tools/language-server + +## [1.0.7](https://github.com/SAP/xml-tools/compare/@xml-tools/language-server@1.0.6...@xml-tools/language-server@1.0.7) (2020-05-17) + +**Note:** Version bump only for package @xml-tools/language-server + +## [1.0.6](https://github.com/SAP/xml-tools/compare/@xml-tools/language-server@1.0.5...@xml-tools/language-server@1.0.6) (2020-04-02) + +**Note:** Version bump only for package @xml-tools/language-server + +## [1.0.5](https://github.com/SAP/xml-tools/compare/@xml-tools/language-server@1.0.4...@xml-tools/language-server@1.0.5) (2020-03-08) + +**Note:** Version bump only for package @xml-tools/language-server + +## [1.0.4](https://github.com/SAP/xml-tools/compare/@xml-tools/language-server@1.0.2...@xml-tools/language-server@1.0.4) (2020-03-08) + +### Bug Fixes + +- fix README.md ([472301d](https://github.com/SAP/xml-tools/commit/472301d)) + +## [1.0.3](https://github.com/SAP/xml-tools/compare/@xml-tools/language-server@1.0.2...@xml-tools/language-server@1.0.3) (2020-03-08) + +### Bug Fixes + +- fix README.md ([472301d](https://github.com/SAP/xml-tools/commit/472301d)) + +## [1.0.2](https://github.com/SAP/xml-tools/compare/@xml-tools/language-server@1.0.1...@xml-tools/language-server@1.0.2) (2020-02-19) + +### Bug Fixes + +- add npmignore file to each package ([5bbf209](https://github.com/SAP/xml-tools/commit/5bbf209)) +- missing import ([2a51ab9](https://github.com/SAP/xml-tools/commit/2a51ab9)) + +## 1.0.1 (2020-02-19) + +**Note:** Version bump only for package @xml-tools/language-server diff --git a/projects/xml-tools/packages/language-server/README.md b/projects/xml-tools/packages/language-server/README.md new file mode 100644 index 00000000..fd0dd0cf --- /dev/null +++ b/projects/xml-tools/packages/language-server/README.md @@ -0,0 +1,41 @@ +[![npm (scoped)](https://img.shields.io/npm/v/@xml-tools/language-server.svg)](https://www.npmjs.com/package/@xml-tools/language-server) + +# @xml-tools/language-server + +XML Language Server + +Current Features: + +- Syntax Diagnostics. + +## Installation + +With npm: + +- `npm install @xml-tools/language-server` + +With Yarn + +- `yarn add @xml-tools/language-server` + +## Usage + +Please see the [TypeScript Definitions](./api.d.ts) for full API details. + +A simple usage example: + +```javascript +const { SERVER_PATH } = require("@xml-tools/language-server"); + +// SERVER_PATH is the only API currently and it is meant to expose the "main" module's absolute +// path which would then be executed in a different process +console.log(SERVER_PATH); // --> .../node_module/@xml-tools/language-server/lib/server.js +``` + +## Support + +Please open [issues](https://github.com/SAP/xml-tols/issues) on github. + +## Contributing + +See [CONTRIBUTING.md](./CONTRIBUTING.md). diff --git a/projects/xml-tools/packages/language-server/api.d.ts b/projects/xml-tools/packages/language-server/api.d.ts new file mode 100644 index 00000000..99cda8dc --- /dev/null +++ b/projects/xml-tools/packages/language-server/api.d.ts @@ -0,0 +1,13 @@ +/** + * Absolute path to the server's "main" module + * This is useful when launching the server in a separate process (e.g via spawn). + */ +export const SERVER_PATH: string; + +export type ServerInitializationOptions = { + /** + * optional name of the IDE plugin/extension using this language server. + * This name would be used in the `source` property of dignostic issues. + */ + consumer?: string; +}; diff --git a/projects/xml-tools/packages/language-server/lib/api.js b/projects/xml-tools/packages/language-server/lib/api.js new file mode 100644 index 00000000..24717b72 --- /dev/null +++ b/projects/xml-tools/packages/language-server/lib/api.js @@ -0,0 +1,26 @@ +const { existsSync } = require("fs"); +const { resolve } = require("path"); + +// for use in productive flows +const bundledPath = resolve(__dirname, "..", "dist", "server.js"); +// for dev flows +const sourcesPath = resolve(__dirname, "server.js"); + +// We assume that if the `node_modules` directory exists then we are running +// in development mode, We rely on the fact that the bundled .vsix file of the +// VSCode extension excludes the this package's `node_modules` folder (among others). +// Note this logic would not work with yarn 2.0 / npm 8 / pnpm as `node_modules` +// is no longer created by those package managers. +const isDevelopmentRun = existsSync(resolve(__dirname, "..", "node_modules")); + +let serverPath; +/* istanbul ignore else - no tests (yet?) on bundled artifacts */ +if (isDevelopmentRun) { + serverPath = sourcesPath; +} else { + serverPath = bundledPath; +} + +module.exports = { + SERVER_PATH: serverPath, +}; diff --git a/projects/xml-tools/packages/language-server/lib/constants.js b/projects/xml-tools/packages/language-server/lib/constants.js new file mode 100644 index 00000000..dde36454 --- /dev/null +++ b/projects/xml-tools/packages/language-server/lib/constants.js @@ -0,0 +1,5 @@ +const DEFAULT_CONSUMER_NAME = "XML LS"; + +module.exports = { + DEFAULT_CONSUMER_NAME, +}; diff --git a/projects/xml-tools/packages/language-server/lib/language-services.js b/projects/xml-tools/packages/language-server/lib/language-services.js new file mode 100644 index 00000000..3d016fa3 --- /dev/null +++ b/projects/xml-tools/packages/language-server/lib/language-services.js @@ -0,0 +1,109 @@ +const { DiagnosticSeverity, Range } = require("vscode-languageserver"); +const { map, forEach } = require("lodash"); +const { parse } = require("@xml-tools/parser"); +const { buildAst } = require("@xml-tools/ast"); +const { checkConstraints } = require("@xml-tools/constraints"); + +/** + * @param {TextDocument} document + * @param {import("chevrotain").ILexingError} error + * @returns {import("vscode-languageserver-types").Diagnostic} + */ +function lexingErrorToDiagnostic(document, error) { + return { + message: error.message, + range: Range.create( + document.positionAt(error.offset), + document.positionAt(error.offset + error.length) + ), + severity: DiagnosticSeverity.Error, + }; +} + +/** + * @param {TextDocument} document + * @param {import("chevrotain").ILexingError} error + * @returns {import("vscode-languageserver-types").Diagnostic} + */ +function parsingErrorToDiagnostic(document, error) { + return { + message: error.message, + range: { + start: document.positionAt(error.token.startOffset), + end: document.positionAt( + error.token.endOffset ? error.token.endOffset : 0 + ), + }, + severity: DiagnosticSeverity.Error, + }; +} + +/** + * @param {TextDocument} document + * @param {import("@xml-tools/validation").ValidationIssue} issue + * @returns {import("vscode-languageserver-types").Diagnostic} + */ +function constraintIssueToDiagnostic(document, issue) { + return { + message: issue.msg, + range: { + start: document.positionAt(issue.position.startOffset), + // Chevrotain Token positions are non-inclusive for endOffsets + end: document.positionAt(issue.position.endOffset + 1), + }, + severity: toDiagnosticSeverity(issue.severity), + }; +} + +/** + * @param {"info" | "warning" | "error"} issueSeverity + * @returns {import("vscode-languageserver-types").DiagnosticSeverity} + */ +function toDiagnosticSeverity(issueSeverity) { + switch (issueSeverity) { + case "error": + return DiagnosticSeverity.Error; + case "warning": + return DiagnosticSeverity.Warning; + case "info": + return DiagnosticSeverity.Information; + /* istanbul ignore next -- runtime check */ + default: + throw Error("Non Exhaustive Match"); + } +} + +/** + * @param {TextDocument} document + * @param {object} opts + * @param {string} opts.consumer + * + * @returns {import("vscode-languageserver-types").Diagnostic[]} + */ +async function validateDocument(document, opts) { + let diagnostics = []; + if (document.languageId === "xml") { + const { cst, tokenVector, lexErrors, parseErrors } = parse( + document.getText() + ); + const ast = buildAst(cst, tokenVector); + const constraintsIssues = checkConstraints(ast); + + diagnostics = [ + ...map(lexErrors, (_) => lexingErrorToDiagnostic(document, _)), + ...map(parseErrors, (_) => parsingErrorToDiagnostic(document, _)), + ...map(constraintsIssues, (_) => + constraintIssueToDiagnostic(document, _) + ), + ]; + } + + forEach(diagnostics, (_) => (_.source = opts.consumer)); + + return diagnostics; +} + +module.exports = { + validateDocument: validateDocument, + toDiagnosticSeverity: toDiagnosticSeverity, +}; diff --git a/projects/xml-tools/packages/language-server/lib/server.js b/projects/xml-tools/packages/language-server/lib/server.js new file mode 100644 index 00000000..0fabfa30 --- /dev/null +++ b/projects/xml-tools/packages/language-server/lib/server.js @@ -0,0 +1,37 @@ +const { + createConnection, + ProposedFeatures, + TextDocuments, + TextDocumentSyncKind, +} = require("vscode-languageserver"); +const { TextDocument } = require("vscode-languageserver-textdocument"); +const { has } = require("lodash"); +const { validateDocument } = require("./language-services"); +const { DEFAULT_CONSUMER_NAME } = require("./constants"); + +const connection = createConnection(ProposedFeatures.all); +const documents = new TextDocuments(TextDocument); + +let consumer; + +connection.onInitialize((params) => { + if (has(params, "initializationOptions.consumer")) { + consumer = params.initializationOptions.consumer; + } else { + consumer = DEFAULT_CONSUMER_NAME; + } + + return { + capabilities: { + textDocumentSync: TextDocumentSyncKind.Full, + }, + }; +}); + +documents.onDidChangeContent(async (event) => { + const diagnostics = await validateDocument(event.document, { consumer }); + connection.sendDiagnostics({ uri: event.document.uri, diagnostics }); +}); + +documents.listen(connection); +connection.listen(); diff --git a/projects/xml-tools/packages/language-server/nyc.config.js b/projects/xml-tools/packages/language-server/nyc.config.js new file mode 100644 index 00000000..cba71070 --- /dev/null +++ b/projects/xml-tools/packages/language-server/nyc.config.js @@ -0,0 +1,13 @@ +// TODO: extract to top level nyc config. +module.exports = { + include: ["lib/**/*.js"], + // Not yet sure how to implement an Integration test for the LSP Server + exclude: ["lib/server.js"], + reporter: ["text", "lcov"], + "check-coverage": true, + all: true, + branches: 100, + lines: 100, + functions: 100, + statements: 100, +}; diff --git a/projects/xml-tools/packages/language-server/package.json b/projects/xml-tools/packages/language-server/package.json new file mode 100644 index 00000000..60594763 --- /dev/null +++ b/projects/xml-tools/packages/language-server/package.json @@ -0,0 +1,49 @@ +{ + "name": "@xml-tools/language-server", + "version": "1.2.0", + "description": "XML language server", + "keywords": [ + "LSP", + "language-server", + "XML" + ], + "main": "lib/api.js", + "license": "Apache-2.0", + "repository": { + "type": "git", + "url": "https://github.com/SAP/xml-tools/" + }, + "typings": "./api.d.ts", + "files": [ + "dist", + "lib", + ".reuse", + "LICENSES", + "api.d.ts" + ], + "engines": { + "node": ">=10.0.0" + }, + "dependencies": { + "@xml-tools/constraints": "^1.2.0", + "@xml-tools/parser": "^1.0.11", + "lodash": "4.17.23", + "vscode-languageserver": "7.0.0", + "vscode-languageserver-textdocument": "1.0.1" + }, + "devDependencies": { + "chevrotain": "^7.0.1", + "vscode-languageserver-types": "3.16.0" + }, + "scripts": { + "ci": "npm-run-all clean type-check coverage bundle", + "clean": "rimraf ./dist ./coverage ./nyc_output", + "test": "mocha \"./test/**/*spec.js\"", + "coverage": "nyc mocha \"./test/**/*spec.js\"", + "type-check": "tsc api.d.ts", + "bundle": "webpack --mode production" + }, + "publishConfig": { + "access": "public" + } +} diff --git a/projects/xml-tools/packages/language-server/test/langauge-services.spec.js b/projects/xml-tools/packages/language-server/test/langauge-services.spec.js new file mode 100644 index 00000000..32d00e70 --- /dev/null +++ b/projects/xml-tools/packages/language-server/test/langauge-services.spec.js @@ -0,0 +1,153 @@ +const { resolve } = require("path"); +const { expect } = require("chai"); +const { TextDocument, DiagnosticSeverity } = require("vscode-languageserver"); +const { DEFAULT_CONSUMER_NAME } = require("../lib/constants"); +const { toDiagnosticSeverity } = require("../lib/language-services"); + +const { validateDocument } = require("../lib/language-services"); +const { SERVER_PATH } = require("../lib/api"); + +describe("the XML Language Services", () => { + it("will get the correct path to server module", async () => { + const serverPath = resolve(__dirname, "../", "lib", "server.js"); + expect(SERVER_PATH).to.deep.equal(serverPath); + }); + + it("will detect a parsing error in an xml document", async () => { + const doc = createTextDocument("xml", ">"); + const pos = { line: 0, character: 0 }; + const expectedDiagnostics = [ + { + message: "Expecting token of type --> OPEN <-- but found --> '>' <--", + range: { + start: pos, + end: pos, + }, + severity: DiagnosticSeverity.Error, + source: "XML LS", + }, + ]; + const diagnostics = await validateDocument(doc, { + consumer: DEFAULT_CONSUMER_NAME, + }); + expect(diagnostics).to.deep.equal(expectedDiagnostics); + }); + + it("will detect a lexing error in an xml document", async () => { + const doc = createTextDocument("xml", ""); + const start = { line: 0, character: 5 }; + const pos = { line: 0, character: 6 }; + + const expectedDiagnostics = [ + { + message: `unexpected character: ->!<- at offset: 5, skipped 1 characters.`, + range: { + start: start, + end: pos, + }, + severity: DiagnosticSeverity.Error, + source: "XML LS", + }, + { + message: `Expecting token of type --> Name <-- but found --> '>' <--`, + range: { + start: pos, + end: pos, + }, + severity: DiagnosticSeverity.Error, + source: "XML LS", + }, + ]; + const diagnostics = await validateDocument(doc, { + consumer: DEFAULT_CONSUMER_NAME, + }); + expect(diagnostics).to.deep.equal(expectedDiagnostics); + }); + + it("will detect a well-formedness issue in an xml document", async () => { + const xmlSnippet = ""; + const doc = createTextDocument("xml", xmlSnippet); + + const diagnostics = await validateDocument(doc, { + consumer: DEFAULT_CONSUMER_NAME, + }); + expect(diagnostics).to.have.lengthOf(2); + expect(diagnostics).to.deep.include.members([ + { + message: 'tags mismatch: "a" must match closing tag: "bc"', + range: { + start: { + line: 0, + character: xmlSnippet.indexOf("a"), + }, + end: { + line: 0, + character: xmlSnippet.indexOf("a") + 1, + }, + }, + severity: 1, + source: "XML LS", + }, + { + message: 'tags mismatch: "bc" must match opening tag: "a"', + range: { + start: { + line: 0, + character: xmlSnippet.indexOf("bc"), + }, + end: { + line: 0, + character: xmlSnippet.indexOf("bc") + 2, + }, + }, + severity: 1, + source: "XML LS", + }, + ]); + }); + + it("will not detect any error in a none XML document", async () => { + const doc = createTextDocument("txt", ">"); + const diagnostics = await validateDocument(doc, { + consumer: DEFAULT_CONSUMER_NAME, + }); + expect(diagnostics).to.deep.equal([]); + }); + + context("severity transformations", () => { + it("will transform `error` severity correctly", () => { + expect(toDiagnosticSeverity("error")).to.equal(DiagnosticSeverity.Error); + }); + + it("will transform `warning` severity correctly", () => { + expect(toDiagnosticSeverity("warning")).to.equal( + DiagnosticSeverity.Warning + ); + }); + + it("will transform `info` severity correctly", () => { + expect(toDiagnosticSeverity("info")).to.equal( + DiagnosticSeverity.Information + ); + }); + }); + + context("custom diagnostic `source` support", () => { + it("will use the `consumer` option in `validateDocument` as the diagnostics `source` prop", async () => { + const doc = createTextDocument("xml", ">"); + const diagnostics = await validateDocument(doc, { + consumer: "my xml eclipse plugin", + }); + expect(diagnostics).to.have.lengthOf(1); + expect(diagnostics[0].source).to.equal("my xml eclipse plugin"); + }); + }); +}); + +/** + * @param {string} languageId + * @param {string} content + */ +function createTextDocument(languageId, content) { + return TextDocument.create("uri", languageId, 0, content); +} diff --git a/projects/xml-tools/packages/language-server/webpack.config.js b/projects/xml-tools/packages/language-server/webpack.config.js new file mode 100644 index 00000000..7225a992 --- /dev/null +++ b/projects/xml-tools/packages/language-server/webpack.config.js @@ -0,0 +1,14 @@ +const path = require("path"); +const baseConfig = require("../../webpack.config.base"); + +const config = { + ...baseConfig, + entry: "./lib/server.js", + output: { + path: path.resolve(__dirname, "dist"), + filename: "server.js", + libraryTarget: "commonjs2", + devtoolModuleFilenameTemplate: "../[resource-path]", + }, +}; +module.exports = config; diff --git a/projects/xml-tools/packages/parser/.npmignore b/projects/xml-tools/packages/parser/.npmignore new file mode 100644 index 00000000..ae12335e --- /dev/null +++ b/projects/xml-tools/packages/parser/.npmignore @@ -0,0 +1,10 @@ +# NYC +.nyc_output +nyc.config.js +coverage + +# tests +test + +# contributing +CONTRIBUTING.md \ No newline at end of file diff --git a/projects/xml-tools/packages/parser/CHANGELOG.md b/projects/xml-tools/packages/parser/CHANGELOG.md new file mode 100644 index 00000000..9fa22d2e --- /dev/null +++ b/projects/xml-tools/packages/parser/CHANGELOG.md @@ -0,0 +1,109 @@ +# Change Log + +All notable changes to this project will be documented in this file. +See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + +## [1.0.11](https://github.com/sap/xml-tools/compare/@xml-tools/parser@1.0.10...@xml-tools/parser@1.0.11) (2021-06-03) + +**Note:** Version bump only for package @xml-tools/parser + +## [1.0.10](https://github.com/sap/xml-tools/compare/@xml-tools/parser@1.0.9...@xml-tools/parser@1.0.10) (2021-02-09) + +**Note:** Version bump only for package @xml-tools/parser + +## [1.0.9](https://github.com/sap/xml-tools/compare/@xml-tools/parser@1.0.8...@xml-tools/parser@1.0.9) (2021-01-20) + +### Bug Fixes + +- missing lib folder in npm packages ([ffed3c2](https://github.com/sap/xml-tools/commit/ffed3c2c54c70aea8b9ded0d53786382bc190cc5)) + +## [1.0.8](https://github.com/sap/xml-tools/compare/@xml-tools/parser@1.0.7...@xml-tools/parser@1.0.8) (2021-01-20) + +**Note:** Version bump only for package @xml-tools/parser + +## [1.0.7](https://github.com/sap/xml-tools/compare/@xml-tools/parser@1.0.6...@xml-tools/parser@1.0.7) (2020-06-29) + +**Note:** Version bump only for package @xml-tools/parser + +## [1.0.6](https://github.com/sap/xml-tools/compare/@xml-tools/parser@1.0.5...@xml-tools/parser@1.0.6) (2020-06-18) + +**Note:** Version bump only for package @xml-tools/parser + +## [1.0.5](https://github.com/sap/xml-tools/compare/@xml-tools/parser@1.0.4...@xml-tools/parser@1.0.5) (2020-05-17) + +**Note:** Version bump only for package @xml-tools/parser + +## [1.0.4](https://github.com/sap/xml-tools/compare/@xml-tools/parser@1.0.3...@xml-tools/parser@1.0.4) (2020-04-02) + +**Note:** Version bump only for package @xml-tools/parser + +## [1.0.3](https://github.com/sap/xml-tools/compare/@xml-tools/parser@1.0.2...@xml-tools/parser@1.0.3) (2020-02-19) + +### Bug Fixes + +- add npmignore file to each package ([5bbf209](https://github.com/sap/xml-tools/commit/5bbf209)) + +## [1.0.2](https://github.com/sap/xml-tools/compare/@xml-tools/parser@1.0.1...@xml-tools/parser@1.0.2) (2020-02-11) + +### Bug Fixes + +- **parser:** names may start with an underscore '\_' ([#103](https://github.com/sap/xml-tools/issues/103)) ([63dfd8f](https://github.com/sap/xml-tools/commit/63dfd8f)) + +## [1.0.1](https://github.com/sap/xml-tools/compare/@xml-tools/parser@1.0.0...@xml-tools/parser@1.0.1) (2020-01-28) + +### Bug Fixes + +- **parser:** allow CData nodes to be multi-line ([#89](https://github.com/sap/xml-tools/issues/89)) ([2b2fbfb](https://github.com/sap/xml-tools/commit/2b2fbfb)) + +# [1.0.0](https://github.com/sap/xml-tools/compare/@xml-tools/parser@0.4.0...@xml-tools/parser@1.0.0) (2020-01-20) + +### Features + +- **parser:** support Basic DocType Declarations ([5b4db21](https://github.com/sap/xml-tools/commit/5b4db21)) + +### BREAKING CHANGES + +- **parser:** Implementing XmlCstVisitor now requires implementing two additional methods: + (docTypeDecl and externalID) + +# [0.4.0](https://github.com/sap/xml-tools/compare/@xml-tools/parser@0.3.4...@xml-tools/parser@0.4.0) (2019-11-20) + +### Bug Fixes + +- **parser:** parsing for multi-line comments ([#46](https://github.com/sap/xml-tools/issues/46)) ([4970e9a](https://github.com/sap/xml-tools/commit/4970e9a)) + +### Features + +- **parser:** expose The Token Vector on the parse result ([31d50f3](https://github.com/sap/xml-tools/commit/31d50f3)) + +## [0.3.4](https://github.com/sap/xml-tools/compare/@xml-tools/parser@0.3.3...@xml-tools/parser@0.3.4) (2019-11-12) + +### Bug Fixes + +- **parser:** fix Lexing of XML processing instructions ([2c503b4](https://github.com/sap/xml-tools/commit/2c503b4)) + +## [0.3.3](https://github.com/sap/xml-tools/compare/@xml-tools/parser@0.3.2...@xml-tools/parser@0.3.3) (2019-11-11) + +### Bug Fixes + +- more CST Visitor API fixes ([89d1cef](https://github.com/sap/xml-tools/commit/89d1cef)) + +## [0.3.2](https://github.com/sap/xml-tools/compare/@xml-tools/parser@0.3.1...@xml-tools/parser@0.3.2) (2019-11-11) + +### Bug Fixes + +- cST Visitor method's Params Type fix ([a0a0e2d](https://github.com/sap/xml-tools/commit/a0a0e2d)) + +## [0.3.1](https://github.com/sap/xml-tools/compare/@xml-tools/parser@0.3.0...@xml-tools/parser@0.3.1) (2019-11-06) + +**Note:** Version bump only for package @xml-tools/parser + +# 0.3.0 (2019-10-28) + +### Features + +- move to lerna independent mode ([6c347a8](https://github.com/sap/xml-tools/commit/6c347a8)), closes [#18](https://github.com/sap/xml-tools/issues/18) + +# 0.2.0 (2019-10-28) + +# 0.1.0 (2019-10-28) diff --git a/projects/xml-tools/packages/parser/CONTRIBUTING.md b/projects/xml-tools/packages/parser/CONTRIBUTING.md new file mode 100644 index 00000000..84eb9447 --- /dev/null +++ b/projects/xml-tools/packages/parser/CONTRIBUTING.md @@ -0,0 +1,4 @@ +# Contribution Guide + +This package does not have any unique development flows. +Please see the top level [Contribution Guide](../../CONTRIBUTING.md). diff --git a/projects/xml-tools/packages/parser/README.md b/projects/xml-tools/packages/parser/README.md new file mode 100644 index 00000000..94dfae17 --- /dev/null +++ b/projects/xml-tools/packages/parser/README.md @@ -0,0 +1,54 @@ +[![npm (scoped)](https://img.shields.io/npm/v/@xml-tools/parser.svg)](https://www.npmjs.com/package/@xml-tools/parser) + +# @xml-tools/parser + +A Fault Tolerant XML Parser which produces a [Concrete Syntax Tree][cst]. + +This means that the Parser will **not** stop on the first error and instead attempt to perform automatic error recovery. +This also means that the CST outputted by the Parser may only have **partial** results. +For example, In a valid XML an attribute must always have a value, however in the CST produced +by this parser an attribute's value may be missing as the XML Text input is not necessarily valid. + +The CST produced by this parser is often used as the input for other packages in the xml-tools scope, e.g: + +- [@xml-tools/ast](../ast) As the input for building an XML AST. +- [@xml-tools/content-assist](../content-assist) As part of the input for the content assist APIs. + +## Installation + +With npm: + +- `npm install @xml-tools/parser` + +With Yarn + +- `yarn add @xml-tools/parser` + +## Usage + +Please see the [TypeScript Definitions](./api.d.ts) for full API details. + +A simple usage example: + +```javascript +const { parse } = require("@xml-tools/parser"); + +const xmlText = ` + Bill + Tim + +`; + +const { cst, lexErrors, parseErrors } = parse(xmlText); +console.log(cst.children["element"][0].children["Name"][0].image); // -> note +``` + +## Support + +Please open [issues](https://github.com/SAP/xml-tols/issues) on github. + +## Contributing + +See [CONTRIBUTING.md](./CONTRIBUTING.md). + +[cst]: https://en.wikipedia.org/wiki/Parse_tree diff --git a/projects/xml-tools/packages/parser/api.d.ts b/projects/xml-tools/packages/parser/api.d.ts new file mode 100644 index 00000000..0805e47b --- /dev/null +++ b/projects/xml-tools/packages/parser/api.d.ts @@ -0,0 +1,186 @@ +import { + CstNode, + ICstVisitor, + ILexingError, + IRecognitionException, + IToken, +} from "chevrotain"; + +export function parse( + text: string, + startProduction?: string +): { + cst: CstNode; + tokenVector: IToken[]; + // TODO: decouple from Chevrotain APIs? + lexErrors: ILexingError[]; + parseErrors: IRecognitionException[]; +}; + +export const BaseXmlCstVisitor: XmlCstVisitorConstructor; +export const BaseXmlCstVisitorWithDefaults: XmlCstVisitorWithDefaultsConstructor< + any, + any +>; + +export abstract class XmlCstVisitor implements ICstVisitor { + // No need to implement these two methods + // Generic Visit method implemented by the Chevrotain Library + visit(cstNode: CstNode | CstNode[], param?: IN): OUT; + validateVisitor(): void; + + document(ctx: DocumentCtx, param?: IN): OUT; + prolog(ctx: PrologCtx, param?: IN): OUT; + docTypeDecl(ctx: DocTypeDeclCtx, param?: IN): OUT; + externalID(ctx: ExternalIDCtx, param?: IN): OUT; + content(ctx: ContentCtx, param?: IN): OUT; + element(ctx: ElementCtx, param?: IN): OUT; + reference(ctx: ReferenceCtx, param?: IN): OUT; + attribute(ctx: AttributeCtx, param?: IN): OUT; + chardata(ctx: ChardataCtx, param?: IN): OUT; + misc(ctx: MiscCtx, param?: IN): OUT; +} + +interface XmlCstVisitorConstructor { + new (): XmlCstVisitor; +} + +export abstract class XmlCstVisitorWithDefaults + implements ICstVisitor { + // No need to implement these two methods + // Generic Visit method implemented by the Chevrotain Library + visit(cstNode: CstNode | CstNode[], param?: IN): OUT; + validateVisitor(): void; + + document(ctx: DocumentCtx, param?: IN): OUT; + prolog(ctx: PrologCtx, param?: IN): OUT; + docTypeDecl(ctx: DocTypeDeclCtx, param?: IN): OUT; + externalID(ctx: ExternalIDCtx, param?: IN): OUT; + content(ctx: ContentCtx, param?: IN): OUT; + element(ctx: ElementCtx, param?: IN): OUT; + reference(ctx: ReferenceCtx, param?: IN): OUT; + attribute(ctx: AttributeCtx, param?: IN): OUT; + chardata(ctx: ChardataCtx, param?: IN): OUT; + misc(ctx: MiscCtx, param?: IN): OUT; +} + +interface XmlCstVisitorWithDefaultsConstructor { + new (): XmlCstVisitorWithDefaults; +} + +export interface DocumentCstNode extends CstNode { + name: "document"; + children: DocumentCtx; +} +export type DocumentCtx = { + prolog: PrologCstNode[]; + docTypeDecl: DocTypeDeclNode[]; + misc: MiscCstNode[]; + element: ElementCstNode[]; +}; + +export interface PrologCstNode extends CstNode { + name: "prolog"; + children: PrologCtx; +} + +export type PrologCtx = { + XMLDeclOpen: IToken[]; + attribute: AttributeCstNode[]; + SPECIAL_CLOSE: IToken[]; +}; + +export interface DocTypeDeclNode extends CstNode { + name: "docTypeDecl"; + children: DocTypeDeclCtx; +} + +export type DocTypeDeclCtx = { + DocType: IToken[]; + Name: IToken[]; + externalID: ExternalIDNode[]; +}; + +export interface ExternalIDNode extends CstNode { + name: "ExternalIDNode"; + children: ExternalIDCtx; +} + +export type ExternalIDCtx = { + System: IToken[]; + Public: IToken[]; + PubIDLiteral: IToken[]; + SystemLiteral: IToken[]; +}; + +export interface ContentCstNode extends CstNode { + name: "content"; + children: ContentCtx; +} + +export type ContentCtx = { + chardata: ChardataCstNode[]; + element: ElementCstNode[]; + reference: ReferenceCstNode[]; + CData: IToken[]; + Comment: IToken[]; +}; + +export interface ElementCstNode extends CstNode { + name: "element"; + children: ElementCtx; +} + +export type ElementCtx = { + OPEN: IToken[]; + Name: IToken[]; + attribute: AttributeCstNode[]; + START_CLOSE: IToken[]; + content: ContentCstNode[]; + SLASH_OPEN: IToken[]; + END_NAME: IToken[]; + END: IToken[]; + SLASH_CLOSE: IToken[]; +}; + +export interface ReferenceCstNode extends CstNode { + name: "reference"; + children: ReferenceCtx; +} + +export type ReferenceCtx = { + EntityRef: IToken[]; + CharRef: IToken[]; +}; + +export interface AttributeCstNode extends CstNode { + name: "attribute"; + children: AttributeCtx; +} + +export type AttributeCtx = { + Name: IToken[]; + EQUALS: IToken[]; + STRING: IToken[]; +}; + +export interface ChardataCstNode extends CstNode { + name: "chardata"; + children: ChardataCtx; +} + +export type ChardataCtx = { + TEXT: IToken[]; + SEA_WS: IToken[]; +}; + +export interface MiscCstNode extends CstNode { + name: "misc"; + children: MiscCtx; +} + +export type MiscCtx = { + Comment: IToken[]; + PROCESSING_INSTRUCTION: IToken[]; + SEA_WS: IToken[]; +}; diff --git a/projects/xml-tools/packages/parser/lib/api.js b/projects/xml-tools/packages/parser/lib/api.js new file mode 100644 index 00000000..8e6d8ea3 --- /dev/null +++ b/projects/xml-tools/packages/parser/lib/api.js @@ -0,0 +1,21 @@ +const { xmlLexer } = require("./lexer"); +const { xmlParser } = require("./parser"); + +module.exports = { + parse: function parse(text) { + const lexResult = xmlLexer.tokenize(text); + // setting a new input will RESET the parser instance's state. + xmlParser.input = lexResult.tokens; + // any top level rule may be used as an entry point + const cst = xmlParser.document(); + + return { + cst: cst, + tokenVector: lexResult.tokens, + lexErrors: lexResult.errors, + parseErrors: xmlParser.errors, + }; + }, + + BaseXmlCstVisitor: xmlParser.getBaseCstVisitorConstructor(), +}; diff --git a/projects/xml-tools/packages/parser/lib/lexer.js b/projects/xml-tools/packages/parser/lib/lexer.js new file mode 100644 index 00000000..e7e0686e --- /dev/null +++ b/projects/xml-tools/packages/parser/lib/lexer.js @@ -0,0 +1,206 @@ +const { createToken: createTokenOrg, Lexer } = require("chevrotain"); + +// A little mini DSL for easier lexer definition. +const fragments = {}; +const f = fragments; + +function FRAGMENT(name, def) { + fragments[name] = typeof def === "string" ? def : def.source; +} + +function makePattern(strings, ...args) { + let combined = ""; + for (let i = 0; i < strings.length; i++) { + combined += strings[i]; + if (i < args.length) { + let pattern = args[i]; + // By wrapping in a RegExp (none) capturing group + // We enabled the safe usage of qualifiers and assertions. + combined += `(?:${pattern})`; + } + } + return new RegExp(combined); +} + +const tokensArray = []; +const tokensDictionary = {}; + +function createToken(options) { + const newTokenType = createTokenOrg(options); + tokensArray.push(newTokenType); + tokensDictionary[options.name] = newTokenType; + return newTokenType; +} + +FRAGMENT( + "NameStartChar", + "(:|[a-zA-Z]|_|\\u2070-\\u218F|\\u2C00-\\u2FEF|\\u3001-\\uD7FF|\\uF900-\\uFDCF|\\uFDF0-\\uFFFD)" +); + +FRAGMENT( + "NameChar", + makePattern`${f.NameStartChar}|-|\\.|\\d|\\u00B7||[\\u0300-\\u036F]|[\\u203F-\\u2040]` +); +FRAGMENT("Name", makePattern`${f.NameStartChar}(${f.NameChar})*`); + +const Comment = createToken({ + name: "Comment", + pattern: //, + // A Comment may span multiple lines. + line_breaks: true, +}); + +const CData = createToken({ + name: "CData", + pattern: //, + line_breaks: true, +}); + +const DocType = createToken({ + name: "DocType", + pattern: //, + group: Lexer.SKIPPED, +}); + +const EntityRef = createToken({ + name: "EntityRef", + pattern: makePattern`&${f.Name};`, +}); + +const CharRef = createToken({ + name: "CharRef", + pattern: /&#\d+;|&#x[a-fA-F0-9]/, +}); + +const SEA_WS = createToken({ + name: "SEA_WS", + pattern: /( |\t|\n|\r\n)+/, +}); + +const XMLDeclOpen = createToken({ + name: "XMLDeclOpen", + pattern: /<\?xml[ \t\r\n]/, + push_mode: "INSIDE", +}); + +const SLASH_OPEN = createToken({ + name: "SLASH_OPEN", + pattern: /<\//, + push_mode: "INSIDE", +}); + +const INVALID_SLASH_OPEN = createToken({ + name: "INVALID_SLASH_OPEN", + pattern: /<\//, + categories: [SLASH_OPEN], +}); + +const PROCESSING_INSTRUCTION = createToken({ + name: "PROCESSING_INSTRUCTION", + pattern: makePattern`<\\?${f.Name}.*\\?>`, +}); + +const OPEN = createToken({ name: "OPEN", pattern: /john +// - The second '<' will be skipped because in the mode "INSIDE" '<' is not recognized. +// - This means the AST will include only a single element instead of two +const INVALID_OPEN_INSIDE = createToken({ + name: "INVALID_OPEN_INSIDE", + pattern: //, pop_mode: true }); + +const SPECIAL_CLOSE = createToken({ + name: "SPECIAL_CLOSE", + pattern: /\?>/, + pop_mode: true, +}); + +const SLASH_CLOSE = createToken({ + name: "SLASH_CLOSE", + pattern: /\/>/, + pop_mode: true, +}); + +const SLASH = createToken({ name: "SLASH", pattern: /\// }); + +const STRING = createToken({ + name: "STRING", + pattern: /"[^<"]*"|'[^<']*'/, +}); + +const EQUALS = createToken({ name: "EQUALS", pattern: /=/ }); + +const Name = createToken({ name: "Name", pattern: makePattern`${f.Name}` }); + +const S = createToken({ + name: "S", + pattern: /[ \t\r\n]/, + group: Lexer.SKIPPED, +}); + +const xmlLexerDefinition = { + defaultMode: "OUTSIDE", + + modes: { + OUTSIDE: [ + Comment, + CData, + DocType, + IgnoredDTD, + EntityRef, + CharRef, + SEA_WS, + XMLDeclOpen, + SLASH_OPEN, + PROCESSING_INSTRUCTION, + OPEN, + TEXT, + ], + INSIDE: [ + // Tokens from `OUTSIDE` to improve error recovery behavior + Comment, + INVALID_SLASH_OPEN, + INVALID_OPEN_INSIDE, + // "Real" `INSIDE` tokens + CLOSE, + SPECIAL_CLOSE, + SLASH_CLOSE, + SLASH, + EQUALS, + STRING, + Name, + S, + ], + }, +}; + +const xmlLexer = new Lexer(xmlLexerDefinition, { + // Reducing the amount of position tracking can provide a small performance boost (<10%) + // Likely best to keep the full info for better error position reporting and + // to expose "fuller" ITokens from the Lexer. + positionTracking: "full", + ensureOptimizations: false, + + // TODO: inspect definitions for XML line terminators + lineTerminatorCharacters: ["\n"], + lineTerminatorsPattern: /\n|\r\n/g, +}); + +module.exports = { + xmlLexer, + tokensDictionary, +}; diff --git a/projects/xml-tools/packages/parser/lib/parser.js b/projects/xml-tools/packages/parser/lib/parser.js new file mode 100644 index 00000000..313757c5 --- /dev/null +++ b/projects/xml-tools/packages/parser/lib/parser.js @@ -0,0 +1,211 @@ +const { CstParser, tokenMatcher } = require("chevrotain"); +const { tokensDictionary: t } = require("./lexer"); + +class Parser extends CstParser { + constructor() { + super(t, { + maxLookahead: 1, + recoveryEnabled: true, + nodeLocationTracking: "full", + }); + + this.deletionRecoveryEnabled = true; + + const $ = this; + + $.RULE("document", () => { + $.OPTION(() => { + $.SUBRULE($.prolog); + }); + + $.MANY(() => { + $.SUBRULE($.misc); + }); + + $.OPTION2(() => { + $.SUBRULE($.docTypeDecl); + }); + + $.MANY2(() => { + $.SUBRULE2($.misc); + }); + + $.SUBRULE($.element); + + $.MANY3(() => { + $.SUBRULE3($.misc); + }); + }); + + $.RULE("prolog", () => { + $.CONSUME(t.XMLDeclOpen); + $.MANY(() => { + $.SUBRULE($.attribute); + }); + $.CONSUME(t.SPECIAL_CLOSE); + }); + + // https://www.w3.org/TR/xml/#NT-doctypedecl + $.RULE("docTypeDecl", () => { + $.CONSUME(t.DocType); + $.CONSUME(t.Name); + + $.OPTION(() => { + $.SUBRULE($.externalID); + }); + + // The internal subSet part is intentionally not implemented because we do not at this + // time wish to implement a full DTD Parser as part of this project... + // https://www.w3.org/TR/xml/#NT-intSubset + + $.CONSUME(t.CLOSE); + }); + + $.RULE("externalID", () => { + // Using gates to assert the value of the "Name" Identifiers. + // We could use Categories to model un-reserved keywords, however I am not sure + // The added complexity is needed at this time... + $.OR([ + { + GATE: () => $.LA(1).image === "SYSTEM", + ALT: () => { + $.CONSUME2(t.Name, { LABEL: "System" }); + $.CONSUME(t.STRING, { LABEL: "SystemLiteral" }); + }, + }, + { + GATE: () => $.LA(1).image === "PUBLIC", + ALT: () => { + $.CONSUME3(t.Name, { LABEL: "Public" }); + $.CONSUME2(t.STRING, { LABEL: "PubIDLiteral" }); + $.CONSUME3(t.STRING, { LABEL: "SystemLiteral" }); + }, + }, + ]); + }); + + $.RULE("content", () => { + $.MANY(() => { + $.OR([ + { ALT: () => $.SUBRULE($.element) }, + { ALT: () => $.SUBRULE($.chardata) }, + { ALT: () => $.SUBRULE($.reference) }, + { ALT: () => $.CONSUME(t.CData) }, + { ALT: () => $.CONSUME(t.PROCESSING_INSTRUCTION) }, + { ALT: () => $.CONSUME(t.Comment) }, + ]); + }); + }); + + $.RULE("element", () => { + $.CONSUME(t.OPEN); + try { + this.deletionRecoveryEnabled = false; + // disabling single token deletion here + // because `< + // ` + // will be parsed as: `` + // and the next element will be lost + $.CONSUME(t.Name); + } finally { + this.deletionRecoveryEnabled = true; + } + $.MANY(() => { + $.SUBRULE($.attribute); + }); + + $.OR([ + { + ALT: () => { + $.CONSUME(t.CLOSE, { LABEL: "START_CLOSE" }); + $.SUBRULE($.content); + $.CONSUME(t.SLASH_OPEN); + $.CONSUME2(t.Name, { LABEL: "END_NAME" }); + $.CONSUME2(t.CLOSE, { LABEL: "END" }); + }, + }, + { + ALT: () => { + $.CONSUME(t.SLASH_CLOSE); + }, + }, + ]); + }); + + $.RULE("reference", () => { + $.OR([ + { ALT: () => $.CONSUME(t.EntityRef) }, + { ALT: () => $.CONSUME(t.CharRef) }, + ]); + }); + + $.RULE("attribute", () => { + $.CONSUME(t.Name); + try { + this.deletionRecoveryEnabled = false; + // disabling single token deletion here + // because `attrib1 attrib2="666` + // will be parsed as: `attrib1="666` + $.CONSUME(t.EQUALS); + // disabling single token deletion here + // to avoid new elementName being + $.CONSUME(t.STRING); + } finally { + this.deletionRecoveryEnabled = true; + } + }); + + $.RULE("chardata", () => { + $.OR([ + { ALT: () => $.CONSUME(t.TEXT) }, + { ALT: () => $.CONSUME(t.SEA_WS) }, + ]); + }); + + $.RULE("misc", () => { + $.OR([ + { ALT: () => $.CONSUME(t.Comment) }, + { ALT: () => $.CONSUME(t.PROCESSING_INSTRUCTION) }, + { ALT: () => $.CONSUME(t.SEA_WS) }, + ]); + }); + + this.performSelfAnalysis(); + } + + canRecoverWithSingleTokenDeletion(expectedTokType) { + if (this.deletionRecoveryEnabled === false) { + return false; + } + return super.canRecoverWithSingleTokenDeletion(expectedTokType); + } + + // TODO: provide this fix upstream to chevrotain + // https://github.com/SAP/chevrotain/issues/1055 + /* istanbul ignore next - should be tested as part of Chevrotain */ + findReSyncTokenType() { + const allPossibleReSyncTokTypes = this.flattenFollowSet(); + // this loop will always terminate as EOF is always in the follow stack and also always (virtually) in the input + let nextToken = this.LA(1); + let k = 2; + /* eslint-disable-next-line no-constant-condition -- see above comment */ + while (true) { + const foundMatch = allPossibleReSyncTokTypes.find((resyncTokType) => { + const canMatch = tokenMatcher(nextToken, resyncTokType); + return canMatch; + }); + if (foundMatch !== undefined) { + return foundMatch; + } + nextToken = this.LA(k); + k++; + } + } +} + +// Re-use the same parser instance +const xmlParser = new Parser(); + +module.exports = { + xmlParser, +}; diff --git a/projects/xml-tools/packages/parser/package.json b/projects/xml-tools/packages/parser/package.json new file mode 100644 index 00000000..9a27b583 --- /dev/null +++ b/projects/xml-tools/packages/parser/package.json @@ -0,0 +1,46 @@ +{ + "name": "@xml-tools/parser", + "version": "1.0.11", + "description": "XML Parser Implemented in JavaScript", + "keywords": [ + "xml", + "parser" + ], + "main": "lib/api.js", + "repository": "https://github.com/sap/xml-tools/", + "license": "Apache-2.0", + "typings": "./api.d.ts", + "files": [ + "lib", + ".reuse", + "LICENSES", + "api.d.ts" + ], + "dependencies": { + "chevrotain": "7.1.1" + }, + "devDependencies": { + "klaw-sync": "6.0.0" + }, + "scripts": { + "ci": "npm-run-all clean type-check coverage:*", + "clean": "rimraf ./coverage ./nyc_output", + "test": "mocha \"./test/**/*spec.js\"", + "coverage:run": "nyc mocha \"./test/**/*spec.js\"", + "coverage:check": "nyc check-coverage --lines 100 --branches 100 --statements 100 --functions 100", + "snapshots:update": "node ./scripts/update-snapshots.js", + "type-check": "tsc api.d.ts" + }, + "publishConfig": { + "access": "public" + }, + "nyc": { + "include": [ + "lib/**/*.js" + ], + "reporter": [ + "text", + "lcov" + ] + } +} diff --git a/projects/xml-tools/packages/parser/scripts/update-snapshots.js b/projects/xml-tools/packages/parser/scripts/update-snapshots.js new file mode 100644 index 00000000..4ca4313a --- /dev/null +++ b/projects/xml-tools/packages/parser/scripts/update-snapshots.js @@ -0,0 +1,31 @@ +"use strict"; +const klawSync = require("klaw-sync"); +const { resolve } = require("path"); +const { readFileSync, writeFileSync } = require("fs"); +const { format } = require("prettier"); + +const { transformCstForAssertions } = require("../test/utils"); +const { parse } = require("../"); + +const testsDir = resolve(__dirname, "..", "test"); + +const sampleFiles = klawSync(testsDir, { nodir: true }); +const xmlSampleFiles = sampleFiles.filter((fileDesc) => { + if (fileDesc.path.includes("node_modules")) { + return false; + } + return fileDesc.path.endsWith("input.xml"); +}); + +xmlSampleFiles.forEach((fileDesc) => { + const xmlInput = readFileSync(fileDesc.path, "utf8"); + const simpleNewLinesInput = xmlInput.replace(/\r\n/g, "\n"); + console.log(`Reading <${fileDesc.path}>`); + const { cst } = parse(simpleNewLinesInput); + transformCstForAssertions(cst); + const snapshotOutput = `module.exports = { cst : ${JSON.stringify(cst)}}`; + const formattedSnapshotOutput = format(snapshotOutput, { parser: "babel" }); + const outputFilePath = fileDesc.path.replace(/input.xml$/, "output.js"); + console.log(`writing <${outputFilePath}>`); + writeFileSync(outputFilePath, formattedSnapshotOutput); +}); diff --git a/projects/xml-tools/packages/parser/test/parser-smoke-spec.js b/projects/xml-tools/packages/parser/test/parser-smoke-spec.js new file mode 100644 index 00000000..4e28b236 --- /dev/null +++ b/projects/xml-tools/packages/parser/test/parser-smoke-spec.js @@ -0,0 +1,132 @@ +const { expect } = require("chai"); + +const { parse } = require("../lib/api"); + +describe("The XML Parser", () => { + it("can parse a simple XML without errors", () => { + const inputText = + '\n' + + "\n" + + ' \n' + + " Gambardella, Matthew\n" + + " XML Developer's Guide\n" + + " Computer\n" + + " 44.95\n" + + " 2000-10-01\n" + + " An in-depth look at creating applications \n" + + " with XML.\n" + + " \n" + + ' \n' + + " Ralls, Kim\n" + + " Midnight Rain\n" + + " Fantasy\n" + + " 5.95\n" + + " 2000-12-16\n" + + " A former architect battles corporate zombies, \n" + + " an evil sorceress, and her own childhood to become queen \n" + + " of the world.\n" + + " \n" + + ' \n' + + " Corets, Eva\n" + + " Maeve Ascendant\n" + + " Fantasy\n" + + " 5.95\n" + + " 2000-11-17\n" + + " After the collapse of a nanotechnology \n" + + " society in England, the young survivors lay the \n" + + " foundation for a new society.\n" + + " \n" + + ' \n' + + " Corets, Eva\n" + + " Oberon's Legacy\n" + + " Fantasy\n" + + " 5.95\n" + + " 2001-03-10\n" + + " In post-apocalypse England, the mysterious \n" + + " agent known only as Oberon helps to create a new life \n" + + " for the inhabitants of London. Sequel to Maeve \n" + + " Ascendant.\n" + + " \n" + + ' \n' + + " Corets, Eva\n" + + " The Sundered Grail\n" + + " Fantasy\n" + + " 5.95\n" + + " 2001-09-10\n" + + " The two daughters of Maeve, half-sisters, \n" + + " battle one another for control of England. Sequel to \n" + + " Oberon's Legacy.\n" + + " \n" + + ' \n' + + " Randall, Cynthia\n" + + " Lover Birds\n" + + " Romance\n" + + " 4.95\n" + + " 2000-09-02\n" + + " When Carla meets Paul at an ornithology \n" + + " conference, tempers fly as feathers get ruffled.\n" + + " \n" + + ' \n' + + " Thurman, Paula\n" + + " Splish Splash\n" + + " Romance\n" + + " 4.95\n" + + " 2000-11-02\n" + + " A deep sea diver finds true love twenty \n" + + " thousand leagues beneath the sea.\n" + + " \n" + + ' \n' + + " Knorr, Stefan\n" + + " Creepy Crawlies\n" + + " Horror\n" + + " 4.95\n" + + " 2000-12-06\n" + + " An anthology of horror stories about roaches,\n" + + " centipedes, scorpions and other insects.\n" + + " \n" + + ' \n' + + " Kress, Peter\n" + + " Paradox Lost\n" + + " Science Fiction\n" + + " 6.95\n" + + " 2000-11-02\n" + + " After an inadvertant trip through a Heisenberg\n" + + " Uncertainty Device, James Salway discovers the problems \n" + + " of being quantum.\n" + + " \n" + + ' \n' + + " O'Brien, Tim\n" + + " Microsoft .NET: The Programming Bible\n" + + " Computer\n" + + " 36.95\n" + + " 2000-12-09\n" + + " Microsoft's .NET initiative is explored in \n" + + " detail in this deep programmer's reference.\n" + + " \n" + + ' \n' + + " O'Brien, Tim\n" + + " MSXML3: A Comprehensive Guide\n" + + " Computer\n" + + " 36.95\n" + + " 2000-12-01\n" + + " The Microsoft MSXML3 parser is covered in \n" + + " detail, with attention to XML DOM interfaces, XSLT processing, \n" + + " SAX and more.\n" + + " \n" + + ' \n' + + " Galos, Mike\n" + + " Visual Studio 7: A Comprehensive Guide\n" + + " Computer\n" + + " 49.95\n" + + " 2001-04-16\n" + + " Microsoft Visual Studio 7 is explored in depth,\n" + + " looking at how Visual Basic, Visual C++, C#, and ASP+ are \n" + + " integrated into a comprehensive development \n" + + " environment.\n" + + " \n" + + ""; + + const lexAndParseResult = parse(inputText); + expect(lexAndParseResult.parseErrors).to.be.empty; + }); +}); diff --git a/projects/xml-tools/packages/parser/test/sample-test.js b/projects/xml-tools/packages/parser/test/sample-test.js new file mode 100644 index 00000000..0d98e827 --- /dev/null +++ b/projects/xml-tools/packages/parser/test/sample-test.js @@ -0,0 +1,33 @@ +const { expect } = require("chai"); +const { readFileSync } = require("fs"); +const { resolve, basename } = require("path"); +const { partialRight } = require("lodash"); + +const { transformCstForAssertions } = require("./utils"); +const { parse } = require("../"); + +function executeSampleTest(dirPath, assertNoErrors) { + it("Can Parse an XML text", () => { + const inputPath = resolve(dirPath, "input.xml"); + const inputText = readFileSync(inputPath).toString("utf8"); + const simpleNewLinesInput = inputText.replace(/\r\n/g, "\n"); + const { cst, lexErrors, parseErrors } = parse(simpleNewLinesInput); + if (assertNoErrors === true) { + expect(lexErrors).to.be.empty; + expect(parseErrors).to.be.empty; + } + transformCstForAssertions(cst); + const expectedOutput = require(resolve(dirPath, "output.js")).cst; + expect(cst).to.deep.eql(expectedOutput); + }); +} + +function testNameFromDir(dirPath) { + return basename(dirPath); +} + +module.exports = { + executeValidSampleTest: partialRight(executeSampleTest, true), + executeInValidSampleTest: partialRight(executeSampleTest, false), + testNameFromDir: testNameFromDir, +}; diff --git a/projects/xml-tools/packages/parser/test/snapshots/invalid/attributes/input.xml b/projects/xml-tools/packages/parser/test/snapshots/invalid/attributes/input.xml new file mode 100644 index 00000000..c1d7025c --- /dev/null +++ b/projects/xml-tools/packages/parser/test/snapshots/invalid/attributes/input.xml @@ -0,0 +1 @@ + diff --git a/projects/xml-tools/packages/parser/test/snapshots/invalid/attributes/output.js b/projects/xml-tools/packages/parser/test/snapshots/invalid/attributes/output.js new file mode 100644 index 00000000..af1f3251 --- /dev/null +++ b/projects/xml-tools/packages/parser/test/snapshots/invalid/attributes/output.js @@ -0,0 +1,129 @@ +module.exports = { + cst: { + name: "document", + children: { + element: [ + { + name: "element", + children: { + OPEN: [ + { image: "<", startOffset: 0, endOffset: 0, tokenType: "OPEN" }, + ], + Name: [ + { + image: "note", + startOffset: 1, + endOffset: 4, + tokenType: "Name", + }, + ], + attribute: [ + { + name: "attribute", + children: { + Name: [ + { + image: "attr1", + startOffset: 6, + endOffset: 10, + tokenType: "Name", + }, + ], + }, + location: { startOffset: 6, endOffset: 10 }, + recoveredNode: true, + }, + { + name: "attribute", + children: { + Name: [ + { + image: "attr2", + startOffset: 12, + endOffset: 16, + tokenType: "Name", + }, + ], + EQUALS: [ + { + image: "=", + startOffset: 17, + endOffset: 17, + tokenType: "EQUALS", + }, + ], + STRING: [ + { + image: '"333"', + startOffset: 18, + endOffset: 22, + tokenType: "STRING", + }, + ], + }, + location: { startOffset: 12, endOffset: 22 }, + }, + ], + START_CLOSE: [ + { + image: ">", + startOffset: 23, + endOffset: 23, + tokenType: "CLOSE", + }, + ], + content: [ + { + name: "content", + children: {}, + location: { startOffset: null, endOffset: null }, + }, + ], + SLASH_OPEN: [ + { + image: "", + startOffset: 30, + endOffset: 30, + tokenType: "CLOSE", + }, + ], + }, + location: { startOffset: 0, endOffset: 30 }, + }, + ], + misc: [ + { + name: "misc", + children: { + SEA_WS: [ + { + image: "\n", + startOffset: 31, + endOffset: 31, + tokenType: "SEA_WS", + }, + ], + }, + location: { startOffset: 31, endOffset: 31 }, + }, + ], + }, + location: { startOffset: 0, endOffset: 31 }, + }, +}; diff --git a/projects/xml-tools/packages/parser/test/snapshots/invalid/attributes/sample.spec.js b/projects/xml-tools/packages/parser/test/snapshots/invalid/attributes/sample.spec.js new file mode 100644 index 00000000..7e6e9a0b --- /dev/null +++ b/projects/xml-tools/packages/parser/test/snapshots/invalid/attributes/sample.spec.js @@ -0,0 +1,7 @@ +const { + executeInValidSampleTest, + testNameFromDir, +} = require("../../../sample-test"); +describe(`${testNameFromDir(__dirname)}`, () => { + executeInValidSampleTest(__dirname); +}); diff --git a/projects/xml-tools/packages/parser/test/snapshots/invalid/element-closing-name/input.xml b/projects/xml-tools/packages/parser/test/snapshots/invalid/element-closing-name/input.xml new file mode 100644 index 00000000..c2024272 --- /dev/null +++ b/projects/xml-tools/packages/parser/test/snapshots/invalid/element-closing-name/input.xml @@ -0,0 +1 @@ + diff --git a/projects/xml-tools/packages/parser/test/snapshots/invalid/element-closing-name/output.js b/projects/xml-tools/packages/parser/test/snapshots/invalid/element-closing-name/output.js new file mode 100644 index 00000000..9c9493ca --- /dev/null +++ b/projects/xml-tools/packages/parser/test/snapshots/invalid/element-closing-name/output.js @@ -0,0 +1,77 @@ +module.exports = { + cst: { + name: "document", + children: { + element: [ + { + name: "element", + children: { + OPEN: [ + { image: "<", startOffset: 0, endOffset: 0, tokenType: "OPEN" }, + ], + Name: [ + { + image: "note", + startOffset: 1, + endOffset: 4, + tokenType: "Name", + }, + ], + START_CLOSE: [ + { image: ">", startOffset: 5, endOffset: 5, tokenType: "CLOSE" }, + ], + content: [ + { + name: "content", + children: {}, + location: { startOffset: null, endOffset: null }, + }, + ], + SLASH_OPEN: [ + { + image: "", + startOffset: 18, + endOffset: 18, + tokenType: "CLOSE", + }, + ], + }, + location: { startOffset: 0, endOffset: 18 }, + }, + ], + misc: [ + { + name: "misc", + children: { + SEA_WS: [ + { + image: "\n", + startOffset: 19, + endOffset: 19, + tokenType: "SEA_WS", + }, + ], + }, + location: { startOffset: 19, endOffset: 19 }, + }, + ], + }, + location: { startOffset: 0, endOffset: 19 }, + }, +}; diff --git a/projects/xml-tools/packages/parser/test/snapshots/invalid/element-closing-name/sample.spec.js b/projects/xml-tools/packages/parser/test/snapshots/invalid/element-closing-name/sample.spec.js new file mode 100644 index 00000000..7e6e9a0b --- /dev/null +++ b/projects/xml-tools/packages/parser/test/snapshots/invalid/element-closing-name/sample.spec.js @@ -0,0 +1,7 @@ +const { + executeInValidSampleTest, + testNameFromDir, +} = require("../../../sample-test"); +describe(`${testNameFromDir(__dirname)}`, () => { + executeInValidSampleTest(__dirname); +}); diff --git a/projects/xml-tools/packages/parser/test/snapshots/valid/attributes/input.xml b/projects/xml-tools/packages/parser/test/snapshots/valid/attributes/input.xml new file mode 100644 index 00000000..c11455bd --- /dev/null +++ b/projects/xml-tools/packages/parser/test/snapshots/valid/attributes/input.xml @@ -0,0 +1 @@ + diff --git a/projects/xml-tools/packages/parser/test/snapshots/valid/attributes/output.js b/projects/xml-tools/packages/parser/test/snapshots/valid/attributes/output.js new file mode 100644 index 00000000..68f930f4 --- /dev/null +++ b/projects/xml-tools/packages/parser/test/snapshots/valid/attributes/output.js @@ -0,0 +1,144 @@ +module.exports = { + cst: { + name: "document", + children: { + element: [ + { + name: "element", + children: { + OPEN: [ + { image: "<", startOffset: 0, endOffset: 0, tokenType: "OPEN" }, + ], + Name: [ + { + image: "note", + startOffset: 1, + endOffset: 4, + tokenType: "Name", + }, + ], + attribute: [ + { + name: "attribute", + children: { + Name: [ + { + image: "attr1", + startOffset: 6, + endOffset: 10, + tokenType: "Name", + }, + ], + EQUALS: [ + { + image: "=", + startOffset: 11, + endOffset: 11, + tokenType: "EQUALS", + }, + ], + STRING: [ + { + image: '"666"', + startOffset: 12, + endOffset: 16, + tokenType: "STRING", + }, + ], + }, + location: { startOffset: 6, endOffset: 16 }, + }, + { + name: "attribute", + children: { + Name: [ + { + image: "_attr2", + startOffset: 18, + endOffset: 23, + tokenType: "Name", + }, + ], + EQUALS: [ + { + image: "=", + startOffset: 24, + endOffset: 24, + tokenType: "EQUALS", + }, + ], + STRING: [ + { + image: '"333"', + startOffset: 25, + endOffset: 29, + tokenType: "STRING", + }, + ], + }, + location: { startOffset: 18, endOffset: 29 }, + }, + ], + START_CLOSE: [ + { + image: ">", + startOffset: 30, + endOffset: 30, + tokenType: "CLOSE", + }, + ], + content: [ + { + name: "content", + children: {}, + location: { startOffset: null, endOffset: null }, + }, + ], + SLASH_OPEN: [ + { + image: "", + startOffset: 37, + endOffset: 37, + tokenType: "CLOSE", + }, + ], + }, + location: { startOffset: 0, endOffset: 37 }, + }, + ], + misc: [ + { + name: "misc", + children: { + SEA_WS: [ + { + image: "\n", + startOffset: 38, + endOffset: 38, + tokenType: "SEA_WS", + }, + ], + }, + location: { startOffset: 38, endOffset: 38 }, + }, + ], + }, + location: { startOffset: 0, endOffset: 38 }, + }, +}; diff --git a/projects/xml-tools/packages/parser/test/snapshots/valid/attributes/sample.spec.js b/projects/xml-tools/packages/parser/test/snapshots/valid/attributes/sample.spec.js new file mode 100644 index 00000000..afb88de9 --- /dev/null +++ b/projects/xml-tools/packages/parser/test/snapshots/valid/attributes/sample.spec.js @@ -0,0 +1,7 @@ +const { + executeValidSampleTest, + testNameFromDir, +} = require("../../../sample-test"); +describe(`${testNameFromDir(__dirname)}`, () => { + executeValidSampleTest(__dirname); +}); diff --git a/projects/xml-tools/packages/parser/test/snapshots/valid/cdata/input.xml b/projects/xml-tools/packages/parser/test/snapshots/valid/cdata/input.xml new file mode 100644 index 00000000..d359a2f0 --- /dev/null +++ b/projects/xml-tools/packages/parser/test/snapshots/valid/cdata/input.xml @@ -0,0 +1,3 @@ + + Hello, world!]]> + diff --git a/projects/xml-tools/packages/parser/test/snapshots/valid/cdata/output.js b/projects/xml-tools/packages/parser/test/snapshots/valid/cdata/output.js new file mode 100644 index 00000000..ed00044d --- /dev/null +++ b/projects/xml-tools/packages/parser/test/snapshots/valid/cdata/output.js @@ -0,0 +1,116 @@ +module.exports = { + cst: { + name: "document", + children: { + element: [ + { + name: "element", + children: { + OPEN: [ + { image: "<", startOffset: 0, endOffset: 0, tokenType: "OPEN" }, + ], + Name: [ + { + image: "note", + startOffset: 1, + endOffset: 4, + tokenType: "Name", + }, + ], + START_CLOSE: [ + { image: ">", startOffset: 5, endOffset: 5, tokenType: "CLOSE" }, + ], + content: [ + { + name: "content", + children: { + chardata: [ + { + name: "chardata", + children: { + SEA_WS: [ + { + image: "\n ", + startOffset: 6, + endOffset: 10, + tokenType: "SEA_WS", + }, + ], + }, + location: { startOffset: 6, endOffset: 10 }, + }, + { + name: "chardata", + children: { + SEA_WS: [ + { + image: "\n", + startOffset: 57, + endOffset: 57, + tokenType: "SEA_WS", + }, + ], + }, + location: { startOffset: 57, endOffset: 57 }, + }, + ], + CData: [ + { + image: "Hello, world!]]>", + startOffset: 11, + endOffset: 56, + tokenType: "CData", + }, + ], + }, + location: { startOffset: 6, endOffset: 57 }, + }, + ], + SLASH_OPEN: [ + { + image: "", + startOffset: 64, + endOffset: 64, + tokenType: "CLOSE", + }, + ], + }, + location: { startOffset: 0, endOffset: 64 }, + }, + ], + misc: [ + { + name: "misc", + children: { + SEA_WS: [ + { + image: "\n", + startOffset: 65, + endOffset: 65, + tokenType: "SEA_WS", + }, + ], + }, + location: { startOffset: 65, endOffset: 65 }, + }, + ], + }, + location: { startOffset: 0, endOffset: 65 }, + }, +}; diff --git a/projects/xml-tools/packages/parser/test/snapshots/valid/cdata/sample.spec.js b/projects/xml-tools/packages/parser/test/snapshots/valid/cdata/sample.spec.js new file mode 100644 index 00000000..afb88de9 --- /dev/null +++ b/projects/xml-tools/packages/parser/test/snapshots/valid/cdata/sample.spec.js @@ -0,0 +1,7 @@ +const { + executeValidSampleTest, + testNameFromDir, +} = require("../../../sample-test"); +describe(`${testNameFromDir(__dirname)}`, () => { + executeValidSampleTest(__dirname); +}); diff --git a/projects/xml-tools/packages/parser/test/snapshots/valid/comments/input.xml b/projects/xml-tools/packages/parser/test/snapshots/valid/comments/input.xml new file mode 100644 index 00000000..f6d16dee --- /dev/null +++ b/projects/xml-tools/packages/parser/test/snapshots/valid/comments/input.xml @@ -0,0 +1,6 @@ + + + + Bill + + diff --git a/projects/xml-tools/packages/parser/test/snapshots/valid/comments/output.js b/projects/xml-tools/packages/parser/test/snapshots/valid/comments/output.js new file mode 100644 index 00000000..9e1d655f --- /dev/null +++ b/projects/xml-tools/packages/parser/test/snapshots/valid/comments/output.js @@ -0,0 +1,271 @@ +module.exports = { + cst: { + name: "document", + children: { + misc: [ + { + name: "misc", + children: { + Comment: [ + { + image: "", + startOffset: 0, + endOffset: 21, + tokenType: "Comment", + }, + ], + }, + location: { startOffset: 0, endOffset: 21 }, + }, + { + name: "misc", + children: { + SEA_WS: [ + { + image: "\n", + startOffset: 22, + endOffset: 22, + tokenType: "SEA_WS", + }, + ], + }, + location: { startOffset: 22, endOffset: 22 }, + }, + { + name: "misc", + children: { + SEA_WS: [ + { + image: "\n", + startOffset: 82, + endOffset: 82, + tokenType: "SEA_WS", + }, + ], + }, + location: { startOffset: 82, endOffset: 82 }, + }, + { + name: "misc", + children: { + Comment: [ + { + image: "", + startOffset: 83, + endOffset: 104, + tokenType: "Comment", + }, + ], + }, + location: { startOffset: 83, endOffset: 104 }, + }, + { + name: "misc", + children: { + SEA_WS: [ + { + image: "\n", + startOffset: 105, + endOffset: 105, + tokenType: "SEA_WS", + }, + ], + }, + location: { startOffset: 105, endOffset: 105 }, + }, + ], + element: [ + { + name: "element", + children: { + OPEN: [ + { image: "<", startOffset: 23, endOffset: 23, tokenType: "OPEN" }, + ], + Name: [ + { + image: "note", + startOffset: 24, + endOffset: 27, + tokenType: "Name", + }, + ], + START_CLOSE: [ + { + image: ">", + startOffset: 28, + endOffset: 28, + tokenType: "CLOSE", + }, + ], + content: [ + { + name: "content", + children: { + chardata: [ + { + name: "chardata", + children: { + SEA_WS: [ + { + image: "\n ", + startOffset: 29, + endOffset: 33, + tokenType: "SEA_WS", + }, + ], + }, + location: { startOffset: 29, endOffset: 33 }, + }, + { + name: "chardata", + children: { + SEA_WS: [ + { + image: "\n ", + startOffset: 56, + endOffset: 60, + tokenType: "SEA_WS", + }, + ], + }, + location: { startOffset: 56, endOffset: 60 }, + }, + { + name: "chardata", + children: { + SEA_WS: [ + { + image: "\n", + startOffset: 74, + endOffset: 74, + tokenType: "SEA_WS", + }, + ], + }, + location: { startOffset: 74, endOffset: 74 }, + }, + ], + Comment: [ + { + image: "", + startOffset: 34, + endOffset: 55, + tokenType: "Comment", + }, + ], + element: [ + { + name: "element", + children: { + OPEN: [ + { + image: "<", + startOffset: 61, + endOffset: 61, + tokenType: "OPEN", + }, + ], + Name: [ + { + image: "to", + startOffset: 62, + endOffset: 63, + tokenType: "Name", + }, + ], + START_CLOSE: [ + { + image: ">", + startOffset: 64, + endOffset: 64, + tokenType: "CLOSE", + }, + ], + content: [ + { + name: "content", + children: { + chardata: [ + { + name: "chardata", + children: { + TEXT: [ + { + image: "Bill", + startOffset: 65, + endOffset: 68, + tokenType: "TEXT", + }, + ], + }, + location: { startOffset: 65, endOffset: 68 }, + }, + ], + }, + location: { startOffset: 65, endOffset: 68 }, + }, + ], + SLASH_OPEN: [ + { + image: "", + startOffset: 73, + endOffset: 73, + tokenType: "CLOSE", + }, + ], + }, + location: { startOffset: 61, endOffset: 73 }, + }, + ], + }, + location: { startOffset: 29, endOffset: 74 }, + }, + ], + SLASH_OPEN: [ + { + image: "", + startOffset: 81, + endOffset: 81, + tokenType: "CLOSE", + }, + ], + }, + location: { startOffset: 23, endOffset: 81 }, + }, + ], + }, + location: { startOffset: 0, endOffset: 105 }, + }, +}; diff --git a/projects/xml-tools/packages/parser/test/snapshots/valid/comments/sample.spec.js b/projects/xml-tools/packages/parser/test/snapshots/valid/comments/sample.spec.js new file mode 100644 index 00000000..afb88de9 --- /dev/null +++ b/projects/xml-tools/packages/parser/test/snapshots/valid/comments/sample.spec.js @@ -0,0 +1,7 @@ +const { + executeValidSampleTest, + testNameFromDir, +} = require("../../../sample-test"); +describe(`${testNameFromDir(__dirname)}`, () => { + executeValidSampleTest(__dirname); +}); diff --git a/projects/xml-tools/packages/parser/test/snapshots/valid/complex-contents/input.xml b/projects/xml-tools/packages/parser/test/snapshots/valid/complex-contents/input.xml new file mode 100644 index 00000000..8aca3184 --- /dev/null +++ b/projects/xml-tools/packages/parser/test/snapshots/valid/complex-contents/input.xml @@ -0,0 +1,2 @@ + + diff --git a/projects/xml-tools/packages/parser/test/snapshots/valid/complex-contents/output.js b/projects/xml-tools/packages/parser/test/snapshots/valid/complex-contents/output.js new file mode 100644 index 00000000..97c8c1fd --- /dev/null +++ b/projects/xml-tools/packages/parser/test/snapshots/valid/complex-contents/output.js @@ -0,0 +1,120 @@ +module.exports = { + cst: { + name: "document", + children: { + prolog: [ + { + name: "prolog", + children: { + XMLDeclOpen: [ + { + image: "", + startOffset: 6, + endOffset: 7, + tokenType: "SPECIAL_CLOSE", + }, + ], + }, + location: { startOffset: 0, endOffset: 7 }, + }, + ], + misc: [ + { + name: "misc", + children: { + SEA_WS: [ + { + image: "\n", + startOffset: 8, + endOffset: 8, + tokenType: "SEA_WS", + }, + ], + }, + location: { startOffset: 8, endOffset: 8 }, + }, + { + name: "misc", + children: { + SEA_WS: [ + { + image: "\n", + startOffset: 22, + endOffset: 22, + tokenType: "SEA_WS", + }, + ], + }, + location: { startOffset: 22, endOffset: 22 }, + }, + ], + element: [ + { + name: "element", + children: { + OPEN: [ + { image: "<", startOffset: 9, endOffset: 9, tokenType: "OPEN" }, + ], + Name: [ + { + image: "note", + startOffset: 10, + endOffset: 13, + tokenType: "Name", + }, + ], + START_CLOSE: [ + { + image: ">", + startOffset: 14, + endOffset: 14, + tokenType: "CLOSE", + }, + ], + content: [ + { + name: "content", + children: {}, + location: { startOffset: null, endOffset: null }, + }, + ], + SLASH_OPEN: [ + { + image: "", + startOffset: 21, + endOffset: 21, + tokenType: "CLOSE", + }, + ], + }, + location: { startOffset: 9, endOffset: 21 }, + }, + ], + }, + location: { startOffset: 0, endOffset: 22 }, + }, +}; diff --git a/projects/xml-tools/packages/parser/test/snapshots/valid/complex-contents/sample.spec.js b/projects/xml-tools/packages/parser/test/snapshots/valid/complex-contents/sample.spec.js new file mode 100644 index 00000000..afb88de9 --- /dev/null +++ b/projects/xml-tools/packages/parser/test/snapshots/valid/complex-contents/sample.spec.js @@ -0,0 +1,7 @@ +const { + executeValidSampleTest, + testNameFromDir, +} = require("../../../sample-test"); +describe(`${testNameFromDir(__dirname)}`, () => { + executeValidSampleTest(__dirname); +}); diff --git a/projects/xml-tools/packages/parser/test/snapshots/valid/doc-type-public/input.xml b/projects/xml-tools/packages/parser/test/snapshots/valid/doc-type-public/input.xml new file mode 100644 index 00000000..6f089fb0 --- /dev/null +++ b/projects/xml-tools/packages/parser/test/snapshots/valid/doc-type-public/input.xml @@ -0,0 +1,2 @@ + + diff --git a/projects/xml-tools/packages/parser/test/snapshots/valid/doc-type-public/output.js b/projects/xml-tools/packages/parser/test/snapshots/valid/doc-type-public/output.js new file mode 100644 index 00000000..62b0b1f7 --- /dev/null +++ b/projects/xml-tools/packages/parser/test/snapshots/valid/doc-type-public/output.js @@ -0,0 +1,160 @@ +module.exports = { + cst: { + name: "document", + children: { + docTypeDecl: [ + { + name: "docTypeDecl", + children: { + DocType: [ + { + image: "", + startOffset: 62, + endOffset: 62, + tokenType: "CLOSE", + }, + ], + }, + location: { startOffset: 0, endOffset: 62 }, + }, + ], + misc: [ + { + name: "misc", + children: { + SEA_WS: [ + { + image: "\n", + startOffset: 63, + endOffset: 63, + tokenType: "SEA_WS", + }, + ], + }, + location: { startOffset: 63, endOffset: 63 }, + }, + { + name: "misc", + children: { + SEA_WS: [ + { + image: "\n", + startOffset: 77, + endOffset: 77, + tokenType: "SEA_WS", + }, + ], + }, + location: { startOffset: 77, endOffset: 77 }, + }, + ], + element: [ + { + name: "element", + children: { + OPEN: [ + { image: "<", startOffset: 64, endOffset: 64, tokenType: "OPEN" }, + ], + Name: [ + { + image: "note", + startOffset: 65, + endOffset: 68, + tokenType: "Name", + }, + ], + START_CLOSE: [ + { + image: ">", + startOffset: 69, + endOffset: 69, + tokenType: "CLOSE", + }, + ], + content: [ + { + name: "content", + children: {}, + location: { startOffset: null, endOffset: null }, + }, + ], + SLASH_OPEN: [ + { + image: "", + startOffset: 76, + endOffset: 76, + tokenType: "CLOSE", + }, + ], + }, + location: { startOffset: 64, endOffset: 76 }, + }, + ], + }, + location: { startOffset: 0, endOffset: 77 }, + }, +}; diff --git a/projects/xml-tools/packages/parser/test/snapshots/valid/doc-type-public/sample.spec.js b/projects/xml-tools/packages/parser/test/snapshots/valid/doc-type-public/sample.spec.js new file mode 100644 index 00000000..afb88de9 --- /dev/null +++ b/projects/xml-tools/packages/parser/test/snapshots/valid/doc-type-public/sample.spec.js @@ -0,0 +1,7 @@ +const { + executeValidSampleTest, + testNameFromDir, +} = require("../../../sample-test"); +describe(`${testNameFromDir(__dirname)}`, () => { + executeValidSampleTest(__dirname); +}); diff --git a/projects/xml-tools/packages/parser/test/snapshots/valid/doc-type-system/input.xml b/projects/xml-tools/packages/parser/test/snapshots/valid/doc-type-system/input.xml new file mode 100644 index 00000000..d9eba62b --- /dev/null +++ b/projects/xml-tools/packages/parser/test/snapshots/valid/doc-type-system/input.xml @@ -0,0 +1,2 @@ + + diff --git a/projects/xml-tools/packages/parser/test/snapshots/valid/doc-type-system/output.js b/projects/xml-tools/packages/parser/test/snapshots/valid/doc-type-system/output.js new file mode 100644 index 00000000..175c063b --- /dev/null +++ b/projects/xml-tools/packages/parser/test/snapshots/valid/doc-type-system/output.js @@ -0,0 +1,152 @@ +module.exports = { + cst: { + name: "document", + children: { + docTypeDecl: [ + { + name: "docTypeDecl", + children: { + DocType: [ + { + image: "", + startOffset: 37, + endOffset: 37, + tokenType: "CLOSE", + }, + ], + }, + location: { startOffset: 0, endOffset: 37 }, + }, + ], + misc: [ + { + name: "misc", + children: { + SEA_WS: [ + { + image: "\n", + startOffset: 38, + endOffset: 38, + tokenType: "SEA_WS", + }, + ], + }, + location: { startOffset: 38, endOffset: 38 }, + }, + { + name: "misc", + children: { + SEA_WS: [ + { + image: "\n", + startOffset: 52, + endOffset: 52, + tokenType: "SEA_WS", + }, + ], + }, + location: { startOffset: 52, endOffset: 52 }, + }, + ], + element: [ + { + name: "element", + children: { + OPEN: [ + { image: "<", startOffset: 39, endOffset: 39, tokenType: "OPEN" }, + ], + Name: [ + { + image: "note", + startOffset: 40, + endOffset: 43, + tokenType: "Name", + }, + ], + START_CLOSE: [ + { + image: ">", + startOffset: 44, + endOffset: 44, + tokenType: "CLOSE", + }, + ], + content: [ + { + name: "content", + children: {}, + location: { startOffset: null, endOffset: null }, + }, + ], + SLASH_OPEN: [ + { + image: "", + startOffset: 51, + endOffset: 51, + tokenType: "CLOSE", + }, + ], + }, + location: { startOffset: 39, endOffset: 51 }, + }, + ], + }, + location: { startOffset: 0, endOffset: 52 }, + }, +}; diff --git a/projects/xml-tools/packages/parser/test/snapshots/valid/doc-type-system/sample.spec.js b/projects/xml-tools/packages/parser/test/snapshots/valid/doc-type-system/sample.spec.js new file mode 100644 index 00000000..afb88de9 --- /dev/null +++ b/projects/xml-tools/packages/parser/test/snapshots/valid/doc-type-system/sample.spec.js @@ -0,0 +1,7 @@ +const { + executeValidSampleTest, + testNameFromDir, +} = require("../../../sample-test"); +describe(`${testNameFromDir(__dirname)}`, () => { + executeValidSampleTest(__dirname); +}); diff --git a/projects/xml-tools/packages/parser/test/snapshots/valid/elements-short-form/input.xml b/projects/xml-tools/packages/parser/test/snapshots/valid/elements-short-form/input.xml new file mode 100644 index 00000000..f9d7191f --- /dev/null +++ b/projects/xml-tools/packages/parser/test/snapshots/valid/elements-short-form/input.xml @@ -0,0 +1,4 @@ + + Bill + + diff --git a/projects/xml-tools/packages/parser/test/snapshots/valid/elements-short-form/output.js b/projects/xml-tools/packages/parser/test/snapshots/valid/elements-short-form/output.js new file mode 100644 index 00000000..ce3dee65 --- /dev/null +++ b/projects/xml-tools/packages/parser/test/snapshots/valid/elements-short-form/output.js @@ -0,0 +1,264 @@ +module.exports = { + cst: { + name: "document", + children: { + element: [ + { + name: "element", + children: { + OPEN: [ + { image: "<", startOffset: 0, endOffset: 0, tokenType: "OPEN" }, + ], + Name: [ + { + image: "note", + startOffset: 1, + endOffset: 4, + tokenType: "Name", + }, + ], + START_CLOSE: [ + { image: ">", startOffset: 5, endOffset: 5, tokenType: "CLOSE" }, + ], + content: [ + { + name: "content", + children: { + chardata: [ + { + name: "chardata", + children: { + SEA_WS: [ + { + image: "\n ", + startOffset: 6, + endOffset: 10, + tokenType: "SEA_WS", + }, + ], + }, + location: { startOffset: 6, endOffset: 10 }, + }, + { + name: "chardata", + children: { + SEA_WS: [ + { + image: "\n ", + startOffset: 24, + endOffset: 28, + tokenType: "SEA_WS", + }, + ], + }, + location: { startOffset: 24, endOffset: 28 }, + }, + { + name: "chardata", + children: { + SEA_WS: [ + { + image: "\n", + startOffset: 47, + endOffset: 47, + tokenType: "SEA_WS", + }, + ], + }, + location: { startOffset: 47, endOffset: 47 }, + }, + ], + element: [ + { + name: "element", + children: { + OPEN: [ + { + image: "<", + startOffset: 11, + endOffset: 11, + tokenType: "OPEN", + }, + ], + Name: [ + { + image: "to", + startOffset: 12, + endOffset: 13, + tokenType: "Name", + }, + ], + START_CLOSE: [ + { + image: ">", + startOffset: 14, + endOffset: 14, + tokenType: "CLOSE", + }, + ], + content: [ + { + name: "content", + children: { + chardata: [ + { + name: "chardata", + children: { + TEXT: [ + { + image: "Bill", + startOffset: 15, + endOffset: 18, + tokenType: "TEXT", + }, + ], + }, + location: { startOffset: 15, endOffset: 18 }, + }, + ], + }, + location: { startOffset: 15, endOffset: 18 }, + }, + ], + SLASH_OPEN: [ + { + image: "", + startOffset: 23, + endOffset: 23, + tokenType: "CLOSE", + }, + ], + }, + location: { startOffset: 11, endOffset: 23 }, + }, + { + name: "element", + children: { + OPEN: [ + { + image: "<", + startOffset: 29, + endOffset: 29, + tokenType: "OPEN", + }, + ], + Name: [ + { + image: "from", + startOffset: 30, + endOffset: 33, + tokenType: "Name", + }, + ], + attribute: [ + { + name: "attribute", + children: { + Name: [ + { + image: "name", + startOffset: 35, + endOffset: 38, + tokenType: "Name", + }, + ], + EQUALS: [ + { + image: "=", + startOffset: 39, + endOffset: 39, + tokenType: "EQUALS", + }, + ], + STRING: [ + { + image: '"tim"', + startOffset: 40, + endOffset: 44, + tokenType: "STRING", + }, + ], + }, + location: { startOffset: 35, endOffset: 44 }, + }, + ], + SLASH_CLOSE: [ + { + image: "/>", + startOffset: 45, + endOffset: 46, + tokenType: "SLASH_CLOSE", + }, + ], + }, + location: { startOffset: 29, endOffset: 46 }, + }, + ], + }, + location: { startOffset: 6, endOffset: 47 }, + }, + ], + SLASH_OPEN: [ + { + image: "", + startOffset: 54, + endOffset: 54, + tokenType: "CLOSE", + }, + ], + }, + location: { startOffset: 0, endOffset: 54 }, + }, + ], + misc: [ + { + name: "misc", + children: { + SEA_WS: [ + { + image: "\n", + startOffset: 55, + endOffset: 55, + tokenType: "SEA_WS", + }, + ], + }, + location: { startOffset: 55, endOffset: 55 }, + }, + ], + }, + location: { startOffset: 0, endOffset: 55 }, + }, +}; diff --git a/projects/xml-tools/packages/parser/test/snapshots/valid/elements-short-form/sample.spec.js b/projects/xml-tools/packages/parser/test/snapshots/valid/elements-short-form/sample.spec.js new file mode 100644 index 00000000..afb88de9 --- /dev/null +++ b/projects/xml-tools/packages/parser/test/snapshots/valid/elements-short-form/sample.spec.js @@ -0,0 +1,7 @@ +const { + executeValidSampleTest, + testNameFromDir, +} = require("../../../sample-test"); +describe(`${testNameFromDir(__dirname)}`, () => { + executeValidSampleTest(__dirname); +}); diff --git a/projects/xml-tools/packages/parser/test/snapshots/valid/elements/input.xml b/projects/xml-tools/packages/parser/test/snapshots/valid/elements/input.xml new file mode 100644 index 00000000..77dfac07 --- /dev/null +++ b/projects/xml-tools/packages/parser/test/snapshots/valid/elements/input.xml @@ -0,0 +1,4 @@ + + Bill + Tim + diff --git a/projects/xml-tools/packages/parser/test/snapshots/valid/elements/output.js b/projects/xml-tools/packages/parser/test/snapshots/valid/elements/output.js new file mode 100644 index 00000000..12e6bbf5 --- /dev/null +++ b/projects/xml-tools/packages/parser/test/snapshots/valid/elements/output.js @@ -0,0 +1,280 @@ +module.exports = { + cst: { + name: "document", + children: { + element: [ + { + name: "element", + children: { + OPEN: [ + { image: "<", startOffset: 0, endOffset: 0, tokenType: "OPEN" }, + ], + Name: [ + { + image: "note", + startOffset: 1, + endOffset: 4, + tokenType: "Name", + }, + ], + START_CLOSE: [ + { image: ">", startOffset: 5, endOffset: 5, tokenType: "CLOSE" }, + ], + content: [ + { + name: "content", + children: { + chardata: [ + { + name: "chardata", + children: { + SEA_WS: [ + { + image: "\n ", + startOffset: 6, + endOffset: 10, + tokenType: "SEA_WS", + }, + ], + }, + location: { startOffset: 6, endOffset: 10 }, + }, + { + name: "chardata", + children: { + SEA_WS: [ + { + image: "\n ", + startOffset: 24, + endOffset: 28, + tokenType: "SEA_WS", + }, + ], + }, + location: { startOffset: 24, endOffset: 28 }, + }, + { + name: "chardata", + children: { + SEA_WS: [ + { + image: "\n", + startOffset: 45, + endOffset: 45, + tokenType: "SEA_WS", + }, + ], + }, + location: { startOffset: 45, endOffset: 45 }, + }, + ], + element: [ + { + name: "element", + children: { + OPEN: [ + { + image: "<", + startOffset: 11, + endOffset: 11, + tokenType: "OPEN", + }, + ], + Name: [ + { + image: "to", + startOffset: 12, + endOffset: 13, + tokenType: "Name", + }, + ], + START_CLOSE: [ + { + image: ">", + startOffset: 14, + endOffset: 14, + tokenType: "CLOSE", + }, + ], + content: [ + { + name: "content", + children: { + chardata: [ + { + name: "chardata", + children: { + TEXT: [ + { + image: "Bill", + startOffset: 15, + endOffset: 18, + tokenType: "TEXT", + }, + ], + }, + location: { startOffset: 15, endOffset: 18 }, + }, + ], + }, + location: { startOffset: 15, endOffset: 18 }, + }, + ], + SLASH_OPEN: [ + { + image: "", + startOffset: 23, + endOffset: 23, + tokenType: "CLOSE", + }, + ], + }, + location: { startOffset: 11, endOffset: 23 }, + }, + { + name: "element", + children: { + OPEN: [ + { + image: "<", + startOffset: 29, + endOffset: 29, + tokenType: "OPEN", + }, + ], + Name: [ + { + image: "from", + startOffset: 30, + endOffset: 33, + tokenType: "Name", + }, + ], + START_CLOSE: [ + { + image: ">", + startOffset: 34, + endOffset: 34, + tokenType: "CLOSE", + }, + ], + content: [ + { + name: "content", + children: { + chardata: [ + { + name: "chardata", + children: { + TEXT: [ + { + image: "Tim", + startOffset: 35, + endOffset: 37, + tokenType: "TEXT", + }, + ], + }, + location: { startOffset: 35, endOffset: 37 }, + }, + ], + }, + location: { startOffset: 35, endOffset: 37 }, + }, + ], + SLASH_OPEN: [ + { + image: "", + startOffset: 44, + endOffset: 44, + tokenType: "CLOSE", + }, + ], + }, + location: { startOffset: 29, endOffset: 44 }, + }, + ], + }, + location: { startOffset: 6, endOffset: 45 }, + }, + ], + SLASH_OPEN: [ + { + image: "", + startOffset: 52, + endOffset: 52, + tokenType: "CLOSE", + }, + ], + }, + location: { startOffset: 0, endOffset: 52 }, + }, + ], + misc: [ + { + name: "misc", + children: { + SEA_WS: [ + { + image: "\n", + startOffset: 53, + endOffset: 53, + tokenType: "SEA_WS", + }, + ], + }, + location: { startOffset: 53, endOffset: 53 }, + }, + ], + }, + location: { startOffset: 0, endOffset: 53 }, + }, +}; diff --git a/projects/xml-tools/packages/parser/test/snapshots/valid/elements/sample.spec.js b/projects/xml-tools/packages/parser/test/snapshots/valid/elements/sample.spec.js new file mode 100644 index 00000000..afb88de9 --- /dev/null +++ b/projects/xml-tools/packages/parser/test/snapshots/valid/elements/sample.spec.js @@ -0,0 +1,7 @@ +const { + executeValidSampleTest, + testNameFromDir, +} = require("../../../sample-test"); +describe(`${testNameFromDir(__dirname)}`, () => { + executeValidSampleTest(__dirname); +}); diff --git a/projects/xml-tools/packages/parser/test/snapshots/valid/entity-char-ref/input.xml b/projects/xml-tools/packages/parser/test/snapshots/valid/entity-char-ref/input.xml new file mode 100644 index 00000000..816e0202 --- /dev/null +++ b/projects/xml-tools/packages/parser/test/snapshots/valid/entity-char-ref/input.xml @@ -0,0 +1,5 @@ + + &to; + Ӓ + Tim + diff --git a/projects/xml-tools/packages/parser/test/snapshots/valid/entity-char-ref/output.js b/projects/xml-tools/packages/parser/test/snapshots/valid/entity-char-ref/output.js new file mode 100644 index 00000000..de9ebca3 --- /dev/null +++ b/projects/xml-tools/packages/parser/test/snapshots/valid/entity-char-ref/output.js @@ -0,0 +1,246 @@ +module.exports = { + cst: { + name: "document", + children: { + element: [ + { + name: "element", + children: { + OPEN: [ + { image: "<", startOffset: 0, endOffset: 0, tokenType: "OPEN" }, + ], + Name: [ + { + image: "note", + startOffset: 1, + endOffset: 4, + tokenType: "Name", + }, + ], + START_CLOSE: [ + { image: ">", startOffset: 5, endOffset: 5, tokenType: "CLOSE" }, + ], + content: [ + { + name: "content", + children: { + chardata: [ + { + name: "chardata", + children: { + SEA_WS: [ + { + image: "\n ", + startOffset: 6, + endOffset: 10, + tokenType: "SEA_WS", + }, + ], + }, + location: { startOffset: 6, endOffset: 10 }, + }, + { + name: "chardata", + children: { + SEA_WS: [ + { + image: "\n ", + startOffset: 15, + endOffset: 19, + tokenType: "SEA_WS", + }, + ], + }, + location: { startOffset: 15, endOffset: 19 }, + }, + { + name: "chardata", + children: { + SEA_WS: [ + { + image: "\n ", + startOffset: 27, + endOffset: 31, + tokenType: "SEA_WS", + }, + ], + }, + location: { startOffset: 27, endOffset: 31 }, + }, + { + name: "chardata", + children: { + SEA_WS: [ + { + image: "\n", + startOffset: 48, + endOffset: 48, + tokenType: "SEA_WS", + }, + ], + }, + location: { startOffset: 48, endOffset: 48 }, + }, + ], + reference: [ + { + name: "reference", + children: { + EntityRef: [ + { + image: "&to;", + startOffset: 11, + endOffset: 14, + tokenType: "EntityRef", + }, + ], + }, + location: { startOffset: 11, endOffset: 14 }, + }, + { + name: "reference", + children: { + CharRef: [ + { + image: "Ӓ", + startOffset: 20, + endOffset: 26, + tokenType: "CharRef", + }, + ], + }, + location: { startOffset: 20, endOffset: 26 }, + }, + ], + element: [ + { + name: "element", + children: { + OPEN: [ + { + image: "<", + startOffset: 32, + endOffset: 32, + tokenType: "OPEN", + }, + ], + Name: [ + { + image: "from", + startOffset: 33, + endOffset: 36, + tokenType: "Name", + }, + ], + START_CLOSE: [ + { + image: ">", + startOffset: 37, + endOffset: 37, + tokenType: "CLOSE", + }, + ], + content: [ + { + name: "content", + children: { + chardata: [ + { + name: "chardata", + children: { + TEXT: [ + { + image: "Tim", + startOffset: 38, + endOffset: 40, + tokenType: "TEXT", + }, + ], + }, + location: { startOffset: 38, endOffset: 40 }, + }, + ], + }, + location: { startOffset: 38, endOffset: 40 }, + }, + ], + SLASH_OPEN: [ + { + image: "", + startOffset: 47, + endOffset: 47, + tokenType: "CLOSE", + }, + ], + }, + location: { startOffset: 32, endOffset: 47 }, + }, + ], + }, + location: { startOffset: 6, endOffset: 48 }, + }, + ], + SLASH_OPEN: [ + { + image: "", + startOffset: 55, + endOffset: 55, + tokenType: "CLOSE", + }, + ], + }, + location: { startOffset: 0, endOffset: 55 }, + }, + ], + misc: [ + { + name: "misc", + children: { + SEA_WS: [ + { + image: "\n", + startOffset: 56, + endOffset: 56, + tokenType: "SEA_WS", + }, + ], + }, + location: { startOffset: 56, endOffset: 56 }, + }, + ], + }, + location: { startOffset: 0, endOffset: 56 }, + }, +}; diff --git a/projects/xml-tools/packages/parser/test/snapshots/valid/entity-char-ref/sample.spec.js b/projects/xml-tools/packages/parser/test/snapshots/valid/entity-char-ref/sample.spec.js new file mode 100644 index 00000000..afb88de9 --- /dev/null +++ b/projects/xml-tools/packages/parser/test/snapshots/valid/entity-char-ref/sample.spec.js @@ -0,0 +1,7 @@ +const { + executeValidSampleTest, + testNameFromDir, +} = require("../../../sample-test"); +describe(`${testNameFromDir(__dirname)}`, () => { + executeValidSampleTest(__dirname); +}); diff --git a/projects/xml-tools/packages/parser/test/snapshots/valid/multi-line-comments/input.xml b/projects/xml-tools/packages/parser/test/snapshots/valid/multi-line-comments/input.xml new file mode 100644 index 00000000..e47b7cec --- /dev/null +++ b/projects/xml-tools/packages/parser/test/snapshots/valid/multi-line-comments/input.xml @@ -0,0 +1,13 @@ + + + + Bill + + diff --git a/projects/xml-tools/packages/parser/test/snapshots/valid/multi-line-comments/output.js b/projects/xml-tools/packages/parser/test/snapshots/valid/multi-line-comments/output.js new file mode 100644 index 00000000..f96208bc --- /dev/null +++ b/projects/xml-tools/packages/parser/test/snapshots/valid/multi-line-comments/output.js @@ -0,0 +1,271 @@ +module.exports = { + cst: { + name: "document", + children: { + misc: [ + { + name: "misc", + children: { + Comment: [ + { + image: "", + startOffset: 0, + endOffset: 31, + tokenType: "Comment", + }, + ], + }, + location: { startOffset: 0, endOffset: 31 }, + }, + { + name: "misc", + children: { + SEA_WS: [ + { + image: "\n", + startOffset: 32, + endOffset: 32, + tokenType: "SEA_WS", + }, + ], + }, + location: { startOffset: 32, endOffset: 32 }, + }, + { + name: "misc", + children: { + SEA_WS: [ + { + image: "\n", + startOffset: 102, + endOffset: 102, + tokenType: "SEA_WS", + }, + ], + }, + location: { startOffset: 102, endOffset: 102 }, + }, + { + name: "misc", + children: { + Comment: [ + { + image: "", + startOffset: 103, + endOffset: 126, + tokenType: "Comment", + }, + ], + }, + location: { startOffset: 103, endOffset: 126 }, + }, + { + name: "misc", + children: { + SEA_WS: [ + { + image: "\n", + startOffset: 127, + endOffset: 127, + tokenType: "SEA_WS", + }, + ], + }, + location: { startOffset: 127, endOffset: 127 }, + }, + ], + element: [ + { + name: "element", + children: { + OPEN: [ + { image: "<", startOffset: 33, endOffset: 33, tokenType: "OPEN" }, + ], + Name: [ + { + image: "note", + startOffset: 34, + endOffset: 37, + tokenType: "Name", + }, + ], + START_CLOSE: [ + { + image: ">", + startOffset: 38, + endOffset: 38, + tokenType: "CLOSE", + }, + ], + content: [ + { + name: "content", + children: { + chardata: [ + { + name: "chardata", + children: { + SEA_WS: [ + { + image: "\n ", + startOffset: 39, + endOffset: 43, + tokenType: "SEA_WS", + }, + ], + }, + location: { startOffset: 39, endOffset: 43 }, + }, + { + name: "chardata", + children: { + SEA_WS: [ + { + image: "\n ", + startOffset: 76, + endOffset: 80, + tokenType: "SEA_WS", + }, + ], + }, + location: { startOffset: 76, endOffset: 80 }, + }, + { + name: "chardata", + children: { + SEA_WS: [ + { + image: "\n", + startOffset: 94, + endOffset: 94, + tokenType: "SEA_WS", + }, + ], + }, + location: { startOffset: 94, endOffset: 94 }, + }, + ], + Comment: [ + { + image: "", + startOffset: 44, + endOffset: 75, + tokenType: "Comment", + }, + ], + element: [ + { + name: "element", + children: { + OPEN: [ + { + image: "<", + startOffset: 81, + endOffset: 81, + tokenType: "OPEN", + }, + ], + Name: [ + { + image: "to", + startOffset: 82, + endOffset: 83, + tokenType: "Name", + }, + ], + START_CLOSE: [ + { + image: ">", + startOffset: 84, + endOffset: 84, + tokenType: "CLOSE", + }, + ], + content: [ + { + name: "content", + children: { + chardata: [ + { + name: "chardata", + children: { + TEXT: [ + { + image: "Bill", + startOffset: 85, + endOffset: 88, + tokenType: "TEXT", + }, + ], + }, + location: { startOffset: 85, endOffset: 88 }, + }, + ], + }, + location: { startOffset: 85, endOffset: 88 }, + }, + ], + SLASH_OPEN: [ + { + image: "", + startOffset: 93, + endOffset: 93, + tokenType: "CLOSE", + }, + ], + }, + location: { startOffset: 81, endOffset: 93 }, + }, + ], + }, + location: { startOffset: 39, endOffset: 94 }, + }, + ], + SLASH_OPEN: [ + { + image: "", + startOffset: 101, + endOffset: 101, + tokenType: "CLOSE", + }, + ], + }, + location: { startOffset: 33, endOffset: 101 }, + }, + ], + }, + location: { startOffset: 0, endOffset: 127 }, + }, +}; diff --git a/projects/xml-tools/packages/parser/test/snapshots/valid/multi-line-comments/sample.spec.js b/projects/xml-tools/packages/parser/test/snapshots/valid/multi-line-comments/sample.spec.js new file mode 100644 index 00000000..afb88de9 --- /dev/null +++ b/projects/xml-tools/packages/parser/test/snapshots/valid/multi-line-comments/sample.spec.js @@ -0,0 +1,7 @@ +const { + executeValidSampleTest, + testNameFromDir, +} = require("../../../sample-test"); +describe(`${testNameFromDir(__dirname)}`, () => { + executeValidSampleTest(__dirname); +}); diff --git a/projects/xml-tools/packages/parser/test/snapshots/valid/namespaces/input.xml b/projects/xml-tools/packages/parser/test/snapshots/valid/namespaces/input.xml new file mode 100644 index 00000000..4a72de40 --- /dev/null +++ b/projects/xml-tools/packages/parser/test/snapshots/valid/namespaces/input.xml @@ -0,0 +1,6 @@ + + + Some Chair + 50 + 67 + diff --git a/projects/xml-tools/packages/parser/test/snapshots/valid/namespaces/output.js b/projects/xml-tools/packages/parser/test/snapshots/valid/namespaces/output.js new file mode 100644 index 00000000..20f356d6 --- /dev/null +++ b/projects/xml-tools/packages/parser/test/snapshots/valid/namespaces/output.js @@ -0,0 +1,426 @@ +module.exports = { + cst: { + name: "document", + children: { + misc: [ + { + name: "misc", + children: { + SEA_WS: [ + { + image: "\n", + startOffset: 0, + endOffset: 0, + tokenType: "SEA_WS", + }, + ], + }, + location: { startOffset: 0, endOffset: 0 }, + }, + { + name: "misc", + children: { + SEA_WS: [ + { + image: "\n", + startOffset: 144, + endOffset: 144, + tokenType: "SEA_WS", + }, + ], + }, + location: { startOffset: 144, endOffset: 144 }, + }, + ], + element: [ + { + name: "element", + children: { + OPEN: [ + { image: "<", startOffset: 1, endOffset: 1, tokenType: "OPEN" }, + ], + Name: [ + { + image: "f:table", + startOffset: 2, + endOffset: 8, + tokenType: "Name", + }, + ], + attribute: [ + { + name: "attribute", + children: { + Name: [ + { + image: "xmlns:f", + startOffset: 10, + endOffset: 16, + tokenType: "Name", + }, + ], + EQUALS: [ + { + image: "=", + startOffset: 17, + endOffset: 17, + tokenType: "EQUALS", + }, + ], + STRING: [ + { + image: '"https://blah.com/furniture"', + startOffset: 18, + endOffset: 45, + tokenType: "STRING", + }, + ], + }, + location: { startOffset: 10, endOffset: 45 }, + }, + ], + START_CLOSE: [ + { + image: ">", + startOffset: 46, + endOffset: 46, + tokenType: "CLOSE", + }, + ], + content: [ + { + name: "content", + children: { + chardata: [ + { + name: "chardata", + children: { + SEA_WS: [ + { + image: "\n ", + startOffset: 47, + endOffset: 51, + tokenType: "SEA_WS", + }, + ], + }, + location: { startOffset: 47, endOffset: 51 }, + }, + { + name: "chardata", + children: { + SEA_WS: [ + { + image: "\n ", + startOffset: 79, + endOffset: 83, + tokenType: "SEA_WS", + }, + ], + }, + location: { startOffset: 79, endOffset: 83 }, + }, + { + name: "chardata", + children: { + SEA_WS: [ + { + image: "\n ", + startOffset: 105, + endOffset: 109, + tokenType: "SEA_WS", + }, + ], + }, + location: { startOffset: 105, endOffset: 109 }, + }, + { + name: "chardata", + children: { + SEA_WS: [ + { + image: "\n", + startOffset: 133, + endOffset: 133, + tokenType: "SEA_WS", + }, + ], + }, + location: { startOffset: 133, endOffset: 133 }, + }, + ], + element: [ + { + name: "element", + children: { + OPEN: [ + { + image: "<", + startOffset: 52, + endOffset: 52, + tokenType: "OPEN", + }, + ], + Name: [ + { + image: "f:name", + startOffset: 53, + endOffset: 58, + tokenType: "Name", + }, + ], + START_CLOSE: [ + { + image: ">", + startOffset: 59, + endOffset: 59, + tokenType: "CLOSE", + }, + ], + content: [ + { + name: "content", + children: { + chardata: [ + { + name: "chardata", + children: { + TEXT: [ + { + image: "Some Chair", + startOffset: 60, + endOffset: 69, + tokenType: "TEXT", + }, + ], + }, + location: { startOffset: 60, endOffset: 69 }, + }, + ], + }, + location: { startOffset: 60, endOffset: 69 }, + }, + ], + SLASH_OPEN: [ + { + image: "", + startOffset: 78, + endOffset: 78, + tokenType: "CLOSE", + }, + ], + }, + location: { startOffset: 52, endOffset: 78 }, + }, + { + name: "element", + children: { + OPEN: [ + { + image: "<", + startOffset: 84, + endOffset: 84, + tokenType: "OPEN", + }, + ], + Name: [ + { + image: "f:width", + startOffset: 85, + endOffset: 91, + tokenType: "Name", + }, + ], + START_CLOSE: [ + { + image: ">", + startOffset: 92, + endOffset: 92, + tokenType: "CLOSE", + }, + ], + content: [ + { + name: "content", + children: { + chardata: [ + { + name: "chardata", + children: { + TEXT: [ + { + image: "50", + startOffset: 93, + endOffset: 94, + tokenType: "TEXT", + }, + ], + }, + location: { startOffset: 93, endOffset: 94 }, + }, + ], + }, + location: { startOffset: 93, endOffset: 94 }, + }, + ], + SLASH_OPEN: [ + { + image: "", + startOffset: 104, + endOffset: 104, + tokenType: "CLOSE", + }, + ], + }, + location: { startOffset: 84, endOffset: 104 }, + }, + { + name: "element", + children: { + OPEN: [ + { + image: "<", + startOffset: 110, + endOffset: 110, + tokenType: "OPEN", + }, + ], + Name: [ + { + image: "f:length", + startOffset: 111, + endOffset: 118, + tokenType: "Name", + }, + ], + START_CLOSE: [ + { + image: ">", + startOffset: 119, + endOffset: 119, + tokenType: "CLOSE", + }, + ], + content: [ + { + name: "content", + children: { + chardata: [ + { + name: "chardata", + children: { + TEXT: [ + { + image: "67", + startOffset: 120, + endOffset: 121, + tokenType: "TEXT", + }, + ], + }, + location: { + startOffset: 120, + endOffset: 121, + }, + }, + ], + }, + location: { startOffset: 120, endOffset: 121 }, + }, + ], + SLASH_OPEN: [ + { + image: "", + startOffset: 132, + endOffset: 132, + tokenType: "CLOSE", + }, + ], + }, + location: { startOffset: 110, endOffset: 132 }, + }, + ], + }, + location: { startOffset: 47, endOffset: 133 }, + }, + ], + SLASH_OPEN: [ + { + image: "", + startOffset: 143, + endOffset: 143, + tokenType: "CLOSE", + }, + ], + }, + location: { startOffset: 1, endOffset: 143 }, + }, + ], + }, + location: { startOffset: 0, endOffset: 144 }, + }, +}; diff --git a/projects/xml-tools/packages/parser/test/snapshots/valid/namespaces/sample.spec.js b/projects/xml-tools/packages/parser/test/snapshots/valid/namespaces/sample.spec.js new file mode 100644 index 00000000..afb88de9 --- /dev/null +++ b/projects/xml-tools/packages/parser/test/snapshots/valid/namespaces/sample.spec.js @@ -0,0 +1,7 @@ +const { + executeValidSampleTest, + testNameFromDir, +} = require("../../../sample-test"); +describe(`${testNameFromDir(__dirname)}`, () => { + executeValidSampleTest(__dirname); +}); diff --git a/projects/xml-tools/packages/parser/test/snapshots/valid/processing-instruction/input.xml b/projects/xml-tools/packages/parser/test/snapshots/valid/processing-instruction/input.xml new file mode 100644 index 00000000..bbf92201 --- /dev/null +++ b/projects/xml-tools/packages/parser/test/snapshots/valid/processing-instruction/input.xml @@ -0,0 +1,4 @@ + + + + diff --git a/projects/xml-tools/packages/parser/test/snapshots/valid/processing-instruction/output.js b/projects/xml-tools/packages/parser/test/snapshots/valid/processing-instruction/output.js new file mode 100644 index 00000000..1f83bd52 --- /dev/null +++ b/projects/xml-tools/packages/parser/test/snapshots/valid/processing-instruction/output.js @@ -0,0 +1,151 @@ +module.exports = { + cst: { + name: "document", + children: { + misc: [ + { + name: "misc", + children: { + PROCESSING_INSTRUCTION: [ + { + image: + '', + startOffset: 0, + endOffset: 67, + tokenType: "PROCESSING_INSTRUCTION", + }, + ], + }, + location: { startOffset: 0, endOffset: 67 }, + }, + { + name: "misc", + children: { + SEA_WS: [ + { + image: "\n", + startOffset: 68, + endOffset: 68, + tokenType: "SEA_WS", + }, + ], + }, + location: { startOffset: 68, endOffset: 68 }, + }, + { + name: "misc", + children: { + SEA_WS: [ + { + image: "\n", + startOffset: 156, + endOffset: 156, + tokenType: "SEA_WS", + }, + ], + }, + location: { startOffset: 156, endOffset: 156 }, + }, + ], + element: [ + { + name: "element", + children: { + OPEN: [ + { image: "<", startOffset: 69, endOffset: 69, tokenType: "OPEN" }, + ], + Name: [ + { + image: "note", + startOffset: 70, + endOffset: 73, + tokenType: "Name", + }, + ], + START_CLOSE: [ + { + image: ">", + startOffset: 74, + endOffset: 74, + tokenType: "CLOSE", + }, + ], + content: [ + { + name: "content", + children: { + chardata: [ + { + name: "chardata", + children: { + SEA_WS: [ + { + image: "\n ", + startOffset: 75, + endOffset: 79, + tokenType: "SEA_WS", + }, + ], + }, + location: { startOffset: 75, endOffset: 79 }, + }, + { + name: "chardata", + children: { + SEA_WS: [ + { + image: "\n", + startOffset: 148, + endOffset: 148, + tokenType: "SEA_WS", + }, + ], + }, + location: { startOffset: 148, endOffset: 148 }, + }, + ], + PROCESSING_INSTRUCTION: [ + { + image: + '', + startOffset: 80, + endOffset: 147, + tokenType: "PROCESSING_INSTRUCTION", + }, + ], + }, + location: { startOffset: 75, endOffset: 148 }, + }, + ], + SLASH_OPEN: [ + { + image: "", + startOffset: 155, + endOffset: 155, + tokenType: "CLOSE", + }, + ], + }, + location: { startOffset: 69, endOffset: 155 }, + }, + ], + }, + location: { startOffset: 0, endOffset: 156 }, + }, +}; diff --git a/projects/xml-tools/packages/parser/test/snapshots/valid/processing-instruction/sample.spec.js b/projects/xml-tools/packages/parser/test/snapshots/valid/processing-instruction/sample.spec.js new file mode 100644 index 00000000..afb88de9 --- /dev/null +++ b/projects/xml-tools/packages/parser/test/snapshots/valid/processing-instruction/sample.spec.js @@ -0,0 +1,7 @@ +const { + executeValidSampleTest, + testNameFromDir, +} = require("../../../sample-test"); +describe(`${testNameFromDir(__dirname)}`, () => { + executeValidSampleTest(__dirname); +}); diff --git a/projects/xml-tools/packages/parser/test/snapshots/valid/prolog/input.xml b/projects/xml-tools/packages/parser/test/snapshots/valid/prolog/input.xml new file mode 100644 index 00000000..30f696f1 --- /dev/null +++ b/projects/xml-tools/packages/parser/test/snapshots/valid/prolog/input.xml @@ -0,0 +1,2 @@ + + diff --git a/projects/xml-tools/packages/parser/test/snapshots/valid/prolog/output.js b/projects/xml-tools/packages/parser/test/snapshots/valid/prolog/output.js new file mode 100644 index 00000000..2bcd3c66 --- /dev/null +++ b/projects/xml-tools/packages/parser/test/snapshots/valid/prolog/output.js @@ -0,0 +1,182 @@ +module.exports = { + cst: { + name: "document", + children: { + prolog: [ + { + name: "prolog", + children: { + XMLDeclOpen: [ + { + image: "", + startOffset: 36, + endOffset: 37, + tokenType: "SPECIAL_CLOSE", + }, + ], + }, + location: { startOffset: 0, endOffset: 37 }, + }, + ], + misc: [ + { + name: "misc", + children: { + SEA_WS: [ + { + image: "\n", + startOffset: 38, + endOffset: 38, + tokenType: "SEA_WS", + }, + ], + }, + location: { startOffset: 38, endOffset: 38 }, + }, + { + name: "misc", + children: { + SEA_WS: [ + { + image: "\n", + startOffset: 52, + endOffset: 52, + tokenType: "SEA_WS", + }, + ], + }, + location: { startOffset: 52, endOffset: 52 }, + }, + ], + element: [ + { + name: "element", + children: { + OPEN: [ + { image: "<", startOffset: 39, endOffset: 39, tokenType: "OPEN" }, + ], + Name: [ + { + image: "note", + startOffset: 40, + endOffset: 43, + tokenType: "Name", + }, + ], + START_CLOSE: [ + { + image: ">", + startOffset: 44, + endOffset: 44, + tokenType: "CLOSE", + }, + ], + content: [ + { + name: "content", + children: {}, + location: { startOffset: null, endOffset: null }, + }, + ], + SLASH_OPEN: [ + { + image: "", + startOffset: 51, + endOffset: 51, + tokenType: "CLOSE", + }, + ], + }, + location: { startOffset: 39, endOffset: 51 }, + }, + ], + }, + location: { startOffset: 0, endOffset: 52 }, + }, +}; diff --git a/projects/xml-tools/packages/parser/test/snapshots/valid/prolog/sample.spec.js b/projects/xml-tools/packages/parser/test/snapshots/valid/prolog/sample.spec.js new file mode 100644 index 00000000..afb88de9 --- /dev/null +++ b/projects/xml-tools/packages/parser/test/snapshots/valid/prolog/sample.spec.js @@ -0,0 +1,7 @@ +const { + executeValidSampleTest, + testNameFromDir, +} = require("../../../sample-test"); +describe(`${testNameFromDir(__dirname)}`, () => { + executeValidSampleTest(__dirname); +}); diff --git a/projects/xml-tools/packages/parser/test/utils.js b/projects/xml-tools/packages/parser/test/utils.js new file mode 100644 index 00000000..a6fafcb3 --- /dev/null +++ b/projects/xml-tools/packages/parser/test/utils.js @@ -0,0 +1,47 @@ +const { has, forEach, flatMap, identity } = require("lodash"); + +function transformCstForAssertions(cstNode) { + if (has(cstNode, "children")) { + reduceLocationInfo(cstNode.location); + const allChildren = flatMap(cstNode.children, identity); + forEach(allChildren, transformCstForAssertions); + } else if (has(cstNode, "image")) { + reduceTokenInfo(cstNode); + } else { + throw Error("None Exhaustive Match"); + } +} + +function reduceLocationInfo(loc) { + if (isNaN(loc.startOffset)) { + loc.startOffset = null; + } + + if (isNaN(loc.endOffset)) { + loc.endOffset = null; + } + delete loc.startLine; + delete loc.endLine; + delete loc.startColumn; + delete loc.endColumn; +} + +function reduceTokenInfo(tok) { + if (isNaN(tok.startOffset)) { + tok.startOffset = null; + } + + if (isNaN(tok.endOffset)) { + tok.endOffset = null; + } + delete tok.startLine; + delete tok.endLine; + delete tok.startColumn; + delete tok.endColumn; + delete tok.tokenTypeIdx; + tok.tokenType = tok.tokenType.name; +} + +module.exports = { + transformCstForAssertions: transformCstForAssertions, +}; diff --git a/projects/xml-tools/packages/simple-schema/.npmignore b/projects/xml-tools/packages/simple-schema/.npmignore new file mode 100644 index 00000000..ae12335e --- /dev/null +++ b/projects/xml-tools/packages/simple-schema/.npmignore @@ -0,0 +1,10 @@ +# NYC +.nyc_output +nyc.config.js +coverage + +# tests +test + +# contributing +CONTRIBUTING.md \ No newline at end of file diff --git a/projects/xml-tools/packages/simple-schema/CHANGELOG.md b/projects/xml-tools/packages/simple-schema/CHANGELOG.md new file mode 100644 index 00000000..13c7d370 --- /dev/null +++ b/projects/xml-tools/packages/simple-schema/CHANGELOG.md @@ -0,0 +1,155 @@ +# Change Log + +All notable changes to this project will be documented in this file. +See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + +# [3.1.0](https://github.com/sap/xml-tools/compare/@xml-tools/simple-schema@3.0.5...@xml-tools/simple-schema@3.1.0) (2026-02-11) + +### Features + +- update lodash dependency to version 4.17.23 ([#461](https://github.com/sap/xml-tools/issues/461)) ([23a319d](https://github.com/sap/xml-tools/commit/23a319d6ed42a4a3885ec5748eb127147a8960bc)) + +## [3.0.5](https://github.com/sap/xml-tools/compare/@xml-tools/simple-schema@3.0.4...@xml-tools/simple-schema@3.0.5) (2021-06-03) + +**Note:** Version bump only for package @xml-tools/simple-schema + +## [3.0.4](https://github.com/sap/xml-tools/compare/@xml-tools/simple-schema@3.0.3...@xml-tools/simple-schema@3.0.4) (2021-02-09) + +**Note:** Version bump only for package @xml-tools/simple-schema + +## [3.0.3](https://github.com/sap/xml-tools/compare/@xml-tools/simple-schema@3.0.2...@xml-tools/simple-schema@3.0.3) (2021-01-21) + +**Note:** Version bump only for package @xml-tools/simple-schema + +## [3.0.2](https://github.com/sap/xml-tools/compare/@xml-tools/simple-schema@3.0.1...@xml-tools/simple-schema@3.0.2) (2021-01-20) + +### Bug Fixes + +- missing lib folder in npm packages ([ffed3c2](https://github.com/sap/xml-tools/commit/ffed3c2c54c70aea8b9ded0d53786382bc190cc5)) + +## [3.0.1](https://github.com/sap/xml-tools/compare/@xml-tools/simple-schema@3.0.0...@xml-tools/simple-schema@3.0.1) (2021-01-20) + +**Note:** Version bump only for package @xml-tools/simple-schema + +# [3.0.0](https://github.com/sap/xml-tools/compare/@xml-tools/simple-schema@2.1.1...@xml-tools/simple-schema@3.0.0) (2020-06-29) + +### Features + +- **ast:** attribute inside a prolog are now of XMLPrologAttribute type ([#208](https://github.com/sap/xml-tools/issues/208)) ([a303db5](https://github.com/sap/xml-tools/commit/a303db5)) + +### BREAKING CHANGES + +- **ast:** Vistors that need to handle attributes inside prolog must now implement the `visitPrologAttribute` method. + +## [2.1.1](https://github.com/sap/xml-tools/compare/@xml-tools/simple-schema@2.1.0...@xml-tools/simple-schema@2.1.1) (2020-06-18) + +**Note:** Version bump only for package @xml-tools/simple-schema + +# [2.1.0](https://github.com/sap/xml-tools/compare/@xml-tools/simple-schema@2.0.11...@xml-tools/simple-schema@2.1.0) (2020-05-17) + +### Features + +- **common:** add utility functions for xmlns attributes ([#173](https://github.com/sap/xml-tools/issues/173)) ([20d6c09](https://github.com/sap/xml-tools/commit/20d6c09)) + +## [2.0.11](https://github.com/sap/xml-tools/compare/@xml-tools/simple-schema@2.0.10...@xml-tools/simple-schema@2.0.11) (2020-05-05) + +**Note:** Version bump only for package @xml-tools/simple-schema + +## [2.0.10](https://github.com/sap/xml-tools/compare/@xml-tools/simple-schema@2.0.9...@xml-tools/simple-schema@2.0.10) (2020-04-02) + +**Note:** Version bump only for package @xml-tools/simple-schema + +## [2.0.9](https://github.com/sap/xml-tools/compare/@xml-tools/simple-schema@2.0.8...@xml-tools/simple-schema@2.0.9) (2020-02-19) + +### Bug Fixes + +- add npmignore file to each package ([5bbf209](https://github.com/sap/xml-tools/commit/5bbf209)) + +## [2.0.8](https://github.com/sap/xml-tools/compare/@xml-tools/simple-schema@2.0.7...@xml-tools/simple-schema@2.0.8) (2020-02-11) + +**Note:** Version bump only for package @xml-tools/simple-schema + +## [2.0.7](https://github.com/sap/xml-tools/compare/@xml-tools/simple-schema@2.0.6...@xml-tools/simple-schema@2.0.7) (2020-02-11) + +### Bug Fixes + +- remove extraneous prefix completion items ([#98](https://github.com/sap/xml-tools/issues/98)) ([9b52e70](https://github.com/sap/xml-tools/commit/9b52e70)) + +## [2.0.6](https://github.com/sap/xml-tools/compare/@xml-tools/simple-schema@2.0.4...@xml-tools/simple-schema@2.0.6) (2020-02-10) + +**Note:** Version bump only for package @xml-tools/simple-schema + +## [2.0.5](https://github.com/sap/xml-tools/compare/@xml-tools/simple-schema@2.0.4...@xml-tools/simple-schema@2.0.5) (2020-02-07) + +**Note:** Version bump only for package @xml-tools/simple-schema + +## [2.0.4](https://github.com/sap/xml-tools/compare/@xml-tools/simple-schema@2.0.3...@xml-tools/simple-schema@2.0.4) (2020-02-06) + +**Note:** Version bump only for package @xml-tools/simple-schema + +## [2.0.3](https://github.com/sap/xml-tools/compare/@xml-tools/simple-schema@2.0.2...@xml-tools/simple-schema@2.0.3) (2020-02-06) + +**Note:** Version bump only for package @xml-tools/simple-schema + +## [2.0.2](https://github.com/sap/xml-tools/compare/@xml-tools/simple-schema@2.0.1...@xml-tools/simple-schema@2.0.2) (2020-01-28) + +**Note:** Version bump only for package @xml-tools/simple-schema + +## [2.0.1](https://github.com/sap/xml-tools/compare/@xml-tools/simple-schema@2.0.0...@xml-tools/simple-schema@2.0.1) (2020-01-20) + +**Note:** Version bump only for package @xml-tools/simple-schema + +# [2.0.0](https://github.com/sap/xml-tools/compare/@xml-tools/simple-schema@1.0.0...@xml-tools/simple-schema@2.0.0) (2019-12-18) + +### Features + +- **content-assist:** decouple content assist from parsing ([#58](https://github.com/sap/xml-tools/issues/58)) ([3688da8](https://github.com/sap/xml-tools/commit/3688da8)) + +### BREAKING CHANGES + +- **content-assist:** getSuggestions now uses ast, cst and tokenVector instead of text + +# [1.0.0](https://github.com/sap/xml-tools/compare/@xml-tools/simple-schema@0.0.7...@xml-tools/simple-schema@1.0.0) (2019-12-08) + +### Features + +- **ast:** attributesRange and guessedAttributeRange support ([8ce840d](https://github.com/sap/xml-tools/commit/8ce840d)), closes [#51](https://github.com/sap/xml-tools/issues/51) + +### BREAKING CHANGES + +- **ast:** buildAst now requires a tokenVector argument + +## [0.0.7](https://github.com/sap/xml-tools/compare/@xml-tools/simple-schema@0.0.6...@xml-tools/simple-schema@0.0.7) (2019-12-05) + +**Note:** Version bump only for package @xml-tools/simple-schema + +## [0.0.6](https://github.com/sap/xml-tools/compare/@xml-tools/simple-schema@0.0.5...@xml-tools/simple-schema@0.0.6) (2019-12-05) + +### Bug Fixes + +- **simple-schema:** duplicate element error ([f434af8](https://github.com/sap/xml-tools/commit/f434af8)) + +## [0.0.5](https://github.com/sap/xml-tools/compare/@xml-tools/simple-schema@0.0.4...@xml-tools/simple-schema@0.0.5) (2019-12-03) + +### Bug Fixes + +- api type definitions ([46d6c2c](https://github.com/sap/xml-tools/commit/46d6c2c)) +- simple schema with xml namespaces ([#52](https://github.com/sap/xml-tools/issues/52)) ([bb0163e](https://github.com/sap/xml-tools/commit/bb0163e)) + +## [0.0.4](https://github.com/sap/xml-tools/compare/@xml-tools/simple-schema@0.0.3...@xml-tools/simple-schema@0.0.4) (2019-11-20) + +**Note:** Version bump only for package @xml-tools/simple-schema + +## [0.0.3](https://github.com/sap/xml-tools/compare/@xml-tools/simple-schema@0.0.2...@xml-tools/simple-schema@0.0.3) (2019-11-12) + +### Bug Fixes + +- **simple-schema:** fix Simple-Schema API for enumValue ([0e4fdd8](https://github.com/sap/xml-tools/commit/0e4fdd8)) + +## [0.0.2](https://github.com/sap/xml-tools/compare/@xml-tools/simple-schema@0.0.1...@xml-tools/simple-schema@0.0.2) (2019-11-11) + +**Note:** Version bump only for package @xml-tools/simple-schema + +## 0.0.1 (2019-11-11) + +**Note:** Version bump only for package @xml-tools/simple-schema diff --git a/projects/xml-tools/packages/simple-schema/CONTRIBUTING.md b/projects/xml-tools/packages/simple-schema/CONTRIBUTING.md new file mode 100644 index 00000000..84eb9447 --- /dev/null +++ b/projects/xml-tools/packages/simple-schema/CONTRIBUTING.md @@ -0,0 +1,4 @@ +# Contribution Guide + +This package does not have any unique development flows. +Please see the top level [Contribution Guide](../../CONTRIBUTING.md). diff --git a/projects/xml-tools/packages/simple-schema/README.md b/projects/xml-tools/packages/simple-schema/README.md new file mode 100644 index 00000000..8211168d --- /dev/null +++ b/projects/xml-tools/packages/simple-schema/README.md @@ -0,0 +1,119 @@ +[![npm (scoped)](https://img.shields.io/npm/v/@xml-tools/simple-schema.svg)](https://www.npmjs.com/package/@xml-tools/simple-schema) + +# @xml-tools/simple-schema + +This package includes three elements: + +- Data structure definition for a basic XML schema represented as a JavaScript object literal. +- Validation logic to inspect a given XML conforms to a Schema. +- Content Assist logic that provides suggestions using the Schema information. + +## Installation + +With npm: + +- `npm install @xml-tools/simple-schema` + +With Yarn + +- `yarn add @xml-tools/simple-schema` + +## Usage + +Please see the [TypeScript Definitions](./api.d.ts) for full API details. + +- Defining a Schema: + ```javascript + const schema = { + name: "people", + required: true, + cardinality: "single", + attributes: {}, + elements: { + person: { + name: "person", + required: false, + cardinality: "many", + attributes: { + eyeColor: { + key: "eyeColor", + required: false, + value: ["grey", "blue", "green", "red"], + }, + }, + elements: { + name: { + cardinality: "single", + required: true, + name: "name", + attributes: {}, + elements: {}, + }, + }, + }, + }, + }; + ``` +- A Simple XML Text + ```javascript + // A Sample XML Text + const xmlText = ` + + Daenerys Targaryen + + `; + ``` +- Performing Validations + + ```javascript + const { parse } = require("@xml-tools/parser"); + const { buildAst } = require("@xml-tools/ast"); + const { validate } = require("@xml-tools/validation"); + const { getSchemaValidators } = require("@xml-tools/simple-schema"); + + const { cst, tokenVector } = parse(xmlText); + const xmlDoc = buildAst(cst, tokenVector); + const schemaValidators = getSchemaValidators(schema); + const issues = validate({ + doc: xmlDoc, + validators: { + attribute: [schemaValidators.attribute], + element: [schemaValidators.element], + }, + }); + + console.log(issues[0].msg); // Expecting one of but found + console.log(issues[0].position); // { startOffset: 46, endOffset: 53 } + ``` + +- Using Content Assist APIs + + ```javascript + const { getSuggestions } = require("@xml-tools/content-assist"); + const { getSchemaSuggestionsProviders } = require("@xml-tools/simple-schema"); + + const schemaSuggestionsProviders = getSchemaSuggestionsProviders(schema); + + const suggestions = getSuggestions({ + text: xmlText, + offset: 47, // within the eyeColor quoted value + providers: { + attributeValue: [ + schemaSuggestionsProviders.schemaAttributeValueCompletion, + ], + attributeName: [schemaSuggestionsProviders.schemaAttributeNameCompletion], + elementName: [schemaSuggestionsProviders.schemaElementNameCompletion], + }, + }); + + console.log(suggestions[0]); // { text: 'grey', label: 'grey' } + console.log(suggestions[1]); // { text: 'blue', label: 'blue' } + ``` + +## Support + +Please open [issues](https://github.com/SAP/xml-tols/issues) on github. + +## Contributing + +See [CONTRIBUTING.md](./CONTRIBUTING.md). diff --git a/projects/xml-tools/packages/simple-schema/api.d.ts b/projects/xml-tools/packages/simple-schema/api.d.ts new file mode 100644 index 00000000..7f6040ce --- /dev/null +++ b/projects/xml-tools/packages/simple-schema/api.d.ts @@ -0,0 +1,77 @@ +import { XMLAstNode, XMLAttribute, XMLElement } from "@xml-tools/ast"; +import { + ValidationIssue, + AttributeValidator, + ElementValidator, +} from "@xml-tools/validation"; +import { + AttributeNameCompletion, + AttributeValueCompletion, + ElementContentCompletion, + ElementNameCompletion, +} from "@xml-tools/content-assist"; + +// Represents the root element in an XML document +declare type SimpleSchema = XSSElement & { + // There may only be a single top level element in XML + cardinality: "single"; + // The top level element in mandatory in XML + require: true; +}; + +declare type XSSElement = { + required: boolean; + cardinality: "many" | "single"; + name: string; + namespace?: string; + + attributesType?: "open" | "closed"; + attributes: Record; + + elementsType?: "open" | "closed"; + elements: Record; + // TODO: textValue definition (for simple pure text only?) +}; + +declare type XSSAttribute = { + required: boolean; + key: string; + value?: XSSValue; +}; + +// TODO: could be expended to support more primitive types and also complex types such as `union type` +declare type XSSValue = RegExp | XSSValueEnum; + +declare type XSSValueEnum = string[]; + +declare function getSchemaValidators( + schema: SimpleSchema +): { + attribute: AttributeValidator; + element: ElementValidator; +}; + +interface CompletionSuggestion { + text: string; + label?: string; + docs?: string; + commitCharacter?: string; + isNamespace?: boolean; + /** + * A measure of how certain we are about this suggestion's relevance. + * This value could be used to: + * - Filter out less relevant suggestions when there are too many possible suggestions. + * - Sort the suggestions by relevance. + */ + confidence?: number; +} + +declare function getSchemaSuggestionsProviders( + schema: SimpleSchema +): { + // TBD in the future... + // schemaElementContentCompletion: ElementContentCompletion; + schemaElementNameCompletion: ElementNameCompletion; + schemaAttributeNameCompletion: AttributeNameCompletion; + schemaAttributeValueCompletion: AttributeValueCompletion; +}; diff --git a/projects/xml-tools/packages/simple-schema/lib/api.js b/projects/xml-tools/packages/simple-schema/lib/api.js new file mode 100644 index 00000000..200fc047 --- /dev/null +++ b/projects/xml-tools/packages/simple-schema/lib/api.js @@ -0,0 +1,7 @@ +const { getSchemaValidators } = require("./get-validators"); +const { getSchemaSuggestionsProviders } = require("./get-content-assist"); + +module.exports = { + getSchemaValidators: getSchemaValidators, + getSchemaSuggestionsProviders: getSchemaSuggestionsProviders, +}; diff --git a/projects/xml-tools/packages/simple-schema/lib/content-assist/attribute-name.js b/projects/xml-tools/packages/simple-schema/lib/content-assist/attribute-name.js new file mode 100644 index 00000000..c48ea666 --- /dev/null +++ b/projects/xml-tools/packages/simple-schema/lib/content-assist/attribute-name.js @@ -0,0 +1,30 @@ +const { difference, map } = require("lodash"); + +/** + * @param {XMLElement} elementNode + * @param {XSSElement} xssElement + * + * @returns {CompletionSuggestion[]} + */ +function attributeNameCompletion(elementNode, xssElement) { + const possibleSuggestions = map(xssElement.attributes, (_) => _.key); + const existingAttribNames = map(elementNode.attributes, (_) => _.key); + const possibleNewSuggestions = difference( + possibleSuggestions, + existingAttribNames + ); + + const suggestions = map(possibleNewSuggestions, (_) => { + return { + text: _, + label: _, + commitCharacter: "=", + }; + }); + + return suggestions; +} + +module.exports = { + attributeNameCompletion: attributeNameCompletion, +}; diff --git a/projects/xml-tools/packages/simple-schema/lib/content-assist/attribute-value.js b/projects/xml-tools/packages/simple-schema/lib/content-assist/attribute-value.js new file mode 100644 index 00000000..6ca4c3a7 --- /dev/null +++ b/projects/xml-tools/packages/simple-schema/lib/content-assist/attribute-value.js @@ -0,0 +1,40 @@ +const { has, isRegExp, forEach, isArray } = require("lodash"); + +/** + * @param {XMLAttribute} attributeNode + * @param {XSSAttribute} xssAttribute + * @param {string} prefix + * + * @returns {CompletionSuggestion[]} + */ +function attributeValueCompletion(attributeNode, xssAttribute, prefix = "") { + // An XSS Attribute definition may not specify any constraints on a value + if (has(xssAttribute, "value") === false) { + return []; + } + + const suggestions = []; + const valueDef = xssAttribute.value; + /* istanbul ignore else - defensive programming */ + if (isRegExp(valueDef)) { + // No suggestions for regExp value definitions... + } else if (isArray(valueDef)) { + forEach(valueDef, (enumVal) => { + if (enumVal.startsWith(prefix)) { + suggestions.push({ + text: enumVal.substring(prefix.length), + label: enumVal, + }); + } + }); + } else { + /* istanbul ignore next defensive programming */ + throw Error("None Exhaustive Match"); + } + + return suggestions; +} + +module.exports = { + attributeValueCompletion: attributeValueCompletion, +}; diff --git a/projects/xml-tools/packages/simple-schema/lib/content-assist/element-name.js b/projects/xml-tools/packages/simple-schema/lib/content-assist/element-name.js new file mode 100644 index 00000000..0f2da9fb --- /dev/null +++ b/projects/xml-tools/packages/simple-schema/lib/content-assist/element-name.js @@ -0,0 +1,96 @@ +const { difference, map, filter, has, pickBy } = require("lodash"); +const { DEFAULT_NS } = require("@xml-tools/ast"); + +// https://www.w3.org/TR/2009/REC-xml-names-20091208/#NT-PrefixedName +const NAMESPACE_PATTERN = /^(?:([^:]*):)?([^:]*)$/; +/** + * + * Note that the Element (XML/XSS) are of the parent node of the element + * in which content assist was requested. + * + * @param {XMLElement} elementNode + * @param {XSSElement} xssElement + * + * @returns {CompletionSuggestion[]} + */ +function elementNameCompletion(elementNode, xssElement, prefix = "") { + const match = prefix.match(NAMESPACE_PATTERN); + if (match === null) { + return []; + } + // If there is no prefix, use the default namespace prefix + const namespacePrefix = match[1] ? match[1] : DEFAULT_NS; + const elementNamespaceUri = elementNode.namespaces[namespacePrefix]; + const possibleElements = filter( + xssElement.elements, + (_) => + has(_, "namespace") === false || + (_.namespace && _.namespace === elementNamespaceUri) + ); + const possibleSuggestionsWithoutExistingSingular = applicableElements( + xssElement.elements, + elementNode.subElements, + possibleElements + ); + + const suggestions = map(possibleSuggestionsWithoutExistingSingular, (_) => { + return { + text: _, + label: _, + }; + }); + + if (namespacePrefix === undefined || namespacePrefix === DEFAULT_NS) { + // Can't really suggest anything for the `implicit` default namespace... + const namespacesWithoutDefault = pickBy( + elementNode.namespaces, + (uri, prefix) => prefix !== DEFAULT_NS + ); + const applicableNamespaces = pickBy(namespacesWithoutDefault, (uri) => { + const possibleElements = filter( + xssElement.elements, + (element) => + has(element, "namespace") === true && element.namespace === uri + ); + const possibleSuggestionsWithoutExistingSingular = applicableElements( + xssElement.elements, + elementNode.subElements, + possibleElements + ); + const namespaceHasApplicableElements = + possibleSuggestionsWithoutExistingSingular.length > 0; + return namespaceHasApplicableElements; + }); + const namespaceSuggestions = map(applicableNamespaces, (uri, prefix) => ({ + text: prefix, + label: prefix, + commitCharacter: ":", + isNamespace: true, + })); + return [...namespaceSuggestions, ...suggestions]; + } + return suggestions; +} + +function applicableElements(xssElements, subElements, possibleElements) { + const allPossibleSuggestions = map( + possibleElements, + (element) => element.name + ); + const notSingularElem = filter( + xssElements, + (element) => element.cardinality === "many" + ); + const notSingularElemNames = map(notSingularElem, (element) => element.name); + const existingElemNames = map(subElements, (element) => element.name); + const existingSingular = difference(existingElemNames, notSingularElemNames); + const possibleSuggestionsWithoutExistingSingular = difference( + allPossibleSuggestions, + existingSingular + ); + return possibleSuggestionsWithoutExistingSingular; +} + +module.exports = { + elementNameCompletion: elementNameCompletion, +}; diff --git a/projects/xml-tools/packages/simple-schema/lib/get-content-assist.js b/projects/xml-tools/packages/simple-schema/lib/get-content-assist.js new file mode 100644 index 00000000..e4ef8eb3 --- /dev/null +++ b/projects/xml-tools/packages/simple-schema/lib/get-content-assist.js @@ -0,0 +1,62 @@ +const { attributeNameCompletion } = require("./content-assist/attribute-name"); +const { + attributeValueCompletion, +} = require("./content-assist/attribute-value"); +const { elementNameCompletion } = require("./content-assist/element-name"); +const { findElementXssDef, findAttributeXssDef } = require("./path"); + +function getSchemaSuggestionsProviders(schema) { + const attributeNameProvider = buildAttributeNameProvider(schema); + const attributeValueProvider = buildAttributeValueProvider(schema); + const elementNameProvider = buildElementNameProvider(schema); + + return { + schemaElementNameCompletion: elementNameProvider, + schemaAttributeNameCompletion: attributeNameProvider, + schemaAttributeValueCompletion: attributeValueProvider, + }; +} + +/** + * @param {SimpleSchema} schema + */ +function buildAttributeNameProvider(schema) { + return ({ element, prefix }) => { + const xssElementDef = findElementXssDef(element, schema); + if (xssElementDef !== undefined) { + return attributeNameCompletion(element, xssElementDef, prefix); + } else { + return []; + } + }; +} + +/** + * @param {SimpleSchema} schema + */ +function buildElementNameProvider(schema) { + return ({ element, prefix }) => { + // Note we are finding the definition for the element's parent + // Because the information on possible sibling elements exists there... + const xssElementDef = findElementXssDef(element.parent, schema); + if (xssElementDef !== undefined) { + return elementNameCompletion(element.parent, xssElementDef, prefix); + } else { + return []; + } + }; +} + +/** + * @param {SimpleSchema} schema + */ +function buildAttributeValueProvider(schema) { + return ({ attribute, prefix }) => { + const attributeXssDef = findAttributeXssDef(attribute, schema); + return attributeValueCompletion(attribute, attributeXssDef, prefix); + }; +} + +module.exports = { + getSchemaSuggestionsProviders: getSchemaSuggestionsProviders, +}; diff --git a/projects/xml-tools/packages/simple-schema/lib/get-validators.js b/projects/xml-tools/packages/simple-schema/lib/get-validators.js new file mode 100644 index 00000000..3cedb410 --- /dev/null +++ b/projects/xml-tools/packages/simple-schema/lib/get-validators.js @@ -0,0 +1,99 @@ +const { validateAttributeValue } = require("./validators/attribute-value"); + +const { + validateDuplicateSubElements, +} = require("./validators/duplicate-sub-elements"); + +const { + validateRequiredAttributes, +} = require("./validators/required-attributes"); + +const { + validateRequiredSubElements, +} = require("./validators/required-sub-elements"); + +const { + validateUnknownAttributes, +} = require("./validators/unknown-attributes"); + +const { + validateUnknownSubElements, +} = require("./validators/unknown-sub-elements"); + +const { findAttributeXssDef, findElementXssDef } = require("./path"); + +function getSchemaValidators(schema) { + const attributeValidator = buildAttributeValidator(schema); + const elementValidator = buildElementValidator(schema); + + return { + attribute: attributeValidator, + element: elementValidator, + }; +} + +/** + * @param {SimpleSchema} schema + */ +function buildAttributeValidator(schema) { + return (attributeNode) => { + let issues = []; + const xssAttributeDef = findAttributeXssDef(attributeNode, schema); + + if (xssAttributeDef !== undefined) { + const attributeValueIssues = validateAttributeValue( + attributeNode, + xssAttributeDef + ); + issues = issues.concat(attributeValueIssues); + } + + return issues; + }; +} + +/** + * @param {SimpleSchema} schema + */ +function buildElementValidator(schema) { + return (elementNode) => { + let issues = []; + const xssElementDef = findElementXssDef(elementNode, schema); + + if (xssElementDef !== undefined) { + const duplicateElementsIssues = validateDuplicateSubElements( + elementNode, + xssElementDef + ); + const requiredAttributesIssues = validateRequiredAttributes( + elementNode, + xssElementDef + ); + const requiredSubElementsIssues = validateRequiredSubElements( + elementNode, + xssElementDef + ); + const unknownAttributesIssues = validateUnknownAttributes( + elementNode, + xssElementDef + ); + const unknownSubElementsIssues = validateUnknownSubElements( + elementNode, + xssElementDef + ); + issues = issues.concat( + duplicateElementsIssues, + requiredAttributesIssues, + requiredSubElementsIssues, + unknownAttributesIssues, + unknownSubElementsIssues + ); + } + + return issues; + }; +} + +module.exports = { + getSchemaValidators: getSchemaValidators, +}; diff --git a/projects/xml-tools/packages/simple-schema/lib/path.js b/projects/xml-tools/packages/simple-schema/lib/path.js new file mode 100644 index 00000000..619c1aa6 --- /dev/null +++ b/projects/xml-tools/packages/simple-schema/lib/path.js @@ -0,0 +1,69 @@ +const { drop, map, forEach, first } = require("lodash"); + +/** + * @param {XMLAttribute} attribNode + * @param {SimpleSchema} schema + */ +function findAttributeXssDef(attribNode, schema) { + const xssElement = findElementXssDef(attribNode.parent, schema); + + let xssAttribute = undefined; + if (xssElement !== undefined) { + const attributeName = attribNode.key; + xssAttribute = xssElement.attributes[attributeName]; + } + + return xssAttribute; +} + +/** + * @param {XMLElement} node + * @param {SimpleSchema} schema + */ +function findElementXssDef(node, schema) { + const ancestors = getAstNodeAncestors(node); + const elementsPath = map(ancestors, "name"); + + const rootElement = first(elementsPath); + // Root Element mis-match, The Schema cannot provide any attribute validations for this XML AST. + if (rootElement !== schema.name) { + return undefined; + } + let xssElement = schema; + forEach(drop(elementsPath), (elemName) => { + // traverse subElements + xssElement = xssElement.elements[elemName]; + if (xssElement === undefined) { + return false; + } + }); + + return xssElement; +} + +/** + * @param {XMLAstNode} node + * + * @returns {XMLAstNode[]} - The Ancestors do not include the XMLDocument + */ +function getAstNodeAncestors(node) { + const ancestors = []; + ancestors.push(node); + let currAncestor = node.parent; + while ( + currAncestor !== undefined && + // The Simple Schema only starts at the root Element (not the Root Document). + currAncestor.type !== "XMLDocument" + ) { + ancestors.push(currAncestor); + currAncestor = currAncestor.parent; + } + ancestors.reverse(); + + return ancestors; +} + +module.exports = { + findAttributeXssDef: findAttributeXssDef, + findElementXssDef: findElementXssDef, +}; diff --git a/projects/xml-tools/packages/simple-schema/lib/validators/attribute-value.js b/projects/xml-tools/packages/simple-schema/lib/validators/attribute-value.js new file mode 100644 index 00000000..0cde64c1 --- /dev/null +++ b/projects/xml-tools/packages/simple-schema/lib/validators/attribute-value.js @@ -0,0 +1,57 @@ +const { isRegExp, isArray, includes, has } = require("lodash"); +const { tokenToOffsetPosition } = require("./utils"); + +/** + * @param {XMLAttribute} attributeNode + * @param {XSSAttribute }xssAttribute + * + * @returns {ValidationIssue[]} + */ +function validateAttributeValue(attributeNode, xssAttribute) { + const issues = []; + + const valueDef = xssAttribute.value; + + // An XSS Attribute definition may not specify any constraints on a value + if (has(xssAttribute, "value") === false) { + return issues; + } + + const actualValue = attributeNode.value; + if (actualValue === null) { + // we cannot validate a partial attribute AST without an actual value... + return issues; + } + + // This is always safe because at this point we know the attribute has a value + const errPosition = tokenToOffsetPosition(attributeNode.syntax.value); + /* istanbul ignore else defensive programming */ + if (isRegExp(valueDef)) { + if (valueDef.test(actualValue) === false) { + issues.push({ + msg: `Expecting Value matching <${valueDef.toString()}> but found <${actualValue}>`, + node: attributeNode, + severity: "error", + position: errPosition, + }); + } + } else if (isArray(valueDef)) { + if (includes(valueDef, actualValue) === false) { + issues.push({ + msg: `Expecting one of <${valueDef.toString()}> but found <${actualValue}>`, + node: attributeNode, + severity: "error", + position: errPosition, + }); + } + } else { + /* istanbul ignore next defensive programming */ + throw Error("None Exhaustive Match"); + } + + return issues; +} + +module.exports = { + validateAttributeValue: validateAttributeValue, +}; diff --git a/projects/xml-tools/packages/simple-schema/lib/validators/duplicate-sub-elements.js b/projects/xml-tools/packages/simple-schema/lib/validators/duplicate-sub-elements.js new file mode 100644 index 00000000..fc9d0284 --- /dev/null +++ b/projects/xml-tools/packages/simple-schema/lib/validators/duplicate-sub-elements.js @@ -0,0 +1,42 @@ +const { map, forEach, includes, filter, groupBy } = require("lodash"); +const { tokenToOffsetPosition } = require("./utils"); + +/** + * @param {XMLElement} elem + * @param {XSSElement} schema + * + * @returns {ValidationIssue[]} + */ +function validateDuplicateSubElements(elem, schema) { + const allowedDupElem = filter( + schema.elements, + (_) => _.cardinality === "many" + ); + const allowedDupElemNames = map(allowedDupElem, (_) => _.name); + + const actualSubElemByName = groupBy(elem.subElements, (_) => _.name); + const issues = []; + forEach(actualSubElemByName, (dupElements, dupElementsName) => { + const allowedDup = includes(allowedDupElemNames, dupElementsName); + const hasConfiguration = schema.elements[dupElementsName] !== undefined; + const hasDuplicates = dupElements.length > 1; + + if (allowedDup === false && hasDuplicates && hasConfiguration) { + forEach(dupElements, (dupElem) => { + issues.push({ + msg: `Duplicate Sub-Element: <${dupElem.name}> only a single occurrence of this Sub-Element is allowed here.`, + node: dupElem, + severity: "error", + // safe assumption that we have an `openName` (see above condition) + position: tokenToOffsetPosition(dupElem.syntax.openName), + }); + }); + } + }); + + return issues; +} + +module.exports = { + validateDuplicateSubElements: validateDuplicateSubElements, +}; diff --git a/projects/xml-tools/packages/simple-schema/lib/validators/required-attributes.js b/projects/xml-tools/packages/simple-schema/lib/validators/required-attributes.js new file mode 100644 index 00000000..073cfe5d --- /dev/null +++ b/projects/xml-tools/packages/simple-schema/lib/validators/required-attributes.js @@ -0,0 +1,39 @@ +const { map, filter, difference } = require("lodash"); +const { tokenToOffsetPosition } = require("./utils"); + +/** + * @param {XMLElement} elem + * @param {XSSElement} schema + * + * @returns {ValidationIssue[]} + */ +function validateRequiredAttributes(elem, schema) { + const requiredAttribsDef = filter( + schema.attributes, + (_) => _.required === true + ); + const requiredAttribNames = map(requiredAttribsDef, (_) => _.key); + + const actualAttribNames = map(elem.attributes, (_) => _.key); + const missingAttributesNames = difference( + requiredAttribNames, + actualAttribNames + ); + + // This elementName must always exist, otherwise we could not locate the relevant schema definition + // so this validation could have never executed... + const errPosition = tokenToOffsetPosition(elem.syntax.openName); + const issues = map(missingAttributesNames, (_) => { + return { + msg: `Missing Required Attribute: <${_}>`, + node: elem, + severity: "error", + position: errPosition, + }; + }); + return issues; +} + +module.exports = { + validateRequiredAttributes: validateRequiredAttributes, +}; diff --git a/projects/xml-tools/packages/simple-schema/lib/validators/required-sub-elements.js b/projects/xml-tools/packages/simple-schema/lib/validators/required-sub-elements.js new file mode 100644 index 00000000..48dd3cec --- /dev/null +++ b/projects/xml-tools/packages/simple-schema/lib/validators/required-sub-elements.js @@ -0,0 +1,39 @@ +const { map, filter, difference } = require("lodash"); +const { tokenToOffsetPosition } = require("./utils"); + +/** + * @param {XMLElement} elem + * @param {XSSElement} schema + * + * @returns {ValidationIssue[]} + */ +function validateRequiredSubElements(elem, schema) { + const requiredSubElemsDef = filter( + schema.elements, + (_) => _.required === true + ); + const requiredElemNames = map(requiredSubElemsDef, (_) => _.name); + + const actualSubElemNameNames = map(elem.subElements, (_) => _.name); + const missingSubElemNames = difference( + requiredElemNames, + actualSubElemNameNames + ); + + // This elementName must always exist, otherwise we could not locate the relevant schema definition + // so this validation could have never executed... + const errPosition = tokenToOffsetPosition(elem.syntax.openName); + const issues = map(missingSubElemNames, (_) => { + return { + msg: `Missing Required Sub-Element: <${_}>`, + node: elem, + severity: "error", + position: errPosition, + }; + }); + return issues; +} + +module.exports = { + validateRequiredSubElements: validateRequiredSubElements, +}; diff --git a/projects/xml-tools/packages/simple-schema/lib/validators/unknown-attributes.js b/projects/xml-tools/packages/simple-schema/lib/validators/unknown-attributes.js new file mode 100644 index 00000000..3c7930bd --- /dev/null +++ b/projects/xml-tools/packages/simple-schema/lib/validators/unknown-attributes.js @@ -0,0 +1,46 @@ +const { map, includes, forEach } = require("lodash"); +const { isXMLNamespaceKey } = require("@xml-tools/common"); +const { tokenToOffsetPosition } = require("./utils"); + +/** + * @param {XMLElement} elem + * @param {XSSElement} schema + * + * @returns {ValidationIssue[]} + */ +function validateUnknownAttributes(elem, schema) { + // This validation is only relevant if the Schema disallows unknown attributes. + if (schema.attributesType !== "closed") { + return []; + } + const allowedAttribNames = map(schema.attributes, (_) => _.key); + + const issues = []; + forEach(elem.attributes, (attrib) => { + /* istanbul ignore else - Defensive programming, but cannot + * reproduce this branch with current error recovery heuristics + */ + if (attrib.key !== null) { + if ( + includes(allowedAttribNames, attrib.key) === false && + isXMLNamespaceKey({ key: attrib.key, includeEmptyPrefix: true }) === + false + ) { + issues.push({ + msg: `Unknown Attribute: <${ + attrib.key + }> only [${allowedAttribNames.toString()}] attributes are allowed`, + node: attrib, + severity: "error", + // safe assumption that we have a `key` token (see above condition) + position: tokenToOffsetPosition(attrib.syntax.key), + }); + } + } + }); + return issues; +} + +module.exports = { + validateUnknownAttributes: validateUnknownAttributes, +}; diff --git a/projects/xml-tools/packages/simple-schema/lib/validators/unknown-sub-elements.js b/projects/xml-tools/packages/simple-schema/lib/validators/unknown-sub-elements.js new file mode 100644 index 00000000..54a5094f --- /dev/null +++ b/projects/xml-tools/packages/simple-schema/lib/validators/unknown-sub-elements.js @@ -0,0 +1,40 @@ +const { map, forEach, includes } = require("lodash"); +const { tokenToOffsetPosition } = require("./utils"); + +/** + * @param {XMLElement} elem + * @param {XSSElement} schema + * + * @returns {ValidationIssue[]} + */ +function validateUnknownSubElements(elem, schema) { + // This validation is only relevant if the Schema disallows unknown elements. + if (schema.elementsType !== "closed") { + return []; + } + + const allowedElemNames = map(schema.elements, (_) => _.name); + + const issues = []; + forEach(elem.subElements, (subElem) => { + if (subElem.name !== null) { + if (includes(allowedElemNames, subElem.name) === false) { + issues.push({ + msg: `Unknown Sub-Element: <${ + subElem.name + }> only [${allowedElemNames.toString()}] Sub-Elements are allowed`, + node: subElem, + severity: "error", + // safe assumption that we have an `openName` (see above condition) + position: tokenToOffsetPosition(subElem.syntax.openName), + }); + } + } + }); + + return issues; +} + +module.exports = { + validateUnknownSubElements: validateUnknownSubElements, +}; diff --git a/projects/xml-tools/packages/simple-schema/lib/validators/utils.js b/projects/xml-tools/packages/simple-schema/lib/validators/utils.js new file mode 100644 index 00000000..95560d1e --- /dev/null +++ b/projects/xml-tools/packages/simple-schema/lib/validators/utils.js @@ -0,0 +1,9 @@ +const { pick } = require("lodash"); + +function tokenToOffsetPosition(token) { + return pick(token, ["startOffset", "endOffset"]); +} + +module.exports = { + tokenToOffsetPosition: tokenToOffsetPosition, +}; diff --git a/projects/xml-tools/packages/simple-schema/package.json b/projects/xml-tools/packages/simple-schema/package.json new file mode 100644 index 00000000..e0dbaea4 --- /dev/null +++ b/projects/xml-tools/packages/simple-schema/package.json @@ -0,0 +1,48 @@ +{ + "name": "@xml-tools/simple-schema", + "version": "3.1.0", + "description": "XML Simple Schema", + "keywords": [ + "xml", + "validation" + ], + "main": "lib/api.js", + "repository": "https://github.com/sap/xml-tools/", + "license": "Apache-2.0", + "typings": "./api.d.ts", + "files": [ + "lib", + ".reuse", + "LICENSES", + "api.d.ts" + ], + "dependencies": { + "@xml-tools/ast": "^5.1.0", + "@xml-tools/content-assist": "^3.2.0", + "lodash": "4.17.23" + }, + "devDependencies": { + "@xml-tools/parser": "^1.0.11", + "@xml-tools/validation": "^1.1.0" + }, + "scripts": { + "ci": "npm-run-all clean type-check coverage:*", + "clean": "rimraf ./coverage ./nyc_output", + "test": "mocha \"./test/**/*spec.js\"", + "coverage:run": "nyc mocha \"./test/**/*spec.js\"", + "coverage:check": "nyc check-coverage --lines 100 --branches 100 --statements 100 --functions 100", + "type-check": "tsc api.d.ts" + }, + "publishConfig": { + "access": "public" + }, + "nyc": { + "include": [ + "lib/**/*.js" + ], + "reporter": [ + "text", + "lcov" + ] + } +} diff --git a/projects/xml-tools/packages/simple-schema/test/content-assist/atrribute-name-spec.js b/projects/xml-tools/packages/simple-schema/test/content-assist/atrribute-name-spec.js new file mode 100644 index 00000000..eaae3b49 --- /dev/null +++ b/projects/xml-tools/packages/simple-schema/test/content-assist/atrribute-name-spec.js @@ -0,0 +1,189 @@ +const { expect } = require("chai"); +const { suggestionsBySchema } = require("./utils"); + +describe("The XML Simple Schema", () => { + describe("Content Assist Functionality", () => { + context("In Attribute Names Scenario", () => { + it("provides suggestion: without prefix", () => { + const xmlText = ` + + `; + + const schema = { + required: true, + cardinality: "single", + name: "people", + attributes: {}, + + elements: { + person: { + name: "person", + required: false, + cardinality: "many", + attributes: { + name: { + required: false, + key: "name", + }, + age: { + required: false, + key: "age", + }, + address: { + required: false, + key: "address", + }, + }, + elements: {}, + }, + }, + }; + + const suggestions = suggestionsBySchema(xmlText, schema); + expect(suggestions).to.deep.include.members([ + { + label: "name", + text: "name", + commitCharacter: "=", + }, + { + label: "age", + text: "age", + commitCharacter: "=", + }, + { + label: "address", + text: "address", + commitCharacter: "=", + }, + ]); + expect(suggestions).to.have.lengthOf(3); + }); + + it("provides suggestion: with prefix", () => { + const xmlText = ` + + `; + + const schema = { + required: true, + cardinality: "single", + name: "people", + attributes: {}, + + elements: { + person: { + name: "person", + required: false, + cardinality: "many", + attributes: { + name: { + required: false, + key: "name", + }, + age: { + required: false, + key: "age", + }, + address: { + required: false, + key: "address", + }, + }, + elements: {}, + }, + }, + }; + + const suggestions = suggestionsBySchema(xmlText, schema); + expect(suggestions).to.deep.include.members([ + { + label: "name", + text: "name", + commitCharacter: "=", + }, + { + label: "age", + text: "age", + commitCharacter: "=", + }, + { + label: "address", + text: "address", + commitCharacter: "=", + }, + ]); + expect(suggestions).to.have.lengthOf(3); + }); + it("does not crash wehen xss element does not exist", () => { + const xmlText = ` + + `; + + const schema = { + required: true, + cardinality: "single", + name: "people", + attributes: {}, + + elements: {}, + }; + + const suggestions = suggestionsBySchema(xmlText, schema); + expect(suggestions).to.deep.include.members([]); + expect(suggestions).to.have.lengthOf(0); + }); + + it("filters suggestions by existing attributes", () => { + const xmlText = ` + + `; + + const schema = { + required: true, + cardinality: "single", + name: "people", + attributes: {}, + + elements: { + person: { + name: "person", + required: false, + cardinality: "many", + attributes: { + name: { + required: false, + key: "name", + }, + age: { + required: false, + key: "age", + }, + address: { + required: false, + key: "address", + }, + }, + elements: {}, + }, + }, + }; + + const suggestions = suggestionsBySchema(xmlText, schema); + expect(suggestions).to.deep.include.members([ + { + label: "name", + text: "name", + commitCharacter: "=", + }, + { + label: "address", + text: "address", + commitCharacter: "=", + }, + ]); + expect(suggestions).to.have.lengthOf(2); + }); + }); + }); +}); diff --git a/projects/xml-tools/packages/simple-schema/test/content-assist/atrribute-value-spec.js b/projects/xml-tools/packages/simple-schema/test/content-assist/atrribute-value-spec.js new file mode 100644 index 00000000..e4a61c21 --- /dev/null +++ b/projects/xml-tools/packages/simple-schema/test/content-assist/atrribute-value-spec.js @@ -0,0 +1,181 @@ +const { expect } = require("chai"); +const { suggestionsBySchema } = require("./utils"); + +describe("The XML Simple Schema", () => { + describe("Content Assist Functionality", () => { + context("In Attribute Values Scenario", () => { + it("provides suggestion for enum without prefix", () => { + const xmlText = ` + + `; + + const schema = { + required: true, + cardinality: "single", + name: "people", + attributes: {}, + + elements: { + person: { + name: "person", + required: false, + cardinality: "many", + attributes: { + age: { + required: false, + key: "age", + value: ["66", "67", "666"], + }, + }, + elements: {}, + }, + }, + }; + + const suggestions = suggestionsBySchema(xmlText, schema); + expect(suggestions).to.have.lengthOf(3); + expect(suggestions).to.deep.include.members([ + { + label: "66", + text: "66", + }, + { + label: "67", + text: "67", + }, + { + label: "666", + text: "666", + }, + ]); + }); + + it("Filters suggestions for enum using prefix", () => { + const xmlText = ` + + `; + + const schema = { + required: true, + cardinality: "single", + name: "people", + attributes: {}, + + elements: { + person: { + name: "person", + required: false, + cardinality: "many", + attributes: { + age: { + required: false, + key: "age", + value: ["66", "67", "666"], + }, + }, + elements: {}, + }, + }, + }; + + const suggestions = suggestionsBySchema(xmlText, schema); + expect(suggestions).to.have.lengthOf(3); + expect(suggestions).to.deep.include.members([ + { + label: "66", + text: "6", + }, + { + label: "67", + text: "7", + }, + { + label: "666", + text: "66", + }, + ]); + }); + + it("Does not provide suggestions for regExp value Definitions", () => { + const xmlText = ` + + `; + + const schema = { + required: true, + cardinality: "single", + name: "people", + attributes: {}, + + elements: { + person: { + name: "person", + required: false, + cardinality: "many", + attributes: { + age: { + required: false, + key: "age", + value: /^\d\d$/, + }, + }, + elements: {}, + }, + }, + }; + + const suggestions = suggestionsBySchema(xmlText, schema); + expect(suggestions).to.have.lengthOf(0); + }); + + it("Does not provide suggestions if there is no XSS value definition", () => { + const xmlText = ` + + `; + + const schema = { + required: true, + cardinality: "single", + name: "people", + attributes: {}, + + elements: { + person: { + name: "person", + required: false, + cardinality: "many", + attributes: { + age: { + required: false, + key: "age", + }, + }, + elements: {}, + }, + }, + }; + + const suggestions = suggestionsBySchema(xmlText, schema); + expect(suggestions).to.have.lengthOf(0); + }); + + it("Does not crash if there is no XSS value definition", () => { + const xmlText = ` + + `; + + const schema = { + required: true, + cardinality: "single", + name: "people", + attributes: {}, + + elements: {}, + }; + + const suggestions = suggestionsBySchema(xmlText, schema); + expect(suggestions).to.have.lengthOf(0); + }); + }); + }); +}); diff --git a/projects/xml-tools/packages/simple-schema/test/content-assist/element-name-spec.js b/projects/xml-tools/packages/simple-schema/test/content-assist/element-name-spec.js new file mode 100644 index 00000000..3477017e --- /dev/null +++ b/projects/xml-tools/packages/simple-schema/test/content-assist/element-name-spec.js @@ -0,0 +1,667 @@ +const { expect } = require("chai"); +const { suggestionsBySchema } = require("./utils"); + +describe("The XML Simple Schema", () => { + describe("Content Assist Functionality", () => { + context("In Element Names Scenario", () => { + it("provides suggestion: without prefix", () => { + const xmlText = ` + + <⇶ + + `; + + const schema = { + required: true, + cardinality: "single", + name: "people", + attributes: {}, + + elements: { + person: { + name: "person", + required: false, + cardinality: "many", + attributes: {}, + elements: { + name: { + cardinality: "single", + required: false, + name: "name", + attributes: {}, + elements: {}, + }, + age: { + cardinality: "single", + required: false, + name: "age", + attributes: {}, + elements: {}, + }, + address: { + cardinality: "many", + required: false, + name: "address", + attributes: {}, + elements: {}, + }, + }, + }, + }, + }; + + const suggestions = suggestionsBySchema(xmlText, schema); + expect(suggestions).to.deep.include.members([ + { + label: "name", + text: "name", + }, + { + label: "age", + text: "age", + }, + { + label: "address", + text: "address", + }, + ]); + expect(suggestions).to.have.lengthOf(3); + }); + + it("provides suggestion: with prefix", () => { + const xmlText = ` + + + `; + + const schema = { + required: true, + cardinality: "single", + name: "people", + attributes: {}, + + elements: { + person: { + name: "person", + required: false, + cardinality: "many", + attributes: {}, + elements: { + name: { + cardinality: "single", + required: false, + name: "name", + attributes: {}, + elements: {}, + }, + age: { + cardinality: "single", + required: false, + name: "age", + attributes: {}, + elements: {}, + }, + address: { + cardinality: "many", + required: false, + name: "address", + attributes: {}, + elements: {}, + }, + }, + }, + }, + }; + + const suggestions = suggestionsBySchema(xmlText, schema); + expect(suggestions).to.deep.include.members([ + { + label: "name", + text: "name", + }, + { + label: "age", + text: "age", + }, + { + label: "address", + text: "address", + }, + ]); + expect(suggestions).to.have.lengthOf(3); + }); + + it("filters pre-existing sub-elements with `single` cardinality", () => { + const xmlText = ` + + 66 + <⇶ + + `; + + const schema = { + required: true, + cardinality: "single", + name: "people", + attributes: {}, + + elements: { + person: { + name: "person", + required: false, + cardinality: "many", + attributes: {}, + elements: { + name: { + cardinality: "single", + required: false, + name: "name", + attributes: {}, + elements: {}, + }, + age: { + cardinality: "single", + required: false, + name: "age", + attributes: {}, + elements: {}, + }, + address: { + cardinality: "many", + required: false, + name: "address", + attributes: {}, + elements: {}, + }, + }, + }, + }, + }; + + const suggestions = suggestionsBySchema(xmlText, schema); + expect(suggestions).to.deep.include.members([ + { + label: "name", + text: "name", + }, + { + label: "address", + text: "address", + }, + ]); + expect(suggestions).to.have.lengthOf(2); + }); + + it("allows multiple sub-elements with `multiple` cardinality", () => { + const xmlText = ` + + 66 + + <⇶ + `; + + const schema = { + required: true, + cardinality: "single", + name: "people", + attributes: {}, + + elements: { + person: { + name: "person", + required: false, + cardinality: "many", + attributes: {}, + elements: { + name: { + cardinality: "single", + required: false, + name: "name", + attributes: {}, + elements: {}, + }, + age: { + cardinality: "single", + required: false, + name: "age", + attributes: {}, + elements: {}, + }, + address: { + cardinality: "many", + required: false, + name: "address", + attributes: {}, + elements: {}, + }, + }, + }, + }, + }; + + const suggestions = suggestionsBySchema(xmlText, schema); + expect(suggestions).to.deep.include.members([ + { + label: "person", + text: "person", + }, + ]); + expect(suggestions).to.have.lengthOf(1); + }); + }); + + it("does not crash if there is no xss element", () => { + const xmlText = ` + + <⇶ + + `; + + const schema = { + required: true, + cardinality: "single", + name: "people", + attributes: {}, + + elements: {}, + }; + + const suggestions = suggestionsBySchema(xmlText, schema); + expect(suggestions).to.deep.include.members([]); + expect(suggestions).to.have.lengthOf(0); + }); + + it("nested elements with namespaces", () => { + const xmlText = ` + + <⇶ + + `; + + const schema = { + required: true, + cardinality: "single", + name: "people", + namespace: "http://namespace.com/1", + attributes: {}, + elements: { + person: { + name: "person", + namespace: "http://namespace.com/2", + required: false, + cardinality: "many", + attributes: {}, + elements: { + name: { + cardinality: "single", + required: false, + name: "name", + namespace: "http://namespace.com/2", + attributes: {}, + elements: {}, + }, + friends: { + cardinality: "single", + required: false, + name: "friends", + namespace: "http://namespace.com/1", + attributes: {}, + elements: { + person: { + name: "person", + namespace: "http://namespace.com/2", + required: false, + cardinality: "many", + attributes: { + name: { + cardinality: "single", + required: false, + name: "name", + namespace: "http://namespace.com/2", + attributes: {}, + elements: {}, + }, + }, + }, + }, + }, + age: { + cardinality: "single", + required: false, + name: "age", + namespace: "http://namespace.com/2", + attributes: {}, + elements: {}, + }, + address: { + cardinality: "many", + required: false, + name: "address", + namespace: "http://namespace.com/2", + attributes: {}, + elements: {}, + }, + }, + }, + }, + }; + + const suggestions = suggestionsBySchema(xmlText, schema); + expect(suggestions).to.deep.include.members([ + { + text: "abc", + label: "abc", + isNamespace: true, + commitCharacter: ":", + }, + { + label: "name", + text: "name", + }, + { + label: "age", + text: "age", + }, + { + label: "address", + text: "address", + }, + ]); + expect(suggestions).to.have.lengthOf(4); + }); + + it("with namespace prefix", () => { + const xmlText = ` + + + `; + + const schema = { + required: true, + cardinality: "single", + name: "people", + namespace: "http://namespace.com/1", + attributes: {}, + elements: { + person: { + name: "person", + namespace: "http://namespace.com/2", + required: false, + cardinality: "many", + attributes: {}, + elements: { + name: { + cardinality: "single", + required: false, + name: "name", + namespace: "http://namespace.com/1", + attributes: {}, + elements: {}, + }, + age: { + cardinality: "single", + required: false, + name: "age", + namespace: "http://namespace.com/1", + attributes: {}, + elements: {}, + }, + address: { + cardinality: "many", + required: false, + name: "address", + namespace: "http://namespace.com/2", + attributes: {}, + elements: {}, + }, + }, + }, + }, + }; + + const suggestions = suggestionsBySchema(xmlText, schema); + expect(suggestions).to.deep.include.members([ + { + label: "name", + text: "name", + }, + { + label: "age", + text: "age", + }, + ]); + expect(suggestions).to.have.lengthOf(2); + }); + + it("should not crash with invalid prefix", () => { + const xmlText = ` + + <:::⇶ + + `; + + const schema = { + required: true, + cardinality: "single", + name: "people", + namespace: "http://namespace.com/1", + attributes: {}, + elements: { + person: { + name: "person", + namespace: "http://namespace.com/2", + required: false, + cardinality: "many", + attributes: {}, + elements: { + name: { + cardinality: "single", + required: false, + name: "name", + namespace: "http://namespace.com/2", + attributes: {}, + elements: {}, + }, + age: { + cardinality: "single", + required: false, + name: "age", + namespace: "http://namespace.com/2", + attributes: {}, + elements: {}, + }, + address: { + cardinality: "many", + required: false, + name: "address", + namespace: "http://namespace.com/2", + attributes: {}, + elements: {}, + }, + }, + }, + }, + }; + + const suggestions = suggestionsBySchema(xmlText, schema); + expect(suggestions).to.deep.include.members([]); + expect(suggestions).to.have.lengthOf(0); + }); + + it("should not provide extraneous namespace prefixes", () => { + const xmlText = ` + + <⇶ + + `; + + const schema = { + required: true, + cardinality: "single", + name: "people", + namespace: "http://namespace.com/1", + attributes: {}, + elements: { + person: { + name: "person", + namespace: "http://namespace.com/2", + required: false, + cardinality: "many", + attributes: {}, + elements: { + name: { + cardinality: "single", + required: false, + name: "name", + namespace: "http://namespace.com/2", + attributes: {}, + elements: {}, + }, + age: { + cardinality: "single", + required: false, + name: "age", + namespace: "http://namespace.com/2", + attributes: {}, + elements: {}, + }, + address: { + cardinality: "many", + required: false, + name: "address", + namespace: "http://namespace.com/2", + attributes: {}, + elements: {}, + }, + }, + }, + }, + }; + + const suggestions = suggestionsBySchema(xmlText, schema); + expect(suggestions).to.deep.include.members([ + { + label: "name", + text: "name", + }, + { + label: "age", + text: "age", + }, + { + label: "address", + text: "address", + }, + ]); + expect(suggestions).to.have.lengthOf(3); + expect(suggestions).to.not.deep.include.members([ + { + label: "abc", + text: "abc", + }, + ]); + }); + + it("should not provide extraneous namespace prefixes for singular items", () => { + const xmlText = ` + + + <⇶ + + `; + + const schema = { + required: true, + cardinality: "single", + name: "people", + namespace: "http://namespace.com/1", + attributes: {}, + elements: { + person: { + name: "person", + namespace: "http://namespace.com/2", + required: false, + cardinality: "many", + attributes: {}, + elements: { + name: { + cardinality: "single", + required: false, + name: "name", + namespace: "http://namespace.com/2", + attributes: {}, + elements: {}, + }, + age: { + cardinality: "single", + required: false, + name: "age", + namespace: "http://namespace.com/2", + attributes: {}, + elements: {}, + }, + address: { + cardinality: "many", + required: false, + name: "address", + namespace: "http://namespace.com/2", + attributes: {}, + elements: {}, + }, + friends: { + cardinality: "single", + required: false, + name: "friends", + namespace: "http://namespace.com/1", + attributes: {}, + elements: { + person: { + name: "person", + namespace: "http://namespace.com/2", + required: false, + cardinality: "many", + attributes: { + name: { + cardinality: "single", + required: false, + name: "name", + namespace: "http://namespace.com/2", + attributes: {}, + elements: {}, + }, + }, + }, + }, + }, + }, + }, + }, + }; + + const suggestions = suggestionsBySchema(xmlText, schema); + expect(suggestions).to.deep.include.members([ + { + label: "name", + text: "name", + }, + { + label: "age", + text: "age", + }, + { + label: "address", + text: "address", + }, + ]); + expect(suggestions).to.have.lengthOf(3); + expect(suggestions).to.not.deep.include.members([ + { + label: "abc", + text: "abc", + }, + ]); + }); + }); +}); diff --git a/projects/xml-tools/packages/simple-schema/test/content-assist/utils.js b/projects/xml-tools/packages/simple-schema/test/content-assist/utils.js new file mode 100644 index 00000000..0ebe17a9 --- /dev/null +++ b/projects/xml-tools/packages/simple-schema/test/content-assist/utils.js @@ -0,0 +1,36 @@ +const { parse } = require("@xml-tools/parser"); +const { buildAst } = require("@xml-tools/ast"); +const { getSuggestions } = require("@xml-tools/content-assist"); +const { getSchemaSuggestionsProviders } = require("../../"); + +/** + * + * @param {string} xmlText + * @param {SimpleSchema} schema + * @returns {ValidationIssue[] | *} + */ +function suggestionsBySchema(xmlText, schema) { + const schemaSuggestionsProviders = getSchemaSuggestionsProviders(schema); + const realXmlText = xmlText.replace("⇶", ""); + const offset = xmlText.indexOf("⇶"); + const { cst, tokenVector } = parse(realXmlText); + const ast = buildAst(cst, tokenVector); + const suggestions = getSuggestions({ + cst, + tokenVector, + ast, + offset: offset, + providers: { + attributeValue: [ + schemaSuggestionsProviders.schemaAttributeValueCompletion, + ], + attributeName: [schemaSuggestionsProviders.schemaAttributeNameCompletion], + elementName: [schemaSuggestionsProviders.schemaElementNameCompletion], + }, + }); + return suggestions; +} + +module.exports = { + suggestionsBySchema: suggestionsBySchema, +}; diff --git a/projects/xml-tools/packages/simple-schema/test/validators/attribute-value-spec.js b/projects/xml-tools/packages/simple-schema/test/validators/attribute-value-spec.js new file mode 100644 index 00000000..91790eac --- /dev/null +++ b/projects/xml-tools/packages/simple-schema/test/validators/attribute-value-spec.js @@ -0,0 +1,250 @@ +const { expect } = require("chai"); +const { validateBySchema } = require("./utils"); + +describe("The XML Simple Schema", () => { + describe("Validations Functionality", () => { + context("attributes values", () => { + context("enum values", () => { + it("positive", () => { + const xmlText = ` + Bill + Tim + `; + + const schema = { + required: true, + cardinality: "single", + name: "note", + attributes: { + private: { + required: false, + key: "private", + value: ["true", "false"], + }, + }, + elements: {}, + }; + const issues = validateBySchema(xmlText, schema); + expect(issues).to.have.lengthOf(1); + const issue = issues[0]; + expect(issue.msg).to.eql( + "Expecting one of but found " + ); + expect(issue.severity).to.eql("error"); + expect(issue.node.key).to.eql("private"); + expect(issue.position).to.eql({ startOffset: 14, endOffset: 23 }); + }); + + it("negative", () => { + const xmlText = ` + + Bill + Tim + `; + + const schema = { + required: true, + cardinality: "single", + name: "note", + attributes: { + private: { + required: false, + key: "private", + value: ["true", "false"], + }, + }, + elements: {}, + }; + const issues = validateBySchema(xmlText, schema); + expect(issues).to.be.empty; + }); + }); + + context("regexp value", () => { + it("positive", () => { + const xmlText = ` + Bill + Tim + `; + + const schema = { + required: true, + cardinality: "single", + name: "note", + attributeType: "open", + attributes: { + private: { + required: false, + key: "private", + value: /^(true|false)$/, + }, + }, + elements: {}, + }; + const issues = validateBySchema(xmlText, schema); + expect(issues).to.have.lengthOf(1); + const issue = issues[0]; + expect(issue.msg).to.eql( + "Expecting Value matching but found " + ); + expect(issue.severity).to.eql("error"); + expect(issue.node.key).to.eql("private"); + }); + + it("negative", () => { + const xmlText = ` + Bill + Tim + `; + + const schema = { + required: true, + cardinality: "single", + name: "note", + attributes: { + private: { + required: false, + key: "private", + value: /^(true|false)$/, + }, + }, + elements: {}, + }; + const issues = validateBySchema(xmlText, schema); + expect(issues).to.be.empty; + }); + }); + + context("won't validate edge cases", () => { + it("if there is no value in the XML Text", () => { + const xmlText = ` + Bill + Tim + `; + + const schema = { + required: true, + cardinality: "single", + name: "note", + attributes: { + private: { + required: false, + key: "private", + value: ["true", "false"], + }, + }, + elements: {}, + }; + const issues = validateBySchema(xmlText, schema); + expect(issues).to.be.empty; + }); + + it("if there is no value definition in the XSS", () => { + const xmlText = ` + Bill + Tim + `; + + const schema = { + required: true, + cardinality: "single", + name: "note", + attributes: { + private: { + required: false, + key: "private", + }, + }, + elements: {}, + }; + const issues = validateBySchema(xmlText, schema); + expect(issues).to.be.empty; + }); + + it("if there is no matching schema node", () => { + const xmlText = ` + + `; + + const schema = { + required: true, + cardinality: "single", + name: "people", + attributes: {}, + + elements: { + // Typo - can't find people.person path in the schema + OOPS: { + name: "OOPS", + required: false, + cardinality: "many", + attributes: { + age: { + required: false, + key: "age", + value: "66", + }, + }, + elements: {}, + }, + }, + }; + const issues = validateBySchema(xmlText, schema); + expect(issues).to.be.empty; + }); + + it("if there is full schema mismatch", () => { + const xmlText = ` + + `; + + const schema = { + required: true, + cardinality: "single", + // "humans" !== "person" this schema is completely irrelevant for this scenario. + name: "humans", + attributes: {}, + + elements: { + person: { + name: "person", + required: false, + cardinality: "many", + attributes: { + age: { + required: false, + key: "age", + value: "66", + }, + }, + elements: {}, + }, + }, + }; + const issues = validateBySchema(xmlText, schema); + expect(issues).to.be.empty; + }); + + it("on an xml prolog", () => { + // This test is only to ensure code coverage is special prolog branch. + // There is no way to actually test be behaivor directly as we don't actually do anything with + // a prolong (yet) in the XSS, and there is no way to define prolog XSS constraints. + const xmlText = ` + + `; + + const schema = { + required: true, + cardinality: "single", + name: "person", + attributes: {}, + + elements: {}, + }; + const issues = validateBySchema(xmlText, schema); + expect(issues).to.be.empty; + }); + }); + }); + }); +}); diff --git a/projects/xml-tools/packages/simple-schema/test/validators/element-duplicate-sub-elements-spec.js b/projects/xml-tools/packages/simple-schema/test/validators/element-duplicate-sub-elements-spec.js new file mode 100644 index 00000000..a6ea77f4 --- /dev/null +++ b/projects/xml-tools/packages/simple-schema/test/validators/element-duplicate-sub-elements-spec.js @@ -0,0 +1,189 @@ +const { expect } = require("chai"); +const { map, countBy, identity } = require("lodash"); +const { validateBySchema } = require("./utils"); + +describe("The XML Simple Schema", () => { + describe("Validations Functionality", () => { + context("element duplicate sub elements", () => { + it("Detects a single duplicate sub element", () => { + const xmlText = ` + + Duck + Duck + + `; + + const schema = { + required: true, + cardinality: "single", + name: "people", + attributes: {}, + + elements: { + person: { + name: "person", + required: false, + cardinality: "many", + attributes: {}, + elements: { + lastName: { + lastName: "name", + cardinality: "single", + attributes: {}, + elements: {}, + }, + }, + }, + }, + }; + const issues = validateBySchema(xmlText, schema); + expect(issues).to.have.lengthOf(2); + const issue1 = issues[0]; + expect(issue1.msg).to.eql( + "Duplicate Sub-Element: only a single occurrence of this Sub-Element is allowed here." + ); + expect(issue1.severity).to.eql("error"); + expect(issue1.node.name).to.eql("lastName"); + expect(issue1.position).to.eql({ startOffset: 55, endOffset: 62 }); + + const issue2 = issues[1]; + expect(issue2.msg).to.eql( + "Duplicate Sub-Element: only a single occurrence of this Sub-Element is allowed here." + ); + expect(issue2.severity).to.eql("error"); + expect(issue2.node.name).to.eql("lastName"); + expect(issue2.position).to.eql({ startOffset: 100, endOffset: 107 }); + }); + + it("Detects multiple duplicate sub elements", () => { + const xmlText = ` + + Duck + Duck + Duck + +
NY
+
NY
+
+
`; + + const schema = { + required: true, + cardinality: "single", + name: "people", + attributes: {}, + + elements: { + person: { + name: "person", + required: false, + cardinality: "many", + attributes: {}, + elements: { + lastName: { + name: "lastName", + cardinality: "single", + attributes: {}, + elements: {}, + }, + address: { + name: "address", + cardinality: "single", + attributes: {}, + elements: {}, + }, + }, + }, + }, + }; + const issues = validateBySchema(xmlText, schema); + expect(issues).to.have.lengthOf(5); + const messages = map(issues, "msg"); + const countByMessages = countBy(messages, identity); + expect(countByMessages).to.eql({ + "Duplicate Sub-Element: only a single occurrence of this Sub-Element is allowed here.": 3, + "Duplicate Sub-Element:
only a single occurrence of this Sub-Element is allowed here.": 2, + }); + }); + + it("Won't complain when no sub elements breach their cardinality constraints", () => { + const xmlText = ` + + Donald + 85 +
NY
+
IL
+
+
`; + + const schema = { + required: true, + cardinality: "single", + name: "people", + attributes: {}, + + elements: { + person: { + name: "person", + required: false, + cardinality: "many", + attributes: {}, + elements: { + name: { + name: "name", + cardinality: "single", + attributes: {}, + elements: {}, + }, + age: { + name: "age", + cardinality: "single", + attributes: {}, + elements: {}, + }, + address: { + name: "address", + cardinality: "many", + attributes: {}, + elements: {}, + }, + }, + }, + }, + }; + const issues = validateBySchema(xmlText, schema); + expect(issues).to.be.empty; + }); + + it("Won't complain when no sub elements are defined", () => { + const xmlText = ` + + Donald + 85 +
NY
+
IL
+
+
`; + + const schema = { + required: true, + cardinality: "single", + name: "people", + attributes: {}, + + elements: { + person: { + name: "person", + required: false, + cardinality: "many", + attributes: {}, + elements: {}, + }, + }, + }; + const issues = validateBySchema(xmlText, schema); + expect(issues).to.be.empty; + }); + }); + }); +}); diff --git a/projects/xml-tools/packages/simple-schema/test/validators/element-required-attributes-spec.js b/projects/xml-tools/packages/simple-schema/test/validators/element-required-attributes-spec.js new file mode 100644 index 00000000..28845c97 --- /dev/null +++ b/projects/xml-tools/packages/simple-schema/test/validators/element-required-attributes-spec.js @@ -0,0 +1,126 @@ +const { expect } = require("chai"); +const { map } = require("lodash"); +const { validateBySchema } = require("./utils"); + +describe("The XML Simple Schema", () => { + describe("Validations Functionality", () => { + context("element required attribute", () => { + it("Detects a missing attribute", () => { + const xmlText = ` + + `; + + const schema = { + required: true, + cardinality: "single", + name: "people", + attributes: {}, + + elements: { + person: { + name: "person", + required: false, + cardinality: "many", + attributes: { + age: { + required: true, + key: "age", + }, + }, + elements: {}, + }, + }, + }; + const issues = validateBySchema(xmlText, schema); + expect(issues).to.have.lengthOf(1); + const issue = issues[0]; + expect(issue.msg).to.eql("Missing Required Attribute: "); + expect(issue.severity).to.eql("error"); + expect(issue.node.name).to.eql("person"); + expect(issue.position).to.eql({ startOffset: 27, endOffset: 32 }); + }); + + it("Detects multiple missing attributes", () => { + const xmlText = ` + + `; + + const schema = { + required: true, + cardinality: "single", + name: "people", + attributes: {}, + + elements: { + person: { + name: "person", + required: false, + cardinality: "many", + attributes: { + age: { + required: true, + key: "age", + }, + firstName: { + required: true, + key: "firstName", + }, + lastName: { + required: true, + key: "lastName", + }, + }, + elements: {}, + }, + }, + }; + const issues = validateBySchema(xmlText, schema); + expect(issues).to.have.lengthOf(3); + const messages = map(issues, "msg"); + expect(messages).to.have.members([ + "Missing Required Attribute: ", + "Missing Required Attribute: ", + "Missing Required Attribute: ", + ]); + }); + + it("Won't complain when all required attributes are present", () => { + const xmlText = ` + + `; + + const schema = { + required: true, + cardinality: "single", + name: "people", + attributes: {}, + + elements: { + person: { + name: "person", + required: false, + cardinality: "many", + attributes: { + age: { + required: true, + key: "age", + }, + firstName: { + required: true, + key: "firstName", + }, + lastName: { + required: true, + key: "lastName", + }, + }, + elements: {}, + }, + }, + }; + const issues = validateBySchema(xmlText, schema); + expect(issues).to.be.empty; + }); + }); + }); +}); diff --git a/projects/xml-tools/packages/simple-schema/test/validators/element-required-elements-spec.js b/projects/xml-tools/packages/simple-schema/test/validators/element-required-elements-spec.js new file mode 100644 index 00000000..6204b9c1 --- /dev/null +++ b/projects/xml-tools/packages/simple-schema/test/validators/element-required-elements-spec.js @@ -0,0 +1,140 @@ +const { expect } = require("chai"); +const { map } = require("lodash"); +const { validateBySchema } = require("./utils"); + +describe("The XML Simple Schema", () => { + describe("Validations Functionality", () => { + context("element required sub elements", () => { + it("Detects a missing sub element", () => { + const xmlText = ` + + Duck + + `; + + const schema = { + required: true, + cardinality: "single", + name: "people", + attributes: {}, + + elements: { + person: { + name: "person", + required: false, + cardinality: "many", + attributes: {}, + elements: { + name: { + name: "name", + required: true, + cardinality: "many", + attributes: {}, + elements: {}, + }, + }, + }, + }, + }; + const issues = validateBySchema(xmlText, schema); + expect(issues).to.have.lengthOf(1); + const issue = issues[0]; + expect(issue.msg).to.eql("Missing Required Sub-Element: "); + expect(issue.severity).to.eql("error"); + expect(issue.node.name).to.eql("person"); + expect(issue.position).to.eql({ startOffset: 27, endOffset: 32 }); + }); + + it("Detects multiple missing sub elements", () => { + const xmlText = ` + + Duck + + `; + + const schema = { + required: true, + cardinality: "single", + name: "people", + attributes: {}, + + elements: { + person: { + name: "person", + required: false, + cardinality: "many", + attributes: {}, + elements: { + name: { + name: "name", + required: true, + cardinality: "many", + attributes: {}, + elements: {}, + }, + age: { + name: "age", + required: true, + cardinality: "many", + attributes: {}, + elements: {}, + }, + }, + }, + }, + }; + const issues = validateBySchema(xmlText, schema); + expect(issues).to.have.lengthOf(2); + const messages = map(issues, "msg"); + expect(messages).to.have.members([ + "Missing Required Sub-Element: ", + "Missing Required Sub-Element: ", + ]); + }); + + it("Won't complain when all required sub-elements are present", () => { + const xmlText = ` + + Donald + 85 + Duck + + `; + + const schema = { + required: true, + cardinality: "single", + name: "people", + attributes: {}, + + elements: { + person: { + name: "person", + required: false, + cardinality: "many", + attributes: {}, + elements: { + name: { + name: "name", + required: true, + cardinality: "many", + attributes: {}, + elements: {}, + }, + age: { + name: "age", + required: true, + cardinality: "many", + attributes: {}, + elements: {}, + }, + }, + }, + }, + }; + const issues = validateBySchema(xmlText, schema); + expect(issues).to.be.empty; + }); + }); + }); +}); diff --git a/projects/xml-tools/packages/simple-schema/test/validators/element-unknown-attributes-spec.js b/projects/xml-tools/packages/simple-schema/test/validators/element-unknown-attributes-spec.js new file mode 100644 index 00000000..582d91b2 --- /dev/null +++ b/projects/xml-tools/packages/simple-schema/test/validators/element-unknown-attributes-spec.js @@ -0,0 +1,195 @@ +const { expect } = require("chai"); +const { map } = require("lodash"); +const { validateBySchema } = require("./utils"); + +describe("The XML Simple Schema", () => { + describe("Validations Functionality", () => { + context("element unknown attribute", () => { + it("Detects a single unknown attribute", () => { + const xmlText = ` + + `; + + const schema = { + required: true, + cardinality: "single", + name: "people", + attributes: {}, + + elements: { + person: { + name: "person", + required: false, + attributesType: "closed", + cardinality: "many", + attributes: { + age: { + key: "age", + }, + }, + elements: {}, + }, + }, + }; + const issues = validateBySchema(xmlText, schema); + expect(issues).to.have.lengthOf(1); + const issue = issues[0]; + expect(issue.msg).to.eql( + "Unknown Attribute: only [age] attributes are allowed" + ); + expect(issue.severity).to.eql("error"); + expect(issue.node.key).to.eql("name"); + expect(issue.position).to.eql({ startOffset: 34, endOffset: 37 }); + }); + + it("Detects multiple missing attributes", () => { + const xmlText = ` + + `; + + const schema = { + required: true, + cardinality: "single", + name: "people", + attributes: {}, + + elements: { + person: { + name: "person", + required: false, + attributesType: "closed", + cardinality: "many", + attributes: { + age: { + key: "age", + }, + firstName: { + key: "firstName", + }, + lastName: { + key: "lastName", + }, + }, + elements: {}, + }, + }, + }; + const issues = validateBySchema(xmlText, schema); + expect(issues).to.have.lengthOf(3); + const messages = map(issues, "msg"); + expect(messages).to.have.members([ + "Unknown Attribute: only [age,firstName,lastName] attributes are allowed", + "Unknown Attribute: only [age,firstName,lastName] attributes are allowed", + "Unknown Attribute: only [age,firstName,lastName] attributes are allowed", + ]); + }); + + it("Won't complain when all attributes are known", () => { + const xmlText = ` + + `; + + const schema = { + required: true, + cardinality: "single", + name: "people", + attributes: {}, + + elements: { + person: { + name: "person", + required: false, + cardinality: "many", + attributesType: "closed", + attributes: { + age: { + key: "age", + }, + firstName: { + key: "firstName", + }, + lastName: { + key: "lastName", + }, + }, + elements: {}, + }, + }, + }; + const issues = validateBySchema(xmlText, schema); + expect(issues).to.be.empty; + }); + + it("Default namespace attribute is always known", () => { + const xmlText = ` + + `; + + const schema = { + required: true, + name: "people", + cardinality: "single", + attributesType: "closed", + attributes: { + description: { + key: "description", + required: false, + }, + }, + + elements: { + person: { + name: "person", + required: false, + cardinality: "many", + attributes: { + age: { + required: true, + key: "age", + }, + }, + elements: {}, + }, + }, + }; + const issues = validateBySchema(xmlText, schema); + expect(issues).to.have.lengthOf(0); + }); + it("Namespace attribute with prefix is always known", () => { + const xmlText = ` + + `; + + const schema = { + required: true, + name: "people", + cardinality: "single", + attributesType: "closed", + attributes: { + description: { + key: "description", + required: false, + }, + }, + + elements: { + person: { + name: "person", + required: false, + cardinality: "many", + attributes: { + age: { + required: true, + key: "age", + }, + }, + elements: {}, + }, + }, + }; + const issues = validateBySchema(xmlText, schema); + expect(issues).to.have.lengthOf(0); + }); + }); + }); +}); diff --git a/projects/xml-tools/packages/simple-schema/test/validators/element-unknown-sub-elements-spec.js b/projects/xml-tools/packages/simple-schema/test/validators/element-unknown-sub-elements-spec.js new file mode 100644 index 00000000..dabb397e --- /dev/null +++ b/projects/xml-tools/packages/simple-schema/test/validators/element-unknown-sub-elements-spec.js @@ -0,0 +1,193 @@ +const { expect } = require("chai"); +const { map } = require("lodash"); +const { validateBySchema } = require("./utils"); + +describe("The XML Simple Schema", () => { + describe("Validations Functionality", () => { + context("element unknown sub elements", () => { + it("Detects a single unknown sub element", () => { + const xmlText = ` + + Duck + + `; + + const schema = { + required: true, + cardinality: "single", + name: "people", + attributes: {}, + + elements: { + person: { + name: "person", + required: false, + cardinality: "many", + elementsType: "closed", + attributes: {}, + elements: { + name: { + name: "name", + cardinality: "many", + attributes: {}, + elements: {}, + }, + }, + }, + }, + }; + const issues = validateBySchema(xmlText, schema); + expect(issues).to.have.lengthOf(1); + const issue = issues[0]; + expect(issue.msg).to.eql( + "Unknown Sub-Element: only [name] Sub-Elements are allowed" + ); + expect(issue.severity).to.eql("error"); + expect(issue.node.name).to.eql("lastName"); + expect(issue.position).to.eql({ startOffset: 55, endOffset: 62 }); + }); + + it("Detects multiple unknown sub elements", () => { + const xmlText = ` + + Duck +
NY
+
+
`; + + const schema = { + required: true, + cardinality: "single", + name: "people", + attributes: {}, + + elements: { + person: { + name: "person", + required: false, + cardinality: "many", + attributes: {}, + elementsType: "closed", + elements: { + name: { + name: "name", + cardinality: "many", + attributes: {}, + elements: {}, + }, + age: { + name: "age", + cardinality: "many", + attributes: {}, + elements: {}, + }, + }, + }, + }, + }; + const issues = validateBySchema(xmlText, schema); + expect(issues).to.have.lengthOf(2); + const messages = map(issues, "msg"); + expect(messages).to.have.members([ + "Unknown Sub-Element: only [name,age] Sub-Elements are allowed", + "Unknown Sub-Element:
only [name,age] Sub-Elements are allowed", + ]); + }); + + it("Won't complain when all sub-elements are known", () => { + const xmlText = ` + + Donald + 85 + + `; + + const schema = { + required: true, + cardinality: "single", + name: "people", + attributes: {}, + + elements: { + person: { + name: "person", + required: false, + cardinality: "many", + attributes: {}, + elementsType: "closed", + elements: { + name: { + name: "name", + cardinality: "many", + attributes: {}, + elements: {}, + }, + age: { + name: "age", + cardinality: "many", + attributes: {}, + elements: {}, + }, + address: { + name: "address", + cardinality: "many", + attributes: {}, + elements: {}, + }, + }, + }, + }, + }; + const issues = validateBySchema(xmlText, schema); + expect(issues).to.be.empty; + }); + + it("Won't complain for partial sub elements", () => { + const xmlText = ` + + < + + `; + + const schema = { + required: true, + cardinality: "single", + name: "people", + attributes: {}, + + elements: { + person: { + name: "person", + required: false, + cardinality: "many", + attributes: {}, + elementsType: "closed", + elements: { + name: { + name: "name", + cardinality: "many", + attributes: {}, + elements: {}, + }, + age: { + name: "age", + cardinality: "many", + attributes: {}, + elements: {}, + }, + address: { + name: "address", + cardinality: "many", + attributes: {}, + elements: {}, + }, + }, + }, + }, + }; + const issues = validateBySchema(xmlText, schema); + expect(issues).to.be.empty; + }); + }); + }); +}); diff --git a/projects/xml-tools/packages/simple-schema/test/validators/utils.js b/projects/xml-tools/packages/simple-schema/test/validators/utils.js new file mode 100644 index 00000000..d680a6ce --- /dev/null +++ b/projects/xml-tools/packages/simple-schema/test/validators/utils.js @@ -0,0 +1,28 @@ +const { parse } = require("@xml-tools/parser"); +const { buildAst } = require("@xml-tools/ast"); +const { validate } = require("@xml-tools/validation"); +const { getSchemaValidators } = require("../../"); + +/** + * + * @param {string} xmlText + * @param {SimpleSchema} schema + * @returns {ValidationIssue[] | *} + */ +function validateBySchema(xmlText, schema) { + const { cst, tokenVector } = parse(xmlText); + const xmlDoc = buildAst(cst, tokenVector); + const schemaValidators = getSchemaValidators(schema); + const issues = validate({ + doc: xmlDoc, + validators: { + attribute: [schemaValidators.attribute], + element: [schemaValidators.element], + }, + }); + return issues; +} + +module.exports = { + validateBySchema: validateBySchema, +}; diff --git a/projects/xml-tools/packages/validation/.npmignore b/projects/xml-tools/packages/validation/.npmignore new file mode 100644 index 00000000..ae12335e --- /dev/null +++ b/projects/xml-tools/packages/validation/.npmignore @@ -0,0 +1,10 @@ +# NYC +.nyc_output +nyc.config.js +coverage + +# tests +test + +# contributing +CONTRIBUTING.md \ No newline at end of file diff --git a/projects/xml-tools/packages/validation/CHANGELOG.md b/projects/xml-tools/packages/validation/CHANGELOG.md new file mode 100644 index 00000000..c83bbc79 --- /dev/null +++ b/projects/xml-tools/packages/validation/CHANGELOG.md @@ -0,0 +1,130 @@ +# Change Log + +All notable changes to this project will be documented in this file. +See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + +# [1.1.0](https://github.com/sap/xml-tools/compare/@xml-tools/validation@1.0.16...@xml-tools/validation@1.1.0) (2026-02-11) + +### Features + +- update lodash dependency to version 4.17.23 ([#461](https://github.com/sap/xml-tools/issues/461)) ([23a319d](https://github.com/sap/xml-tools/commit/23a319d6ed42a4a3885ec5748eb127147a8960bc)) + +## [1.0.16](https://github.com/sap/xml-tools/compare/@xml-tools/validation@1.0.15...@xml-tools/validation@1.0.16) (2021-06-03) + +**Note:** Version bump only for package @xml-tools/validation + +## [1.0.15](https://github.com/sap/xml-tools/compare/@xml-tools/validation@1.0.14...@xml-tools/validation@1.0.15) (2021-02-09) + +**Note:** Version bump only for package @xml-tools/validation + +## [1.0.14](https://github.com/sap/xml-tools/compare/@xml-tools/validation@1.0.13...@xml-tools/validation@1.0.14) (2021-01-21) + +**Note:** Version bump only for package @xml-tools/validation + +## [1.0.13](https://github.com/sap/xml-tools/compare/@xml-tools/validation@1.0.12...@xml-tools/validation@1.0.13) (2021-01-20) + +### Bug Fixes + +- missing lib folder in npm packages ([ffed3c2](https://github.com/sap/xml-tools/commit/ffed3c2c54c70aea8b9ded0d53786382bc190cc5)) + +## [1.0.12](https://github.com/sap/xml-tools/compare/@xml-tools/validation@1.0.11...@xml-tools/validation@1.0.12) (2021-01-20) + +**Note:** Version bump only for package @xml-tools/validation + +## [1.0.11](https://github.com/sap/xml-tools/compare/@xml-tools/validation@1.0.10...@xml-tools/validation@1.0.11) (2020-06-29) + +**Note:** Version bump only for package @xml-tools/validation + +## [1.0.10](https://github.com/sap/xml-tools/compare/@xml-tools/validation@1.0.9...@xml-tools/validation@1.0.10) (2020-06-18) + +**Note:** Version bump only for package @xml-tools/validation + +## [1.0.9](https://github.com/sap/xml-tools/compare/@xml-tools/validation@1.0.8...@xml-tools/validation@1.0.9) (2020-05-17) + +**Note:** Version bump only for package @xml-tools/validation + +## [1.0.8](https://github.com/sap/xml-tools/compare/@xml-tools/validation@1.0.7...@xml-tools/validation@1.0.8) (2020-05-05) + +**Note:** Version bump only for package @xml-tools/validation + +## [1.0.7](https://github.com/sap/xml-tools/compare/@xml-tools/validation@1.0.6...@xml-tools/validation@1.0.7) (2020-04-02) + +**Note:** Version bump only for package @xml-tools/validation + +## [1.0.6](https://github.com/sap/xml-tools/compare/@xml-tools/validation@1.0.5...@xml-tools/validation@1.0.6) (2020-02-19) + +### Bug Fixes + +- add npmignore file to each package ([5bbf209](https://github.com/sap/xml-tools/commit/5bbf209)) + +## [1.0.5](https://github.com/sap/xml-tools/compare/@xml-tools/validation@1.0.4...@xml-tools/validation@1.0.5) (2020-02-11) + +**Note:** Version bump only for package @xml-tools/validation + +## [1.0.4](https://github.com/sap/xml-tools/compare/@xml-tools/validation@1.0.2...@xml-tools/validation@1.0.4) (2020-02-10) + +**Note:** Version bump only for package @xml-tools/validation + +## [1.0.3](https://github.com/sap/xml-tools/compare/@xml-tools/validation@1.0.2...@xml-tools/validation@1.0.3) (2020-02-07) + +**Note:** Version bump only for package @xml-tools/validation + +## [1.0.2](https://github.com/sap/xml-tools/compare/@xml-tools/validation@1.0.1...@xml-tools/validation@1.0.2) (2020-01-28) + +**Note:** Version bump only for package @xml-tools/validation + +## [1.0.1](https://github.com/sap/xml-tools/compare/@xml-tools/validation@1.0.0...@xml-tools/validation@1.0.1) (2020-01-20) + +**Note:** Version bump only for package @xml-tools/validation + +# [1.0.0](https://github.com/sap/xml-tools/compare/@xml-tools/validation@0.3.8...@xml-tools/validation@1.0.0) (2019-12-08) + +### Features + +- **ast:** attributesRange and guessedAttributeRange support ([8ce840d](https://github.com/sap/xml-tools/commit/8ce840d)), closes [#51](https://github.com/sap/xml-tools/issues/51) + +### BREAKING CHANGES + +- **ast:** buildAst now requires a tokenVector argument + +## [0.3.8](https://github.com/sap/xml-tools/compare/@xml-tools/validation@0.3.7...@xml-tools/validation@0.3.8) (2019-12-05) + +**Note:** Version bump only for package @xml-tools/validation + +## [0.3.7](https://github.com/sap/xml-tools/compare/@xml-tools/validation@0.3.6...@xml-tools/validation@0.3.7) (2019-12-03) + +### Bug Fixes + +- api type definitions ([46d6c2c](https://github.com/sap/xml-tools/commit/46d6c2c)) + +## [0.3.6](https://github.com/sap/xml-tools/compare/@xml-tools/validation@0.3.5...@xml-tools/validation@0.3.6) (2019-11-20) + +**Note:** Version bump only for package @xml-tools/validation + +## [0.3.5](https://github.com/sap/xml-tools/compare/@xml-tools/validation@0.3.4...@xml-tools/validation@0.3.5) (2019-11-12) + +**Note:** Version bump only for package @xml-tools/validation + +## [0.3.4](https://github.com/sap/xml-tools/compare/@xml-tools/validation@0.3.3...@xml-tools/validation@0.3.4) (2019-11-11) + +**Note:** Version bump only for package @xml-tools/validation + +## [0.3.3](https://github.com/sap/xml-tools/compare/@xml-tools/validation@0.3.2...@xml-tools/validation@0.3.3) (2019-11-11) + +**Note:** Version bump only for package @xml-tools/validation + +## [0.3.2](https://github.com/sap/xml-tools/compare/@xml-tools/validation@0.3.1...@xml-tools/validation@0.3.2) (2019-11-06) + +**Note:** Version bump only for package @xml-tools/validation + +## [0.3.1](https://github.com/sap/xml-tools/compare/@xml-tools/validation@0.3.0...@xml-tools/validation@0.3.1) (2019-11-06) + +**Note:** Version bump only for package @xml-tools/validation + +# 0.3.0 (2019-10-28) + +### Features + +- move to lerna independent mode ([6c347a8](https://github.com/sap/xml-tools/commit/6c347a8)), closes [#18](https://github.com/sap/xml-tools/issues/18) + +# 0.2.0 (2019-10-28) diff --git a/projects/xml-tools/packages/validation/CONTRIBUTING.md b/projects/xml-tools/packages/validation/CONTRIBUTING.md new file mode 100644 index 00000000..84eb9447 --- /dev/null +++ b/projects/xml-tools/packages/validation/CONTRIBUTING.md @@ -0,0 +1,4 @@ +# Contribution Guide + +This package does not have any unique development flows. +Please see the top level [Contribution Guide](../../CONTRIBUTING.md). diff --git a/projects/xml-tools/packages/validation/README.md b/projects/xml-tools/packages/validation/README.md new file mode 100644 index 00000000..24248c7c --- /dev/null +++ b/projects/xml-tools/packages/validation/README.md @@ -0,0 +1,70 @@ +[![npm (scoped)](https://img.shields.io/npm/v/@xml-tools/validation.svg)](https://www.npmjs.com/package/@xml-tools/validation) + +# @xml-tools/validation + +APIs which assist in implementing Validations / Diagnostics logic for XML. + +## Installation + +With npm: + +- `npm install @xml-tools/validation` + +With Yarn + +- `yarn add @xml-tools/validation` + +## Usage + +Please see the [TypeScript Definitions](./api.d.ts) for full API details. + +A simple usage example: + +```javascript +const { parse } = require("xml-tools/parser"); +const { buildAst } = require("xml-tools/ast"); +const { validate } = require("xml-tools/validation"); + +const xmlText = ` + Bill + +`; + +const { cst, tokenVector } = parse(xmlText); +const xmlDocAst = buildAst(cst, tokenVector); +const issues = validate({ + doc: xmlDocAst, + validators: { + element: [ + (node) => { + if (node.name === "note") { + const hasFrom = node.subElements.find( + (subNode) => subNode.name === "from" + ); + if (hasFrom === undefined) { + return [ + { + msg: "A Note Element **must** have a `from` subElement", + node: node, + }, + ]; + } + } + return []; + }, + ], + }, +}); + +console.log(issues[0].msg); // -> "A Note Element **must** have a `from` subElement" +// Issue position can be extracted from the relevant ASTNode. +console.log(issues[0].node.position.endLine); // -> 3 +``` + +## Support + +Please open [issues](https://github.com/SAP/xml-tols/issues) on github. + +## Contributing + +See [CONTRIBUTING.md](./CONTRIBUTING.md). diff --git a/projects/xml-tools/packages/validation/api.d.ts b/projects/xml-tools/packages/validation/api.d.ts new file mode 100644 index 00000000..2575f631 --- /dev/null +++ b/projects/xml-tools/packages/validation/api.d.ts @@ -0,0 +1,36 @@ +import { + XMLAstNode, + XMLAttribute, + XMLElement, + XMLDocument, +} from "@xml-tools/ast"; + +declare type AttributeValidator = (node: XMLAttribute) => ValidationIssue[]; +declare type ElementValidator = (node: XMLElement) => ValidationIssue[]; +declare function validate(options: { + doc: XMLDocument; + validators: { + attribute?: AttributeValidator[]; + element?: ElementValidator[]; + }; +}): ValidationIssue[]; + +interface ValidationIssue { + msg: string; + /** + * ASTNode where the issue occurred. + */ + node: XMLAstNode; + /** + * The Severity is optional as that may need to be determined outside + * the validation logic, For example in an IDE's inspections configuration. + */ + severity?: "info" | "warning" | "error"; + + /** + * Optional property to indicate a more specific error location. + * For example: For Missing Attribute error in an element we may want to + * only mark the Element **Name** area instead of the whole element. + */ + position?: { startOffset: number; endOffset: number }; +} diff --git a/projects/xml-tools/packages/validation/lib/api.js b/projects/xml-tools/packages/validation/lib/api.js new file mode 100644 index 00000000..d09bfa2e --- /dev/null +++ b/projects/xml-tools/packages/validation/lib/api.js @@ -0,0 +1,5 @@ +const { validate } = require("./validate"); + +module.exports = { + validate: validate, +}; diff --git a/projects/xml-tools/packages/validation/lib/validate.js b/projects/xml-tools/packages/validation/lib/validate.js new file mode 100644 index 00000000..5887ae45 --- /dev/null +++ b/projects/xml-tools/packages/validation/lib/validate.js @@ -0,0 +1,37 @@ +const { accept } = require("@xml-tools/ast"); +const { defaultsDeep, flatMap } = require("lodash"); + +function validate(options) { + const actualOptions = defaultsDeep(options, { + validators: { + attribute: [], + element: [], + }, + }); + + let issues = []; + + const validateVisitor = { + visitXMLElement: function (node) { + const newIssues = flatMap(actualOptions.validators.element, (validator) => + validator(node) + ); + issues = issues.concat(newIssues); + }, + visitXMLAttribute: function (node) { + const newIssues = flatMap( + actualOptions.validators.attribute, + (validator) => validator(node) + ); + issues = issues.concat(newIssues); + }, + }; + + accept(actualOptions.doc, validateVisitor); + + return issues; +} + +module.exports = { + validate: validate, +}; diff --git a/projects/xml-tools/packages/validation/package.json b/projects/xml-tools/packages/validation/package.json new file mode 100644 index 00000000..1ec65437 --- /dev/null +++ b/projects/xml-tools/packages/validation/package.json @@ -0,0 +1,46 @@ +{ + "name": "@xml-tools/validation", + "version": "1.1.0", + "description": "XML validation APIs", + "keywords": [ + "xml", + "validation" + ], + "main": "lib/api.js", + "repository": "https://github.com/sap/xml-tools/", + "license": "Apache-2.0", + "typings": "./api.d.ts", + "files": [ + "lib", + ".reuse", + "LICENSES", + "api.d.ts" + ], + "dependencies": { + "@xml-tools/ast": "^5.1.0", + "lodash": "4.17.23" + }, + "devDependencies": { + "@xml-tools/parser": "^1.0.11" + }, + "scripts": { + "ci": "npm-run-all clean type-check coverage:*", + "clean": "rimraf ./coverage ./nyc_output", + "test": "mocha \"./test/**/*spec.js\"", + "coverage:run": "nyc mocha \"./test/**/*spec.js\"", + "coverage:check": "nyc check-coverage --lines 100 --branches 100 --statements 100 --functions 100", + "type-check": "tsc api.d.ts" + }, + "publishConfig": { + "access": "public" + }, + "nyc": { + "include": [ + "lib/**/*.js" + ], + "reporter": [ + "text", + "lcov" + ] + } +} diff --git a/projects/xml-tools/packages/validation/test/validate-apis-spec.js b/projects/xml-tools/packages/validation/test/validate-apis-spec.js new file mode 100644 index 00000000..39e716d2 --- /dev/null +++ b/projects/xml-tools/packages/validation/test/validate-apis-spec.js @@ -0,0 +1,62 @@ +const { expect } = require("chai"); +const { map } = require("lodash"); +const { parse } = require("@xml-tools/parser"); +const { buildAst } = require("@xml-tools/ast"); +const { validate } = require("../"); + +describe("The XML Validations APIs", () => { + it("can validate attributes", () => { + const xmlText = ` + Bill + Tim + `; + + const ast = xmlTextToAst(xmlText); + + const issues = validate({ + doc: ast, + validators: { + element: [ + (node) => { + return [{ msg: node.name }]; + }, + ], + }, + }); + + const msgNodeNames = map(issues, (_) => _.msg); + expect(msgNodeNames).to.have.lengthOf(3); + expect(msgNodeNames).to.have.members(["note", "to", "from"]); + }); + + it("can validate elements", () => { + const xmlText = ` + + Gates + Tim + `; + + const ast = xmlTextToAst(xmlText); + + const issues = validate({ + doc: ast, + validators: { + attribute: [ + (node) => { + return [{ msg: node.key }]; + }, + ], + }, + }); + + const msgNodeNames = map(issues, (_) => _.msg); + expect(msgNodeNames).to.have.lengthOf(2); + expect(msgNodeNames).to.have.members(["clearance", "hidden"]); + }); +}); + +function xmlTextToAst(text) { + const { cst, tokenVector } = parse(text); + const ast = buildAst(cst, tokenVector); + return ast; +} diff --git a/projects/xml-tools/packages/xml-toolkit/.gitignore b/projects/xml-tools/packages/xml-toolkit/.gitignore new file mode 100644 index 00000000..d487759d --- /dev/null +++ b/projects/xml-tools/packages/xml-toolkit/.gitignore @@ -0,0 +1,4 @@ +# These files will be copied from the root package during bundling. +/LICENSE + +.cache \ No newline at end of file diff --git a/projects/xml-tools/packages/xml-toolkit/.vscodeignore b/projects/xml-tools/packages/xml-toolkit/.vscodeignore new file mode 100644 index 00000000..d3f9af35 --- /dev/null +++ b/projects/xml-tools/packages/xml-toolkit/.vscodeignore @@ -0,0 +1,20 @@ + +# We are bundling our extension so no need to include the dependencies separately. +node_modules + +# Except a **subset** language-server package which is executed as a separate process +# Note only one star (*) is expended in these dirs. +!**/@xml-tools/language-server/* +!**/@xml-tools/language-server/dist/* + +# For debugging of bundled language-server in production (source maps) +!**/@xml-tools/language-server/lib/** + +# The VSCode Extension's dev artifacts +coverage/** +.nyc_output/** +test/** +scripts + +# Loaded via gitcdn.xyz in the README, avoid packaging to reduce .vsix size +resources/readme/** \ No newline at end of file diff --git a/projects/xml-tools/packages/xml-toolkit/CHANGELOG.md b/projects/xml-tools/packages/xml-toolkit/CHANGELOG.md new file mode 100644 index 00000000..192a6a4c --- /dev/null +++ b/projects/xml-tools/packages/xml-toolkit/CHANGELOG.md @@ -0,0 +1,96 @@ +# Change Log + +All notable changes to this project will be documented in this file. +See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + +# [1.3.0](https://github.com/SAP/xml-tools/tree/master/packages/xml-toolkit/compare/xml-toolkit@1.2.1...xml-toolkit@1.3.0) (2026-02-11) + +### Features + +- update lodash dependency to version 4.17.23 ([#461](https://github.com/SAP/xml-tools/tree/master/packages/xml-toolkit/issues/461)) ([23a319d](https://github.com/SAP/xml-tools/tree/master/packages/xml-toolkit/commit/23a319d6ed42a4a3885ec5748eb127147a8960bc)) + +## [1.2.1](https://github.com/SAP/xml-tools/tree/master/packages/xml-toolkit/compare/xml-toolkit@1.2.0...xml-toolkit@1.2.1) (2025-07-15) + +### Bug Fixes + +- updating xml-toolkit README.md file ([#458](https://github.com/SAP/xml-tools/tree/master/packages/xml-toolkit/issues/458)) ([2283302](https://github.com/SAP/xml-tools/tree/master/packages/xml-toolkit/commit/2283302fae161106299179e5aa8822a9bc7010bc)) + +# [1.2.0](https://github.com/SAP/xml-tools/tree/master/packages/xml-toolkit/compare/xml-toolkit@1.1.1...xml-toolkit@1.2.0) (2025-07-14) + +### Features + +- new xml-toolkit logo ([#454](https://github.com/SAP/xml-tools/tree/master/packages/xml-toolkit/issues/454)) ([4a1888f](https://github.com/SAP/xml-tools/tree/master/packages/xml-toolkit/commit/4a1888fb5128a8373d6e16de5db7e97ba6577311)) + +## [1.1.1](https://github.com/SAP/xml-tools/tree/master/packages/xml-toolkit/compare/xml-toolkit@1.1.0...xml-toolkit@1.1.1) (2021-06-03) + +**Note:** Version bump only for package xml-toolkit + +# [1.1.0](https://github.com/SAP/xml-tools/tree/master/packages/xml-toolkit/compare/xml-toolkit@1.0.5...xml-toolkit@1.1.0) (2021-02-09) + +### Bug Fixes + +- use extension name as diagnostic source ([#297](https://github.com/SAP/xml-tools/tree/master/packages/xml-toolkit/issues/297)) ([f6d7dc4](https://github.com/SAP/xml-tools/tree/master/packages/xml-toolkit/commit/f6d7dc42a05c4e1d7c9ab3f1fbe35ecb1eb820fa)) + +### Features + +- well formed constraints validations ([#293](https://github.com/SAP/xml-tools/tree/master/packages/xml-toolkit/issues/293)) ([8a9d535](https://github.com/SAP/xml-tools/tree/master/packages/xml-toolkit/commit/8a9d535e95a52b9dfc1068d2e203b09bd08e1066)), closes [#13](https://github.com/SAP/xml-tools/tree/master/packages/xml-toolkit/issues/13) [#14](https://github.com/SAP/xml-tools/tree/master/packages/xml-toolkit/issues/14) + +## [1.0.5](https://github.com/SAP/xml-tools/tree/master/packages/xml-toolkit/compare/xml-toolkit@1.0.4...xml-toolkit@1.0.5) (2021-01-20) + +**Note:** Version bump only for package xml-toolkit + +## [1.0.4](https://github.com/SAP/xml-tools/tree/master/packages/xml-toolkit/compare/xml-toolkit@1.0.3...xml-toolkit@1.0.4) (2021-01-20) + +**Note:** Version bump only for package xml-toolkit + +## [1.0.3](https://github.com/SAP/xml-tools/tree/master/packages/xml-toolkit/compare/xml-toolkit@1.0.2...xml-toolkit@1.0.3) (2020-06-29) + +**Note:** Version bump only for package xml-toolkit + +## [1.0.2](https://github.com/SAP/xml-tools/tree/master/packages/xml-toolkit/compare/xml-toolkit@1.0.1...xml-toolkit@1.0.2) (2020-06-18) + +**Note:** Version bump only for package xml-toolkit + +## [1.0.1](https://github.com/SAP/xml-tools/tree/master/packages/xml-toolkit/compare/xml-toolkit@1.0.0...xml-toolkit@1.0.1) (2020-05-26) + +**Note:** Version bump only for package xml-toolkit + +# [1.0.0](https://github.com/SAP/xml-tools/tree/master/packages/xml-toolkit/compare/xml-toolkit@0.1.7...xml-toolkit@1.0.0) (2020-05-25) + +### chore + +- **xml-toolkit:** first major version ([9e1f5a9](https://github.com/SAP/xml-tools/tree/master/packages/xml-toolkit/commit/9e1f5a9)) + +### BREAKING CHANGES + +- **xml-toolkit:** First Major Version + +## 0.1.7 (2020-05-21) + +**Note:** Version bump only for package xml-toolkit + +## [0.1.6](https://github.com/SAP/xml-tools/compare/xml-for-vscode@0.1.5...xml-for-vscode@0.1.6) (2020-05-17) + +### Bug Fixes + +- **xml-for-vscode:** incorrect vscode engine version ([88adfa3](https://github.com/SAP/xml-tools/commit/88adfa3)) + +## [0.1.5](https://github.com/SAP/xml-tools/compare/xml-for-vscode@0.1.4...xml-for-vscode@0.1.5) (2020-05-17) + +**Note:** Version bump only for package xml-for-vscode + +## [0.1.4](https://github.com/SAP/xml-tools/compare/xml-for-vscode@0.1.3...xml-for-vscode@0.1.4) (2020-05-05) + +**Note:** Version bump only for package xml-for-vscode + +## [0.1.3](https://github.com/SAP/xml-tools/compare/xml-for-vscode@0.1.2...xml-for-vscode@0.1.3) (2020-04-02) + +**Note:** Version bump only for package xml-for-vscode + +## [0.1.2](https://github.com/SAP/xml-tools/compare/xml-for-vscode@0.1.1...xml-for-vscode@0.1.2) (2020-03-08) + +**Note:** Version bump only for package xml-for-vscode + +## 0.1.1 (2020-03-08) + +**Note:** Version bump only for package xml-for-vscode diff --git a/projects/xml-tools/packages/xml-toolkit/README.md b/projects/xml-tools/packages/xml-toolkit/README.md new file mode 100644 index 00000000..e63f923d --- /dev/null +++ b/projects/xml-tools/packages/xml-toolkit/README.md @@ -0,0 +1,48 @@ +# XML Toolkit + +The XML toolkit is a VSCode extension that provides XML language editor support. + +## Features + +### XML syntax validations + +The syntax validations use a fault-tolerant parser. This enables +the detection of **multiple** syntax errors. + +### XML Well-formed validations + +- Unique Attribute Names. +- Tag open/close names are aligned. + +#### Preview + +![](https://raw.githubusercontent.com/SAP/xml-tools/master/packages/xml-toolkit/resources/readme/preview-syntax-validation.png) + +## Installation + +### From the VS Code Marketplace + +In the [XML Toolkit](https://marketplace.visualstudio.com/items?itemName=SAPOSS.xml-toolkit) +page, click **Install**. + +### From Github releases + +1. Go to [GitHub Releases](https://github.com/sap/xml-tools/releases). +2. Search for the `.vsix` archive under `xml-toolkit\@x.y.z` releases. + - Replace `x.y.z` with the desired version number. +3. Follow the instructions for installing an extension from a `.vsix` + file in the [VSCode's guide](https://code.visualstudio.com/docs/editor/extension-gallery#_install-from-a-vsix). + +## Usage + +The extension is activated when an `.xml` file is opened. +Both lexing and parsing errors will be shown as squiggly red underlines +and in the Problems view. + +## Support + +You can open [issues](https://github.com/SAP/xml-tools/issues) on GitHub. + +## Contributing + +See [CONTRIBUTING.md](./CONTRIBUTING.md). diff --git a/projects/xml-tools/packages/xml-toolkit/lib/extension.js b/projects/xml-tools/packages/xml-toolkit/lib/extension.js new file mode 100644 index 00000000..b75797f1 --- /dev/null +++ b/projects/xml-tools/packages/xml-toolkit/lib/extension.js @@ -0,0 +1,64 @@ +const { readFile: readFileCallback } = require("fs"); +const { resolve } = require("path"); +const { promisify } = require("util"); +const { workspace } = require("vscode"); +const { LanguageClient, TransportKind } = require("vscode-languageclient"); +const { SERVER_PATH } = require("@xml-tools/language-server"); + +const readFile = promisify(readFileCallback); + +let client; + +async function activate(context) { + const debugOptions = { execArgv: ["--nolazy", "--inspect=6009"] }; + + const serverOptions = { + run: { module: SERVER_PATH, transport: TransportKind.ipc }, + debug: { + module: SERVER_PATH, + transport: TransportKind.ipc, + options: debugOptions, + }, + }; + + const meta = JSON.parse( + await readFile(resolve(context.extensionPath, "package.json"), "utf8") + ); + + /** + * @type {import("@xml-tools/language-server).ServerInitializationOptions} + */ + const initializationOptions = { + consumer: meta.displayName, + }; + + let clientOptions = { + documentSelector: [{ scheme: "file", language: "xml" }], + synchronize: { + fileEvents: workspace.createFileSystemWatcher("**/*.xml"), + }, + initializationOptions: initializationOptions, + }; + + client = new LanguageClient( + "XMLforVSCode", + "XML For VSCode", + serverOptions, + clientOptions + ); + + client.start(); +} + +function deactivate() { + /* istanbul ignore if - scenario that cannot be reached in productive code */ + if (!client) { + return undefined; + } + return client.stop(); +} + +module.exports = { + activate: activate, + deactivate: deactivate, +}; diff --git a/projects/xml-tools/packages/xml-toolkit/package.json b/projects/xml-tools/packages/xml-toolkit/package.json new file mode 100644 index 00000000..4a08d216 --- /dev/null +++ b/projects/xml-tools/packages/xml-toolkit/package.json @@ -0,0 +1,58 @@ +{ + "name": "xml-toolkit", + "version": "1.3.0", + "private": true, + "displayName": "XML Toolkit", + "description": "Language Support for XML", + "publisher": "SAPOSS", + "icon": "resources/xml-toolkit.png", + "keywords": [ + "xml" + ], + "categories": [ + "Programming Languages" + ], + "license": "Apache-2.0", + "repository": { + "type": "git", + "url": "https://github.com/SAP/xml-tools/tree/master/packages/xml-toolkit" + }, + "engines": { + "vscode": "^1.56.0" + }, + "activationEvents": [ + "onLanguage:xml" + ], + "main": "./lib/extension", + "scripts": { + "ci": "npm-run-all clean coverage:* bundle package", + "clean": "rimraf ./dist ./coverage ./nyc_output *.vsix NOTICE LICENSE", + "test": "node test/runTest.js", + "coverage:run": "nyc node test/runTest.js", + "coverage:check": "nyc check-coverage --lines 100 --branches 100 --statements 100 --functions 100", + "vsix:prepare": "sh prepare-vsix-package.sh", + "bundle": "webpack --mode production", + "package": "node ./scripts/package-vsix" + }, + "dependencies": { + "@xml-tools/language-server": "^1.2.0", + "vscode-languageclient": "6.1.3" + }, + "devDependencies": { + "@types/vscode": "1.56.0", + "deep-equal-in-any-order": "1.0.28", + "lodash": "4.17.23", + "proxyquire": "2.1.3", + "vsce": "1.84.0", + "@vscode/test-electron": "^2.4.0" + }, + "nyc": { + "include": [ + "lib/**/*.js" + ], + "reporter": [ + "text", + "lcov" + ] + } +} diff --git a/projects/xml-tools/packages/xml-toolkit/resources/readme/preview-syntax-validation.png b/projects/xml-tools/packages/xml-toolkit/resources/readme/preview-syntax-validation.png new file mode 100644 index 0000000000000000000000000000000000000000..cf2a261f25478db549bbfcd264a071d1951a9440 GIT binary patch literal 105106 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ɑ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 { + // Shallow + expect( + monoRepoNoHoist, + `Add "${extName}/${_}" to root monorepo package.json[workspaces.nohoist]` + ).to.include(`${extName}/${_}`); + // Transitive + expect( + monoRepoNoHoist, + `Add "${extName}/${_}/**" to root monorepo package.json[workspaces.nohoist]` + ).to.include(`${extName}/${_}/**`); +}); + +// The path to the language server must be resolved from **inside** the VSCode Ext's node_modules. +const langServerDir = resolve( + __dirname, + "..", + "node_modules", + "@xml-tools", + "language-server" +); + +// **Hot-Patching** VSCE using proxyquire. +const rootExtDir = resolve(__dirname, ".."); +const getDepsStub = { + getDependencies: async () => [rootExtDir, langServerDir], +}; +const { packageCommand } = proxyquire("vsce/out/package", { + "./npm": getDepsStub, +}); + +const pkgJsonPath = resolve(rootExtDir, "package.json"); +// Read & save the original literal representation of the pkg.json +// To avoid dealing with re-formatting (prettier) later on. +const pkgJsonOrgStr = readFileSync(pkgJsonPath, "utf8"); +const pkgJson = JSON.parse(pkgJsonOrgStr); +// During development flows the `main` should point to the compiled sourced +// for fast dev feedback loops. +expect(pkgJson.main).to.equal("./lib/extension"); +// During production flows the main should point to the bundled sources +// to reduce loading time. +pkgJson.main = "./dist/extension"; +writeJsonSync(pkgJsonPath, pkgJson, { spaces: 2, EOF: "\n" }); + +// Ensure License and copywrite related files are part of the packaged .vsix +const rootMonoRepoDir = resolve(__dirname, "..", "..", ".."); +const licenseRootMonoRepoPath = resolve(rootMonoRepoDir, "LICENSE"); +const licenseExtPath = resolve(rootExtDir, "LICENSE"); +copyFileSync(licenseRootMonoRepoPath, licenseExtPath); + +const licensesDirPath = resolve(rootMonoRepoDir, "LICENSES"); +const licensesDirExtPath = resolve(rootExtDir, "LICENSES"); +emptyDirSync(licensesDirExtPath); +copySync(licensesDirPath, licensesDirExtPath); + +const reuseDirPath = resolve(rootMonoRepoDir, ".reuse"); +const reuseDirExtPath = resolve(rootExtDir, "LICENSES"); +emptyDirSync(reuseDirExtPath); +copySync(reuseDirPath, reuseDirExtPath); + +packageCommand({ + cwd: rootExtDir, + packagePath: undefined, + baseContentUrl: undefined, + baseImagesUrl: undefined, + useYarn: true, + ignoreFile: undefined, + expandGitHubIssueLinks: undefined, +}) + .catch((e) => { + console.error(e.message); + process.exitCode = 666; + }) + .finally(() => { + // revert changes to the pkg.json, ensure clean git working directory + writeFileSync(pkgJsonPath, pkgJsonOrgStr); + }); diff --git a/projects/xml-tools/packages/xml-toolkit/test/runTest.js b/projects/xml-tools/packages/xml-toolkit/test/runTest.js new file mode 100644 index 00000000..a5e047c9 --- /dev/null +++ b/projects/xml-tools/packages/xml-toolkit/test/runTest.js @@ -0,0 +1,42 @@ +const { resolve } = require("path"); +const { runTests } = require("@vscode/test-electron"); + +async function main() { + try { + // NODE_OPTIONS set by nyc for coverage instrumentation are not supported + // in Electron/VS Code packaged apps and cause the test runner to fail. + // Clear them before launching VS Code to avoid this issue. + delete process.env.NODE_OPTIONS; + + // The path to the extension test environment + // Passed to --extensionTestsEnv + const extensionTestsEnv = { + path: resolve(__dirname, "..", "..", "test", "suite", "textFixure"), + }; + + // The folder containing the Extension Manifest package.json + // Passed to `--extensionDevelopmentPath` + const extensionDevelopmentPath = resolve(__dirname, ".."); + + // The path to the extension test script + // Passed to --extensionTestsPath + const extensionTestsPath = resolve(__dirname, "suite", "index"); + + // Download VS Code, unzip it and run the integration test + await runTests({ + extensionTestsEnv, + extensionDevelopmentPath, + extensionTestsPath, + // Required for headless CI environments (Jenkins, etc.) where no + // display server is available. Without these flags Electron refuses + // to start and the test run fails immediately. + launchArgs: ["--headless", "--disable-gpu", "--no-sandbox"], + }); + } catch (err) { + // Log the underlying error so CI logs actually show what went wrong. + console.error("Failed to run tests:", err); + process.exit(1); + } +} + +main(); diff --git a/projects/xml-tools/packages/xml-toolkit/test/suite/extension.test.js b/projects/xml-tools/packages/xml-toolkit/test/suite/extension.test.js new file mode 100644 index 00000000..fd9617fb --- /dev/null +++ b/projects/xml-tools/packages/xml-toolkit/test/suite/extension.test.js @@ -0,0 +1,129 @@ +const { resolve } = require("path"); +const vscode = require("vscode"); +const fs = require("fs").promises; +const { deactivate } = require("../../lib/extension"); +const deepEqualInAnyOrder = require("deep-equal-in-any-order"); +const chai = require("chai"); +const { expect } = chai; +chai.use(deepEqualInAnyOrder); + +const docPath = resolve( + __dirname, + "..", + "..", + "test", + "testFixure", + "test.xml" +); +const docUri = vscode.Uri.file(docPath); + +describe("XML for VSCode extension", () => { + before(async function () { + await vscode.workspace.openTextDocument(docUri); + await vscode.window.showTextDocument(docUri); + // We need to explicitly wait for extension to load + await sleep(1000); + }); + + after(async function () { + await setContent(""); + const extensionStatus = await deactivate(); + expect(extensionStatus).to.equal(undefined); + }); + + it("Will show lexing error in problems view", async function () { + const content = ` + + Tove + Jani + Reminder + Don't forget me this weekend! + `; + await setContent(content); + await testDiagnostics(docUri, [ + { + severity: vscode.DiagnosticSeverity.Error, + message: + "unexpected character: ->!<- at offset: 12, skipped 1 characters.", + range: toRange(1, 11, 1, 12), + source: "XML Toolkit", + }, + ]); + }); + + it("Will show parsing errors in problems view", async function () { + const content = ` + + Tove + Jani + Reminder + Don't forget me this weekend! + STRING <-- but found --> '>' <--", + range: toRange(2, 14, 2, 14), + source: "XML Toolkit", + }, + { + severity: vscode.DiagnosticSeverity.Error, + message: "Expecting token of type --> CLOSE <-- but found --> '' <--", + range: toRange(6, null, 0, 0), + source: "XML Toolkit", + }, + ]); + }); + + it("Will show multiple errors in problems view", async function () { + const content = ` + + Tove + Jani + Reminder + Don't forget me this weekend! + `; + await setContent(content); + await testDiagnostics(docUri, [ + { + severity: vscode.DiagnosticSeverity.Error, + message: "Expecting token of type --> STRING <-- but found --> '>' <--", + range: toRange(2, 14, 2, 14), + source: "XML Toolkit", + }, + { + severity: vscode.DiagnosticSeverity.Error, + message: "Expecting token of type --> STRING <-- but found --> '>' <--", + range: toRange(3, 16, 3, 16), + source: "XML Toolkit", + }, + { + severity: vscode.DiagnosticSeverity.Error, + message: "Expecting token of type --> STRING <-- but found --> '>' <--", + range: toRange(4, 19, 4, 19), + source: "XML Toolkit", + }, + ]); + }); + + function toRange(sLine, sChar, eLine, eChar) { + const start = new vscode.Position(sLine, sChar); + const end = new vscode.Position(eLine, eChar); + return new vscode.Range(start, end); + } + + async function setContent(content) { + await fs.writeFile(docPath, content); + await sleep(1000); + } + + async function sleep(ms) { + return new Promise((resolve) => setTimeout(resolve, ms)); + } + + async function testDiagnostics(docUri, expectedDiagnostics) { + const actualDiagnostics = vscode.languages.getDiagnostics(docUri); + expect(expectedDiagnostics).to.deep.equalInAnyOrder(actualDiagnostics); + } +}); diff --git a/projects/xml-tools/packages/xml-toolkit/test/suite/index.js b/projects/xml-tools/packages/xml-toolkit/test/suite/index.js new file mode 100644 index 00000000..1b4b20d0 --- /dev/null +++ b/projects/xml-tools/packages/xml-toolkit/test/suite/index.js @@ -0,0 +1,43 @@ +const { resolve } = require("path"); +const Mocha = require("mocha"); +const glob = require("glob"); + +function run() { + // Create the mocha test + const mocha = new Mocha({ + ui: "bdd", + timeout: 20000, + color: true, + }); + + const testsRoot = resolve(__dirname); + + return new Promise((c, e) => { + glob("**.test.js", { cwd: testsRoot }, (err, files) => { + if (err) { + return e(err); + } + + // Add files to the test suite + files.forEach((f) => mocha.addFile(resolve(testsRoot, f))); + + try { + // Run the mocha test + mocha.run((failures) => { + if (failures > 0) { + e(new Error(`${failures} tests failed.`)); + } else { + c(); + } + }); + } catch (err) { + console.error(err); + e(err); + } + }); + }); +} + +module.exports = { + run: run, +}; diff --git a/projects/xml-tools/packages/xml-toolkit/test/testFixure/test.xml b/projects/xml-tools/packages/xml-toolkit/test/testFixure/test.xml new file mode 100644 index 00000000..e69de29b diff --git a/projects/xml-tools/packages/xml-toolkit/webpack.config.js b/projects/xml-tools/packages/xml-toolkit/webpack.config.js new file mode 100644 index 00000000..6875758c --- /dev/null +++ b/projects/xml-tools/packages/xml-toolkit/webpack.config.js @@ -0,0 +1,23 @@ +const path = require("path"); +const baseConfig = require("../../webpack.config.base"); + +const config = { + ...baseConfig, + entry: "./lib/extension.js", + output: { + // the bundle is stored in the 'dist' folder (check package.json), 📖 -> https://webpack.js.org/configuration/output/ + path: path.resolve(__dirname, "dist"), + filename: "extension.js", + libraryTarget: "commonjs2", + devtoolModuleFilenameTemplate: "../[resource-path]", + }, + // 📖 -> https://webpack.js.org/configuration/externals/ + externals: { + // the vscode-module is created on-the-fly and must be excluded. Add other modules that cannot be webpack'ed. + vscode: "commonjs vscode", + // the language-server must be bundled separately as it is executed in a separate process (by path). + "@xml-tools/language-server": "commonjs @xml-tools/language-server", + }, +}; + +module.exports = config; From eaa3ecfd9a374098e719469dec620bf15c01b783 Mon Sep 17 00:00:00 2001 From: badrislamovrolan Date: Mon, 21 Sep 2026 02:38:24 +0200 Subject: [PATCH 2/8] chore(xml-tools): prettier reflow Isolated formatting pass with the monorepo's Prettier, kept in its own commit so the whole-tree reflow does not obscure the logic/wiring changes that follow. Signed-off-by: badrislamovrolan --- projects/xml-tools/packages/ast/test/sample-test.js | 5 ++--- projects/xml-tools/packages/parser/api.d.ts | 3 ++- projects/xml-tools/packages/simple-schema/api.d.ts | 8 ++------ 3 files changed, 6 insertions(+), 10 deletions(-) diff --git a/projects/xml-tools/packages/ast/test/sample-test.js b/projects/xml-tools/packages/ast/test/sample-test.js index a4ad9007..96a45d43 100644 --- a/projects/xml-tools/packages/ast/test/sample-test.js +++ b/projects/xml-tools/packages/ast/test/sample-test.js @@ -15,9 +15,8 @@ function executeSampleTest(dirPath, assertNoErrors) { const inputPath = resolve(dirPath, "input.xml"); const inputText = readFileSync(inputPath).toString("utf8"); const simpleNewLinesInput = inputText.replace(/\r\n/g, "\n"); - const { cst, tokenVector, lexErrors, parseErrors } = parse( - simpleNewLinesInput - ); + const { cst, tokenVector, lexErrors, parseErrors } = + parse(simpleNewLinesInput); if (assertNoErrors === true) { expect(lexErrors).to.be.empty; expect(parseErrors).to.be.empty; diff --git a/projects/xml-tools/packages/parser/api.d.ts b/projects/xml-tools/packages/parser/api.d.ts index 0805e47b..d85b17dd 100644 --- a/projects/xml-tools/packages/parser/api.d.ts +++ b/projects/xml-tools/packages/parser/api.d.ts @@ -46,7 +46,8 @@ interface XmlCstVisitorConstructor { } export abstract class XmlCstVisitorWithDefaults - implements ICstVisitor { + implements ICstVisitor +{ // No need to implement these two methods // Generic Visit method implemented by the Chevrotain Library visit(cstNode: CstNode | CstNode[], param?: IN): OUT; diff --git a/projects/xml-tools/packages/simple-schema/api.d.ts b/projects/xml-tools/packages/simple-schema/api.d.ts index 7f6040ce..e48796b8 100644 --- a/projects/xml-tools/packages/simple-schema/api.d.ts +++ b/projects/xml-tools/packages/simple-schema/api.d.ts @@ -44,9 +44,7 @@ declare type XSSValue = RegExp | XSSValueEnum; declare type XSSValueEnum = string[]; -declare function getSchemaValidators( - schema: SimpleSchema -): { +declare function getSchemaValidators(schema: SimpleSchema): { attribute: AttributeValidator; element: ElementValidator; }; @@ -66,9 +64,7 @@ interface CompletionSuggestion { confidence?: number; } -declare function getSchemaSuggestionsProviders( - schema: SimpleSchema -): { +declare function getSchemaSuggestionsProviders(schema: SimpleSchema): { // TBD in the future... // schemaElementContentCompletion: ElementContentCompletion; schemaElementNameCompletion: ElementNameCompletion; From 45dedeb25b27fc4a4f85b9ea6d03a5243af80c33 Mon Sep 17 00:00:00 2001 From: badrislamovrolan Date: Mon, 21 Sep 2026 03:06:02 +0200 Subject: [PATCH 3/8] chore(xml-tools): wire packages for pnpm monorepo build Adapt the migrated xml-tools packages to build and test under the app-studio-toolkit pnpm workspace: - Intra-project deps -> workspace:*; add missing transitive deps that yarn's flat node_modules had masked (chevrotain, @xml-tools/ast, @xml-tools/common). - package.json: repository.directory + bugs pointing at the monorepo; drop engines.node; drop inline nyc config; list CHANGELOG in files. - Per-package tsconfig.json (noEmit type-check) and nyc.config.js (lib/**/*.js @ 100%), overriding the root's src/** false-green. - language-server: detect the bundled dist/server.js directly instead of keying off node_modules (unreliable under pnpm's symlinks). - xml-toolkit: rework the VSIX packaging for pnpm (resolve the sibling language-server via its node_modules symlink so vsce keeps it a child; copy LICENSE/LICENSES from the monorepo root before mutating pkg.json; drop yarn nohoist coupling). Tighten .vscodeignore/.gitignore. ci runs clean+bundle+package (electron integration tests need a display). - CHANGELOG.md -> CHANGELOG.old.md with a historic-note header; refresh READMEs to point Support/Contributing at the monorepo. Signed-off-by: badrislamovrolan --- pnpm-lock.yaml | 445 +++++++++++++++++- projects/xml-tools/README.md | 10 +- .../{CHANGELOG.md => CHANGELOG.old.md} | 5 + .../packages/ast-position/CONTRIBUTING.md | 4 - .../xml-tools/packages/ast-position/README.md | 4 +- .../packages/ast-position/nyc.config.js | 11 + .../packages/ast-position/package.json | 26 +- .../packages/ast-position/tsconfig.json | 6 + projects/xml-tools/packages/ast/.npmignore | 10 - .../ast/{CHANGELOG.md => CHANGELOG.old.md} | 5 + .../xml-tools/packages/ast/CONTRIBUTING.md | 4 - projects/xml-tools/packages/ast/README.md | 4 +- projects/xml-tools/packages/ast/nyc.config.js | 11 + projects/xml-tools/packages/ast/package.json | 29 +- projects/xml-tools/packages/ast/tsconfig.json | 6 + projects/xml-tools/packages/common/.npmignore | 10 - .../common/{CHANGELOG.md => CHANGELOG.old.md} | 5 + .../xml-tools/packages/common/CONTRIBUTING.md | 4 - projects/xml-tools/packages/common/README.md | 4 +- .../xml-tools/packages/common/nyc.config.js | 11 + .../xml-tools/packages/common/package.json | 25 +- .../xml-tools/packages/common/tsconfig.json | 6 + .../xml-tools/packages/constraints/.npmignore | 10 - .../{CHANGELOG.md => CHANGELOG.old.md} | 5 + .../packages/constraints/CONTRIBUTING.md | 4 - .../xml-tools/packages/constraints/README.md | 4 +- .../packages/constraints/nyc.config.js | 11 + .../packages/constraints/package.json | 26 +- .../packages/constraints/tsconfig.json | 6 + .../packages/content-assist/.npmignore | 10 - .../{CHANGELOG.md => CHANGELOG.old.md} | 5 + .../packages/content-assist/CONTRIBUTING.md | 4 - .../packages/content-assist/README.md | 4 +- .../packages/content-assist/nyc.config.js | 11 + .../packages/content-assist/package.json | 30 +- .../packages/content-assist/tsconfig.json | 6 + .../packages/language-server/.npmignore | 10 - .../{CHANGELOG.md => CHANGELOG.old.md} | 5 + .../packages/language-server/README.md | 4 +- .../packages/language-server/lib/api.js | 25 +- .../packages/language-server/nyc.config.js | 6 +- .../packages/language-server/package.json | 20 +- .../packages/language-server/tsconfig.json | 6 + .../language-server/webpack.config.js | 2 +- projects/xml-tools/packages/parser/.npmignore | 10 - .../parser/{CHANGELOG.md => CHANGELOG.old.md} | 5 + .../xml-tools/packages/parser/CONTRIBUTING.md | 4 - projects/xml-tools/packages/parser/README.md | 4 +- .../xml-tools/packages/parser/nyc.config.js | 11 + .../xml-tools/packages/parser/package.json | 22 +- .../xml-tools/packages/parser/tsconfig.json | 6 + .../packages/simple-schema/.npmignore | 10 - .../{CHANGELOG.md => CHANGELOG.old.md} | 5 + .../packages/simple-schema/CONTRIBUTING.md | 4 - .../packages/simple-schema/README.md | 4 +- .../packages/simple-schema/nyc.config.js | 11 + .../packages/simple-schema/package.json | 31 +- .../packages/simple-schema/tsconfig.json | 6 + .../xml-tools/packages/validation/.npmignore | 10 - .../{CHANGELOG.md => CHANGELOG.old.md} | 5 + .../packages/validation/CONTRIBUTING.md | 4 - .../xml-tools/packages/validation/README.md | 4 +- .../packages/validation/nyc.config.js | 11 + .../packages/validation/package.json | 26 +- .../packages/validation/tsconfig.json | 6 + .../xml-tools/packages/xml-toolkit/.gitignore | 10 +- .../packages/xml-toolkit/.vscodeignore | 19 +- .../{CHANGELOG.md => CHANGELOG.old.md} | 5 + .../xml-tools/packages/xml-toolkit/README.md | 8 +- .../packages/xml-toolkit/nyc.config.js | 11 + .../packages/xml-toolkit/package.json | 17 +- .../xml-toolkit/scripts/package-vsix.js | 104 ++-- .../packages/xml-toolkit/webpack.config.js | 2 +- 73 files changed, 880 insertions(+), 354 deletions(-) rename projects/xml-tools/packages/ast-position/{CHANGELOG.md => CHANGELOG.old.md} (88%) delete mode 100644 projects/xml-tools/packages/ast-position/CONTRIBUTING.md create mode 100644 projects/xml-tools/packages/ast-position/nyc.config.js create mode 100644 projects/xml-tools/packages/ast-position/tsconfig.json delete mode 100644 projects/xml-tools/packages/ast/.npmignore rename projects/xml-tools/packages/ast/{CHANGELOG.md => CHANGELOG.old.md} (95%) delete mode 100644 projects/xml-tools/packages/ast/CONTRIBUTING.md create mode 100644 projects/xml-tools/packages/ast/nyc.config.js create mode 100644 projects/xml-tools/packages/ast/tsconfig.json delete mode 100644 projects/xml-tools/packages/common/.npmignore rename projects/xml-tools/packages/common/{CHANGELOG.md => CHANGELOG.old.md} (90%) delete mode 100644 projects/xml-tools/packages/common/CONTRIBUTING.md create mode 100644 projects/xml-tools/packages/common/nyc.config.js create mode 100644 projects/xml-tools/packages/common/tsconfig.json delete mode 100644 projects/xml-tools/packages/constraints/.npmignore rename projects/xml-tools/packages/constraints/{CHANGELOG.md => CHANGELOG.old.md} (76%) delete mode 100644 projects/xml-tools/packages/constraints/CONTRIBUTING.md create mode 100644 projects/xml-tools/packages/constraints/nyc.config.js create mode 100644 projects/xml-tools/packages/constraints/tsconfig.json delete mode 100644 projects/xml-tools/packages/content-assist/.npmignore rename projects/xml-tools/packages/content-assist/{CHANGELOG.md => CHANGELOG.old.md} (95%) delete mode 100644 projects/xml-tools/packages/content-assist/CONTRIBUTING.md create mode 100644 projects/xml-tools/packages/content-assist/nyc.config.js create mode 100644 projects/xml-tools/packages/content-assist/tsconfig.json delete mode 100644 projects/xml-tools/packages/language-server/.npmignore rename projects/xml-tools/packages/language-server/{CHANGELOG.md => CHANGELOG.old.md} (92%) create mode 100644 projects/xml-tools/packages/language-server/tsconfig.json delete mode 100644 projects/xml-tools/packages/parser/.npmignore rename projects/xml-tools/packages/parser/{CHANGELOG.md => CHANGELOG.old.md} (93%) delete mode 100644 projects/xml-tools/packages/parser/CONTRIBUTING.md create mode 100644 projects/xml-tools/packages/parser/nyc.config.js create mode 100644 projects/xml-tools/packages/parser/tsconfig.json delete mode 100644 projects/xml-tools/packages/simple-schema/.npmignore rename projects/xml-tools/packages/simple-schema/{CHANGELOG.md => CHANGELOG.old.md} (95%) delete mode 100644 projects/xml-tools/packages/simple-schema/CONTRIBUTING.md create mode 100644 projects/xml-tools/packages/simple-schema/nyc.config.js create mode 100644 projects/xml-tools/packages/simple-schema/tsconfig.json delete mode 100644 projects/xml-tools/packages/validation/.npmignore rename projects/xml-tools/packages/validation/{CHANGELOG.md => CHANGELOG.old.md} (94%) delete mode 100644 projects/xml-tools/packages/validation/CONTRIBUTING.md create mode 100644 projects/xml-tools/packages/validation/nyc.config.js create mode 100644 projects/xml-tools/packages/validation/tsconfig.json rename projects/xml-tools/packages/xml-toolkit/{CHANGELOG.md => CHANGELOG.old.md} (93%) create mode 100644 projects/xml-tools/packages/xml-toolkit/nyc.config.js diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f7fab9de..dff38a4a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1871,6 +1871,181 @@ importers: specifier: ^4.2.3 version: 4.9.5 + projects/xml-tools/packages/ast: + dependencies: + '@xml-tools/common': + specifier: workspace:* + version: link:../common + '@xml-tools/parser': + specifier: workspace:* + version: link:../parser + lodash: + specifier: 4.17.23 + version: 4.17.23 + devDependencies: + chevrotain: + specifier: 7.1.1 + version: 7.1.1 + klaw-sync: + specifier: 6.0.0 + version: 6.0.0 + + projects/xml-tools/packages/ast-position: + dependencies: + '@xml-tools/ast': + specifier: workspace:* + version: link:../ast + devDependencies: + '@xml-tools/parser': + specifier: workspace:* + version: link:../parser + + projects/xml-tools/packages/common: + dependencies: + lodash: + specifier: 4.17.23 + version: 4.17.23 + devDependencies: + '@xml-tools/parser': + specifier: workspace:* + version: link:../parser + chevrotain: + specifier: 7.1.1 + version: 7.1.1 + + projects/xml-tools/packages/constraints: + dependencies: + '@xml-tools/validation': + specifier: workspace:* + version: link:../validation + lodash: + specifier: 4.17.23 + version: 4.17.23 + devDependencies: + '@xml-tools/ast': + specifier: workspace:* + version: link:../ast + + projects/xml-tools/packages/content-assist: + dependencies: + '@xml-tools/ast': + specifier: workspace:* + version: link:../ast + '@xml-tools/common': + specifier: workspace:* + version: link:../common + '@xml-tools/parser': + specifier: workspace:* + version: link:../parser + lodash: + specifier: 4.17.23 + version: 4.17.23 + devDependencies: + chevrotain: + specifier: 7.1.1 + version: 7.1.1 + + projects/xml-tools/packages/language-server: + dependencies: + '@xml-tools/ast': + specifier: workspace:* + version: link:../ast + '@xml-tools/constraints': + specifier: workspace:* + version: link:../constraints + '@xml-tools/parser': + specifier: workspace:* + version: link:../parser + lodash: + specifier: 4.17.23 + version: 4.17.23 + vscode-languageserver: + specifier: 7.0.0 + version: 7.0.0 + vscode-languageserver-textdocument: + specifier: 1.0.1 + version: 1.0.1 + devDependencies: + chevrotain: + specifier: ^7.0.1 + version: 7.1.1 + vscode-languageserver-types: + specifier: 3.16.0 + version: 3.16.0 + + projects/xml-tools/packages/parser: + dependencies: + chevrotain: + specifier: 7.1.1 + version: 7.1.1 + devDependencies: + klaw-sync: + specifier: 6.0.0 + version: 6.0.0 + + projects/xml-tools/packages/simple-schema: + dependencies: + '@xml-tools/ast': + specifier: workspace:* + version: link:../ast + '@xml-tools/common': + specifier: workspace:* + version: link:../common + '@xml-tools/content-assist': + specifier: workspace:* + version: link:../content-assist + lodash: + specifier: 4.17.23 + version: 4.17.23 + devDependencies: + '@xml-tools/parser': + specifier: workspace:* + version: link:../parser + '@xml-tools/validation': + specifier: workspace:* + version: link:../validation + + projects/xml-tools/packages/validation: + dependencies: + '@xml-tools/ast': + specifier: workspace:* + version: link:../ast + lodash: + specifier: 4.17.23 + version: 4.17.23 + devDependencies: + '@xml-tools/parser': + specifier: workspace:* + version: link:../parser + + projects/xml-tools/packages/xml-toolkit: + dependencies: + '@xml-tools/language-server': + specifier: workspace:* + version: link:../language-server + vscode-languageclient: + specifier: 6.1.3 + version: 6.1.3 + devDependencies: + '@types/vscode': + specifier: 1.56.0 + version: 1.56.0 + '@vscode/test-electron': + specifier: ^2.4.0 + version: 2.5.2 + deep-equal-in-any-order: + specifier: 1.0.28 + version: 1.0.28 + lodash: + specifier: 4.17.23 + version: 4.17.23 + proxyquire: + specifier: 2.1.3 + version: 2.1.3 + vsce: + specifier: 1.84.0 + version: 1.84.0 + projects/yeoman-ui: devDependencies: null-loader: @@ -5528,6 +5703,9 @@ packages: '@types/vscode@1.52.0': resolution: {integrity: sha512-Kt3bvWzAvvF/WH9YEcrCICDp0Z7aHhJGhLJ1BxeyNP6yRjonWqWnAIh35/pXAjswAnWOABrYlF7SwXR9+1nnLA==} + '@types/vscode@1.56.0': + resolution: {integrity: sha512-Q5VmQxOx+L1Y6lIJiGcJzwcyV3pQo/eiW8P+7sNLhFI16tJCwtua2DLjHRcpjbCLNVYpQM73kzfFo1Z0HyP9eQ==} + '@types/vscode@1.75.0': resolution: {integrity: sha512-SAr0PoOhJS6FUq5LjNr8C/StBKALZwDVm3+U4pjF/3iYkt3GioJOPV/oB1Sf1l7lROe4TgrMyL5N1yaEgTWycw==} @@ -5727,6 +5905,10 @@ packages: '@vscode-logging/wrapper@2.0.9': resolution: {integrity: sha512-kTx73Ev1O85oS51s6CnIep/F02uwTGrpuUZMi5/uMaxcU8AdzYWpHVeD1T1BuvInaQhHdADzVnF2gpehKYUbog==} + '@vscode/test-electron@2.5.2': + resolution: {integrity: sha512-8ukpxv4wYe0iWMRQU18jhzJOHkeGKbnw7xWRX3Zw1WJA4cEKbHcmmLPdPrPtL6rhDcrlCZN+xKRpv09n4gRHYg==} + engines: {node: '>=16'} + '@vscode/vsce-sign-alpine-arm64@2.0.5': resolution: {integrity: sha512-XVmnF40APwRPXSLYA28Ye+qWxB25KhSVpF2eZVtVOs6g7fkpOxsVnpRU1Bz2xG4ySI79IRuapDJoAQFkoOgfdQ==} cpu: [arm64] @@ -6583,6 +6765,9 @@ packages: azure-devops-node-api@12.5.0: resolution: {integrity: sha512-R5eFskGvOm3U/GzeAuxRkUsAl0hrAwGgWn6zAd2KrZmrEhWZVqLew4OOupbQlXUuojUzpGtq62SmdhJ06N88og==} + azure-devops-node-api@7.2.0: + resolution: {integrity: sha512-pMfGJ6gAQ7LRKTHgiRF+8iaUUeGAI0c8puLaqHLc7B8AR7W6GJLozK9RFeUHFjEGybC9/EB3r67WPd7e46zQ8w==} + b4a@1.8.1: resolution: {integrity: sha512-aiqre1Nr0B/6DgE2N5vwTc+2/oQZ4Wh1t4NznYY4E00y8LCt6NqdRv81so00oo27D8MVKTpUa/MwUUtBLXCoDw==} peerDependencies: @@ -7097,6 +7282,9 @@ packages: resolution: {integrity: sha512-WDrybc/gKFpTYQutKIK6UvfcuxijIZfMfXaYm8NMsPQxSYvf+13fXUJ4rztGGbJcBQ/GF55gvrZ0Bc0bj/mqvg==} engines: {node: '>=20.18.1'} + chevrotain@7.1.1: + resolution: {integrity: sha512-wy3mC1x4ye+O+QkEinVJkPf5u2vsrDIYW9G7ZuwFl6v/Yu0LwUuT2POsb+NUWApebyxfkQq6+yDfRExbnI5rcw==} + chokidar@3.6.0: resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} engines: {node: '>= 8.10.0'} @@ -7991,6 +8179,9 @@ packages: resolution: {integrity: sha512-SUwdGfqdKOwxCPeVYjwSyRpJ7Z+fhpwIAtmCUdZIWZ/YP5R9WAsyuSgpLVDi9bjWoN2LXHNss/dk3urXtdQxGg==} engines: {node: '>=6'} + deep-equal-in-any-order@1.0.28: + resolution: {integrity: sha512-qq3jffpGmAG9kGpZGKusjRwoGxmFgIqNW076HQmV9rNdrFsgTcpuCyp6dBhzdVCWgQDkgRmvZLYAilV4u2BsfQ==} + deep-equal-in-any-order@1.1.20: resolution: {integrity: sha512-GTpQxcQx28KvV6ChrHb4AcL5z+eFEymtoSUaWe+FEakEpZ/66jTvz52xLUym/sK4Pn1sbYLsCV5rEURDeiFl3w==} @@ -8310,6 +8501,9 @@ packages: resolution: {integrity: sha512-uDfvUjVrfGJJhymx/kz6prltenw1u7WrCg1oa94zYY8xxVpLLUu045LAT0dhDZdXG58/EpPL/5kA180fQ/qudg==} engines: {node: '>=10'} + emoji-regex@10.6.0: + resolution: {integrity: sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==} + emoji-regex@8.0.0: resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} @@ -9768,6 +9962,9 @@ packages: engines: {node: '>=12.0.0'} hasBin: true + immediate@3.0.6: + resolution: {integrity: sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==} + immutable@4.3.8: resolution: {integrity: sha512-d/Ld9aLbKpNwyl0KiM2CT1WYvkitQ1TSvmRtkcV8FKStiDoA7Slzgjmb/1G2yhKM1p0XeNOieaTbFZmU1d3Xuw==} @@ -10153,6 +10350,10 @@ packages: resolution: {integrity: sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==} engines: {node: '>=10'} + is-unicode-supported@1.3.0: + resolution: {integrity: sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ==} + engines: {node: '>=12'} + is-unicode-supported@2.1.0: resolution: {integrity: sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==} engines: {node: '>=18'} @@ -10779,6 +10980,9 @@ packages: resolution: {integrity: sha512-4Dj8Rf+fQ+/Pn7C5qeEX02op1WfOss3PKTE9Nsop3Dx+6UPxlm1dr/og7o2cRa5hNN07CACr4NFzRLtj/rjWog==} engines: {'0': node >=0.6.0} + jszip@3.10.2: + resolution: {integrity: sha512-3l+rb15IOWtUhU0H5MFqES/T6Kh7abYwjosBey/vD6hDt8zoEffkSC5Ws5SGtgVw3gBx2NEbhTeSW1+kWkpyTQ==} + just-diff-apply@5.5.0: resolution: {integrity: sha512-OYTthRfSh55WOItVqwpefPtNt2VdKsq5AnAK6apdtR6yCH8pr0CmSr710J0Mf+WdQy7K/OzMy7K2MgAfdQURDw==} @@ -10850,6 +11054,9 @@ packages: resolution: {integrity: sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==} engines: {node: '>=0.10.0'} + klaw-sync@6.0.0: + resolution: {integrity: sha512-nIeuVSzdCCs6TDPTqI8w1Yre34sSq7AkZ4B3sfOBbI2CgVSB4Du4aLQijFU2+lhAFCwt9+42Hel6lQNIv6AntQ==} + kleur@3.0.3: resolution: {integrity: sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==} engines: {node: '>=6'} @@ -10908,6 +11115,9 @@ packages: resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} engines: {node: '>= 0.8.0'} + lie@3.3.0: + resolution: {integrity: sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==} + lilconfig@2.1.0: resolution: {integrity: sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==} engines: {node: '>=10'} @@ -11070,6 +11280,9 @@ packages: lodash@4.17.21: resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} + lodash@4.17.23: + resolution: {integrity: sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==} + lodash@4.18.1: resolution: {integrity: sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==} @@ -11085,6 +11298,10 @@ packages: resolution: {integrity: sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==} engines: {node: '>=10'} + log-symbols@6.0.0: + resolution: {integrity: sha512-i24m8rpwhmPIS4zscNzK6MSEhk0DUWa/8iYQWxhffV8jkI4Phvs3F+quL5xvS0gdQR0FyTCMMH33Y78dDTzzIw==} + engines: {node: '>=18'} + log-symbols@7.0.1: resolution: {integrity: sha512-ja1E3yCr9i/0hmBVaM0bfwDjnGy8I/s6PP4DFp+yP+a+mrHO4Rm7DtmnqROTUkHIkqffC84YY7AeqX6oFk0WFg==} engines: {node: '>=18'} @@ -12045,6 +12262,10 @@ packages: resolution: {integrity: sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==} engines: {node: '>=10'} + ora@8.2.0: + resolution: {integrity: sha512-weP+BZ8MVNnlCm8c0Qdc1WSWq4Qn7I+9CJGm7Qali6g44e/PUzbjNqJX5NJ9ljlNMosfJvg1fKEGILklK9cwnw==} + engines: {node: '>=18'} + ora@9.4.1: resolution: {integrity: sha512-6VlU9MLXbjVQD04AZCMX28hVtA5bUoadvUqO76MUCVA0ilwJbMiHsITRPfyVm6p/BC0Av/BXMujx39WCe1LEqw==} engines: {node: '>=20'} @@ -12057,6 +12278,9 @@ packages: resolution: {integrity: sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==} engines: {node: '>=0.10.0'} + os@0.1.1: + resolution: {integrity: sha512-jg06S2xr5De63mLjZVJDf3/k37tpjppr2LR7MUOsxv8XuUCVpCnvbCksXCBcB5gQqQf/K0+87WGTRlAj5q7r1A==} + osenv@0.1.5: resolution: {integrity: sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==} deprecated: This package is no longer supported. @@ -12217,6 +12441,9 @@ packages: resolution: {integrity: sha512-NWrGqneZImDdcMU/7vMcAOo1bIi5h/pmpJqe7/jdsy85BA/s5MSaU/KlpxwW/IVPmIwBcq2uKPrBWWhEWhtxag==} engines: {node: '>=8'} + pako@1.0.11: + resolution: {integrity: sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==} + param-case@3.0.4: resolution: {integrity: sha512-RXlj7zCYokReqWpOPH9oYivUzLYZ5vAPIfEmCTNViosC78F8F0H9y7T7gG2M39ymgutxF5gcFEsyZQSph9Bp3A==} @@ -13037,6 +13264,9 @@ packages: resolution: {integrity: sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==} engines: {node: '>=0.10.0'} + regexp-to-ast@0.5.0: + resolution: {integrity: sha512-tlbJqcMHnPKI9zSrystikWKwHkBqu2a/Sgw01h3zFjvYrMxEDYHzzoMZnUrbIfpTFEsoRnnviOXNCzFiSc54Qw==} + regexp.prototype.flags@1.5.4: resolution: {integrity: sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==} engines: {node: '>= 0.4'} @@ -13693,6 +13923,9 @@ packages: resolution: {integrity: sha512-LJhUYUvItdQ0LkJTmPeaEObWXAqFyfmP85x0tch/ez9cahmhlBBLbIqDFnvBnUJGagb0JbIQrkBs1wJ+yRYpEw==} engines: {node: '>= 10.0.0', npm: '>= 3.0.0'} + sort-any@1.2.9: + resolution: {integrity: sha512-6LEFNVLeWrbCpFxhS+KdZypqY4XrtOA1dY1vQSacR91SJ/sXKUrR1wK7AUgLOXlLVXnaws3NcZ8lZPMdfkhlHw==} + sort-any@2.0.0: resolution: {integrity: sha512-T9JoiDewQEmWcnmPn/s9h/PH9t3d/LSWi0RgVmXSuDYeZXTZOZ1/wrK2PHaptuR1VXe3clLLt0pD6sgVOwjNEA==} @@ -13861,6 +14094,10 @@ packages: resolution: {integrity: sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==} engines: {node: '>= 0.8'} + stdin-discarder@0.2.2: + resolution: {integrity: sha512-UhDfHmA92YAlNnCfhmq0VeNL5bDbiZGg7sZ2IvPsXubGkiNa9EC+tUTsjBRsYUAz87btI6/1wf4XoVvQ3uRnmQ==} + engines: {node: '>=18'} + stdin-discarder@0.3.2: resolution: {integrity: sha512-eCPu1qRxPVkl5605OTWF8Wz40b4Mf45NY5LQmVPQ599knfs5QhASUm9GbJ5BDMDOXgrnh0wyEdvzmL//YMlw0A==} engines: {node: '>=18'} @@ -13917,6 +14154,10 @@ packages: resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} engines: {node: '>=12'} + string-width@7.2.0: + resolution: {integrity: sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==} + engines: {node: '>=18'} + string-width@8.2.1: resolution: {integrity: sha512-IIaP0g3iy9Cyy18w3M9YcaDudujEAVHKt3a3QJg1+sr/oX96TbaGUubG0hJyCjCBThFH+tFpcIyoUHUn1ogaLA==} engines: {node: '>=20'} @@ -14233,6 +14474,10 @@ packages: resolution: {integrity: sha512-WMi/OQ2axVTf/ykqCQgXiIct+mSQDFdH2fkwhPwgEwvJ1kSzZRiinb0zF2Xb8u4+OqPChmyI6MEu4EezNJz+FQ==} hasBin: true + tmp@0.0.29: + resolution: {integrity: sha512-89PTqMWGDva+GqClOqBV9s3SMh7MA3Mq0pJUdAoHuF65YoE7O0LermaZkVfT5/Ngfo18H4eYiyG7zKOtnEbxsw==} + engines: {node: '>=0.4.0'} + tmp@0.0.33: resolution: {integrity: sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==} engines: {node: '>=0.6.0'} @@ -14467,6 +14712,10 @@ packages: tunnel-agent@0.6.0: resolution: {integrity: sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==} + tunnel@0.0.4: + resolution: {integrity: sha512-o9QYRJN5WgS8oCtqvwzzcfnzaTnDPr7HpUsQdSXscTyzXbjvl4wSHPTUKOKzEaDeQvOuyRtt3ui+ujM7x7TReQ==} + engines: {node: '>=0.6.11 <=0.7.0 || >=0.7.3'} + tunnel@0.0.6: resolution: {integrity: sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg==} engines: {node: '>=0.6.11 <=0.7.0 || >=0.7.3'} @@ -14555,6 +14804,9 @@ packages: resolution: {integrity: sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==} engines: {node: '>= 0.4'} + typed-rest-client@1.2.0: + resolution: {integrity: sha512-FrUshzZ1yxH8YwGR29PWWnfksLEILbWJydU7zfIRkyH7kAEzB62uMAl2WY6EyolWpLpVHeJGgQm45/MaruaHpw==} + typed-rest-client@1.8.6: resolution: {integrity: sha512-xcQpTEAJw2DP7GqVNECh4dD+riS+C1qndXLfBCJ3xk0kqprtGN491P5KlmrDbKdtuW8NEcP/5ChxiJI3S9WYTA==} @@ -14593,6 +14845,9 @@ packages: underscore@1.13.1: resolution: {integrity: sha512-hzSoAVtJF+3ZtiFX0VgfFPHEDRm7Y/QPjGyNo4TVdnDTdft3tr8hEkD25a1jC+TjTuE7tkHGKkhwCgs9dgBB2g==} + underscore@1.8.3: + resolution: {integrity: sha512-5WsVTFcH1ut/kkhAaHf4PVgI8c7++GiVcpCGxPouI6ZVjsqPnSDf8h/8HtVqc0t4fzRXwnMK70EcZeAs3PIddg==} + undici-types@6.21.0: resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} @@ -14973,16 +15228,53 @@ packages: deprecated: vsce has been renamed to @vscode/vsce. Install using @vscode/vsce instead. hasBin: true + vsce@1.84.0: + resolution: {integrity: sha512-mRJDTMC/1GO7byz6dleY28CZo97cM1p0nU3EEMT5bGMpi5Yga3dKdgTvmwgSQF5BYWozNSNSqN1zih5SYzMZjA==} + engines: {node: '>= 10'} + deprecated: vsce has been renamed to @vscode/vsce. Install using @vscode/vsce instead. + hasBin: true + vscode-jsonrpc@4.0.0: resolution: {integrity: sha512-perEnXQdQOJMTDFNv+UF3h1Y0z4iSiaN9jIlb0OqIYgosPCZGYh/MCUlkFtV2668PL69lRDO32hmvL2yiidUYg==} engines: {node: '>=8.0.0 || >=10.0.0'} + vscode-jsonrpc@6.0.0: + resolution: {integrity: sha512-wnJA4BnEjOSyFMvjZdpiOwhSq9uDoK8e/kpRJDTaMYzwlkrhG1fwDIZI94CLsLzlCK5cIbMMtFlJlfR57Lavmg==} + engines: {node: '>=8.0.0 || >=10.0.0'} + + vscode-jsonrpc@9.0.2: + resolution: {integrity: sha512-SbQSV9yRemARxeXw6LU5sS6Zq0e9/DgCCX5yelH263ZQWukbTk8EF8fjTrr1dziasf4GwlJbvTwFnTrnQFWZXQ==} + engines: {node: '>=14.0.0'} + + vscode-languageclient@6.1.3: + resolution: {integrity: sha512-YciJxk08iU5LmWu7j5dUt9/1OLjokKET6rME3cI4BRpiF6HZlusm2ZwPt0MYJ0lV5y43sZsQHhyon2xBg4ZJVA==} + engines: {vscode: ^1.41.0} + + vscode-languageserver-protocol@3.16.0: + resolution: {integrity: sha512-sdeUoAawceQdgIfTI+sdcwkiK2KU+2cbEYA0agzM2uqaUy2UpnnGHtWTHVEtS0ES4zHU0eMFRGN+oQgDxlD66A==} + + vscode-languageserver-protocol@3.18.3: + resolution: {integrity: sha512-DF49+WeV5py4zO5hhobp60jjsDSK0lAqA0OuKBLBvp423HPWQcCbhZz3JgyfIewsEz2f8U+X75xNIFHdiXZm2w==} + + vscode-languageserver-textdocument@1.0.1: + resolution: {integrity: sha512-UIcJDjX7IFkck7cSkNNyzIz5FyvpQfY7sdzVy+wkKN/BLaD4DQ0ppXQrKePomCxTS7RrolK1I0pey0bG9eh8dA==} + vscode-languageserver-textdocument@1.0.4: resolution: {integrity: sha512-/xhqXP/2A2RSs+J8JNXpiiNVvvNM0oTosNVmQnunlKvq9o4mupHOBAnnzH0lwIPKazXKvAKsVp1kr+H/K4lgoQ==} + vscode-languageserver-types@3.16.0: + resolution: {integrity: sha512-k8luDIWJWyenLc5ToFQQMaSrqCHiLwyKPHKPQZ5zz21vM+vIVUSvsRpcbiECH4WR88K2XZqc4ScRcZ7nk/jbeA==} + vscode-languageserver-types@3.17.5: resolution: {integrity: sha512-Ld1VelNuX9pdF39h2Hgaeb5hEZM2Z3jUrrMgWQAu82jMtZp7p3vJT3BzToKtZI7NgQssZje5o0zryOrhQvzQAg==} + vscode-languageserver-types@3.18.3: + resolution: {integrity: sha512-XIlzJ7Qp/jzSI1ds7/FwPAWrPeTZA7pAtlW4hdJ1J6xXWJL6dR9QYnDhJOdLzdKhUQ5Mm6mvUMw+3DcOQQasPw==} + + vscode-languageserver@7.0.0: + resolution: {integrity: sha512-60HTx5ID+fLRcgdHfmz0LDZAXYEV68fzwG0JWwEPBode9NuMYTIxuYXPg4ngO8i8+Ou0lM7y6GzaYWbiDL0drw==} + hasBin: true + vscode-test@1.5.2: resolution: {integrity: sha512-x9PVfKxF6EInH9iSFGQi0V8H5zIW1fC7RAer6yNQR6sy3WyOwlWkuT3I+wf75xW/cO53hxMi1aj/EvqQfDFOAg==} engines: {node: '>=8.9.3'} @@ -20109,6 +20401,8 @@ snapshots: '@types/vscode@1.52.0': {} + '@types/vscode@1.56.0': {} + '@types/vscode@1.75.0': {} '@types/webpack-env@1.18.8': {} @@ -20423,6 +20717,16 @@ snapshots: transitivePeerDependencies: - supports-color + '@vscode/test-electron@2.5.2': + dependencies: + http-proxy-agent: 7.0.2 + https-proxy-agent: 7.0.6 + jszip: 3.10.2 + ora: 8.2.0 + semver: 7.6.3 + transitivePeerDependencies: + - supports-color + '@vscode/vsce-sign-alpine-arm64@2.0.5': optional: true @@ -21824,6 +22128,13 @@ snapshots: tunnel: 0.0.6 typed-rest-client: 1.8.6 + azure-devops-node-api@7.2.0: + dependencies: + os: 0.1.1 + tunnel: 0.0.4 + typed-rest-client: 1.2.0 + underscore: 1.8.3 + b4a@1.8.1: {} babel-eslint@10.1.0(eslint@7.32.0): @@ -22535,6 +22846,10 @@ snapshots: undici: 7.25.0 whatwg-mimetype: 4.0.0 + chevrotain@7.1.1: + dependencies: + regexp-to-ast: 0.5.0 + chokidar@3.6.0: dependencies: anymatch: 3.1.3 @@ -23285,6 +23600,11 @@ snapshots: dependencies: type-detect: 4.1.0 + deep-equal-in-any-order@1.0.28: + dependencies: + lodash.mapvalues: 4.6.0 + sort-any: 1.2.9 + deep-equal-in-any-order@1.1.20: dependencies: lodash.mapvalues: 4.6.0 @@ -23574,6 +23894,8 @@ snapshots: emittery@0.8.1: {} + emoji-regex@10.6.0: {} + emoji-regex@8.0.0: {} emoji-regex@9.2.2: {} @@ -25349,7 +25671,7 @@ snapshots: http-proxy-agent@7.0.2: dependencies: - agent-base: 7.1.1 + agent-base: 7.1.4 debug: 4.4.3 transitivePeerDependencies: - supports-color @@ -25505,6 +25827,8 @@ snapshots: dependencies: queue: 6.0.2 + immediate@3.0.6: {} + immutable@4.3.8: {} immutable@5.1.9: {} @@ -25867,6 +26191,8 @@ snapshots: is-unicode-supported@0.1.0: {} + is-unicode-supported@1.3.0: {} + is-unicode-supported@2.1.0: {} is-utf8@0.2.1: {} @@ -27028,6 +27354,13 @@ snapshots: json-schema: 0.2.3 verror: 1.10.0 + jszip@3.10.2: + dependencies: + lie: 3.3.0 + pako: 1.0.11 + readable-stream: 2.3.8 + setimmediate: 1.0.5 + just-diff-apply@5.5.0: {} just-diff@5.2.0: {} @@ -27132,6 +27465,10 @@ snapshots: kind-of@6.0.3: {} + klaw-sync@6.0.0: + dependencies: + graceful-fs: 4.2.11 + kleur@3.0.3: {} klona@2.0.6: {} @@ -27184,6 +27521,10 @@ snapshots: prelude-ls: 1.2.1 type-check: 0.4.0 + lie@3.3.0: + dependencies: + immediate: 3.0.6 + lilconfig@2.1.0: {} lines-and-columns@1.2.4: {} @@ -27355,6 +27696,8 @@ snapshots: lodash@4.17.21: {} + lodash@4.17.23: {} + lodash@4.18.1: {} log-driver@1.2.7: {} @@ -27368,6 +27711,11 @@ snapshots: chalk: 4.1.2 is-unicode-supported: 0.1.0 + log-symbols@6.0.0: + dependencies: + chalk: 5.6.2 + is-unicode-supported: 1.3.0 + log-symbols@7.0.1: dependencies: is-unicode-supported: 2.1.0 @@ -28668,6 +29016,18 @@ snapshots: strip-ansi: 6.0.0 wcwidth: 1.0.1 + ora@8.2.0: + dependencies: + chalk: 5.6.2 + cli-cursor: 5.0.0 + cli-spinners: 2.9.2 + is-interactive: 2.0.0 + is-unicode-supported: 2.1.0 + log-symbols: 6.0.0 + stdin-discarder: 0.2.2 + string-width: 7.2.0 + strip-ansi: 7.2.0 + ora@9.4.1: dependencies: chalk: 5.6.2 @@ -28683,6 +29043,8 @@ snapshots: os-tmpdir@1.0.2: {} + os@0.1.1: {} + osenv@0.1.5: dependencies: os-homedir: 1.0.2 @@ -28900,6 +29262,8 @@ snapshots: transitivePeerDependencies: - debug + pako@1.0.11: {} + param-case@3.0.4: dependencies: dot-case: 3.0.4 @@ -29722,6 +30086,8 @@ snapshots: extend-shallow: 3.0.2 safe-regex: 1.1.0 + regexp-to-ast@0.5.0: {} + regexp.prototype.flags@1.5.4: dependencies: call-bind: 1.0.9 @@ -30542,6 +30908,10 @@ snapshots: ip-address: 10.2.0 smart-buffer: 4.2.0 + sort-any@1.2.9: + dependencies: + lodash: 4.18.1 + sort-any@2.0.0: dependencies: lodash: 4.18.1 @@ -30732,6 +31102,8 @@ snapshots: statuses@2.0.2: {} + stdin-discarder@0.2.2: {} + stdin-discarder@0.3.2: {} stop-iteration-iterator@1.1.0: @@ -30801,6 +31173,12 @@ snapshots: emoji-regex: 9.2.2 strip-ansi: 7.2.0 + string-width@7.2.0: + dependencies: + emoji-regex: 10.6.0 + get-east-asian-width: 1.6.0 + strip-ansi: 7.2.0 + string-width@8.2.1: dependencies: get-east-asian-width: 1.6.0 @@ -31148,6 +31526,10 @@ snapshots: dependencies: tldts-core: 6.1.86 + tmp@0.0.29: + dependencies: + os-tmpdir: 1.0.2 + tmp@0.0.33: dependencies: os-tmpdir: 1.0.2 @@ -31398,6 +31780,8 @@ snapshots: dependencies: safe-buffer: 5.2.1 + tunnel@0.0.4: {} + tunnel@0.0.6: {} tweetnacl@0.14.5: {} @@ -31478,6 +31862,11 @@ snapshots: possible-typed-array-names: 1.1.0 reflect.getprototypeof: 1.0.10 + typed-rest-client@1.2.0: + dependencies: + tunnel: 0.0.4 + underscore: 1.8.3 + typed-rest-client@1.8.6: dependencies: qs: 6.13.0 @@ -31510,6 +31899,8 @@ snapshots: underscore@1.13.1: {} + underscore@1.8.3: {} + undici-types@6.21.0: {} undici@6.27.0: {} @@ -31875,12 +32266,64 @@ snapshots: yauzl: 2.10.0 yazl: 2.5.1 + vsce@1.84.0: + dependencies: + azure-devops-node-api: 7.2.0 + chalk: 2.4.2 + cheerio: 1.2.0 + commander: 6.2.1 + denodeify: 1.2.1 + glob: 7.2.0 + leven: 3.1.0 + lodash: 4.18.1 + markdown-it: 10.0.0 + mime: 1.6.0 + minimatch: 3.1.2 + osenv: 0.1.5 + parse-semver: 1.1.1 + read: 1.0.7 + semver: 5.7.1 + tmp: 0.0.29 + typed-rest-client: 1.2.0 + url-join: 1.1.0 + yauzl: 2.10.0 + yazl: 2.5.1 + vscode-jsonrpc@4.0.0: {} + vscode-jsonrpc@6.0.0: {} + + vscode-jsonrpc@9.0.2: {} + + vscode-languageclient@6.1.3: + dependencies: + semver: 6.3.1 + vscode-languageserver-protocol: 3.18.3 + + vscode-languageserver-protocol@3.16.0: + dependencies: + vscode-jsonrpc: 6.0.0 + vscode-languageserver-types: 3.16.0 + + vscode-languageserver-protocol@3.18.3: + dependencies: + vscode-jsonrpc: 9.0.2 + vscode-languageserver-types: 3.18.3 + + vscode-languageserver-textdocument@1.0.1: {} + vscode-languageserver-textdocument@1.0.4: {} + vscode-languageserver-types@3.16.0: {} + vscode-languageserver-types@3.17.5: {} + vscode-languageserver-types@3.18.3: {} + + vscode-languageserver@7.0.0: + dependencies: + vscode-languageserver-protocol: 3.16.0 + vscode-test@1.5.2: dependencies: http-proxy-agent: 4.0.1 diff --git a/projects/xml-tools/README.md b/projects/xml-tools/README.md index 9f2688a6..21abf87e 100644 --- a/projects/xml-tools/README.md +++ b/projects/xml-tools/README.md @@ -1,10 +1,4 @@ -[![CircleCI](https://circleci.com/gh/SAP/xml-tools.svg?style=svg)](https://circleci.com/gh/SAP/xml-tools) -[![Coverage Status](https://coveralls.io/repos/github/SAP/xml-tools/badge.svg?branch=master)](https://coveralls.io/github/SAP/xml-tools?branch=master) -[![Language grade: JavaScript](https://img.shields.io/lgtm/grade/javascript/g/SAP/xml-tools.svg?logo=lgtm&logoWidth=18)](https://lgtm.com/projects/g/SAP/xml-tools/context:javascript) [![styled with prettier](https://img.shields.io/badge/styled_with-prettier-ff69b4.svg)](https://github.com/prettier/prettier) -[![Commitizen friendly](https://img.shields.io/badge/commitizen-friendly-brightgreen.svg)](http://commitizen.github.io/cz-cli/) -[![dependentbot](https://api.dependabot.com/badges/status?host=github&repo=SAP/xml-tools)](https://dependabot.com/) -[![REUSE status](https://api.reuse.software/badge/github.com/SAP/xml-tools)](https://api.reuse.software/info/github.com/SAP/xml-tools) # XML-Tools @@ -45,11 +39,11 @@ It currently contains the following packages: ## Support -Please open [issues](https://github.com/SAP/xml-tools/issues) on github. +Please report issues [here](https://github.com/SAP/app-studio-toolkit/issues/new/choose) and label them with `project:xml-tools`. ## Contributing -See [CONTRIBUTING.md](./CONTRIBUTING.md). +See [CONTRIBUTING.md](https://github.com/SAP/app-studio-toolkit/blob/main/CONTRIBUTING.md). [mono-repo]: https://github.com/babel/babel/blob/master/doc/design/monorepo.md [xml]: https://github.com/babel/babel/blob/master/doc/design/monorepo.md diff --git a/projects/xml-tools/packages/ast-position/CHANGELOG.md b/projects/xml-tools/packages/ast-position/CHANGELOG.old.md similarity index 88% rename from projects/xml-tools/packages/ast-position/CHANGELOG.md rename to projects/xml-tools/packages/ast-position/CHANGELOG.old.md index 9d1c5929..b8400d2f 100644 --- a/projects/xml-tools/packages/ast-position/CHANGELOG.md +++ b/projects/xml-tools/packages/ast-position/CHANGELOG.old.md @@ -1,3 +1,8 @@ +> **Historic changelog.** This records changes made while the project lived in the +> standalone [SAP/xml-tools](https://github.com/SAP/xml-tools) repository, before it was +> migrated into [SAP/app-studio-toolkit](https://github.com/SAP/app-studio-toolkit). +> Newer entries are generated by Changesets in `CHANGELOG.md`. + # Change Log All notable changes to this project will be documented in this file. diff --git a/projects/xml-tools/packages/ast-position/CONTRIBUTING.md b/projects/xml-tools/packages/ast-position/CONTRIBUTING.md deleted file mode 100644 index 84eb9447..00000000 --- a/projects/xml-tools/packages/ast-position/CONTRIBUTING.md +++ /dev/null @@ -1,4 +0,0 @@ -# Contribution Guide - -This package does not have any unique development flows. -Please see the top level [Contribution Guide](../../CONTRIBUTING.md). diff --git a/projects/xml-tools/packages/ast-position/README.md b/projects/xml-tools/packages/ast-position/README.md index 720484b7..62afa846 100644 --- a/projects/xml-tools/packages/ast-position/README.md +++ b/projects/xml-tools/packages/ast-position/README.md @@ -23,10 +23,10 @@ as declared in the TypeScript signatures above. ## Support -Please open [issues](https://github.com/SAP/xml-tols/issues) on github. +Please report issues [here](https://github.com/SAP/app-studio-toolkit/issues/new/choose) and label them with `project:xml-tools`. ## Contributing -See [CONTRIBUTING.md](./CONTRIBUTING.md). +See [CONTRIBUTING.md](https://github.com/SAP/app-studio-toolkit/blob/main/CONTRIBUTING.md). [ast]: https://en.wikipedia.org/wiki/Abstract_syntax_tree diff --git a/projects/xml-tools/packages/ast-position/nyc.config.js b/projects/xml-tools/packages/ast-position/nyc.config.js new file mode 100644 index 00000000..3e3f8962 --- /dev/null +++ b/projects/xml-tools/packages/ast-position/nyc.config.js @@ -0,0 +1,11 @@ +module.exports = { + // xml-tools sources live in lib/ (not src/), so this local config overrides + // the repo-root nyc.config.js include of **/src/** (which would report 0%). + include: ["lib/**/*.js"], + reporter: ["text", "lcov"], + all: true, + branches: 100, + lines: 100, + functions: 100, + statements: 100, +}; diff --git a/projects/xml-tools/packages/ast-position/package.json b/projects/xml-tools/packages/ast-position/package.json index 59e8dcc0..3834df10 100644 --- a/projects/xml-tools/packages/ast-position/package.json +++ b/projects/xml-tools/packages/ast-position/package.json @@ -7,38 +7,38 @@ "position" ], "main": "lib/api.js", - "repository": "https://github.com/sap/xml-tools/", + "repository": { + "type": "git", + "url": "https://github.com/SAP/app-studio-toolkit.git", + "directory": "projects/xml-tools/packages/ast-position" + }, "license": "Apache-2.0", "typings": "./api.d.ts", "files": [ "lib", ".reuse", "LICENSES", - "api.d.ts" + "api.d.ts", + "CHANGELOG.md", + "CHANGELOG.old.md" ], "dependencies": { - "@xml-tools/ast": "^5.1.0" + "@xml-tools/ast": "workspace:*" }, "devDependencies": { - "@xml-tools/parser": "^1.0.11" + "@xml-tools/parser": "workspace:*" }, "scripts": { "ci": "npm-run-all type-check coverage:*", "test": "mocha \"./test/**/*spec.js\"", "coverage:run": "nyc mocha \"./test/**/*spec.js\"", "coverage:check": "nyc check-coverage --lines 100 --branches 100 --statements 100 --functions 100", - "type-check": "tsc api.d.ts" + "type-check": "tsc" }, "publishConfig": { "access": "public" }, - "nyc": { - "include": [ - "lib/**/*.js" - ], - "reporter": [ - "text", - "lcov" - ] + "bugs": { + "url": "https://github.com/SAP/app-studio-toolkit/issues" } } diff --git a/projects/xml-tools/packages/ast-position/tsconfig.json b/projects/xml-tools/packages/ast-position/tsconfig.json new file mode 100644 index 00000000..a4ed07be --- /dev/null +++ b/projects/xml-tools/packages/ast-position/tsconfig.json @@ -0,0 +1,6 @@ +{ + "compilerOptions": { + "noEmit": true + }, + "include": ["./api.d.ts"] +} diff --git a/projects/xml-tools/packages/ast/.npmignore b/projects/xml-tools/packages/ast/.npmignore deleted file mode 100644 index ae12335e..00000000 --- a/projects/xml-tools/packages/ast/.npmignore +++ /dev/null @@ -1,10 +0,0 @@ -# NYC -.nyc_output -nyc.config.js -coverage - -# tests -test - -# contributing -CONTRIBUTING.md \ No newline at end of file diff --git a/projects/xml-tools/packages/ast/CHANGELOG.md b/projects/xml-tools/packages/ast/CHANGELOG.old.md similarity index 95% rename from projects/xml-tools/packages/ast/CHANGELOG.md rename to projects/xml-tools/packages/ast/CHANGELOG.old.md index 37d84e3d..9208cfb6 100644 --- a/projects/xml-tools/packages/ast/CHANGELOG.md +++ b/projects/xml-tools/packages/ast/CHANGELOG.old.md @@ -1,3 +1,8 @@ +> **Historic changelog.** This records changes made while the project lived in the +> standalone [SAP/xml-tools](https://github.com/SAP/xml-tools) repository, before it was +> migrated into [SAP/app-studio-toolkit](https://github.com/SAP/app-studio-toolkit). +> Newer entries are generated by Changesets in `CHANGELOG.md`. + # Change Log All notable changes to this project will be documented in this file. diff --git a/projects/xml-tools/packages/ast/CONTRIBUTING.md b/projects/xml-tools/packages/ast/CONTRIBUTING.md deleted file mode 100644 index 84eb9447..00000000 --- a/projects/xml-tools/packages/ast/CONTRIBUTING.md +++ /dev/null @@ -1,4 +0,0 @@ -# Contribution Guide - -This package does not have any unique development flows. -Please see the top level [Contribution Guide](../../CONTRIBUTING.md). diff --git a/projects/xml-tools/packages/ast/README.md b/projects/xml-tools/packages/ast/README.md index b0b177be..bb5f3940 100644 --- a/projects/xml-tools/packages/ast/README.md +++ b/projects/xml-tools/packages/ast/README.md @@ -63,8 +63,8 @@ accept(xmlDocAst, printVisitor); // -> note, Bill, Tim ## Support -Please open [issues](https://github.com/SAP/xml-tols/issues) on github. +Please report issues [here](https://github.com/SAP/app-studio-toolkit/issues/new/choose) and label them with `project:xml-tools`. ## Contributing -See [CONTRIBUTING.md](./CONTRIBUTING.md). +See [CONTRIBUTING.md](https://github.com/SAP/app-studio-toolkit/blob/main/CONTRIBUTING.md). diff --git a/projects/xml-tools/packages/ast/nyc.config.js b/projects/xml-tools/packages/ast/nyc.config.js new file mode 100644 index 00000000..3e3f8962 --- /dev/null +++ b/projects/xml-tools/packages/ast/nyc.config.js @@ -0,0 +1,11 @@ +module.exports = { + // xml-tools sources live in lib/ (not src/), so this local config overrides + // the repo-root nyc.config.js include of **/src/** (which would report 0%). + include: ["lib/**/*.js"], + reporter: ["text", "lcov"], + all: true, + branches: 100, + lines: 100, + functions: 100, + statements: 100, +}; diff --git a/projects/xml-tools/packages/ast/package.json b/projects/xml-tools/packages/ast/package.json index 2843a1e5..08e4a7e7 100644 --- a/projects/xml-tools/packages/ast/package.json +++ b/projects/xml-tools/packages/ast/package.json @@ -7,22 +7,29 @@ "ast" ], "main": "lib/api.js", - "repository": "https://github.com/sap/xml-tools/", + "repository": { + "type": "git", + "url": "https://github.com/SAP/app-studio-toolkit.git", + "directory": "projects/xml-tools/packages/ast" + }, "license": "Apache-2.0", "typings": "./api.d.ts", "files": [ "lib", ".reuse", "LICENSES", - "api.d.ts" + "api.d.ts", + "CHANGELOG.md", + "CHANGELOG.old.md" ], "dependencies": { - "@xml-tools/common": "^0.2.0", - "@xml-tools/parser": "^1.0.11", + "@xml-tools/common": "workspace:*", + "@xml-tools/parser": "workspace:*", "lodash": "4.17.23" }, "devDependencies": { - "klaw-sync": "6.0.0" + "klaw-sync": "6.0.0", + "chevrotain": "7.1.1" }, "scripts": { "ci": "npm-run-all clean type-check coverage:*", @@ -31,18 +38,12 @@ "coverage:run": "nyc mocha \"./test/**/*spec.js\"", "coverage:check": "nyc check-coverage --lines 100 --branches 100 --statements 100 --functions 100", "snapshots:update": "node ./scripts/update-snapshots.js", - "type-check": "tsc api.d.ts" + "type-check": "tsc" }, "publishConfig": { "access": "public" }, - "nyc": { - "include": [ - "lib/**/*.js" - ], - "reporter": [ - "text", - "lcov" - ] + "bugs": { + "url": "https://github.com/SAP/app-studio-toolkit/issues" } } diff --git a/projects/xml-tools/packages/ast/tsconfig.json b/projects/xml-tools/packages/ast/tsconfig.json new file mode 100644 index 00000000..a4ed07be --- /dev/null +++ b/projects/xml-tools/packages/ast/tsconfig.json @@ -0,0 +1,6 @@ +{ + "compilerOptions": { + "noEmit": true + }, + "include": ["./api.d.ts"] +} diff --git a/projects/xml-tools/packages/common/.npmignore b/projects/xml-tools/packages/common/.npmignore deleted file mode 100644 index ae12335e..00000000 --- a/projects/xml-tools/packages/common/.npmignore +++ /dev/null @@ -1,10 +0,0 @@ -# NYC -.nyc_output -nyc.config.js -coverage - -# tests -test - -# contributing -CONTRIBUTING.md \ No newline at end of file diff --git a/projects/xml-tools/packages/common/CHANGELOG.md b/projects/xml-tools/packages/common/CHANGELOG.old.md similarity index 90% rename from projects/xml-tools/packages/common/CHANGELOG.md rename to projects/xml-tools/packages/common/CHANGELOG.old.md index f58154de..a778e644 100644 --- a/projects/xml-tools/packages/common/CHANGELOG.md +++ b/projects/xml-tools/packages/common/CHANGELOG.old.md @@ -1,3 +1,8 @@ +> **Historic changelog.** This records changes made while the project lived in the +> standalone [SAP/xml-tools](https://github.com/SAP/xml-tools) repository, before it was +> migrated into [SAP/app-studio-toolkit](https://github.com/SAP/app-studio-toolkit). +> Newer entries are generated by Changesets in `CHANGELOG.md`. + # Change Log All notable changes to this project will be documented in this file. diff --git a/projects/xml-tools/packages/common/CONTRIBUTING.md b/projects/xml-tools/packages/common/CONTRIBUTING.md deleted file mode 100644 index 84eb9447..00000000 --- a/projects/xml-tools/packages/common/CONTRIBUTING.md +++ /dev/null @@ -1,4 +0,0 @@ -# Contribution Guide - -This package does not have any unique development flows. -Please see the top level [Contribution Guide](../../CONTRIBUTING.md). diff --git a/projects/xml-tools/packages/common/README.md b/projects/xml-tools/packages/common/README.md index 8c7f4950..c4472614 100644 --- a/projects/xml-tools/packages/common/README.md +++ b/projects/xml-tools/packages/common/README.md @@ -28,8 +28,8 @@ xmlToolsCommon.findNextTextualToken(/*... */); ## Support -Please open [issues](https://github.com/SAP/xml-tols/issues) on github. +Please report issues [here](https://github.com/SAP/app-studio-toolkit/issues/new/choose) and label them with `project:xml-tools`. ## Contributing -See [CONTRIBUTING.md](./CONTRIBUTING.md). +See [CONTRIBUTING.md](https://github.com/SAP/app-studio-toolkit/blob/main/CONTRIBUTING.md). diff --git a/projects/xml-tools/packages/common/nyc.config.js b/projects/xml-tools/packages/common/nyc.config.js new file mode 100644 index 00000000..3e3f8962 --- /dev/null +++ b/projects/xml-tools/packages/common/nyc.config.js @@ -0,0 +1,11 @@ +module.exports = { + // xml-tools sources live in lib/ (not src/), so this local config overrides + // the repo-root nyc.config.js include of **/src/** (which would report 0%). + include: ["lib/**/*.js"], + reporter: ["text", "lcov"], + all: true, + branches: 100, + lines: 100, + functions: 100, + statements: 100, +}; diff --git a/projects/xml-tools/packages/common/package.json b/projects/xml-tools/packages/common/package.json index 56cab68d..07ffee50 100644 --- a/projects/xml-tools/packages/common/package.json +++ b/projects/xml-tools/packages/common/package.json @@ -7,20 +7,27 @@ "validation" ], "main": "lib/api.js", - "repository": "https://github.com/sap/xml-tools/", + "repository": { + "type": "git", + "url": "https://github.com/SAP/app-studio-toolkit.git", + "directory": "projects/xml-tools/packages/common" + }, "license": "Apache-2.0", "typings": "./api.d.ts", "files": [ "lib", ".reuse", "LICENSES", - "api.d.ts" + "api.d.ts", + "CHANGELOG.md", + "CHANGELOG.old.md" ], "dependencies": { "lodash": "4.17.23" }, "devDependencies": { - "@xml-tools/parser": "^1.0.11" + "@xml-tools/parser": "workspace:*", + "chevrotain": "7.1.1" }, "scripts": { "ci": "npm-run-all clean type-check coverage:*", @@ -28,18 +35,12 @@ "test": "mocha \"./test/**/*spec.js\"", "coverage:run": "nyc mocha \"./test/**/*spec.js\"", "coverage:check": "nyc check-coverage --lines 100 --branches 100 --statements 100 --functions 100", - "type-check": "tsc api.d.ts" + "type-check": "tsc" }, "publishConfig": { "access": "public" }, - "nyc": { - "include": [ - "lib/**/*.js" - ], - "reporter": [ - "text", - "lcov" - ] + "bugs": { + "url": "https://github.com/SAP/app-studio-toolkit/issues" } } diff --git a/projects/xml-tools/packages/common/tsconfig.json b/projects/xml-tools/packages/common/tsconfig.json new file mode 100644 index 00000000..a4ed07be --- /dev/null +++ b/projects/xml-tools/packages/common/tsconfig.json @@ -0,0 +1,6 @@ +{ + "compilerOptions": { + "noEmit": true + }, + "include": ["./api.d.ts"] +} diff --git a/projects/xml-tools/packages/constraints/.npmignore b/projects/xml-tools/packages/constraints/.npmignore deleted file mode 100644 index ae12335e..00000000 --- a/projects/xml-tools/packages/constraints/.npmignore +++ /dev/null @@ -1,10 +0,0 @@ -# NYC -.nyc_output -nyc.config.js -coverage - -# tests -test - -# contributing -CONTRIBUTING.md \ No newline at end of file diff --git a/projects/xml-tools/packages/constraints/CHANGELOG.md b/projects/xml-tools/packages/constraints/CHANGELOG.old.md similarity index 76% rename from projects/xml-tools/packages/constraints/CHANGELOG.md rename to projects/xml-tools/packages/constraints/CHANGELOG.old.md index 863b3e60..50c6f84e 100644 --- a/projects/xml-tools/packages/constraints/CHANGELOG.md +++ b/projects/xml-tools/packages/constraints/CHANGELOG.old.md @@ -1,3 +1,8 @@ +> **Historic changelog.** This records changes made while the project lived in the +> standalone [SAP/xml-tools](https://github.com/SAP/xml-tools) repository, before it was +> migrated into [SAP/app-studio-toolkit](https://github.com/SAP/app-studio-toolkit). +> Newer entries are generated by Changesets in `CHANGELOG.md`. + # Change Log All notable changes to this project will be documented in this file. diff --git a/projects/xml-tools/packages/constraints/CONTRIBUTING.md b/projects/xml-tools/packages/constraints/CONTRIBUTING.md deleted file mode 100644 index 84eb9447..00000000 --- a/projects/xml-tools/packages/constraints/CONTRIBUTING.md +++ /dev/null @@ -1,4 +0,0 @@ -# Contribution Guide - -This package does not have any unique development flows. -Please see the top level [Contribution Guide](../../CONTRIBUTING.md). diff --git a/projects/xml-tools/packages/constraints/README.md b/projects/xml-tools/packages/constraints/README.md index 9a9c2c11..96600f93 100644 --- a/projects/xml-tools/packages/constraints/README.md +++ b/projects/xml-tools/packages/constraints/README.md @@ -44,8 +44,8 @@ console.log(validationIssues[0].msg); // --> 'opening tag: "note" must match clo ## Support -Please open [issues](https://github.com/SAP/xml-tols/issues) on github. +Please report issues [here](https://github.com/SAP/app-studio-toolkit/issues/new/choose) and label them with `project:xml-tools`. ## Contributing -See [CONTRIBUTING.md](./CONTRIBUTING.md). +See [CONTRIBUTING.md](https://github.com/SAP/app-studio-toolkit/blob/main/CONTRIBUTING.md). diff --git a/projects/xml-tools/packages/constraints/nyc.config.js b/projects/xml-tools/packages/constraints/nyc.config.js new file mode 100644 index 00000000..3e3f8962 --- /dev/null +++ b/projects/xml-tools/packages/constraints/nyc.config.js @@ -0,0 +1,11 @@ +module.exports = { + // xml-tools sources live in lib/ (not src/), so this local config overrides + // the repo-root nyc.config.js include of **/src/** (which would report 0%). + include: ["lib/**/*.js"], + reporter: ["text", "lcov"], + all: true, + branches: 100, + lines: 100, + functions: 100, + statements: 100, +}; diff --git a/projects/xml-tools/packages/constraints/package.json b/projects/xml-tools/packages/constraints/package.json index 28f42daa..9035b225 100644 --- a/projects/xml-tools/packages/constraints/package.json +++ b/projects/xml-tools/packages/constraints/package.json @@ -7,21 +7,27 @@ "validation" ], "main": "lib/api.js", - "repository": "https://github.com/sap/xml-tools/", + "repository": { + "type": "git", + "url": "https://github.com/SAP/app-studio-toolkit.git", + "directory": "projects/xml-tools/packages/constraints" + }, "license": "Apache-2.0", "typings": "./api.d.ts", "files": [ "lib", ".reuse", "LICENSES", - "api.d.ts" + "api.d.ts", + "CHANGELOG.md", + "CHANGELOG.old.md" ], "dependencies": { - "@xml-tools/validation": "^1.1.0", + "@xml-tools/validation": "workspace:*", "lodash": "4.17.23" }, "devDependencies": { - "@xml-tools/ast": "^5.1.0" + "@xml-tools/ast": "workspace:*" }, "scripts": { "ci": "npm-run-all clean type-check coverage:*", @@ -29,18 +35,12 @@ "test": "mocha \"./test/**/*spec.js\"", "coverage:run": "nyc mocha \"./test/**/*spec.js\"", "coverage:check": "nyc check-coverage --lines 100 --branches 100 --statements 100 --functions 100", - "type-check": "tsc api.d.ts" + "type-check": "tsc" }, "publishConfig": { "access": "public" }, - "nyc": { - "include": [ - "lib/**/*.js" - ], - "reporter": [ - "text", - "lcov" - ] + "bugs": { + "url": "https://github.com/SAP/app-studio-toolkit/issues" } } diff --git a/projects/xml-tools/packages/constraints/tsconfig.json b/projects/xml-tools/packages/constraints/tsconfig.json new file mode 100644 index 00000000..a4ed07be --- /dev/null +++ b/projects/xml-tools/packages/constraints/tsconfig.json @@ -0,0 +1,6 @@ +{ + "compilerOptions": { + "noEmit": true + }, + "include": ["./api.d.ts"] +} diff --git a/projects/xml-tools/packages/content-assist/.npmignore b/projects/xml-tools/packages/content-assist/.npmignore deleted file mode 100644 index ae12335e..00000000 --- a/projects/xml-tools/packages/content-assist/.npmignore +++ /dev/null @@ -1,10 +0,0 @@ -# NYC -.nyc_output -nyc.config.js -coverage - -# tests -test - -# contributing -CONTRIBUTING.md \ No newline at end of file diff --git a/projects/xml-tools/packages/content-assist/CHANGELOG.md b/projects/xml-tools/packages/content-assist/CHANGELOG.old.md similarity index 95% rename from projects/xml-tools/packages/content-assist/CHANGELOG.md rename to projects/xml-tools/packages/content-assist/CHANGELOG.old.md index 1a270864..058cfbc9 100644 --- a/projects/xml-tools/packages/content-assist/CHANGELOG.md +++ b/projects/xml-tools/packages/content-assist/CHANGELOG.old.md @@ -1,3 +1,8 @@ +> **Historic changelog.** This records changes made while the project lived in the +> standalone [SAP/xml-tools](https://github.com/SAP/xml-tools) repository, before it was +> migrated into [SAP/app-studio-toolkit](https://github.com/SAP/app-studio-toolkit). +> Newer entries are generated by Changesets in `CHANGELOG.md`. + # Change Log All notable changes to this project will be documented in this file. diff --git a/projects/xml-tools/packages/content-assist/CONTRIBUTING.md b/projects/xml-tools/packages/content-assist/CONTRIBUTING.md deleted file mode 100644 index 84eb9447..00000000 --- a/projects/xml-tools/packages/content-assist/CONTRIBUTING.md +++ /dev/null @@ -1,4 +0,0 @@ -# Contribution Guide - -This package does not have any unique development flows. -Please see the top level [Contribution Guide](../../CONTRIBUTING.md). diff --git a/projects/xml-tools/packages/content-assist/README.md b/projects/xml-tools/packages/content-assist/README.md index b595dbe4..cda04d7c 100644 --- a/projects/xml-tools/packages/content-assist/README.md +++ b/projects/xml-tools/packages/content-assist/README.md @@ -85,10 +85,10 @@ console.log(suggestions[0].text); // -> dress ## Support -Please open [issues](https://github.com/SAP/xml-tols/issues) on github. +Please report issues [here](https://github.com/SAP/app-studio-toolkit/issues/new/choose) and label them with `project:xml-tools`. ## Contributing -See [CONTRIBUTING.md](./CONTRIBUTING.md). +See [CONTRIBUTING.md](https://github.com/SAP/app-studio-toolkit/blob/main/CONTRIBUTING.md). [ast]: https://en.wikipedia.org/wiki/Abstract_syntax_tree diff --git a/projects/xml-tools/packages/content-assist/nyc.config.js b/projects/xml-tools/packages/content-assist/nyc.config.js new file mode 100644 index 00000000..3e3f8962 --- /dev/null +++ b/projects/xml-tools/packages/content-assist/nyc.config.js @@ -0,0 +1,11 @@ +module.exports = { + // xml-tools sources live in lib/ (not src/), so this local config overrides + // the repo-root nyc.config.js include of **/src/** (which would report 0%). + include: ["lib/**/*.js"], + reporter: ["text", "lcov"], + all: true, + branches: 100, + lines: 100, + functions: 100, + statements: 100, +}; diff --git a/projects/xml-tools/packages/content-assist/package.json b/projects/xml-tools/packages/content-assist/package.json index 16665e7c..0e463af9 100644 --- a/projects/xml-tools/packages/content-assist/package.json +++ b/projects/xml-tools/packages/content-assist/package.json @@ -8,18 +8,25 @@ "content-assist" ], "main": "lib/api.js", - "repository": "https://github.com/sap/xml-tools/", + "repository": { + "type": "git", + "url": "https://github.com/SAP/app-studio-toolkit.git", + "directory": "projects/xml-tools/packages/content-assist" + }, "license": "Apache-2.0", "typings": "./api.d.ts", "files": [ "lib", ".reuse", "LICENSES", - "api.d.ts" + "api.d.ts", + "CHANGELOG.md", + "CHANGELOG.old.md" ], "dependencies": { - "@xml-tools/common": "^0.2.0", - "@xml-tools/parser": "^1.0.11", + "@xml-tools/ast": "workspace:*", + "@xml-tools/common": "workspace:*", + "@xml-tools/parser": "workspace:*", "lodash": "4.17.23" }, "scripts": { @@ -28,18 +35,15 @@ "test": "mocha \"./test/**/*spec.js\"", "coverage:run": "nyc mocha \"./test/**/*spec.js\"", "coverage:check": "nyc check-coverage --lines 100 --branches 100 --statements 100 --functions 100", - "type-check": "tsc api.d.ts" + "type-check": "tsc" }, "publishConfig": { "access": "public" }, - "nyc": { - "include": [ - "lib/**/*.js" - ], - "reporter": [ - "text", - "lcov" - ] + "bugs": { + "url": "https://github.com/SAP/app-studio-toolkit/issues" + }, + "devDependencies": { + "chevrotain": "7.1.1" } } diff --git a/projects/xml-tools/packages/content-assist/tsconfig.json b/projects/xml-tools/packages/content-assist/tsconfig.json new file mode 100644 index 00000000..a4ed07be --- /dev/null +++ b/projects/xml-tools/packages/content-assist/tsconfig.json @@ -0,0 +1,6 @@ +{ + "compilerOptions": { + "noEmit": true + }, + "include": ["./api.d.ts"] +} diff --git a/projects/xml-tools/packages/language-server/.npmignore b/projects/xml-tools/packages/language-server/.npmignore deleted file mode 100644 index ae12335e..00000000 --- a/projects/xml-tools/packages/language-server/.npmignore +++ /dev/null @@ -1,10 +0,0 @@ -# NYC -.nyc_output -nyc.config.js -coverage - -# tests -test - -# contributing -CONTRIBUTING.md \ No newline at end of file diff --git a/projects/xml-tools/packages/language-server/CHANGELOG.md b/projects/xml-tools/packages/language-server/CHANGELOG.old.md similarity index 92% rename from projects/xml-tools/packages/language-server/CHANGELOG.md rename to projects/xml-tools/packages/language-server/CHANGELOG.old.md index b3947f99..af42e22d 100644 --- a/projects/xml-tools/packages/language-server/CHANGELOG.md +++ b/projects/xml-tools/packages/language-server/CHANGELOG.old.md @@ -1,3 +1,8 @@ +> **Historic changelog.** This records changes made while the project lived in the +> standalone [SAP/xml-tools](https://github.com/SAP/xml-tools) repository, before it was +> migrated into [SAP/app-studio-toolkit](https://github.com/SAP/app-studio-toolkit). +> Newer entries are generated by Changesets in `CHANGELOG.md`. + # Change Log All notable changes to this project will be documented in this file. diff --git a/projects/xml-tools/packages/language-server/README.md b/projects/xml-tools/packages/language-server/README.md index fd0dd0cf..44da6f28 100644 --- a/projects/xml-tools/packages/language-server/README.md +++ b/projects/xml-tools/packages/language-server/README.md @@ -34,8 +34,8 @@ console.log(SERVER_PATH); // --> .../node_module/@xml-tools/language-server/lib/ ## Support -Please open [issues](https://github.com/SAP/xml-tols/issues) on github. +Please report issues [here](https://github.com/SAP/app-studio-toolkit/issues/new/choose) and label them with `project:xml-tools`. ## Contributing -See [CONTRIBUTING.md](./CONTRIBUTING.md). +See [CONTRIBUTING.md](https://github.com/SAP/app-studio-toolkit/blob/main/CONTRIBUTING.md). diff --git a/projects/xml-tools/packages/language-server/lib/api.js b/projects/xml-tools/packages/language-server/lib/api.js index 24717b72..a639a43a 100644 --- a/projects/xml-tools/packages/language-server/lib/api.js +++ b/projects/xml-tools/packages/language-server/lib/api.js @@ -1,25 +1,18 @@ const { existsSync } = require("fs"); const { resolve } = require("path"); -// for use in productive flows +// for use in productive flows (webpack-bundled, shipped inside the .vsix) const bundledPath = resolve(__dirname, "..", "dist", "server.js"); -// for dev flows +// for dev flows (unbundled sources) const sourcesPath = resolve(__dirname, "server.js"); -// We assume that if the `node_modules` directory exists then we are running -// in development mode, We rely on the fact that the bundled .vsix file of the -// VSCode extension excludes the this package's `node_modules` folder (among others). -// Note this logic would not work with yarn 2.0 / npm 8 / pnpm as `node_modules` -// is no longer created by those package managers. -const isDevelopmentRun = existsSync(resolve(__dirname, "..", "node_modules")); - -let serverPath; -/* istanbul ignore else - no tests (yet?) on bundled artifacts */ -if (isDevelopmentRun) { - serverPath = sourcesPath; -} else { - serverPath = bundledPath; -} +// Prefer the bundled server if it was produced (production / packaged .vsix), +// otherwise fall back to the unbundled sources (development). +// The previous heuristic keyed off the existence of `node_modules`, which is +// unreliable under pnpm (it always creates a symlinked node_modules), so we +// detect the artifact directly instead. +/* istanbul ignore next - no tests (yet?) on bundled artifacts */ +const serverPath = existsSync(bundledPath) ? bundledPath : sourcesPath; module.exports = { SERVER_PATH: serverPath, diff --git a/projects/xml-tools/packages/language-server/nyc.config.js b/projects/xml-tools/packages/language-server/nyc.config.js index cba71070..3e3f8962 100644 --- a/projects/xml-tools/packages/language-server/nyc.config.js +++ b/projects/xml-tools/packages/language-server/nyc.config.js @@ -1,10 +1,8 @@ -// TODO: extract to top level nyc config. module.exports = { + // xml-tools sources live in lib/ (not src/), so this local config overrides + // the repo-root nyc.config.js include of **/src/** (which would report 0%). include: ["lib/**/*.js"], - // Not yet sure how to implement an Integration test for the LSP Server - exclude: ["lib/server.js"], reporter: ["text", "lcov"], - "check-coverage": true, all: true, branches: 100, lines: 100, diff --git a/projects/xml-tools/packages/language-server/package.json b/projects/xml-tools/packages/language-server/package.json index 60594763..ad78e945 100644 --- a/projects/xml-tools/packages/language-server/package.json +++ b/projects/xml-tools/packages/language-server/package.json @@ -11,7 +11,8 @@ "license": "Apache-2.0", "repository": { "type": "git", - "url": "https://github.com/SAP/xml-tools/" + "url": "https://github.com/SAP/app-studio-toolkit.git", + "directory": "projects/xml-tools/packages/language-server" }, "typings": "./api.d.ts", "files": [ @@ -19,14 +20,14 @@ "lib", ".reuse", "LICENSES", - "api.d.ts" + "api.d.ts", + "CHANGELOG.md", + "CHANGELOG.old.md" ], - "engines": { - "node": ">=10.0.0" - }, "dependencies": { - "@xml-tools/constraints": "^1.2.0", - "@xml-tools/parser": "^1.0.11", + "@xml-tools/ast": "workspace:*", + "@xml-tools/constraints": "workspace:*", + "@xml-tools/parser": "workspace:*", "lodash": "4.17.23", "vscode-languageserver": "7.0.0", "vscode-languageserver-textdocument": "1.0.1" @@ -40,10 +41,13 @@ "clean": "rimraf ./dist ./coverage ./nyc_output", "test": "mocha \"./test/**/*spec.js\"", "coverage": "nyc mocha \"./test/**/*spec.js\"", - "type-check": "tsc api.d.ts", + "type-check": "tsc", "bundle": "webpack --mode production" }, "publishConfig": { "access": "public" + }, + "bugs": { + "url": "https://github.com/SAP/app-studio-toolkit/issues" } } diff --git a/projects/xml-tools/packages/language-server/tsconfig.json b/projects/xml-tools/packages/language-server/tsconfig.json new file mode 100644 index 00000000..a4ed07be --- /dev/null +++ b/projects/xml-tools/packages/language-server/tsconfig.json @@ -0,0 +1,6 @@ +{ + "compilerOptions": { + "noEmit": true + }, + "include": ["./api.d.ts"] +} diff --git a/projects/xml-tools/packages/language-server/webpack.config.js b/projects/xml-tools/packages/language-server/webpack.config.js index 7225a992..5fba1e9b 100644 --- a/projects/xml-tools/packages/language-server/webpack.config.js +++ b/projects/xml-tools/packages/language-server/webpack.config.js @@ -1,5 +1,5 @@ const path = require("path"); -const baseConfig = require("../../webpack.config.base"); +const baseConfig = require("../../../../webpack.config.vscode.base"); const config = { ...baseConfig, diff --git a/projects/xml-tools/packages/parser/.npmignore b/projects/xml-tools/packages/parser/.npmignore deleted file mode 100644 index ae12335e..00000000 --- a/projects/xml-tools/packages/parser/.npmignore +++ /dev/null @@ -1,10 +0,0 @@ -# NYC -.nyc_output -nyc.config.js -coverage - -# tests -test - -# contributing -CONTRIBUTING.md \ No newline at end of file diff --git a/projects/xml-tools/packages/parser/CHANGELOG.md b/projects/xml-tools/packages/parser/CHANGELOG.old.md similarity index 93% rename from projects/xml-tools/packages/parser/CHANGELOG.md rename to projects/xml-tools/packages/parser/CHANGELOG.old.md index 9fa22d2e..9463ca8b 100644 --- a/projects/xml-tools/packages/parser/CHANGELOG.md +++ b/projects/xml-tools/packages/parser/CHANGELOG.old.md @@ -1,3 +1,8 @@ +> **Historic changelog.** This records changes made while the project lived in the +> standalone [SAP/xml-tools](https://github.com/SAP/xml-tools) repository, before it was +> migrated into [SAP/app-studio-toolkit](https://github.com/SAP/app-studio-toolkit). +> Newer entries are generated by Changesets in `CHANGELOG.md`. + # Change Log All notable changes to this project will be documented in this file. diff --git a/projects/xml-tools/packages/parser/CONTRIBUTING.md b/projects/xml-tools/packages/parser/CONTRIBUTING.md deleted file mode 100644 index 84eb9447..00000000 --- a/projects/xml-tools/packages/parser/CONTRIBUTING.md +++ /dev/null @@ -1,4 +0,0 @@ -# Contribution Guide - -This package does not have any unique development flows. -Please see the top level [Contribution Guide](../../CONTRIBUTING.md). diff --git a/projects/xml-tools/packages/parser/README.md b/projects/xml-tools/packages/parser/README.md index 94dfae17..7fe1ffef 100644 --- a/projects/xml-tools/packages/parser/README.md +++ b/projects/xml-tools/packages/parser/README.md @@ -45,10 +45,10 @@ console.log(cst.children["element"][0].children["Name"][0].image); // -> note ## Support -Please open [issues](https://github.com/SAP/xml-tols/issues) on github. +Please report issues [here](https://github.com/SAP/app-studio-toolkit/issues/new/choose) and label them with `project:xml-tools`. ## Contributing -See [CONTRIBUTING.md](./CONTRIBUTING.md). +See [CONTRIBUTING.md](https://github.com/SAP/app-studio-toolkit/blob/main/CONTRIBUTING.md). [cst]: https://en.wikipedia.org/wiki/Parse_tree diff --git a/projects/xml-tools/packages/parser/nyc.config.js b/projects/xml-tools/packages/parser/nyc.config.js new file mode 100644 index 00000000..3e3f8962 --- /dev/null +++ b/projects/xml-tools/packages/parser/nyc.config.js @@ -0,0 +1,11 @@ +module.exports = { + // xml-tools sources live in lib/ (not src/), so this local config overrides + // the repo-root nyc.config.js include of **/src/** (which would report 0%). + include: ["lib/**/*.js"], + reporter: ["text", "lcov"], + all: true, + branches: 100, + lines: 100, + functions: 100, + statements: 100, +}; diff --git a/projects/xml-tools/packages/parser/package.json b/projects/xml-tools/packages/parser/package.json index 9a27b583..d152c474 100644 --- a/projects/xml-tools/packages/parser/package.json +++ b/projects/xml-tools/packages/parser/package.json @@ -7,14 +7,20 @@ "parser" ], "main": "lib/api.js", - "repository": "https://github.com/sap/xml-tools/", + "repository": { + "type": "git", + "url": "https://github.com/SAP/app-studio-toolkit.git", + "directory": "projects/xml-tools/packages/parser" + }, "license": "Apache-2.0", "typings": "./api.d.ts", "files": [ "lib", ".reuse", "LICENSES", - "api.d.ts" + "api.d.ts", + "CHANGELOG.md", + "CHANGELOG.old.md" ], "dependencies": { "chevrotain": "7.1.1" @@ -29,18 +35,12 @@ "coverage:run": "nyc mocha \"./test/**/*spec.js\"", "coverage:check": "nyc check-coverage --lines 100 --branches 100 --statements 100 --functions 100", "snapshots:update": "node ./scripts/update-snapshots.js", - "type-check": "tsc api.d.ts" + "type-check": "tsc" }, "publishConfig": { "access": "public" }, - "nyc": { - "include": [ - "lib/**/*.js" - ], - "reporter": [ - "text", - "lcov" - ] + "bugs": { + "url": "https://github.com/SAP/app-studio-toolkit/issues" } } diff --git a/projects/xml-tools/packages/parser/tsconfig.json b/projects/xml-tools/packages/parser/tsconfig.json new file mode 100644 index 00000000..a4ed07be --- /dev/null +++ b/projects/xml-tools/packages/parser/tsconfig.json @@ -0,0 +1,6 @@ +{ + "compilerOptions": { + "noEmit": true + }, + "include": ["./api.d.ts"] +} diff --git a/projects/xml-tools/packages/simple-schema/.npmignore b/projects/xml-tools/packages/simple-schema/.npmignore deleted file mode 100644 index ae12335e..00000000 --- a/projects/xml-tools/packages/simple-schema/.npmignore +++ /dev/null @@ -1,10 +0,0 @@ -# NYC -.nyc_output -nyc.config.js -coverage - -# tests -test - -# contributing -CONTRIBUTING.md \ No newline at end of file diff --git a/projects/xml-tools/packages/simple-schema/CHANGELOG.md b/projects/xml-tools/packages/simple-schema/CHANGELOG.old.md similarity index 95% rename from projects/xml-tools/packages/simple-schema/CHANGELOG.md rename to projects/xml-tools/packages/simple-schema/CHANGELOG.old.md index 13c7d370..01eb120a 100644 --- a/projects/xml-tools/packages/simple-schema/CHANGELOG.md +++ b/projects/xml-tools/packages/simple-schema/CHANGELOG.old.md @@ -1,3 +1,8 @@ +> **Historic changelog.** This records changes made while the project lived in the +> standalone [SAP/xml-tools](https://github.com/SAP/xml-tools) repository, before it was +> migrated into [SAP/app-studio-toolkit](https://github.com/SAP/app-studio-toolkit). +> Newer entries are generated by Changesets in `CHANGELOG.md`. + # Change Log All notable changes to this project will be documented in this file. diff --git a/projects/xml-tools/packages/simple-schema/CONTRIBUTING.md b/projects/xml-tools/packages/simple-schema/CONTRIBUTING.md deleted file mode 100644 index 84eb9447..00000000 --- a/projects/xml-tools/packages/simple-schema/CONTRIBUTING.md +++ /dev/null @@ -1,4 +0,0 @@ -# Contribution Guide - -This package does not have any unique development flows. -Please see the top level [Contribution Guide](../../CONTRIBUTING.md). diff --git a/projects/xml-tools/packages/simple-schema/README.md b/projects/xml-tools/packages/simple-schema/README.md index 8211168d..bc9a0522 100644 --- a/projects/xml-tools/packages/simple-schema/README.md +++ b/projects/xml-tools/packages/simple-schema/README.md @@ -112,8 +112,8 @@ Please see the [TypeScript Definitions](./api.d.ts) for full API details. ## Support -Please open [issues](https://github.com/SAP/xml-tols/issues) on github. +Please report issues [here](https://github.com/SAP/app-studio-toolkit/issues/new/choose) and label them with `project:xml-tools`. ## Contributing -See [CONTRIBUTING.md](./CONTRIBUTING.md). +See [CONTRIBUTING.md](https://github.com/SAP/app-studio-toolkit/blob/main/CONTRIBUTING.md). diff --git a/projects/xml-tools/packages/simple-schema/nyc.config.js b/projects/xml-tools/packages/simple-schema/nyc.config.js new file mode 100644 index 00000000..3e3f8962 --- /dev/null +++ b/projects/xml-tools/packages/simple-schema/nyc.config.js @@ -0,0 +1,11 @@ +module.exports = { + // xml-tools sources live in lib/ (not src/), so this local config overrides + // the repo-root nyc.config.js include of **/src/** (which would report 0%). + include: ["lib/**/*.js"], + reporter: ["text", "lcov"], + all: true, + branches: 100, + lines: 100, + functions: 100, + statements: 100, +}; diff --git a/projects/xml-tools/packages/simple-schema/package.json b/projects/xml-tools/packages/simple-schema/package.json index e0dbaea4..1908df6e 100644 --- a/projects/xml-tools/packages/simple-schema/package.json +++ b/projects/xml-tools/packages/simple-schema/package.json @@ -7,23 +7,30 @@ "validation" ], "main": "lib/api.js", - "repository": "https://github.com/sap/xml-tools/", + "repository": { + "type": "git", + "url": "https://github.com/SAP/app-studio-toolkit.git", + "directory": "projects/xml-tools/packages/simple-schema" + }, "license": "Apache-2.0", "typings": "./api.d.ts", "files": [ "lib", ".reuse", "LICENSES", - "api.d.ts" + "api.d.ts", + "CHANGELOG.md", + "CHANGELOG.old.md" ], "dependencies": { - "@xml-tools/ast": "^5.1.0", - "@xml-tools/content-assist": "^3.2.0", + "@xml-tools/ast": "workspace:*", + "@xml-tools/common": "workspace:*", + "@xml-tools/content-assist": "workspace:*", "lodash": "4.17.23" }, "devDependencies": { - "@xml-tools/parser": "^1.0.11", - "@xml-tools/validation": "^1.1.0" + "@xml-tools/parser": "workspace:*", + "@xml-tools/validation": "workspace:*" }, "scripts": { "ci": "npm-run-all clean type-check coverage:*", @@ -31,18 +38,12 @@ "test": "mocha \"./test/**/*spec.js\"", "coverage:run": "nyc mocha \"./test/**/*spec.js\"", "coverage:check": "nyc check-coverage --lines 100 --branches 100 --statements 100 --functions 100", - "type-check": "tsc api.d.ts" + "type-check": "tsc" }, "publishConfig": { "access": "public" }, - "nyc": { - "include": [ - "lib/**/*.js" - ], - "reporter": [ - "text", - "lcov" - ] + "bugs": { + "url": "https://github.com/SAP/app-studio-toolkit/issues" } } diff --git a/projects/xml-tools/packages/simple-schema/tsconfig.json b/projects/xml-tools/packages/simple-schema/tsconfig.json new file mode 100644 index 00000000..a4ed07be --- /dev/null +++ b/projects/xml-tools/packages/simple-schema/tsconfig.json @@ -0,0 +1,6 @@ +{ + "compilerOptions": { + "noEmit": true + }, + "include": ["./api.d.ts"] +} diff --git a/projects/xml-tools/packages/validation/.npmignore b/projects/xml-tools/packages/validation/.npmignore deleted file mode 100644 index ae12335e..00000000 --- a/projects/xml-tools/packages/validation/.npmignore +++ /dev/null @@ -1,10 +0,0 @@ -# NYC -.nyc_output -nyc.config.js -coverage - -# tests -test - -# contributing -CONTRIBUTING.md \ No newline at end of file diff --git a/projects/xml-tools/packages/validation/CHANGELOG.md b/projects/xml-tools/packages/validation/CHANGELOG.old.md similarity index 94% rename from projects/xml-tools/packages/validation/CHANGELOG.md rename to projects/xml-tools/packages/validation/CHANGELOG.old.md index c83bbc79..0ab80e99 100644 --- a/projects/xml-tools/packages/validation/CHANGELOG.md +++ b/projects/xml-tools/packages/validation/CHANGELOG.old.md @@ -1,3 +1,8 @@ +> **Historic changelog.** This records changes made while the project lived in the +> standalone [SAP/xml-tools](https://github.com/SAP/xml-tools) repository, before it was +> migrated into [SAP/app-studio-toolkit](https://github.com/SAP/app-studio-toolkit). +> Newer entries are generated by Changesets in `CHANGELOG.md`. + # Change Log All notable changes to this project will be documented in this file. diff --git a/projects/xml-tools/packages/validation/CONTRIBUTING.md b/projects/xml-tools/packages/validation/CONTRIBUTING.md deleted file mode 100644 index 84eb9447..00000000 --- a/projects/xml-tools/packages/validation/CONTRIBUTING.md +++ /dev/null @@ -1,4 +0,0 @@ -# Contribution Guide - -This package does not have any unique development flows. -Please see the top level [Contribution Guide](../../CONTRIBUTING.md). diff --git a/projects/xml-tools/packages/validation/README.md b/projects/xml-tools/packages/validation/README.md index 24248c7c..2ac2de50 100644 --- a/projects/xml-tools/packages/validation/README.md +++ b/projects/xml-tools/packages/validation/README.md @@ -63,8 +63,8 @@ console.log(issues[0].node.position.endLine); // -> 3 ## Support -Please open [issues](https://github.com/SAP/xml-tols/issues) on github. +Please report issues [here](https://github.com/SAP/app-studio-toolkit/issues/new/choose) and label them with `project:xml-tools`. ## Contributing -See [CONTRIBUTING.md](./CONTRIBUTING.md). +See [CONTRIBUTING.md](https://github.com/SAP/app-studio-toolkit/blob/main/CONTRIBUTING.md). diff --git a/projects/xml-tools/packages/validation/nyc.config.js b/projects/xml-tools/packages/validation/nyc.config.js new file mode 100644 index 00000000..3e3f8962 --- /dev/null +++ b/projects/xml-tools/packages/validation/nyc.config.js @@ -0,0 +1,11 @@ +module.exports = { + // xml-tools sources live in lib/ (not src/), so this local config overrides + // the repo-root nyc.config.js include of **/src/** (which would report 0%). + include: ["lib/**/*.js"], + reporter: ["text", "lcov"], + all: true, + branches: 100, + lines: 100, + functions: 100, + statements: 100, +}; diff --git a/projects/xml-tools/packages/validation/package.json b/projects/xml-tools/packages/validation/package.json index 1ec65437..b1a445aa 100644 --- a/projects/xml-tools/packages/validation/package.json +++ b/projects/xml-tools/packages/validation/package.json @@ -7,21 +7,27 @@ "validation" ], "main": "lib/api.js", - "repository": "https://github.com/sap/xml-tools/", + "repository": { + "type": "git", + "url": "https://github.com/SAP/app-studio-toolkit.git", + "directory": "projects/xml-tools/packages/validation" + }, "license": "Apache-2.0", "typings": "./api.d.ts", "files": [ "lib", ".reuse", "LICENSES", - "api.d.ts" + "api.d.ts", + "CHANGELOG.md", + "CHANGELOG.old.md" ], "dependencies": { - "@xml-tools/ast": "^5.1.0", + "@xml-tools/ast": "workspace:*", "lodash": "4.17.23" }, "devDependencies": { - "@xml-tools/parser": "^1.0.11" + "@xml-tools/parser": "workspace:*" }, "scripts": { "ci": "npm-run-all clean type-check coverage:*", @@ -29,18 +35,12 @@ "test": "mocha \"./test/**/*spec.js\"", "coverage:run": "nyc mocha \"./test/**/*spec.js\"", "coverage:check": "nyc check-coverage --lines 100 --branches 100 --statements 100 --functions 100", - "type-check": "tsc api.d.ts" + "type-check": "tsc" }, "publishConfig": { "access": "public" }, - "nyc": { - "include": [ - "lib/**/*.js" - ], - "reporter": [ - "text", - "lcov" - ] + "bugs": { + "url": "https://github.com/SAP/app-studio-toolkit/issues" } } diff --git a/projects/xml-tools/packages/validation/tsconfig.json b/projects/xml-tools/packages/validation/tsconfig.json new file mode 100644 index 00000000..a4ed07be --- /dev/null +++ b/projects/xml-tools/packages/validation/tsconfig.json @@ -0,0 +1,6 @@ +{ + "compilerOptions": { + "noEmit": true + }, + "include": ["./api.d.ts"] +} diff --git a/projects/xml-tools/packages/xml-toolkit/.gitignore b/projects/xml-tools/packages/xml-toolkit/.gitignore index d487759d..905fe8fb 100644 --- a/projects/xml-tools/packages/xml-toolkit/.gitignore +++ b/projects/xml-tools/packages/xml-toolkit/.gitignore @@ -1,4 +1,10 @@ -# These files will be copied from the root package during bundling. +# These files are copied/generated from the monorepo root during VSIX packaging. /LICENSE +/LICENSES +/NOTICE -.cache \ No newline at end of file +# Build output +/dist +*.vsix + +.cache diff --git a/projects/xml-tools/packages/xml-toolkit/.vscodeignore b/projects/xml-tools/packages/xml-toolkit/.vscodeignore index d3f9af35..26b68004 100644 --- a/projects/xml-tools/packages/xml-toolkit/.vscodeignore +++ b/projects/xml-tools/packages/xml-toolkit/.vscodeignore @@ -2,13 +2,20 @@ # We are bundling our extension so no need to include the dependencies separately. node_modules -# Except a **subset** language-server package which is executed as a separate process -# Note only one star (*) is expended in these dirs. -!**/@xml-tools/language-server/* -!**/@xml-tools/language-server/dist/* - -# For debugging of bundled language-server in production (source maps) +# Except the language-server package, which runs as a separate process and is +# loaded by path at runtime. Include only what that needs: its package.json (for +# resolution), its lib/ (api.js computes the server path), and its bundled dist/. +!**/@xml-tools/language-server/package.json !**/@xml-tools/language-server/lib/** +!**/@xml-tools/language-server/dist/** + +# Exclude the language-server's own dev/meta files that the broad rules above pull back in. +**/@xml-tools/language-server/.gitignore +**/@xml-tools/language-server/*.md +**/@xml-tools/language-server/nyc.config.js +**/@xml-tools/language-server/tsconfig.json +**/@xml-tools/language-server/webpack.config.js +**/@xml-tools/language-server/api.d.ts # The VSCode Extension's dev artifacts coverage/** diff --git a/projects/xml-tools/packages/xml-toolkit/CHANGELOG.md b/projects/xml-tools/packages/xml-toolkit/CHANGELOG.old.md similarity index 93% rename from projects/xml-tools/packages/xml-toolkit/CHANGELOG.md rename to projects/xml-tools/packages/xml-toolkit/CHANGELOG.old.md index 192a6a4c..647cac7d 100644 --- a/projects/xml-tools/packages/xml-toolkit/CHANGELOG.md +++ b/projects/xml-tools/packages/xml-toolkit/CHANGELOG.old.md @@ -1,3 +1,8 @@ +> **Historic changelog.** This records changes made while the project lived in the +> standalone [SAP/xml-tools](https://github.com/SAP/xml-tools) repository, before it was +> migrated into [SAP/app-studio-toolkit](https://github.com/SAP/app-studio-toolkit). +> Newer entries are generated by Changesets in `CHANGELOG.md`. + # Change Log All notable changes to this project will be documented in this file. diff --git a/projects/xml-tools/packages/xml-toolkit/README.md b/projects/xml-tools/packages/xml-toolkit/README.md index e63f923d..bbb972c7 100644 --- a/projects/xml-tools/packages/xml-toolkit/README.md +++ b/projects/xml-tools/packages/xml-toolkit/README.md @@ -16,7 +16,7 @@ the detection of **multiple** syntax errors. #### Preview -![](https://raw.githubusercontent.com/SAP/xml-tools/master/packages/xml-toolkit/resources/readme/preview-syntax-validation.png) +![](./resources/readme/preview-syntax-validation.png) ## Installation @@ -27,7 +27,7 @@ page, click **Install**. ### From Github releases -1. Go to [GitHub Releases](https://github.com/sap/xml-tools/releases). +1. Go to [GitHub Releases](https://github.com/SAP/app-studio-toolkit/releases). 2. Search for the `.vsix` archive under `xml-toolkit\@x.y.z` releases. - Replace `x.y.z` with the desired version number. 3. Follow the instructions for installing an extension from a `.vsix` @@ -41,8 +41,8 @@ and in the Problems view. ## Support -You can open [issues](https://github.com/SAP/xml-tools/issues) on GitHub. +Please report issues [here](https://github.com/SAP/app-studio-toolkit/issues/new/choose) and label them with `project:xml-tools`. ## Contributing -See [CONTRIBUTING.md](./CONTRIBUTING.md). +See [CONTRIBUTING.md](https://github.com/SAP/app-studio-toolkit/blob/main/CONTRIBUTING.md). diff --git a/projects/xml-tools/packages/xml-toolkit/nyc.config.js b/projects/xml-tools/packages/xml-toolkit/nyc.config.js new file mode 100644 index 00000000..3e3f8962 --- /dev/null +++ b/projects/xml-tools/packages/xml-toolkit/nyc.config.js @@ -0,0 +1,11 @@ +module.exports = { + // xml-tools sources live in lib/ (not src/), so this local config overrides + // the repo-root nyc.config.js include of **/src/** (which would report 0%). + include: ["lib/**/*.js"], + reporter: ["text", "lcov"], + all: true, + branches: 100, + lines: 100, + functions: 100, + statements: 100, +}; diff --git a/projects/xml-tools/packages/xml-toolkit/package.json b/projects/xml-tools/packages/xml-toolkit/package.json index 4a08d216..605fd558 100644 --- a/projects/xml-tools/packages/xml-toolkit/package.json +++ b/projects/xml-tools/packages/xml-toolkit/package.json @@ -15,7 +15,8 @@ "license": "Apache-2.0", "repository": { "type": "git", - "url": "https://github.com/SAP/xml-tools/tree/master/packages/xml-toolkit" + "url": "https://github.com/SAP/app-studio-toolkit.git", + "directory": "projects/xml-tools/packages/xml-toolkit" }, "engines": { "vscode": "^1.56.0" @@ -25,7 +26,7 @@ ], "main": "./lib/extension", "scripts": { - "ci": "npm-run-all clean coverage:* bundle package", + "ci": "npm-run-all clean bundle package", "clean": "rimraf ./dist ./coverage ./nyc_output *.vsix NOTICE LICENSE", "test": "node test/runTest.js", "coverage:run": "nyc node test/runTest.js", @@ -35,7 +36,7 @@ "package": "node ./scripts/package-vsix" }, "dependencies": { - "@xml-tools/language-server": "^1.2.0", + "@xml-tools/language-server": "workspace:*", "vscode-languageclient": "6.1.3" }, "devDependencies": { @@ -46,13 +47,7 @@ "vsce": "1.84.0", "@vscode/test-electron": "^2.4.0" }, - "nyc": { - "include": [ - "lib/**/*.js" - ], - "reporter": [ - "text", - "lcov" - ] + "bugs": { + "url": "https://github.com/SAP/app-studio-toolkit/issues" } } diff --git a/projects/xml-tools/packages/xml-toolkit/scripts/package-vsix.js b/projects/xml-tools/packages/xml-toolkit/scripts/package-vsix.js index 3092eff2..d8a9d1aa 100644 --- a/projects/xml-tools/packages/xml-toolkit/scripts/package-vsix.js +++ b/projects/xml-tools/packages/xml-toolkit/scripts/package-vsix.js @@ -1,56 +1,46 @@ /* istanbul ignore file */ /** * Workaround to: https://github.com/microsoft/vscode-vsce/issues/300 - * This "sorts of" implements the (broken) `yarn list` with support for workspaces - * by hot-patching VSCE cli tool in combination with yarn's workspaces `nohoist` option. * - * See code comments for details. + * The extension is webpack-bundled, but it loads the language server in a + * separate process from `@xml-tools/language-server`'s bundled `dist/server.js` + * (webpack marks that package as `external`). So the VSIX must include the + * language-server package's `dist/` alongside the extension bundle. * - * Possible disadvantages: - * - Some dev artifacts (e.g coverage reports) may be included in the VSIX. - * - Need to ensure assumptions this logic relies on, (e.g nohoist configuration details). - * - Could break when VSCE dep version changes. + * vsce derives the files to package from the package manager's dependency + * listing, which does not work reliably here (pnpm's symlinked node_modules). + * We therefore hot-patch vsce's dependency resolution to include exactly the + * extension root plus the resolved language-server package directory. + * + * Migrated from the standalone SAP/xml-tools repo, which relied on yarn's + * `nohoist`. Under pnpm we resolve the sibling package via `require.resolve` + * instead, which follows pnpm's symlinks correctly. */ const proxyquire = require("proxyquire"); -const { expect } = require("chai"); const { resolve } = require("path"); -const { forEach } = require("lodash"); -const { readFileSync, writeFileSync, copyFileSync } = require("fs"); -const { writeJsonSync, copySync, emptyDirSync } = require("fs-extra"); - -const extensionRootPkg = require("../package.json"); -const monoRepoRootPkg = require("../../../package.json"); - -const extName = extensionRootPkg.name; -const monoRepoNoHoist = monoRepoRootPkg.workspaces.nohoist; +const { + readFileSync, + writeFileSync, + copyFileSync, + rmSync, + cpSync, +} = require("fs"); -// ensure nohoist is configured correctly so the `language-server` dependency -// of the VSCode extension would be present in the extensions's **own** node_modules dir. -// - https://classic.yarnpkg.com/blog/2018/02/15/nohoist/ -forEach(["@xml-tools/language-server"], (_) => { - // Shallow - expect( - monoRepoNoHoist, - `Add "${extName}/${_}" to root monorepo package.json[workspaces.nohoist]` - ).to.include(`${extName}/${_}`); - // Transitive - expect( - monoRepoNoHoist, - `Add "${extName}/${_}/**" to root monorepo package.json[workspaces.nohoist]` - ).to.include(`${extName}/${_}/**`); -}); +const rootExtDir = resolve(__dirname, ".."); -// The path to the language server must be resolved from **inside** the VSCode Ext's node_modules. +// Resolve the language-server package via the extension's OWN node_modules +// symlink (pnpm creates node_modules/@xml-tools/language-server -> the sibling +// package). We deliberately use this symlink path rather than its realpath: +// vsce rejects file paths that escape the extension root ("../language-server/..."), +// so the package must appear as a CHILD of the extension dir. const langServerDir = resolve( - __dirname, - "..", + rootExtDir, "node_modules", "@xml-tools", "language-server" ); -// **Hot-Patching** VSCE using proxyquire. -const rootExtDir = resolve(__dirname, ".."); +// **Hot-Patching** vsce using proxyquire so it packages exactly these dirs. const getDepsStub = { getDependencies: async () => [rootExtDir, langServerDir], }; @@ -60,39 +50,37 @@ const { packageCommand } = proxyquire("vsce/out/package", { const pkgJsonPath = resolve(rootExtDir, "package.json"); // Read & save the original literal representation of the pkg.json -// To avoid dealing with re-formatting (prettier) later on. +// to avoid dealing with re-formatting (prettier) later on. const pkgJsonOrgStr = readFileSync(pkgJsonPath, "utf8"); const pkgJson = JSON.parse(pkgJsonOrgStr); -// During development flows the `main` should point to the compiled sourced -// for fast dev feedback loops. -expect(pkgJson.main).to.equal("./lib/extension"); -// During production flows the main should point to the bundled sources -// to reduce loading time. -pkgJson.main = "./dist/extension"; -writeJsonSync(pkgJsonPath, pkgJson, { spaces: 2, EOF: "\n" }); -// Ensure License and copywrite related files are part of the packaged .vsix -const rootMonoRepoDir = resolve(__dirname, "..", "..", ".."); -const licenseRootMonoRepoPath = resolve(rootMonoRepoDir, "LICENSE"); -const licenseExtPath = resolve(rootExtDir, "LICENSE"); -copyFileSync(licenseRootMonoRepoPath, licenseExtPath); +// Ensure License / copyright files are part of the packaged .vsix. +// Done BEFORE mutating package.json so a copy failure leaves pkg.json pristine. +// Sourced from the monorepo root (five levels up: scripts -> xml-toolkit -> +// packages -> xml-tools -> projects -> repo root). +const rootMonoRepoDir = resolve(__dirname, "..", "..", "..", "..", ".."); +copyFileSync( + resolve(rootMonoRepoDir, "LICENSE"), + resolve(rootExtDir, "LICENSE") +); -const licensesDirPath = resolve(rootMonoRepoDir, "LICENSES"); const licensesDirExtPath = resolve(rootExtDir, "LICENSES"); -emptyDirSync(licensesDirExtPath); -copySync(licensesDirPath, licensesDirExtPath); +rmSync(licensesDirExtPath, { recursive: true, force: true }); +cpSync(resolve(rootMonoRepoDir, "LICENSES"), licensesDirExtPath, { + recursive: true, +}); -const reuseDirPath = resolve(rootMonoRepoDir, ".reuse"); -const reuseDirExtPath = resolve(rootExtDir, "LICENSES"); -emptyDirSync(reuseDirExtPath); -copySync(reuseDirPath, reuseDirExtPath); +// During development flows the `main` points to the compiled sources for fast +// dev feedback loops; for the packaged .vsix it must point to the bundle. +pkgJson.main = "./dist/extension"; +writeFileSync(pkgJsonPath, JSON.stringify(pkgJson, null, 2) + "\n"); packageCommand({ cwd: rootExtDir, packagePath: undefined, baseContentUrl: undefined, baseImagesUrl: undefined, - useYarn: true, + useYarn: false, ignoreFile: undefined, expandGitHubIssueLinks: undefined, }) diff --git a/projects/xml-tools/packages/xml-toolkit/webpack.config.js b/projects/xml-tools/packages/xml-toolkit/webpack.config.js index 6875758c..6f272ea7 100644 --- a/projects/xml-tools/packages/xml-toolkit/webpack.config.js +++ b/projects/xml-tools/packages/xml-toolkit/webpack.config.js @@ -1,5 +1,5 @@ const path = require("path"); -const baseConfig = require("../../webpack.config.base"); +const baseConfig = require("../../../../webpack.config.vscode.base"); const config = { ...baseConfig, From b07167c0276c158346a252e31dcd679283edcd78 Mon Sep 17 00:00:00 2001 From: badrislamovrolan Date: Mon, 21 Sep 2026 03:06:09 +0200 Subject: [PATCH 4/8] chore(xml-tools): relax eslint rules for migrated sources Add inline per-project overrides for projects/xml-tools, matching the precedent set by the earlier migrations (vscode-logging etc.): the chevrotain-generated parser sources trip require-yield / no-case-declarations / no-useless-escape, and the api.d.ts type surfaces trip no-explicit-any / no-unused-vars / no-redundant-type-constituents. Relaxed rather than rewriting migrated code; TODO to tighten incrementally. Signed-off-by: badrislamovrolan --- .eslintrc.js | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/.eslintrc.js b/.eslintrc.js index 5b449dbd..e564f4c4 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -331,5 +331,29 @@ module.exports = { "@typescript-eslint/no-unused-expressions": "off", }, }, + { + // xml-tools was integrated with these rules relaxed for the migrated code. + // TODO: clean up violations and tighten incrementally. + files: ["projects/xml-tools/**"], + rules: { + "eslint-comments/require-description": "off", + // chevrotain-generated parser sources use generator syntax without yield + // and switch/case lexical declarations. + "require-yield": "off", + "no-case-declarations": "off", + "no-useless-escape": "off", + }, + }, + { + // Additional TypeScript rules for xml-tools packages (api.d.ts type surfaces). + // Legacy code migrated as-is; relaxed to match historical behaviour. + // TODO: clean up violations and tighten incrementally. + files: ["projects/xml-tools/**/*.ts"], + rules: { + "@typescript-eslint/no-explicit-any": "off", + "@typescript-eslint/no-unused-vars": "off", + "@typescript-eslint/no-redundant-type-constituents": "off", + }, + }, ], }; From 89f69dd463f0f984ab0b88039b67d28ecf9fea48 Mon Sep 17 00:00:00 2001 From: badrislamovrolan Date: Mon, 21 Sep 2026 03:06:46 +0200 Subject: [PATCH 5/8] chore(xml-tools): add changeset fixed group for @xml-tools/* libraries The nine published @xml-tools/* packages share cross-dependencies at the type level (ast/content-assist/etc. re-export parser & common types), so they must publish in lockstep to avoid two versions of the same type at runtime. The private xml-toolkit extension versions independently (its version is the VSIX marketplace version). Signed-off-by: badrislamovrolan --- .changeset/config.json | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/.changeset/config.json b/.changeset/config.json index d4d1f04d..815a2fee 100644 --- a/.changeset/config.json +++ b/.changeset/config.json @@ -29,6 +29,17 @@ "@sap-devx/inquirer-gui-single-checkbox-plugin", "@sap-devx/inquirer-gui-tiles-plugin", "@sap-devx/controls-navigator" + ], + [ + "@xml-tools/ast", + "@xml-tools/ast-position", + "@xml-tools/common", + "@xml-tools/constraints", + "@xml-tools/content-assist", + "@xml-tools/language-server", + "@xml-tools/parser", + "@xml-tools/simple-schema", + "@xml-tools/validation" ] ], "linked": [], From 5db1cef348e655b0fa5c0e0b1373fea33226efc9 Mon Sep 17 00:00:00 2001 From: badrislamovrolan Date: Mon, 21 Sep 2026 03:07:04 +0200 Subject: [PATCH 6/8] chore(xml-tools): document why @types/vscode is version-pinned Signed-off-by: badrislamovrolan --- projects/xml-tools/packages/xml-toolkit/package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/projects/xml-tools/packages/xml-toolkit/package.json b/projects/xml-tools/packages/xml-toolkit/package.json index 605fd558..f3769493 100644 --- a/projects/xml-tools/packages/xml-toolkit/package.json +++ b/projects/xml-tools/packages/xml-toolkit/package.json @@ -39,6 +39,7 @@ "@xml-tools/language-server": "workspace:*", "vscode-languageclient": "6.1.3" }, + "//@types/vscode": "Pinned to the minimum VS Code API version supported here (kept from the standalone xml-tools repo). Intentionally not raised to the monorepo root version, which would allow newer APIs and break support for older VS Code.", "devDependencies": { "@types/vscode": "1.56.0", "deep-equal-in-any-order": "1.0.28", From 74555b55a9137f5d3cf585bf33f7b24441a181d8 Mon Sep 17 00:00:00 2001 From: badrislamovrolan Date: Mon, 21 Sep 2026 13:45:31 +0200 Subject: [PATCH 7/8] fix(xml-tools): correct chevrotain dep scope, xml-toolkit release, files arrays Address migration-review findings against the OSS monorepo-migration playbook: - Move chevrotain from devDependencies to dependencies in ast, common and content-assist: their published api.d.ts imports IToken from chevrotain, so consumers' type-check needs it installed (a devDep isn't). Previously only worked via pnpm hoisting through @xml-tools/parser. - Add xml-toolkit as its own fixed group in .changeset/config.json. It is a productive VSIX (private:true) that must still version and get a GitHub Release tag; being in neither fixed nor ignore was the wrong state. - Drop .reuse and LICENSES from the 9 published packages' files arrays: those dirs don't exist here (xml-tools ships no per-package REUSE), so the entries pointed at nothing in the npm artifact. Signed-off-by: badrislamovrolan --- .changeset/config.json | 3 ++- projects/xml-tools/packages/ast-position/package.json | 2 -- projects/xml-tools/packages/ast/package.json | 6 ++---- projects/xml-tools/packages/common/package.json | 6 ++---- projects/xml-tools/packages/constraints/package.json | 2 -- projects/xml-tools/packages/content-assist/package.json | 6 +----- projects/xml-tools/packages/language-server/package.json | 2 -- projects/xml-tools/packages/parser/package.json | 2 -- projects/xml-tools/packages/simple-schema/package.json | 2 -- projects/xml-tools/packages/validation/package.json | 2 -- 10 files changed, 7 insertions(+), 26 deletions(-) diff --git a/.changeset/config.json b/.changeset/config.json index 815a2fee..9cd98c6f 100644 --- a/.changeset/config.json +++ b/.changeset/config.json @@ -40,7 +40,8 @@ "@xml-tools/parser", "@xml-tools/simple-schema", "@xml-tools/validation" - ] + ], + ["xml-toolkit"] ], "linked": [], "access": "restricted", diff --git a/projects/xml-tools/packages/ast-position/package.json b/projects/xml-tools/packages/ast-position/package.json index 3834df10..6cce1150 100644 --- a/projects/xml-tools/packages/ast-position/package.json +++ b/projects/xml-tools/packages/ast-position/package.json @@ -16,8 +16,6 @@ "typings": "./api.d.ts", "files": [ "lib", - ".reuse", - "LICENSES", "api.d.ts", "CHANGELOG.md", "CHANGELOG.old.md" diff --git a/projects/xml-tools/packages/ast/package.json b/projects/xml-tools/packages/ast/package.json index 08e4a7e7..60a4d0fc 100644 --- a/projects/xml-tools/packages/ast/package.json +++ b/projects/xml-tools/packages/ast/package.json @@ -16,8 +16,6 @@ "typings": "./api.d.ts", "files": [ "lib", - ".reuse", - "LICENSES", "api.d.ts", "CHANGELOG.md", "CHANGELOG.old.md" @@ -25,11 +23,11 @@ "dependencies": { "@xml-tools/common": "workspace:*", "@xml-tools/parser": "workspace:*", + "chevrotain": "7.1.1", "lodash": "4.17.23" }, "devDependencies": { - "klaw-sync": "6.0.0", - "chevrotain": "7.1.1" + "klaw-sync": "6.0.0" }, "scripts": { "ci": "npm-run-all clean type-check coverage:*", diff --git a/projects/xml-tools/packages/common/package.json b/projects/xml-tools/packages/common/package.json index 07ffee50..c3d164fa 100644 --- a/projects/xml-tools/packages/common/package.json +++ b/projects/xml-tools/packages/common/package.json @@ -16,18 +16,16 @@ "typings": "./api.d.ts", "files": [ "lib", - ".reuse", - "LICENSES", "api.d.ts", "CHANGELOG.md", "CHANGELOG.old.md" ], "dependencies": { + "chevrotain": "7.1.1", "lodash": "4.17.23" }, "devDependencies": { - "@xml-tools/parser": "workspace:*", - "chevrotain": "7.1.1" + "@xml-tools/parser": "workspace:*" }, "scripts": { "ci": "npm-run-all clean type-check coverage:*", diff --git a/projects/xml-tools/packages/constraints/package.json b/projects/xml-tools/packages/constraints/package.json index 9035b225..48ec659b 100644 --- a/projects/xml-tools/packages/constraints/package.json +++ b/projects/xml-tools/packages/constraints/package.json @@ -16,8 +16,6 @@ "typings": "./api.d.ts", "files": [ "lib", - ".reuse", - "LICENSES", "api.d.ts", "CHANGELOG.md", "CHANGELOG.old.md" diff --git a/projects/xml-tools/packages/content-assist/package.json b/projects/xml-tools/packages/content-assist/package.json index 0e463af9..63896403 100644 --- a/projects/xml-tools/packages/content-assist/package.json +++ b/projects/xml-tools/packages/content-assist/package.json @@ -17,8 +17,6 @@ "typings": "./api.d.ts", "files": [ "lib", - ".reuse", - "LICENSES", "api.d.ts", "CHANGELOG.md", "CHANGELOG.old.md" @@ -27,6 +25,7 @@ "@xml-tools/ast": "workspace:*", "@xml-tools/common": "workspace:*", "@xml-tools/parser": "workspace:*", + "chevrotain": "7.1.1", "lodash": "4.17.23" }, "scripts": { @@ -42,8 +41,5 @@ }, "bugs": { "url": "https://github.com/SAP/app-studio-toolkit/issues" - }, - "devDependencies": { - "chevrotain": "7.1.1" } } diff --git a/projects/xml-tools/packages/language-server/package.json b/projects/xml-tools/packages/language-server/package.json index ad78e945..f40fae60 100644 --- a/projects/xml-tools/packages/language-server/package.json +++ b/projects/xml-tools/packages/language-server/package.json @@ -18,8 +18,6 @@ "files": [ "dist", "lib", - ".reuse", - "LICENSES", "api.d.ts", "CHANGELOG.md", "CHANGELOG.old.md" diff --git a/projects/xml-tools/packages/parser/package.json b/projects/xml-tools/packages/parser/package.json index d152c474..97f8802f 100644 --- a/projects/xml-tools/packages/parser/package.json +++ b/projects/xml-tools/packages/parser/package.json @@ -16,8 +16,6 @@ "typings": "./api.d.ts", "files": [ "lib", - ".reuse", - "LICENSES", "api.d.ts", "CHANGELOG.md", "CHANGELOG.old.md" diff --git a/projects/xml-tools/packages/simple-schema/package.json b/projects/xml-tools/packages/simple-schema/package.json index 1908df6e..a9e811be 100644 --- a/projects/xml-tools/packages/simple-schema/package.json +++ b/projects/xml-tools/packages/simple-schema/package.json @@ -16,8 +16,6 @@ "typings": "./api.d.ts", "files": [ "lib", - ".reuse", - "LICENSES", "api.d.ts", "CHANGELOG.md", "CHANGELOG.old.md" diff --git a/projects/xml-tools/packages/validation/package.json b/projects/xml-tools/packages/validation/package.json index b1a445aa..29579502 100644 --- a/projects/xml-tools/packages/validation/package.json +++ b/projects/xml-tools/packages/validation/package.json @@ -16,8 +16,6 @@ "typings": "./api.d.ts", "files": [ "lib", - ".reuse", - "LICENSES", "api.d.ts", "CHANGELOG.md", "CHANGELOG.old.md" From d601cc35ef2e893391faab93195dc20630f5c291 Mon Sep 17 00:00:00 2001 From: badrislamovrolan Date: Mon, 21 Sep 2026 15:38:43 +0200 Subject: [PATCH 8/8] chore(xml-tools): address migration review - Drop the changeset fixed group. The @xml-tools/* packages ship from an independent-versioned source (ast 5.1.0, common 0.2.0, parser 1.0.11); a fixed group would snap all nine to one version, pushing a fake major bump to public consumers. updateInternalDependencies:patch already bumps dependents, so lockstep is not needed for correctness. Let them version independently, as in the source repo. Also removes the no-op single-member ["xml-toolkit"] group. - Remove the dead vsix:prepare script (prepare-vsix-package.sh does not exist and nothing in ci calls it). Signed-off-by: badrislamovrolan --- .changeset/config.json | 14 +------------- .../xml-tools/packages/xml-toolkit/package.json | 1 - 2 files changed, 1 insertion(+), 14 deletions(-) diff --git a/.changeset/config.json b/.changeset/config.json index 96755db7..ed49f8a2 100644 --- a/.changeset/config.json +++ b/.changeset/config.json @@ -29,19 +29,7 @@ "@sap-devx/inquirer-gui-single-checkbox-plugin", "@sap-devx/inquirer-gui-tiles-plugin", "@sap-devx/controls-navigator" - ], - [ - "@xml-tools/ast", - "@xml-tools/ast-position", - "@xml-tools/common", - "@xml-tools/constraints", - "@xml-tools/content-assist", - "@xml-tools/language-server", - "@xml-tools/parser", - "@xml-tools/simple-schema", - "@xml-tools/validation" - ], - ["xml-toolkit"] + ] ], "linked": [], "access": "restricted", diff --git a/projects/xml-tools/packages/xml-toolkit/package.json b/projects/xml-tools/packages/xml-toolkit/package.json index f3769493..3d8d6e53 100644 --- a/projects/xml-tools/packages/xml-toolkit/package.json +++ b/projects/xml-tools/packages/xml-toolkit/package.json @@ -31,7 +31,6 @@ "test": "node test/runTest.js", "coverage:run": "nyc node test/runTest.js", "coverage:check": "nyc check-coverage --lines 100 --branches 100 --statements 100 --functions 100", - "vsix:prepare": "sh prepare-vsix-package.sh", "bundle": "webpack --mode production", "package": "node ./scripts/package-vsix" },