@hyperfixi/core 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +218 -0
- package/dist/__test-utils__/context-builders.d.ts +42 -0
- package/dist/__test-utils__/error-testing.d.ts +18 -0
- package/dist/__test-utils__/index.d.ts +6 -0
- package/dist/__test-utils__/mock-types.d.ts +57 -0
- package/dist/__test-utils__/parser-context-mock.d.ts +7 -0
- package/dist/__test-utils__/parser-helpers.d.ts +38 -0
- package/dist/api/dom-processor.d.ts +17 -0
- package/dist/api/hyperscript-api.d.ts +68 -0
- package/dist/api/lokascript-api.d.ts +9 -0
- package/dist/ast-utils/analyzer.d.ts +19 -0
- package/dist/ast-utils/documentation.d.ts +8 -0
- package/dist/ast-utils/generator.d.ts +9 -0
- package/dist/ast-utils/index.d.ts +12 -0
- package/dist/ast-utils/index.js +3366 -0
- package/dist/ast-utils/index.mjs +3320 -0
- package/dist/ast-utils/interchange/from-core.d.ts +8 -0
- package/dist/ast-utils/interchange/index.d.ts +6 -0
- package/dist/ast-utils/interchange/lsp.d.ts +108 -0
- package/dist/ast-utils/interchange/to-core.d.ts +8 -0
- package/dist/ast-utils/interchange/types.d.ts +113 -0
- package/dist/ast-utils/query.d.ts +25 -0
- package/dist/ast-utils/transformer.d.ts +14 -0
- package/dist/ast-utils/types.d.ts +162 -0
- package/dist/ast-utils/visitor.d.ts +40 -0
- package/dist/behaviors/boosted.d.ts +29 -0
- package/dist/behaviors/history-swap.d.ts +24 -0
- package/dist/behaviors/index.d.ts +4 -0
- package/dist/behaviors/index.js +1149 -0
- package/dist/behaviors/index.mjs +1139 -0
- package/dist/bundle-generator/generator.d.ts +4 -0
- package/dist/bundle-generator/index.d.ts +6 -0
- package/dist/bundle-generator/index.js +2294 -0
- package/dist/bundle-generator/index.mjs +2271 -0
- package/dist/bundle-generator/parser-templates.d.ts +6 -0
- package/dist/bundle-generator/template-capabilities.d.ts +10 -0
- package/dist/bundle-generator/templates.d.ts +11 -0
- package/dist/bundle-generator/types.d.ts +34 -0
- package/dist/bundles/test-minimal.d.ts +3 -0
- package/dist/bundles/test-standard.d.ts +3 -0
- package/dist/chunks/bridge-I6ceoWxV.js +2 -0
- package/dist/chunks/browser-modular-Dv6PAV3c.js +2 -0
- package/dist/chunks/feature-eventsource-DWb514fy.js +2 -0
- package/dist/chunks/feature-sockets-3PFuvCVY.js +2 -0
- package/dist/chunks/feature-webworker-DTm_eh-E.js +2 -0
- package/dist/commands/advanced/async.d.ts +28 -0
- package/dist/commands/advanced/js.d.ts +28 -0
- package/dist/commands/animation/measure.d.ts +31 -0
- package/dist/commands/animation/settle.d.ts +26 -0
- package/dist/commands/animation/take.d.ts +28 -0
- package/dist/commands/animation/transition.d.ts +31 -0
- package/dist/commands/async/fetch.d.ts +44 -0
- package/dist/commands/async/wait.d.ts +41 -0
- package/dist/commands/behaviors/install.d.ts +41 -0
- package/dist/commands/content/append.d.ts +27 -0
- package/dist/commands/control-flow/break.d.ts +15 -0
- package/dist/commands/control-flow/continue.d.ts +15 -0
- package/dist/commands/control-flow/exit.d.ts +15 -0
- package/dist/commands/control-flow/halt.d.ts +25 -0
- package/dist/commands/control-flow/if.d.ts +45 -0
- package/dist/commands/control-flow/repeat.d.ts +35 -0
- package/dist/commands/control-flow/return.d.ts +23 -0
- package/dist/commands/control-flow/signal-base.d.ts +25 -0
- package/dist/commands/control-flow/throw.d.ts +22 -0
- package/dist/commands/control-flow/unless.d.ts +3 -0
- package/dist/commands/data/decrement.d.ts +3 -0
- package/dist/commands/data/default.d.ts +31 -0
- package/dist/commands/data/get.d.ts +23 -0
- package/dist/commands/data/increment.d.ts +25 -0
- package/dist/commands/data/set.d.ts +56 -0
- package/dist/commands/decorators/index.d.ts +30 -0
- package/dist/commands/dom/__tests__/add-standalone-helpers.d.ts +11 -0
- package/dist/commands/dom/add.d.ts +31 -0
- package/dist/commands/dom/dom-modification-base.d.ts +48 -0
- package/dist/commands/dom/hide.d.ts +12 -0
- package/dist/commands/dom/make.d.ts +21 -0
- package/dist/commands/dom/process-partials.d.ts +39 -0
- package/dist/commands/dom/put.d.ts +31 -0
- package/dist/commands/dom/remove.d.ts +33 -0
- package/dist/commands/dom/show.d.ts +13 -0
- package/dist/commands/dom/swap.d.ts +38 -0
- package/dist/commands/dom/toggle.d.ts +55 -0
- package/dist/commands/dom/visibility-base.d.ts +20 -0
- package/dist/commands/events/send.d.ts +3 -0
- package/dist/commands/events/trigger.d.ts +36 -0
- package/dist/commands/execution/call.d.ts +24 -0
- package/dist/commands/execution/pseudo-command.d.ts +42 -0
- package/dist/commands/helpers/attribute-manipulation.d.ts +11 -0
- package/dist/commands/helpers/batch-dom-operations.d.ts +12 -0
- package/dist/commands/helpers/class-manipulation.d.ts +11 -0
- package/dist/commands/helpers/condition-helpers.d.ts +4 -0
- package/dist/commands/helpers/dom-mutation.d.ts +15 -0
- package/dist/commands/helpers/duration-parsing.d.ts +8 -0
- package/dist/commands/helpers/element-property-access.d.ts +7 -0
- package/dist/commands/helpers/element-resolution.d.ts +17 -0
- package/dist/commands/helpers/error-helpers.d.ts +17 -0
- package/dist/commands/helpers/event-helpers.d.ts +13 -0
- package/dist/commands/helpers/event-waiting.d.ts +37 -0
- package/dist/commands/helpers/index.d.ts +32 -0
- package/dist/commands/helpers/input-validator.d.ts +23 -0
- package/dist/commands/helpers/loop-executor.d.ts +53 -0
- package/dist/commands/helpers/numeric-target-parser.d.ts +14 -0
- package/dist/commands/helpers/property-target.d.ts +30 -0
- package/dist/commands/helpers/selector-type-detection.d.ts +24 -0
- package/dist/commands/helpers/smart-element.d.ts +16 -0
- package/dist/commands/helpers/style-manipulation.d.ts +16 -0
- package/dist/commands/helpers/temporal-modifiers.d.ts +15 -0
- package/dist/commands/helpers/url-argument-parser.d.ts +10 -0
- package/dist/commands/helpers/url-validation.d.ts +7 -0
- package/dist/commands/helpers/variable-access.d.ts +10 -0
- package/dist/commands/helpers/visibility-target-parser.d.ts +11 -0
- package/dist/commands/index.d.ts +139 -0
- package/dist/commands/index.js +9186 -0
- package/dist/commands/index.mjs +9032 -0
- package/dist/commands/navigation/go.d.ts +35 -0
- package/dist/commands/navigation/push-url.d.ts +35 -0
- package/dist/commands/navigation/replace-url.d.ts +3 -0
- package/dist/commands/templates/render.d.ts +48 -0
- package/dist/commands/utility/beep.d.ts +32 -0
- package/dist/commands/utility/copy.d.ts +29 -0
- package/dist/commands/utility/log.d.ts +24 -0
- package/dist/commands/utility/pick.d.ts +26 -0
- package/dist/commands/utility/tell.d.ts +26 -0
- package/dist/compatibility/browser-bundle-animation-generated.d.ts +16 -0
- package/dist/compatibility/browser-bundle-classic-i18n.d.ts +63 -0
- package/dist/compatibility/browser-bundle-classic.d.ts +17 -0
- package/dist/compatibility/browser-bundle-forms-generated.d.ts +16 -0
- package/dist/compatibility/browser-bundle-hybrid-complete.d.ts +28 -0
- package/dist/compatibility/browser-bundle-hybrid-hx.d.ts +43 -0
- package/dist/compatibility/browser-bundle-lite-plus.d.ts +25 -0
- package/dist/compatibility/browser-bundle-lite.d.ts +23 -0
- package/dist/compatibility/browser-bundle-minimal-generated.d.ts +16 -0
- package/dist/compatibility/browser-bundle-minimal-v2.d.ts +17 -0
- package/dist/compatibility/browser-bundle-minimal.d.ts +8 -0
- package/dist/compatibility/browser-bundle-modular.d.ts +18 -0
- package/dist/compatibility/browser-bundle-multilingual.d.ts +19 -0
- package/dist/compatibility/browser-bundle-semantic-complete.d.ts +24 -0
- package/dist/compatibility/browser-bundle-standard-v2.d.ts +17 -0
- package/dist/compatibility/browser-bundle-standard.d.ts +8 -0
- package/dist/compatibility/browser-bundle-textshelf-minimal.d.ts +16 -0
- package/dist/compatibility/browser-bundle-textshelf-profile.d.ts +18 -0
- package/dist/compatibility/browser-bundle.d.ts +140 -0
- package/dist/compatibility/browser-modular.d.ts +53 -0
- package/dist/compatibility/browser-tests/test-utils.d.ts +21 -0
- package/dist/compatibility/eval-hyperscript.d.ts +15 -0
- package/dist/compatibility/feature-loader.d.ts +8 -0
- package/dist/compatibility/hyperscript-adapter.d.ts +38 -0
- package/dist/compatibility/hyperscript-tests/test-adapter.d.ts +13 -0
- package/dist/core/ast-property-utils.d.ts +2 -0
- package/dist/core/base-expression-evaluator.d.ts +70 -0
- package/dist/core/binary-expression-evaluator.d.ts +7 -0
- package/dist/core/call-expression-evaluator.d.ts +7 -0
- package/dist/core/configurable-expression-evaluator.d.ts +5 -0
- package/dist/core/context.d.ts +15 -0
- package/dist/core/dom.d.ts +15 -0
- package/dist/core/evaluator-types.d.ts +5 -0
- package/dist/core/events.d.ts +48 -0
- package/dist/core/executor.d.ts +34 -0
- package/dist/core/expression-evaluator.d.ts +6 -0
- package/dist/core/lazy-expression-evaluator.d.ts +22 -0
- package/dist/core/parser.d.ts +21 -0
- package/dist/core/selector-evaluator.d.ts +15 -0
- package/dist/core/template-literal-evaluator.d.ts +5 -0
- package/dist/dom/attribute-processor.d.ts +40 -0
- package/dist/dom/minimal-attribute-processor.d.ts +20 -0
- package/dist/experimental/binary-tree/accessor.d.ts +10 -0
- package/dist/experimental/binary-tree/ast-serializer.d.ts +26 -0
- package/dist/experimental/binary-tree/benchmark.d.ts +24 -0
- package/dist/experimental/binary-tree/buffer-context.d.ts +27 -0
- package/dist/experimental/binary-tree/deserializer.d.ts +17 -0
- package/dist/experimental/binary-tree/index.d.ts +22 -0
- package/dist/experimental/binary-tree/serializer.d.ts +4 -0
- package/dist/experimental/binary-tree/types.d.ts +54 -0
- package/dist/expressions/base-expression.d.ts +27 -0
- package/dist/expressions/bundles/common-expressions.d.ts +9 -0
- package/dist/expressions/bundles/core-expressions.d.ts +7 -0
- package/dist/expressions/bundles/full-expressions.d.ts +10 -0
- package/dist/expressions/bundles/index.d.ts +7 -0
- package/dist/expressions/comparison/index.d.ts +80 -0
- package/dist/expressions/comparison/utils.d.ts +2 -0
- package/dist/expressions/conversion/impl/bridge.d.ts +117 -0
- package/dist/expressions/conversion/impl/index.d.ts +59 -0
- package/dist/expressions/conversion/index.d.ts +23 -0
- package/dist/expressions/expression-tiers.d.ts +13 -0
- package/dist/expressions/index.d.ts +11 -0
- package/dist/expressions/index.js +6930 -0
- package/dist/expressions/index.mjs +6912 -0
- package/dist/expressions/logical/impl/index.d.ts +54 -0
- package/dist/expressions/logical/impl/pattern-matching.d.ts +58 -0
- package/dist/expressions/logical/index.d.ts +59 -0
- package/dist/expressions/mathematical/index.d.ts +69 -0
- package/dist/expressions/positional/impl/bridge.d.ts +95 -0
- package/dist/expressions/positional/impl/index.d.ts +73 -0
- package/dist/expressions/positional/index.d.ts +26 -0
- package/dist/expressions/properties/impl/index.d.ts +105 -0
- package/dist/expressions/properties/index.d.ts +26 -0
- package/dist/expressions/property/index.d.ts +55 -0
- package/dist/expressions/property-access-utils.d.ts +23 -0
- package/dist/expressions/references/impl/bridge.d.ts +54 -0
- package/dist/expressions/references/impl/index.d.ts +65 -0
- package/dist/expressions/references/index.d.ts +40 -0
- package/dist/expressions/shared/comparison-utils.d.ts +10 -0
- package/dist/expressions/shared/index.d.ts +9 -0
- package/dist/expressions/shared/number-utils.d.ts +7 -0
- package/dist/expressions/shared/validation-utils.d.ts +13 -0
- package/dist/expressions/special/index.d.ts +104 -0
- package/dist/expressions/type-helpers.d.ts +11 -0
- package/dist/expressions/type-registry.d.ts +57 -0
- package/dist/expressions/validation-helpers.d.ts +15 -0
- package/dist/extensions/index.d.ts +3 -0
- package/dist/extensions/tailwind.d.ts +22 -0
- package/dist/features/behaviors.d.ts +153 -0
- package/dist/features/def.d.ts +135 -0
- package/dist/features/eventsource.d.ts +140 -0
- package/dist/features/init.d.ts +135 -0
- package/dist/features/on.d.ts +131 -0
- package/dist/features/predefined-behaviors/dropdown-behavior.d.ts +20 -0
- package/dist/features/predefined-behaviors/index.d.ts +12 -0
- package/dist/features/predefined-behaviors/modal-behavior.d.ts +18 -0
- package/dist/features/predefined-behaviors/toggle-group-behavior.d.ts +23 -0
- package/dist/features/predefined-behaviors/types.d.ts +14 -0
- package/dist/features/sockets.d.ts +162 -0
- package/dist/features/webworker.d.ts +135 -0
- package/dist/htmx/htmx-attribute-processor.d.ts +84 -0
- package/dist/htmx/htmx-translator.d.ts +19 -0
- package/dist/htmx/index.d.ts +3 -0
- package/dist/hyperfixi-classic-i18n.js +2 -0
- package/dist/hyperfixi-hx.js +1 -0
- package/dist/hyperfixi-hybrid-complete.js +1 -0
- package/dist/hyperfixi-lite-plus.js +1 -0
- package/dist/hyperfixi-lite.js +1 -0
- package/dist/hyperfixi-minimal.js +1 -0
- package/dist/hyperfixi-multilingual.js +2 -0
- package/dist/hyperfixi-standard.js +2 -0
- package/dist/hyperfixi.js +2 -0
- package/dist/hyperfixi.mjs +2 -0
- package/dist/index.d.ts +25 -0
- package/dist/index.js +65387 -0
- package/dist/index.min.js +2 -0
- package/dist/index.mjs +65343 -0
- package/dist/lib/index.d.ts +4 -0
- package/dist/lib/morph-adapter.d.ts +22 -0
- package/dist/lib/swap-executor.d.ts +22 -0
- package/dist/lib/view-transitions.d.ts +33 -0
- package/dist/lokascript-browser-classic-i18n.js +2 -0
- package/dist/lokascript-browser-minimal.js +1 -0
- package/dist/lokascript-browser-standard.js +2 -0
- package/dist/lokascript-browser.js +2 -0
- package/dist/lokascript-hybrid-complete.js +1 -0
- package/dist/lokascript-hybrid-hx.js +1 -0
- package/dist/lokascript-lite-plus.js +1 -0
- package/dist/lokascript-lite.js +1 -0
- package/dist/lokascript-multilingual.js +2 -0
- package/dist/lsp-metadata.d.ts +25 -0
- package/dist/lsp-metadata.js +680 -0
- package/dist/lsp-metadata.mjs +670 -0
- package/dist/metadata.d.ts +213 -0
- package/dist/metadata.js +378 -0
- package/dist/metadata.mjs +368 -0
- package/dist/mod.d.ts +63 -0
- package/dist/multilingual/bridge.d.ts +36 -0
- package/dist/multilingual/index.d.ts +32 -0
- package/dist/multilingual/index.js +285 -0
- package/dist/multilingual/index.mjs +278 -0
- package/dist/parser/__types__/test-helpers.d.ts +25 -0
- package/dist/parser/command-node-builder.d.ts +45 -0
- package/dist/parser/command-parsers/animation-commands.d.ts +5 -0
- package/dist/parser/command-parsers/async-commands.d.ts +5 -0
- package/dist/parser/command-parsers/control-flow-commands.d.ts +7 -0
- package/dist/parser/command-parsers/dom-commands.d.ts +8 -0
- package/dist/parser/command-parsers/event-commands.d.ts +4 -0
- package/dist/parser/command-parsers/utility-commands.d.ts +9 -0
- package/dist/parser/command-parsers/variable-commands.d.ts +12 -0
- package/dist/parser/error-handler.d.ts +34 -0
- package/dist/parser/expression-parser.d.ts +6 -0
- package/dist/parser/full-parser.d.ts +4 -0
- package/dist/parser/full-parser.js +6532 -0
- package/dist/parser/full-parser.mjs +6529 -0
- package/dist/parser/helpers/ast-helpers.d.ts +22 -0
- package/dist/parser/helpers/parsing-helpers.d.ts +19 -0
- package/dist/parser/helpers/token-helpers.d.ts +28 -0
- package/dist/parser/hybrid/aliases.d.ts +7 -0
- package/dist/parser/hybrid/aliases.js +44 -0
- package/dist/parser/hybrid/aliases.mjs +37 -0
- package/dist/parser/hybrid/ast-types.d.ts +97 -0
- package/dist/parser/hybrid/ast-types.js +3 -0
- package/dist/parser/hybrid/ast-types.mjs +2 -0
- package/dist/parser/hybrid/index.d.ts +6 -0
- package/dist/parser/hybrid/index.js +1015 -0
- package/dist/parser/hybrid/index.mjs +1005 -0
- package/dist/parser/hybrid/parser-core.d.ts +57 -0
- package/dist/parser/hybrid/parser-core.js +1001 -0
- package/dist/parser/hybrid/parser-core.mjs +999 -0
- package/dist/parser/hybrid/tokenizer.d.ts +9 -0
- package/dist/parser/hybrid/tokenizer.js +242 -0
- package/dist/parser/hybrid/tokenizer.mjs +239 -0
- package/dist/parser/hybrid-parser.d.ts +10 -0
- package/dist/parser/hybrid-parser.js +1078 -0
- package/dist/parser/hybrid-parser.mjs +1071 -0
- package/dist/parser/parser-constants.d.ts +102 -0
- package/dist/parser/parser-interface.d.ts +11 -0
- package/dist/parser/parser-types.d.ts +175 -0
- package/dist/parser/parser.d.ts +146 -0
- package/dist/parser/regex-parser.d.ts +4 -0
- package/dist/parser/regex-parser.js +412 -0
- package/dist/parser/regex-parser.mjs +409 -0
- package/dist/parser/runtime.d.ts +3 -0
- package/dist/parser/semantic-integration.d.ts +61 -0
- package/dist/parser/token-consumer.d.ts +35 -0
- package/dist/parser/token-predicates.d.ts +77 -0
- package/dist/parser/tokenizer.d.ts +57 -0
- package/dist/parser/types.d.ts +118 -0
- package/dist/performance/expression-cache.d.ts +78 -0
- package/dist/performance/integration.d.ts +40 -0
- package/dist/performance/production-monitor.d.ts +67 -0
- package/dist/reference/index.d.ts +41 -0
- package/dist/reference/index.js +586 -0
- package/dist/reference/index.mjs +577 -0
- package/dist/registry/browser-types.d.ts +20 -0
- package/dist/registry/browser-types.js +81 -0
- package/dist/registry/browser-types.mjs +76 -0
- package/dist/registry/context-provider-registry.d.ts +38 -0
- package/dist/registry/environment.d.ts +19 -0
- package/dist/registry/environment.js +36 -0
- package/dist/registry/environment.mjs +30 -0
- package/dist/registry/event-source-registry.d.ts +54 -0
- package/dist/registry/examples/server-commands.d.ts +6 -0
- package/dist/registry/examples/server-event-source.d.ts +32 -0
- package/dist/registry/index.d.ts +54 -0
- package/dist/registry/index.js +7636 -0
- package/dist/registry/index.mjs +7612 -0
- package/dist/registry/multilingual/examples.d.ts +16 -0
- package/dist/registry/multilingual/index.d.ts +68 -0
- package/dist/registry/runtime-integration.d.ts +30 -0
- package/dist/registry/universal-types.d.ts +34 -0
- package/dist/registry/universal-types.js +91 -0
- package/dist/registry/universal-types.mjs +86 -0
- package/dist/runtime/cleanup-registry.d.ts +47 -0
- package/dist/runtime/command-adapter.d.ts +63 -0
- package/dist/runtime/environment.d.ts +75 -0
- package/dist/runtime/runtime-base.d.ts +78 -0
- package/dist/runtime/runtime-experimental.d.ts +18 -0
- package/dist/runtime/runtime-factory.d.ts +30 -0
- package/dist/runtime/runtime.d.ts +19 -0
- package/dist/runtime/temporal-modifiers.d.ts +37 -0
- package/dist/scripts/code-generator.d.ts +64 -0
- package/dist/scripts/generate-missing-commands.d.ts +4 -0
- package/dist/test-setup.d.ts +45 -0
- package/dist/test-utilities.d.ts +52 -0
- package/dist/tokenizer.d.ts +49 -0
- package/dist/types/base-types.d.ts +336 -0
- package/dist/types/code-fix.d.ts +39 -0
- package/dist/types/command-metadata.d.ts +57 -0
- package/dist/types/command-types.d.ts +272 -0
- package/dist/types/context-types.d.ts +106 -0
- package/dist/types/core-context.d.ts +21 -0
- package/dist/types/core.d.ts +203 -0
- package/dist/types/error-codes.d.ts +207 -0
- package/dist/types/expression-types.d.ts +155 -0
- package/dist/types/feature-types.d.ts +81 -0
- package/dist/types/hooks.d.ts +45 -0
- package/dist/types/index.d.ts +32 -0
- package/dist/types/result.d.ts +72 -0
- package/dist/types/template-types.d.ts +162 -0
- package/dist/types/type-guards.d.ts +24 -0
- package/dist/types/unified-types.d.ts +99 -0
- package/dist/utils/debug-events.d.ts +41 -0
- package/dist/utils/debug.d.ts +35 -0
- package/dist/utils/dom-utils.d.ts +16 -0
- package/dist/utils/element-check.d.ts +7 -0
- package/dist/utils/keyboard-shortcuts.d.ts +18 -0
- package/dist/utils/performance.d.ts +40 -0
- package/dist/validation/command-pattern-validator.d.ts +53 -0
- package/dist/validation/common-validators.d.ts +24 -0
- package/dist/validation/lightweight-validators.d.ts +113 -0
- package/dist/validation/partial-validation-types.d.ts +50 -0
- package/dist/validation/partial-validator.d.ts +6 -0
- package/dist/validation/partial-warning-formatter.d.ts +6 -0
- package/dist/validation/validate-cli.d.ts +41 -0
- package/package.json +292 -0
|
@@ -0,0 +1,3320 @@
|
|
|
1
|
+
class VisitorContextImpl {
|
|
2
|
+
constructor(path = [], parent = null) {
|
|
3
|
+
this._skipped = false;
|
|
4
|
+
this._stopped = false;
|
|
5
|
+
this._replaced = undefined;
|
|
6
|
+
this._hasReplacement = false;
|
|
7
|
+
this._path = [];
|
|
8
|
+
this._parent = null;
|
|
9
|
+
this._scope = new Map();
|
|
10
|
+
this._path = [...path];
|
|
11
|
+
this._parent = parent;
|
|
12
|
+
}
|
|
13
|
+
skip() {
|
|
14
|
+
this._skipped = true;
|
|
15
|
+
}
|
|
16
|
+
stop() {
|
|
17
|
+
this._stopped = true;
|
|
18
|
+
}
|
|
19
|
+
replace(node) {
|
|
20
|
+
this._replaced = node;
|
|
21
|
+
this._hasReplacement = true;
|
|
22
|
+
}
|
|
23
|
+
getPath() {
|
|
24
|
+
return [...this._path];
|
|
25
|
+
}
|
|
26
|
+
getParent() {
|
|
27
|
+
return this._parent;
|
|
28
|
+
}
|
|
29
|
+
getScope() {
|
|
30
|
+
return new Map(this._scope);
|
|
31
|
+
}
|
|
32
|
+
setScope(key, value) {
|
|
33
|
+
this._scope.set(key, value);
|
|
34
|
+
}
|
|
35
|
+
get skipped() {
|
|
36
|
+
return this._skipped;
|
|
37
|
+
}
|
|
38
|
+
get stopped() {
|
|
39
|
+
return this._stopped;
|
|
40
|
+
}
|
|
41
|
+
get replacement() {
|
|
42
|
+
return this._replaced;
|
|
43
|
+
}
|
|
44
|
+
get hasReplacement() {
|
|
45
|
+
return this._hasReplacement;
|
|
46
|
+
}
|
|
47
|
+
createChild(key, parent) {
|
|
48
|
+
const child = new VisitorContextImpl([...this._path, key], parent);
|
|
49
|
+
for (const [k, v] of this._scope) {
|
|
50
|
+
child._scope.set(k, v);
|
|
51
|
+
}
|
|
52
|
+
return child;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
class ASTVisitor {
|
|
56
|
+
constructor(handlers) {
|
|
57
|
+
this.handlers = handlers;
|
|
58
|
+
}
|
|
59
|
+
visit(node, context) {
|
|
60
|
+
if (!node)
|
|
61
|
+
return null;
|
|
62
|
+
if (this.handlers.enter) {
|
|
63
|
+
this.handlers.enter(node, context);
|
|
64
|
+
}
|
|
65
|
+
const typeHandler = this.handlers[node.type];
|
|
66
|
+
if (typeHandler) {
|
|
67
|
+
typeHandler(node, context);
|
|
68
|
+
}
|
|
69
|
+
if (context.stopped || context.hasReplacement) {
|
|
70
|
+
return context.replacement;
|
|
71
|
+
}
|
|
72
|
+
if (!context.skipped) {
|
|
73
|
+
const visitedNode = this.visitChildren(node, context);
|
|
74
|
+
if (visitedNode !== node) {
|
|
75
|
+
node = visitedNode;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
if (this.handlers.exit) {
|
|
79
|
+
this.handlers.exit(node, context);
|
|
80
|
+
}
|
|
81
|
+
if (context.hasReplacement) {
|
|
82
|
+
return context.replacement;
|
|
83
|
+
}
|
|
84
|
+
return node;
|
|
85
|
+
}
|
|
86
|
+
visitChildren(node, context) {
|
|
87
|
+
const result = { ...node };
|
|
88
|
+
let modified = false;
|
|
89
|
+
for (const [key, value] of Object.entries(node)) {
|
|
90
|
+
if (key === 'type' ||
|
|
91
|
+
key === 'start' ||
|
|
92
|
+
key === 'end' ||
|
|
93
|
+
key === 'line' ||
|
|
94
|
+
key === 'column') {
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
if (Array.isArray(value)) {
|
|
98
|
+
const newArray = [];
|
|
99
|
+
for (let i = 0; i < value.length; i++) {
|
|
100
|
+
const item = value[i];
|
|
101
|
+
if (this.isASTNode(item)) {
|
|
102
|
+
const childContext = context.createChild(`${key}/${i}`, node);
|
|
103
|
+
const visitedChild = this.visit(item, childContext);
|
|
104
|
+
if (visitedChild === null) {
|
|
105
|
+
modified = true;
|
|
106
|
+
}
|
|
107
|
+
else if (Array.isArray(visitedChild)) {
|
|
108
|
+
newArray.push(...visitedChild);
|
|
109
|
+
modified = true;
|
|
110
|
+
}
|
|
111
|
+
else {
|
|
112
|
+
newArray.push(visitedChild);
|
|
113
|
+
if (visitedChild !== item) {
|
|
114
|
+
modified = true;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
if (childContext.stopped) {
|
|
118
|
+
break;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
else {
|
|
122
|
+
newArray.push(item);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
if (modified || newArray.length !== value.length) {
|
|
126
|
+
result[key] = newArray;
|
|
127
|
+
modified = true;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
else if (this.isASTNode(value)) {
|
|
131
|
+
const childContext = context.createChild(key, node);
|
|
132
|
+
const visitedChild = this.visit(value, childContext);
|
|
133
|
+
if (visitedChild !== value) {
|
|
134
|
+
result[key] = visitedChild;
|
|
135
|
+
modified = true;
|
|
136
|
+
}
|
|
137
|
+
if (childContext.stopped) {
|
|
138
|
+
break;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
return modified ? result : node;
|
|
143
|
+
}
|
|
144
|
+
isASTNode(value) {
|
|
145
|
+
return value && typeof value === 'object' && typeof value.type === 'string';
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
function visit(ast, visitor) {
|
|
149
|
+
if (!ast)
|
|
150
|
+
return null;
|
|
151
|
+
const context = new VisitorContextImpl();
|
|
152
|
+
const result = visitor.visit(ast, context);
|
|
153
|
+
if (result === null)
|
|
154
|
+
return null;
|
|
155
|
+
if (Array.isArray(result))
|
|
156
|
+
return result.length > 0 ? result[0] : null;
|
|
157
|
+
return result;
|
|
158
|
+
}
|
|
159
|
+
function findNodes(ast, predicate) {
|
|
160
|
+
if (!ast)
|
|
161
|
+
return [];
|
|
162
|
+
const results = [];
|
|
163
|
+
const visitor = new ASTVisitor({
|
|
164
|
+
enter(node) {
|
|
165
|
+
if (predicate(node)) {
|
|
166
|
+
results.push(node);
|
|
167
|
+
}
|
|
168
|
+
},
|
|
169
|
+
});
|
|
170
|
+
visit(ast, visitor);
|
|
171
|
+
return results;
|
|
172
|
+
}
|
|
173
|
+
function findFirst(ast, predicate) {
|
|
174
|
+
if (!ast)
|
|
175
|
+
return null;
|
|
176
|
+
let result = null;
|
|
177
|
+
const visitor = new ASTVisitor({
|
|
178
|
+
enter(node, context) {
|
|
179
|
+
if (predicate(node)) {
|
|
180
|
+
result = node;
|
|
181
|
+
context.stop();
|
|
182
|
+
}
|
|
183
|
+
},
|
|
184
|
+
});
|
|
185
|
+
visit(ast, visitor);
|
|
186
|
+
return result;
|
|
187
|
+
}
|
|
188
|
+
function getAncestors(ast, targetNode) {
|
|
189
|
+
if (!ast || !targetNode)
|
|
190
|
+
return [];
|
|
191
|
+
const ancestors = [];
|
|
192
|
+
function findPath(node, path) {
|
|
193
|
+
if (node === targetNode) {
|
|
194
|
+
ancestors.push(...path.reverse());
|
|
195
|
+
return true;
|
|
196
|
+
}
|
|
197
|
+
for (const [key, value] of Object.entries(node)) {
|
|
198
|
+
if (key === 'type' ||
|
|
199
|
+
key === 'start' ||
|
|
200
|
+
key === 'end' ||
|
|
201
|
+
key === 'line' ||
|
|
202
|
+
key === 'column') {
|
|
203
|
+
continue;
|
|
204
|
+
}
|
|
205
|
+
if (Array.isArray(value)) {
|
|
206
|
+
for (const item of value) {
|
|
207
|
+
if (item && typeof item === 'object' && typeof item.type === 'string') {
|
|
208
|
+
if (findPath(item, [...path, node]))
|
|
209
|
+
return true;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
else if (value && typeof value === 'object' && typeof value.type === 'string') {
|
|
214
|
+
if (findPath(value, [...path, node]))
|
|
215
|
+
return true;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
return false;
|
|
219
|
+
}
|
|
220
|
+
findPath(ast, []);
|
|
221
|
+
return ancestors;
|
|
222
|
+
}
|
|
223
|
+
function createTypeCollector(types) {
|
|
224
|
+
const typeSet = new Set(types);
|
|
225
|
+
const collected = {};
|
|
226
|
+
for (const type of types) {
|
|
227
|
+
collected[type] = [];
|
|
228
|
+
}
|
|
229
|
+
return new ASTVisitor({
|
|
230
|
+
enter(node) {
|
|
231
|
+
if (typeSet.has(node.type)) {
|
|
232
|
+
collected[node.type].push(node);
|
|
233
|
+
}
|
|
234
|
+
},
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
function measureDepth(ast) {
|
|
238
|
+
let maxDepth = 0;
|
|
239
|
+
let currentDepth = 0;
|
|
240
|
+
const visitor = new ASTVisitor({
|
|
241
|
+
enter() {
|
|
242
|
+
currentDepth++;
|
|
243
|
+
maxDepth = Math.max(maxDepth, currentDepth);
|
|
244
|
+
},
|
|
245
|
+
exit() {
|
|
246
|
+
currentDepth--;
|
|
247
|
+
},
|
|
248
|
+
});
|
|
249
|
+
visit(ast, visitor);
|
|
250
|
+
return maxDepth;
|
|
251
|
+
}
|
|
252
|
+
function countNodeTypes(ast) {
|
|
253
|
+
const counts = {};
|
|
254
|
+
const visitor = new ASTVisitor({
|
|
255
|
+
enter(node) {
|
|
256
|
+
counts[node.type] = (counts[node.type] || 0) + 1;
|
|
257
|
+
},
|
|
258
|
+
});
|
|
259
|
+
visit(ast, visitor);
|
|
260
|
+
return counts;
|
|
261
|
+
}
|
|
262
|
+
function createVisitorContext() {
|
|
263
|
+
return new VisitorContextImpl();
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
function calculateComplexity(ast) {
|
|
267
|
+
return {
|
|
268
|
+
cyclomatic: calculateCyclomaticComplexity(ast),
|
|
269
|
+
cognitive: calculateCognitiveComplexity(ast),
|
|
270
|
+
halstead: calculateHalsteadMetrics(ast),
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
function calculateCyclomaticComplexity(ast) {
|
|
274
|
+
let complexity = 1;
|
|
275
|
+
const visitor = new ASTVisitor({
|
|
276
|
+
enter(node) {
|
|
277
|
+
if (isDecisionNode(node))
|
|
278
|
+
complexity++;
|
|
279
|
+
},
|
|
280
|
+
});
|
|
281
|
+
visit(ast, visitor);
|
|
282
|
+
return complexity;
|
|
283
|
+
}
|
|
284
|
+
function calculateCognitiveComplexity(ast) {
|
|
285
|
+
let complexity = 0;
|
|
286
|
+
let depth = 0;
|
|
287
|
+
const visitor = new ASTVisitor({
|
|
288
|
+
enter(node) {
|
|
289
|
+
if (isNestingNode(node))
|
|
290
|
+
depth++;
|
|
291
|
+
if (isDecisionNode(node))
|
|
292
|
+
complexity += 1 + depth;
|
|
293
|
+
},
|
|
294
|
+
exit(node) {
|
|
295
|
+
if (isNestingNode(node))
|
|
296
|
+
depth--;
|
|
297
|
+
},
|
|
298
|
+
});
|
|
299
|
+
visit(ast, visitor);
|
|
300
|
+
return complexity;
|
|
301
|
+
}
|
|
302
|
+
function calculateHalsteadMetrics(ast) {
|
|
303
|
+
const operators = new Set();
|
|
304
|
+
const operands = new Set();
|
|
305
|
+
let totalOperators = 0;
|
|
306
|
+
let totalOperands = 0;
|
|
307
|
+
const visitor = new ASTVisitor({
|
|
308
|
+
enter(node) {
|
|
309
|
+
if (node.operator) {
|
|
310
|
+
operators.add(node.operator);
|
|
311
|
+
totalOperators++;
|
|
312
|
+
}
|
|
313
|
+
if (node.name) {
|
|
314
|
+
operands.add(node.name);
|
|
315
|
+
totalOperands++;
|
|
316
|
+
}
|
|
317
|
+
if (node.value !== undefined) {
|
|
318
|
+
operands.add(String(node.value));
|
|
319
|
+
totalOperands++;
|
|
320
|
+
}
|
|
321
|
+
if (node.type && ['command', 'conditional', 'binaryExpression'].includes(node.type)) {
|
|
322
|
+
operators.add(node.type);
|
|
323
|
+
totalOperators++;
|
|
324
|
+
}
|
|
325
|
+
},
|
|
326
|
+
});
|
|
327
|
+
visit(ast, visitor);
|
|
328
|
+
const vocabulary = operators.size + operands.size;
|
|
329
|
+
const length = totalOperators + totalOperands;
|
|
330
|
+
const difficulty = vocabulary > 0 ? (operators.size / 2) * (totalOperands / Math.max(operands.size, 1)) : 0;
|
|
331
|
+
const effort = difficulty * length;
|
|
332
|
+
return { vocabulary, length, difficulty, effort };
|
|
333
|
+
}
|
|
334
|
+
function isDecisionNode(node) {
|
|
335
|
+
return (['conditional', 'binaryExpression', 'logicalExpression'].includes(node.type) ||
|
|
336
|
+
(node.operator && ['&&', '||', '?'].includes(node.operator)));
|
|
337
|
+
}
|
|
338
|
+
function isNestingNode(node) {
|
|
339
|
+
return ['conditional', 'loop', 'function', 'block'].includes(node.type);
|
|
340
|
+
}
|
|
341
|
+
function detectCodeSmells(ast) {
|
|
342
|
+
const smells = [];
|
|
343
|
+
smells.push(...detectExcessiveNesting(ast));
|
|
344
|
+
smells.push(...detectDuplicateCode(ast));
|
|
345
|
+
smells.push(...detectLongCommandChains(ast));
|
|
346
|
+
smells.push(...detectComplexConditions(ast));
|
|
347
|
+
return smells;
|
|
348
|
+
}
|
|
349
|
+
function detectExcessiveNesting(ast) {
|
|
350
|
+
const smells = [];
|
|
351
|
+
const maxDepth = 3;
|
|
352
|
+
const currentDepth = measureDepth(ast);
|
|
353
|
+
if (currentDepth > maxDepth) {
|
|
354
|
+
smells.push({
|
|
355
|
+
type: 'excessive-nesting',
|
|
356
|
+
severity: 'high',
|
|
357
|
+
location: {
|
|
358
|
+
...(ast.start !== undefined && { start: ast.start }),
|
|
359
|
+
...(ast.end !== undefined && { end: ast.end }),
|
|
360
|
+
...(ast.line !== undefined && { line: ast.line }),
|
|
361
|
+
...(ast.column !== undefined && { column: ast.column }),
|
|
362
|
+
},
|
|
363
|
+
message: `Nesting depth of ${currentDepth} exceeds recommended maximum of ${maxDepth}`,
|
|
364
|
+
suggestion: 'Consider extracting nested logic into separate functions or using early returns',
|
|
365
|
+
});
|
|
366
|
+
}
|
|
367
|
+
return smells;
|
|
368
|
+
}
|
|
369
|
+
function detectDuplicateCode(ast) {
|
|
370
|
+
const smells = [];
|
|
371
|
+
const nodeStrings = new Map();
|
|
372
|
+
const visitor = new ASTVisitor({
|
|
373
|
+
enter(node) {
|
|
374
|
+
if (node.type === 'command' || node.type === 'conditional') {
|
|
375
|
+
const nodeString = JSON.stringify(node, ['type', 'name', 'value', 'operator']);
|
|
376
|
+
if (!nodeStrings.has(nodeString))
|
|
377
|
+
nodeStrings.set(nodeString, []);
|
|
378
|
+
nodeStrings.get(nodeString).push(node);
|
|
379
|
+
}
|
|
380
|
+
},
|
|
381
|
+
});
|
|
382
|
+
visit(ast, visitor);
|
|
383
|
+
for (const [, nodes] of nodeStrings) {
|
|
384
|
+
if (nodes.length > 1) {
|
|
385
|
+
const firstNode = nodes[0];
|
|
386
|
+
smells.push({
|
|
387
|
+
type: 'duplicate-code',
|
|
388
|
+
severity: 'medium',
|
|
389
|
+
location: {
|
|
390
|
+
...(firstNode.start !== undefined && { start: firstNode.start }),
|
|
391
|
+
...(firstNode.end !== undefined && { end: firstNode.end }),
|
|
392
|
+
...(firstNode.line !== undefined && { line: firstNode.line }),
|
|
393
|
+
...(firstNode.column !== undefined && { column: firstNode.column }),
|
|
394
|
+
},
|
|
395
|
+
message: `Duplicate code found (${nodes.length} occurrences)`,
|
|
396
|
+
suggestion: 'Consider extracting common logic into a reusable function',
|
|
397
|
+
});
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
return smells;
|
|
401
|
+
}
|
|
402
|
+
function detectLongCommandChains(ast) {
|
|
403
|
+
const smells = [];
|
|
404
|
+
const maxCommands = 5;
|
|
405
|
+
const visitor = new ASTVisitor({
|
|
406
|
+
enter(node) {
|
|
407
|
+
if (node.commands && Array.isArray(node.commands)) {
|
|
408
|
+
const commands = node.commands;
|
|
409
|
+
if (commands.length > maxCommands) {
|
|
410
|
+
smells.push({
|
|
411
|
+
type: 'long-command-chain',
|
|
412
|
+
severity: 'medium',
|
|
413
|
+
location: {
|
|
414
|
+
...(node.start !== undefined && { start: node.start }),
|
|
415
|
+
...(node.end !== undefined && { end: node.end }),
|
|
416
|
+
...(node.line !== undefined && { line: node.line }),
|
|
417
|
+
...(node.column !== undefined && { column: node.column }),
|
|
418
|
+
},
|
|
419
|
+
message: `Command chain with ${commands.length} commands exceeds recommended maximum of ${maxCommands}`,
|
|
420
|
+
suggestion: 'Consider breaking into smaller, focused handlers',
|
|
421
|
+
});
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
},
|
|
425
|
+
});
|
|
426
|
+
visit(ast, visitor);
|
|
427
|
+
return smells;
|
|
428
|
+
}
|
|
429
|
+
function detectComplexConditions(ast) {
|
|
430
|
+
const smells = [];
|
|
431
|
+
const visitor = new ASTVisitor({
|
|
432
|
+
enter(node) {
|
|
433
|
+
if (node.type === 'conditional' && node.condition) {
|
|
434
|
+
const conditionComplexity = calculateExpressionComplexity(node.condition);
|
|
435
|
+
if (conditionComplexity > 3) {
|
|
436
|
+
smells.push({
|
|
437
|
+
type: 'complex-condition',
|
|
438
|
+
severity: 'medium',
|
|
439
|
+
location: {
|
|
440
|
+
...(node.start !== undefined && { start: node.start }),
|
|
441
|
+
...(node.end !== undefined && { end: node.end }),
|
|
442
|
+
...(node.line !== undefined && { line: node.line }),
|
|
443
|
+
...(node.column !== undefined && { column: node.column }),
|
|
444
|
+
},
|
|
445
|
+
message: `Condition complexity of ${conditionComplexity} is too high`,
|
|
446
|
+
suggestion: 'Consider extracting condition logic into named variables',
|
|
447
|
+
});
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
},
|
|
451
|
+
});
|
|
452
|
+
visit(ast, visitor);
|
|
453
|
+
return smells;
|
|
454
|
+
}
|
|
455
|
+
function calculateExpressionComplexity(expr) {
|
|
456
|
+
let complexity = 1;
|
|
457
|
+
const visitor = new ASTVisitor({
|
|
458
|
+
enter(node) {
|
|
459
|
+
if (node.operator && ['&&', '||', '!'].includes(node.operator)) {
|
|
460
|
+
complexity++;
|
|
461
|
+
}
|
|
462
|
+
},
|
|
463
|
+
});
|
|
464
|
+
visit(expr, visitor);
|
|
465
|
+
return complexity;
|
|
466
|
+
}
|
|
467
|
+
function analyzeDependencies(ast) {
|
|
468
|
+
const nodes = new Set();
|
|
469
|
+
const edges = new Map();
|
|
470
|
+
const visitor = new ASTVisitor({
|
|
471
|
+
enter(node) {
|
|
472
|
+
if (node.name === 'set' && node.variable) {
|
|
473
|
+
const varName = node.variable.name;
|
|
474
|
+
nodes.add(varName);
|
|
475
|
+
if (node.value) {
|
|
476
|
+
const deps = extractDependencies(node.value);
|
|
477
|
+
if (!edges.has(varName))
|
|
478
|
+
edges.set(varName, new Set());
|
|
479
|
+
deps.forEach(dep => {
|
|
480
|
+
nodes.add(dep);
|
|
481
|
+
edges.get(varName).add(dep);
|
|
482
|
+
});
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
},
|
|
486
|
+
});
|
|
487
|
+
visit(ast, visitor);
|
|
488
|
+
const cycles = detectCycles(nodes, edges);
|
|
489
|
+
return { nodes, edges, cycles };
|
|
490
|
+
}
|
|
491
|
+
function extractDependencies(valueNode) {
|
|
492
|
+
const deps = [];
|
|
493
|
+
const visitor = new ASTVisitor({
|
|
494
|
+
enter(node) {
|
|
495
|
+
if (node.type === 'identifier')
|
|
496
|
+
deps.push(node.name);
|
|
497
|
+
},
|
|
498
|
+
});
|
|
499
|
+
visit(valueNode, visitor);
|
|
500
|
+
return deps;
|
|
501
|
+
}
|
|
502
|
+
function detectCycles(nodes, edges) {
|
|
503
|
+
const visited = new Set();
|
|
504
|
+
const recursionStack = new Set();
|
|
505
|
+
const cycles = [];
|
|
506
|
+
function dfs(node, path) {
|
|
507
|
+
if (recursionStack.has(node)) {
|
|
508
|
+
const cycleStart = path.indexOf(node);
|
|
509
|
+
cycles.push(path.slice(cycleStart));
|
|
510
|
+
return;
|
|
511
|
+
}
|
|
512
|
+
if (visited.has(node))
|
|
513
|
+
return;
|
|
514
|
+
visited.add(node);
|
|
515
|
+
recursionStack.add(node);
|
|
516
|
+
path.push(node);
|
|
517
|
+
const neighbors = edges.get(node) || new Set();
|
|
518
|
+
for (const neighbor of neighbors) {
|
|
519
|
+
dfs(neighbor, [...path]);
|
|
520
|
+
}
|
|
521
|
+
recursionStack.delete(node);
|
|
522
|
+
}
|
|
523
|
+
for (const node of nodes) {
|
|
524
|
+
if (!visited.has(node))
|
|
525
|
+
dfs(node, []);
|
|
526
|
+
}
|
|
527
|
+
return cycles;
|
|
528
|
+
}
|
|
529
|
+
function findDeadCode(ast) {
|
|
530
|
+
const deadCode = [];
|
|
531
|
+
const definedVars = new Set();
|
|
532
|
+
const usedVars = new Set();
|
|
533
|
+
const varDefinitions = new Map();
|
|
534
|
+
const definitionIdentifiers = new Set();
|
|
535
|
+
const visitor = new ASTVisitor({
|
|
536
|
+
enter(node) {
|
|
537
|
+
if (node.name === 'set' && node.variable) {
|
|
538
|
+
const varName = node.variable.name;
|
|
539
|
+
definedVars.add(varName);
|
|
540
|
+
varDefinitions.set(varName, node);
|
|
541
|
+
definitionIdentifiers.add(varName);
|
|
542
|
+
}
|
|
543
|
+
if (node.type === 'identifier' && node.name) {
|
|
544
|
+
const name = node.name;
|
|
545
|
+
if (!definitionIdentifiers.has(name))
|
|
546
|
+
usedVars.add(name);
|
|
547
|
+
}
|
|
548
|
+
},
|
|
549
|
+
});
|
|
550
|
+
visit(ast, visitor);
|
|
551
|
+
for (const varName of definedVars) {
|
|
552
|
+
if (!usedVars.has(varName)) {
|
|
553
|
+
const defNode = varDefinitions.get(varName);
|
|
554
|
+
deadCode.push({
|
|
555
|
+
type: 'unused-variable',
|
|
556
|
+
name: varName,
|
|
557
|
+
location: {
|
|
558
|
+
...(defNode.start !== undefined && { start: defNode.start }),
|
|
559
|
+
...(defNode.end !== undefined && { end: defNode.end }),
|
|
560
|
+
...(defNode.line !== undefined && { line: defNode.line }),
|
|
561
|
+
...(defNode.column !== undefined && { column: defNode.column }),
|
|
562
|
+
},
|
|
563
|
+
message: `Variable '${varName}' is defined but never used`,
|
|
564
|
+
});
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
let afterHalt = false;
|
|
568
|
+
const visitor2 = new ASTVisitor({
|
|
569
|
+
enter(node) {
|
|
570
|
+
if (node.name === 'halt' || node.name === 'throw') {
|
|
571
|
+
afterHalt = true;
|
|
572
|
+
}
|
|
573
|
+
else if (afterHalt && node.type === 'command') {
|
|
574
|
+
deadCode.push({
|
|
575
|
+
type: 'unreachable-code',
|
|
576
|
+
location: {
|
|
577
|
+
...(node.start !== undefined && { start: node.start }),
|
|
578
|
+
...(node.end !== undefined && { end: node.end }),
|
|
579
|
+
...(node.line !== undefined && { line: node.line }),
|
|
580
|
+
...(node.column !== undefined && { column: node.column }),
|
|
581
|
+
},
|
|
582
|
+
message: 'Unreachable code after halt/throw statement',
|
|
583
|
+
});
|
|
584
|
+
}
|
|
585
|
+
},
|
|
586
|
+
});
|
|
587
|
+
visit(ast, visitor2);
|
|
588
|
+
return deadCode;
|
|
589
|
+
}
|
|
590
|
+
function suggestOptimizations(ast) {
|
|
591
|
+
const suggestions = [];
|
|
592
|
+
suggestions.push(...suggestBatchOperations(ast));
|
|
593
|
+
suggestions.push(...suggestSimplifyConditionals(ast));
|
|
594
|
+
return suggestions;
|
|
595
|
+
}
|
|
596
|
+
function suggestBatchOperations(ast) {
|
|
597
|
+
const suggestions = [];
|
|
598
|
+
const visitor = new ASTVisitor({
|
|
599
|
+
enter(node) {
|
|
600
|
+
if (node.commands && Array.isArray(node.commands)) {
|
|
601
|
+
const commands = node.commands;
|
|
602
|
+
const sameCmdGroups = groupConsecutiveSameCommands(commands);
|
|
603
|
+
for (const group of sameCmdGroups) {
|
|
604
|
+
if (group.length >= 3 && group[0].name === 'add' && sameTarget(group)) {
|
|
605
|
+
suggestions.push({
|
|
606
|
+
type: 'batch-operations',
|
|
607
|
+
description: 'Group class operations',
|
|
608
|
+
suggestion: `Combine multiple add operations: add ${group.map((c) => c.args[0].value).join(' ')} to ${group[0].target?.name || 'target'}`,
|
|
609
|
+
impact: 'high',
|
|
610
|
+
confidence: 0.9,
|
|
611
|
+
});
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
},
|
|
616
|
+
});
|
|
617
|
+
visit(ast, visitor);
|
|
618
|
+
return suggestions;
|
|
619
|
+
}
|
|
620
|
+
function suggestSimplifyConditionals(ast) {
|
|
621
|
+
const suggestions = [];
|
|
622
|
+
const complexity = calculateComplexity(ast);
|
|
623
|
+
if (complexity.cognitive > 3 || complexity.cyclomatic > 3) {
|
|
624
|
+
suggestions.push({
|
|
625
|
+
type: 'simplification',
|
|
626
|
+
description: 'Simplify complex conditionals',
|
|
627
|
+
suggestion: 'Break down complex conditional logic into smaller, named functions',
|
|
628
|
+
impact: 'maintainability',
|
|
629
|
+
confidence: 0.8,
|
|
630
|
+
});
|
|
631
|
+
}
|
|
632
|
+
return suggestions;
|
|
633
|
+
}
|
|
634
|
+
function groupConsecutiveSameCommands(commands) {
|
|
635
|
+
const groups = [];
|
|
636
|
+
let currentGroup = [];
|
|
637
|
+
for (const cmd of commands) {
|
|
638
|
+
if (currentGroup.length === 0 || cmd.name === currentGroup[0].name) {
|
|
639
|
+
currentGroup.push(cmd);
|
|
640
|
+
}
|
|
641
|
+
else {
|
|
642
|
+
if (currentGroup.length > 0)
|
|
643
|
+
groups.push(currentGroup);
|
|
644
|
+
currentGroup = [cmd];
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
if (currentGroup.length > 0)
|
|
648
|
+
groups.push(currentGroup);
|
|
649
|
+
return groups;
|
|
650
|
+
}
|
|
651
|
+
function sameTarget(commands) {
|
|
652
|
+
if (commands.length === 0)
|
|
653
|
+
return true;
|
|
654
|
+
const firstTarget = commands[0].target?.name;
|
|
655
|
+
return commands.every((cmd) => cmd.target?.name === firstTarget);
|
|
656
|
+
}
|
|
657
|
+
function analyzePatterns(ast) {
|
|
658
|
+
const patterns = [];
|
|
659
|
+
if (ast.type === 'eventHandler') {
|
|
660
|
+
patterns.push({
|
|
661
|
+
type: 'event-handler',
|
|
662
|
+
pattern: 'event-handler',
|
|
663
|
+
node: ast,
|
|
664
|
+
bindings: { event: ast.event },
|
|
665
|
+
confidence: 0.9,
|
|
666
|
+
suggestion: 'Consider using event delegation for multiple similar handlers',
|
|
667
|
+
});
|
|
668
|
+
}
|
|
669
|
+
const visitor = new ASTVisitor({
|
|
670
|
+
enter(node) {
|
|
671
|
+
if (node.commands && Array.isArray(node.commands)) {
|
|
672
|
+
const commands = node.commands;
|
|
673
|
+
const toggleCommands = commands.filter((cmd) => cmd.name === 'toggle');
|
|
674
|
+
if (toggleCommands.length >= 2) {
|
|
675
|
+
patterns.push({
|
|
676
|
+
type: 'toggle-pair',
|
|
677
|
+
pattern: 'toggle-pair',
|
|
678
|
+
node,
|
|
679
|
+
bindings: {
|
|
680
|
+
count: toggleCommands.length,
|
|
681
|
+
targets: toggleCommands.map((cmd) => cmd.target?.name || cmd.target?.value),
|
|
682
|
+
},
|
|
683
|
+
confidence: 0.85,
|
|
684
|
+
suggestion: 'Consider using a single toggle with multiple targets',
|
|
685
|
+
});
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
},
|
|
689
|
+
});
|
|
690
|
+
visit(ast, visitor);
|
|
691
|
+
return patterns;
|
|
692
|
+
}
|
|
693
|
+
function analyzeMetrics(ast) {
|
|
694
|
+
const complexity = calculateComplexity(ast);
|
|
695
|
+
const smells = detectCodeSmells(ast);
|
|
696
|
+
const patterns = analyzePatterns(ast);
|
|
697
|
+
const dependencies = analyzeDependencies(ast);
|
|
698
|
+
const maintainabilityIndex = calculateMaintainabilityIndex(complexity, smells);
|
|
699
|
+
const readabilityScore = calculateReadabilityScore(ast, smells);
|
|
700
|
+
return { complexity, smells, patterns, dependencies, maintainabilityIndex, readabilityScore };
|
|
701
|
+
}
|
|
702
|
+
function calculateMaintainabilityIndex(complexity, smells) {
|
|
703
|
+
let score = 100;
|
|
704
|
+
score -= Math.min(complexity.cyclomatic * 2, 30);
|
|
705
|
+
score -= Math.min(complexity.cognitive, 20);
|
|
706
|
+
const highSeveritySmells = smells.filter(s => s.severity === 'high').length;
|
|
707
|
+
const mediumSeveritySmells = smells.filter(s => s.severity === 'medium').length;
|
|
708
|
+
score -= highSeveritySmells * 15;
|
|
709
|
+
score -= mediumSeveritySmells * 8;
|
|
710
|
+
return Math.max(0, Math.min(100, score));
|
|
711
|
+
}
|
|
712
|
+
function calculateReadabilityScore(ast, smells) {
|
|
713
|
+
let score = 100;
|
|
714
|
+
const depth = measureDepth(ast);
|
|
715
|
+
const excessDepth = Math.max(0, depth - 3);
|
|
716
|
+
score -= Math.min(excessDepth * 5, 25);
|
|
717
|
+
const longChains = smells.filter(s => s.type === 'long-command-chain').length;
|
|
718
|
+
score -= longChains * 10;
|
|
719
|
+
const complexConditions = smells.filter(s => s.type === 'complex-condition').length;
|
|
720
|
+
score -= complexConditions * 12;
|
|
721
|
+
return Math.max(0, Math.min(100, score));
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
function generate(ast, options = {}) {
|
|
725
|
+
const opts = {
|
|
726
|
+
minify: false,
|
|
727
|
+
indentation: ' ',
|
|
728
|
+
preserveRaw: true,
|
|
729
|
+
_indentLevel: 0,
|
|
730
|
+
...options,
|
|
731
|
+
};
|
|
732
|
+
return generateNode(ast, opts);
|
|
733
|
+
}
|
|
734
|
+
function generateWithMetadata(ast, options = {}) {
|
|
735
|
+
let nodeCount = 0;
|
|
736
|
+
const opts = {
|
|
737
|
+
minify: false,
|
|
738
|
+
indentation: ' ',
|
|
739
|
+
preserveRaw: true,
|
|
740
|
+
_indentLevel: 0,
|
|
741
|
+
...options,
|
|
742
|
+
};
|
|
743
|
+
const code = generateNode(ast, opts);
|
|
744
|
+
countNodes(ast, () => nodeCount++);
|
|
745
|
+
return { code, nodeCount };
|
|
746
|
+
}
|
|
747
|
+
function generateNode(node, opts) {
|
|
748
|
+
if (!node || typeof node !== 'object')
|
|
749
|
+
return '';
|
|
750
|
+
if (opts.preserveRaw && node.raw && typeof node.raw === 'string') {
|
|
751
|
+
return node.raw;
|
|
752
|
+
}
|
|
753
|
+
switch (node.type) {
|
|
754
|
+
case 'program':
|
|
755
|
+
return generateProgram(node, opts);
|
|
756
|
+
case 'eventHandler':
|
|
757
|
+
return generateEventHandler(node, opts);
|
|
758
|
+
case 'command':
|
|
759
|
+
return generateCommand(node, opts);
|
|
760
|
+
case 'conditional':
|
|
761
|
+
return generateConditional(node, opts);
|
|
762
|
+
case 'behavior':
|
|
763
|
+
return generateBehavior(node, opts);
|
|
764
|
+
case 'function':
|
|
765
|
+
case 'def':
|
|
766
|
+
return generateFunction(node, opts);
|
|
767
|
+
case 'selector':
|
|
768
|
+
return generateSelector(node);
|
|
769
|
+
case 'literal':
|
|
770
|
+
return generateLiteral(node);
|
|
771
|
+
case 'identifier':
|
|
772
|
+
return generateIdentifier(node);
|
|
773
|
+
case 'binaryExpression':
|
|
774
|
+
return generateBinaryExpression(node, opts);
|
|
775
|
+
case 'logicalExpression':
|
|
776
|
+
return generateLogicalExpression(node, opts);
|
|
777
|
+
case 'unaryExpression':
|
|
778
|
+
return generateUnaryExpression(node, opts);
|
|
779
|
+
case 'memberExpression':
|
|
780
|
+
return generateMemberExpression(node, opts);
|
|
781
|
+
case 'possessiveExpression':
|
|
782
|
+
return generatePossessiveExpression(node, opts);
|
|
783
|
+
case 'callExpression':
|
|
784
|
+
return generateCallExpression(node, opts);
|
|
785
|
+
case 'returnStatement':
|
|
786
|
+
return generateReturnStatement(node, opts);
|
|
787
|
+
default:
|
|
788
|
+
return generateFallback(node);
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
function generateProgram(node, opts) {
|
|
792
|
+
const features = node.features || [];
|
|
793
|
+
const separator = opts.minify ? ' ' : '\n\n';
|
|
794
|
+
return features
|
|
795
|
+
.map((f) => generateNode(f, opts))
|
|
796
|
+
.filter(Boolean)
|
|
797
|
+
.join(separator);
|
|
798
|
+
}
|
|
799
|
+
function generateEventHandler(node, opts) {
|
|
800
|
+
const data = node;
|
|
801
|
+
const parts = ['on'];
|
|
802
|
+
if (data.events && data.events.length > 1) {
|
|
803
|
+
parts.push(data.events.join(' or '));
|
|
804
|
+
}
|
|
805
|
+
else {
|
|
806
|
+
parts.push(data.event || 'click');
|
|
807
|
+
}
|
|
808
|
+
if (data.selector && data.selector !== 'me')
|
|
809
|
+
parts.push('from', data.selector);
|
|
810
|
+
if (data.condition)
|
|
811
|
+
parts.push(`[${generateNode(data.condition, opts)}]`);
|
|
812
|
+
const commands = data.commands || [];
|
|
813
|
+
if (commands.length > 0) {
|
|
814
|
+
const cmdSeparator = opts.minify ? ' then ' : '\n' + indent(opts);
|
|
815
|
+
const commandsStr = commands
|
|
816
|
+
.map((cmd) => generateNode(cmd, { ...opts, _indentLevel: (opts._indentLevel || 0) + 1 }))
|
|
817
|
+
.join(cmdSeparator);
|
|
818
|
+
if (opts.minify) {
|
|
819
|
+
parts.push(commandsStr);
|
|
820
|
+
}
|
|
821
|
+
else {
|
|
822
|
+
parts.push('\n' + indent(opts) + commandsStr);
|
|
823
|
+
}
|
|
824
|
+
}
|
|
825
|
+
return parts.join(' ');
|
|
826
|
+
}
|
|
827
|
+
function generateBehavior(node, opts) {
|
|
828
|
+
const data = node;
|
|
829
|
+
const parts = ['behavior'];
|
|
830
|
+
const params = data.parameters || [];
|
|
831
|
+
parts.push(params.length > 0 ? `${data.name || 'unnamed'}(${params.join(', ')})` : data.name || 'unnamed');
|
|
832
|
+
const body = data.body || data.eventHandlers || [];
|
|
833
|
+
if (body.length > 0) {
|
|
834
|
+
const bodyStr = body
|
|
835
|
+
.map((item) => generateNode(item, { ...opts, _indentLevel: (opts._indentLevel || 0) + 1 }))
|
|
836
|
+
.join(opts.minify ? ' ' : '\n' + indent(opts));
|
|
837
|
+
if (opts.minify) {
|
|
838
|
+
parts.push(bodyStr, 'end');
|
|
839
|
+
}
|
|
840
|
+
else {
|
|
841
|
+
parts.push('\n' + indent(opts) + bodyStr + '\n' + 'end');
|
|
842
|
+
}
|
|
843
|
+
}
|
|
844
|
+
else {
|
|
845
|
+
parts.push('end');
|
|
846
|
+
}
|
|
847
|
+
return parts.join(' ');
|
|
848
|
+
}
|
|
849
|
+
function generateFunction(node, opts) {
|
|
850
|
+
const data = node;
|
|
851
|
+
const parts = ['def'];
|
|
852
|
+
const params = data.parameters || data.params || [];
|
|
853
|
+
parts.push(params.length > 0 ? `${data.name || 'unnamed'}(${params.join(', ')})` : data.name || 'unnamed');
|
|
854
|
+
if (data.body) {
|
|
855
|
+
const bodyStr = generateNode(data.body, {
|
|
856
|
+
...opts,
|
|
857
|
+
_indentLevel: (opts._indentLevel || 0) + 1,
|
|
858
|
+
});
|
|
859
|
+
if (opts.minify) {
|
|
860
|
+
parts.push(bodyStr, 'end');
|
|
861
|
+
}
|
|
862
|
+
else {
|
|
863
|
+
parts.push('\n' + indent(opts) + bodyStr + '\n' + 'end');
|
|
864
|
+
}
|
|
865
|
+
}
|
|
866
|
+
else {
|
|
867
|
+
parts.push('end');
|
|
868
|
+
}
|
|
869
|
+
return parts.join(' ');
|
|
870
|
+
}
|
|
871
|
+
function generateCommand(node, opts = {}) {
|
|
872
|
+
const data = node;
|
|
873
|
+
const parts = [data.name || 'unknown'];
|
|
874
|
+
const args = data.args || [];
|
|
875
|
+
for (const arg of args)
|
|
876
|
+
parts.push(generateNode(arg, opts));
|
|
877
|
+
if (data.modifiers) {
|
|
878
|
+
for (const [key, value] of Object.entries(data.modifiers)) {
|
|
879
|
+
if (value)
|
|
880
|
+
parts.push(key, generateNode(value, opts));
|
|
881
|
+
}
|
|
882
|
+
}
|
|
883
|
+
if (data.target) {
|
|
884
|
+
parts.push(getTargetPreposition(data.name), generateNode(data.target, opts));
|
|
885
|
+
}
|
|
886
|
+
if (data.implicitTarget)
|
|
887
|
+
parts.push(generateNode(data.implicitTarget, opts));
|
|
888
|
+
return parts.join(' ');
|
|
889
|
+
}
|
|
890
|
+
function generateConditional(node, opts) {
|
|
891
|
+
const data = node;
|
|
892
|
+
const parts = ['if'];
|
|
893
|
+
if (data.condition)
|
|
894
|
+
parts.push(generateNode(data.condition, opts));
|
|
895
|
+
parts.push('then');
|
|
896
|
+
if (data.then)
|
|
897
|
+
parts.push(generateNode(data.then, opts));
|
|
898
|
+
if (data.else)
|
|
899
|
+
parts.push('else', generateNode(data.else, opts));
|
|
900
|
+
parts.push('end');
|
|
901
|
+
return parts.join(' ');
|
|
902
|
+
}
|
|
903
|
+
function generateReturnStatement(node, opts) {
|
|
904
|
+
const data = node;
|
|
905
|
+
return data.argument ? `return ${generateNode(data.argument, opts)}` : 'return';
|
|
906
|
+
}
|
|
907
|
+
function generateExpression(node, opts = {}) {
|
|
908
|
+
return generateNode(node, opts);
|
|
909
|
+
}
|
|
910
|
+
function generateSelector(node) {
|
|
911
|
+
return node.value || '';
|
|
912
|
+
}
|
|
913
|
+
function generateLiteral(node) {
|
|
914
|
+
const value = node.value;
|
|
915
|
+
if (typeof value === 'string')
|
|
916
|
+
return `'${escapeString(value)}'`;
|
|
917
|
+
if (typeof value === 'boolean')
|
|
918
|
+
return value ? 'true' : 'false';
|
|
919
|
+
if (value === null)
|
|
920
|
+
return 'null';
|
|
921
|
+
if (value === undefined)
|
|
922
|
+
return 'undefined';
|
|
923
|
+
return String(value);
|
|
924
|
+
}
|
|
925
|
+
function generateIdentifier(node) {
|
|
926
|
+
return node.name || '';
|
|
927
|
+
}
|
|
928
|
+
function generateBinaryExpression(node, opts) {
|
|
929
|
+
const data = node;
|
|
930
|
+
return `${generateNode(data.left, opts)} ${data.operator || '+'} ${generateNode(data.right, opts)}`;
|
|
931
|
+
}
|
|
932
|
+
function generateLogicalExpression(node, opts) {
|
|
933
|
+
const data = node;
|
|
934
|
+
return `${generateNode(data.left, opts)} ${data.operator || 'and'} ${generateNode(data.right, opts)}`;
|
|
935
|
+
}
|
|
936
|
+
function generateUnaryExpression(node, opts) {
|
|
937
|
+
const data = node;
|
|
938
|
+
const argument = generateNode(data.argument, opts);
|
|
939
|
+
const operator = data.operator || 'not';
|
|
940
|
+
return data.prefix !== false ? `${operator} ${argument}` : `${argument} ${operator}`;
|
|
941
|
+
}
|
|
942
|
+
function generateMemberExpression(node, opts) {
|
|
943
|
+
const data = node;
|
|
944
|
+
const object = data.object ? generateNode(data.object, opts) : '';
|
|
945
|
+
const property = data.property ? generateNode(data.property, opts) : '';
|
|
946
|
+
if (data.computed)
|
|
947
|
+
return `${object}[${property}]`;
|
|
948
|
+
return object ? `${object}.${property}` : property;
|
|
949
|
+
}
|
|
950
|
+
function generatePossessiveExpression(node, opts) {
|
|
951
|
+
const data = node;
|
|
952
|
+
return `${generateNode(data.object, opts)}'s ${generateNode(data.property, opts)}`;
|
|
953
|
+
}
|
|
954
|
+
function generateCallExpression(node, opts) {
|
|
955
|
+
const data = node;
|
|
956
|
+
const callee = data.callee ? generateNode(data.callee, opts) : 'call';
|
|
957
|
+
const args = (data.arguments || data.args || [])
|
|
958
|
+
.map((arg) => generateNode(arg, opts))
|
|
959
|
+
.join(', ');
|
|
960
|
+
return `${callee}(${args})`;
|
|
961
|
+
}
|
|
962
|
+
function generateFallback(node) {
|
|
963
|
+
const data = node;
|
|
964
|
+
if (data.value !== undefined)
|
|
965
|
+
return String(data.value);
|
|
966
|
+
if (data.name)
|
|
967
|
+
return data.name;
|
|
968
|
+
return '';
|
|
969
|
+
}
|
|
970
|
+
function indent(opts) {
|
|
971
|
+
if (opts.minify)
|
|
972
|
+
return '';
|
|
973
|
+
return (opts.indentation || ' ').repeat(opts._indentLevel || 0);
|
|
974
|
+
}
|
|
975
|
+
function escapeString(str) {
|
|
976
|
+
return str
|
|
977
|
+
.replace(/\\/g, '\\\\')
|
|
978
|
+
.replace(/'/g, "\\'")
|
|
979
|
+
.replace(/\n/g, '\\n')
|
|
980
|
+
.replace(/\r/g, '\\r')
|
|
981
|
+
.replace(/\t/g, '\\t');
|
|
982
|
+
}
|
|
983
|
+
function getTargetPreposition(commandName) {
|
|
984
|
+
switch (commandName) {
|
|
985
|
+
case 'put':
|
|
986
|
+
return 'into';
|
|
987
|
+
case 'add':
|
|
988
|
+
case 'remove':
|
|
989
|
+
return 'from';
|
|
990
|
+
case 'toggle':
|
|
991
|
+
case 'set':
|
|
992
|
+
return 'on';
|
|
993
|
+
default:
|
|
994
|
+
return 'to';
|
|
995
|
+
}
|
|
996
|
+
}
|
|
997
|
+
function countNodes(node, callback) {
|
|
998
|
+
if (!node || typeof node !== 'object')
|
|
999
|
+
return;
|
|
1000
|
+
callback();
|
|
1001
|
+
for (const value of Object.values(node)) {
|
|
1002
|
+
if (Array.isArray(value)) {
|
|
1003
|
+
for (const item of value) {
|
|
1004
|
+
if (item && typeof item === 'object' && item.type)
|
|
1005
|
+
countNodes(item, callback);
|
|
1006
|
+
}
|
|
1007
|
+
}
|
|
1008
|
+
else if (value && typeof value === 'object' && value.type) {
|
|
1009
|
+
countNodes(value, callback);
|
|
1010
|
+
}
|
|
1011
|
+
}
|
|
1012
|
+
}
|
|
1013
|
+
function minify(ast) {
|
|
1014
|
+
return generate(ast, { minify: true });
|
|
1015
|
+
}
|
|
1016
|
+
function format(ast, indentation = ' ') {
|
|
1017
|
+
return generate(ast, { minify: false, indentation });
|
|
1018
|
+
}
|
|
1019
|
+
|
|
1020
|
+
function generateDocumentation(ast) {
|
|
1021
|
+
const eventHandlers = documentEventHandlers(ast);
|
|
1022
|
+
const behaviors = documentBehaviors(ast);
|
|
1023
|
+
const functions = documentFunctions(ast);
|
|
1024
|
+
const metrics = calculateCodeMetrics(ast);
|
|
1025
|
+
let title = 'Hyperscript Documentation';
|
|
1026
|
+
const firstBehavior = behaviors[0];
|
|
1027
|
+
if (firstBehavior) {
|
|
1028
|
+
title = `${firstBehavior.name} Behavior Documentation`;
|
|
1029
|
+
}
|
|
1030
|
+
else if (eventHandlers.length > 0) {
|
|
1031
|
+
const events = eventHandlers.map(h => h.event).filter((e, i, arr) => arr.indexOf(e) === i);
|
|
1032
|
+
title = `Event Handlers: ${events.join(', ')}`;
|
|
1033
|
+
}
|
|
1034
|
+
const description = generateOverallDescription(eventHandlers, behaviors, functions);
|
|
1035
|
+
return {
|
|
1036
|
+
title,
|
|
1037
|
+
description,
|
|
1038
|
+
eventHandlers,
|
|
1039
|
+
behaviors,
|
|
1040
|
+
functions,
|
|
1041
|
+
metrics,
|
|
1042
|
+
generatedAt: new Date().toISOString(),
|
|
1043
|
+
};
|
|
1044
|
+
}
|
|
1045
|
+
function generateMarkdown(ast, options = {}) {
|
|
1046
|
+
const { includeSource = true, includeMetrics = true, includeToc = true, headingLevel = 1, } = options;
|
|
1047
|
+
const doc = generateDocumentation(ast);
|
|
1048
|
+
const h = (level) => '#'.repeat(headingLevel + level - 1);
|
|
1049
|
+
const lines = [];
|
|
1050
|
+
lines.push(`${h(1)} ${doc.title}`, '', doc.description, '');
|
|
1051
|
+
if (includeToc && doc.eventHandlers.length + doc.behaviors.length + doc.functions.length > 2) {
|
|
1052
|
+
lines.push(`${h(2)} Table of Contents`, '');
|
|
1053
|
+
if (doc.eventHandlers.length > 0) {
|
|
1054
|
+
lines.push('- [Event Handlers](#event-handlers)');
|
|
1055
|
+
for (const handler of doc.eventHandlers) {
|
|
1056
|
+
lines.push(` - [${handler.event}](#${slugify(handler.event)})`);
|
|
1057
|
+
}
|
|
1058
|
+
}
|
|
1059
|
+
if (doc.behaviors.length > 0) {
|
|
1060
|
+
lines.push('- [Behaviors](#behaviors)');
|
|
1061
|
+
for (const behavior of doc.behaviors) {
|
|
1062
|
+
lines.push(` - [${behavior.name}](#${slugify(behavior.name)})`);
|
|
1063
|
+
}
|
|
1064
|
+
}
|
|
1065
|
+
if (doc.functions.length > 0) {
|
|
1066
|
+
lines.push('- [Functions](#functions)');
|
|
1067
|
+
for (const fn of doc.functions) {
|
|
1068
|
+
lines.push(` - [${fn.name}](#${slugify(fn.name)})`);
|
|
1069
|
+
}
|
|
1070
|
+
}
|
|
1071
|
+
if (includeMetrics)
|
|
1072
|
+
lines.push('- [Metrics](#metrics)');
|
|
1073
|
+
lines.push('');
|
|
1074
|
+
}
|
|
1075
|
+
if (doc.eventHandlers.length > 0) {
|
|
1076
|
+
lines.push(`${h(2)} Event Handlers`, '');
|
|
1077
|
+
for (const handler of doc.eventHandlers) {
|
|
1078
|
+
lines.push(`${h(3)} ${handler.event}`, '');
|
|
1079
|
+
if (handler.selector)
|
|
1080
|
+
lines.push(`**Source:** \`${handler.selector}\``, '');
|
|
1081
|
+
lines.push(handler.description, '');
|
|
1082
|
+
if (handler.commands.length > 0) {
|
|
1083
|
+
lines.push('**Commands:**', '');
|
|
1084
|
+
for (const cmd of handler.commands)
|
|
1085
|
+
lines.push(`- \`${cmd.name}\`: ${cmd.description}`);
|
|
1086
|
+
lines.push('');
|
|
1087
|
+
}
|
|
1088
|
+
if (includeSource) {
|
|
1089
|
+
lines.push('**Source Code:**', '', '```hyperscript', handler.source, '```', '');
|
|
1090
|
+
}
|
|
1091
|
+
}
|
|
1092
|
+
}
|
|
1093
|
+
if (doc.behaviors.length > 0) {
|
|
1094
|
+
lines.push(`${h(2)} Behaviors`, '');
|
|
1095
|
+
for (const behavior of doc.behaviors) {
|
|
1096
|
+
const params = behavior.parameters.length > 0 ? `(${behavior.parameters.join(', ')})` : '';
|
|
1097
|
+
lines.push(`${h(3)} ${behavior.name}${params}`, '', behavior.description, '');
|
|
1098
|
+
if (behavior.parameters.length > 0) {
|
|
1099
|
+
lines.push('**Parameters:**', '');
|
|
1100
|
+
for (const param of behavior.parameters)
|
|
1101
|
+
lines.push(`- \`${param}\``);
|
|
1102
|
+
lines.push('');
|
|
1103
|
+
}
|
|
1104
|
+
if (behavior.eventHandlers.length > 0) {
|
|
1105
|
+
lines.push('**Event Handlers:**', '');
|
|
1106
|
+
for (const handler of behavior.eventHandlers)
|
|
1107
|
+
lines.push(`- \`on ${handler.event}\`: ${handler.description}`);
|
|
1108
|
+
lines.push('');
|
|
1109
|
+
}
|
|
1110
|
+
if (includeSource) {
|
|
1111
|
+
lines.push('**Source Code:**', '', '```hyperscript', behavior.source, '```', '');
|
|
1112
|
+
}
|
|
1113
|
+
}
|
|
1114
|
+
}
|
|
1115
|
+
if (doc.functions.length > 0) {
|
|
1116
|
+
lines.push(`${h(2)} Functions`, '');
|
|
1117
|
+
for (const fn of doc.functions) {
|
|
1118
|
+
const params = fn.parameters.length > 0 ? `(${fn.parameters.join(', ')})` : '()';
|
|
1119
|
+
lines.push(`${h(3)} ${fn.name}${params}`, '', fn.description, '');
|
|
1120
|
+
if (fn.parameters.length > 0) {
|
|
1121
|
+
lines.push('**Parameters:**', '');
|
|
1122
|
+
for (const param of fn.parameters)
|
|
1123
|
+
lines.push(`- \`${param}\``);
|
|
1124
|
+
lines.push('');
|
|
1125
|
+
}
|
|
1126
|
+
if (fn.returns)
|
|
1127
|
+
lines.push(`**Returns:** ${fn.returns}`, '');
|
|
1128
|
+
if (includeSource) {
|
|
1129
|
+
lines.push('**Source Code:**', '', '```hyperscript', fn.source, '```', '');
|
|
1130
|
+
}
|
|
1131
|
+
}
|
|
1132
|
+
}
|
|
1133
|
+
if (includeMetrics) {
|
|
1134
|
+
lines.push(`${h(2)} Metrics`, '', '| Metric | Value |', '|--------|-------|', `| Event Handlers | ${doc.metrics.eventHandlerCount} |`, `| Behaviors | ${doc.metrics.behaviorCount} |`, `| Functions | ${doc.metrics.functionCount} |`, `| Commands | ${doc.metrics.commandCount} |`, `| Complexity | ${doc.metrics.complexity} |`, `| Cyclomatic Complexity | ${doc.metrics.cyclomaticComplexity} |`, '', `*Generated: ${doc.generatedAt}*`);
|
|
1135
|
+
}
|
|
1136
|
+
return lines.join('\n');
|
|
1137
|
+
}
|
|
1138
|
+
function generateHTML(ast, options = {}) {
|
|
1139
|
+
const doc = generateDocumentation(ast);
|
|
1140
|
+
const title = options.title || doc.title;
|
|
1141
|
+
const sections = [];
|
|
1142
|
+
if (doc.eventHandlers.length > 0) {
|
|
1143
|
+
sections.push(`
|
|
1144
|
+
<section class="event-handlers">
|
|
1145
|
+
<h2>Event Handlers</h2>
|
|
1146
|
+
${doc.eventHandlers
|
|
1147
|
+
.map(h => `
|
|
1148
|
+
<article class="event-handler">
|
|
1149
|
+
<h3>on ${escapeHtml(h.event)}</h3>
|
|
1150
|
+
${h.selector ? `<p class="selector">Source: <code>${escapeHtml(h.selector)}</code></p>` : ''}
|
|
1151
|
+
<p>${escapeHtml(h.description)}</p>
|
|
1152
|
+
<ul class="commands">
|
|
1153
|
+
${h.commands.map(c => `<li><code>${escapeHtml(c.name)}</code>: ${escapeHtml(c.description)}</li>`).join('')}
|
|
1154
|
+
</ul>
|
|
1155
|
+
<pre><code class="language-hyperscript">${escapeHtml(h.source)}</code></pre>
|
|
1156
|
+
</article>
|
|
1157
|
+
`)
|
|
1158
|
+
.join('')}
|
|
1159
|
+
</section>`);
|
|
1160
|
+
}
|
|
1161
|
+
if (doc.behaviors.length > 0) {
|
|
1162
|
+
sections.push(`
|
|
1163
|
+
<section class="behaviors">
|
|
1164
|
+
<h2>Behaviors</h2>
|
|
1165
|
+
${doc.behaviors
|
|
1166
|
+
.map(b => `
|
|
1167
|
+
<article class="behavior">
|
|
1168
|
+
<h3>${escapeHtml(b.name)}${b.parameters.length ? `(${escapeHtml(b.parameters.join(', '))})` : ''}</h3>
|
|
1169
|
+
<p>${escapeHtml(b.description)}</p>
|
|
1170
|
+
${b.parameters.length ? `<h4>Parameters</h4><ul>${b.parameters.map(p => `<li><code>${escapeHtml(p)}</code></li>`).join('')}</ul>` : ''}
|
|
1171
|
+
<pre><code class="language-hyperscript">${escapeHtml(b.source)}</code></pre>
|
|
1172
|
+
</article>
|
|
1173
|
+
`)
|
|
1174
|
+
.join('')}
|
|
1175
|
+
</section>`);
|
|
1176
|
+
}
|
|
1177
|
+
if (doc.functions.length > 0) {
|
|
1178
|
+
sections.push(`
|
|
1179
|
+
<section class="functions">
|
|
1180
|
+
<h2>Functions</h2>
|
|
1181
|
+
${doc.functions
|
|
1182
|
+
.map(f => `
|
|
1183
|
+
<article class="function">
|
|
1184
|
+
<h3>${escapeHtml(f.name)}(${escapeHtml(f.parameters.join(', '))})</h3>
|
|
1185
|
+
<p>${escapeHtml(f.description)}</p>
|
|
1186
|
+
${f.returns ? `<p><strong>Returns:</strong> ${escapeHtml(f.returns)}</p>` : ''}
|
|
1187
|
+
<pre><code class="language-hyperscript">${escapeHtml(f.source)}</code></pre>
|
|
1188
|
+
</article>
|
|
1189
|
+
`)
|
|
1190
|
+
.join('')}
|
|
1191
|
+
</section>`);
|
|
1192
|
+
}
|
|
1193
|
+
sections.push(`
|
|
1194
|
+
<section class="metrics">
|
|
1195
|
+
<h2>Metrics</h2>
|
|
1196
|
+
<table>
|
|
1197
|
+
<tr><th>Metric</th><th>Value</th></tr>
|
|
1198
|
+
<tr><td>Event Handlers</td><td>${doc.metrics.eventHandlerCount}</td></tr>
|
|
1199
|
+
<tr><td>Behaviors</td><td>${doc.metrics.behaviorCount}</td></tr>
|
|
1200
|
+
<tr><td>Functions</td><td>${doc.metrics.functionCount}</td></tr>
|
|
1201
|
+
<tr><td>Commands</td><td>${doc.metrics.commandCount}</td></tr>
|
|
1202
|
+
<tr><td>Complexity</td><td>${doc.metrics.complexity}</td></tr>
|
|
1203
|
+
</table>
|
|
1204
|
+
<p class="timestamp">Generated: ${doc.generatedAt}</p>
|
|
1205
|
+
</section>`);
|
|
1206
|
+
return `<!DOCTYPE html>
|
|
1207
|
+
<html lang="en">
|
|
1208
|
+
<head>
|
|
1209
|
+
<meta charset="UTF-8">
|
|
1210
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
1211
|
+
<title>${escapeHtml(title)}</title>
|
|
1212
|
+
<style>
|
|
1213
|
+
body { font-family: system-ui, -apple-system, sans-serif; max-width: 900px; margin: 0 auto; padding: 2rem; }
|
|
1214
|
+
h1 { border-bottom: 2px solid #333; padding-bottom: 0.5rem; }
|
|
1215
|
+
h2 { color: #2563eb; margin-top: 2rem; }
|
|
1216
|
+
h3 { color: #374151; }
|
|
1217
|
+
pre { background: #f3f4f6; padding: 1rem; border-radius: 0.5rem; overflow-x: auto; }
|
|
1218
|
+
code { font-family: 'SF Mono', Monaco, monospace; }
|
|
1219
|
+
.selector { color: #6b7280; font-size: 0.9rem; }
|
|
1220
|
+
.commands { list-style-type: none; padding-left: 1rem; }
|
|
1221
|
+
.commands li { margin: 0.5rem 0; }
|
|
1222
|
+
table { border-collapse: collapse; width: 100%; }
|
|
1223
|
+
th, td { border: 1px solid #d1d5db; padding: 0.5rem; text-align: left; }
|
|
1224
|
+
th { background: #f9fafb; }
|
|
1225
|
+
.timestamp { color: #9ca3af; font-size: 0.8rem; }
|
|
1226
|
+
article { margin: 1.5rem 0; padding: 1rem; border: 1px solid #e5e7eb; border-radius: 0.5rem; }
|
|
1227
|
+
</style>
|
|
1228
|
+
</head>
|
|
1229
|
+
<body>
|
|
1230
|
+
<h1>${escapeHtml(title)}</h1>
|
|
1231
|
+
<p>${escapeHtml(doc.description)}</p>
|
|
1232
|
+
${sections.join('\n')}
|
|
1233
|
+
</body>
|
|
1234
|
+
</html>`;
|
|
1235
|
+
}
|
|
1236
|
+
function generateJSON(ast) {
|
|
1237
|
+
return JSON.stringify(generateDocumentation(ast), null, 2);
|
|
1238
|
+
}
|
|
1239
|
+
function documentEventHandlers(ast) {
|
|
1240
|
+
const handlers = findNodes(ast, node => node.type === 'eventHandler');
|
|
1241
|
+
return handlers.map(handler => {
|
|
1242
|
+
const data = handler;
|
|
1243
|
+
return {
|
|
1244
|
+
event: data.event || 'unknown',
|
|
1245
|
+
selector: data.selector,
|
|
1246
|
+
description: generateEventHandlerDescription(data),
|
|
1247
|
+
commands: documentCommands(data.commands || []),
|
|
1248
|
+
source: generate(handler),
|
|
1249
|
+
};
|
|
1250
|
+
});
|
|
1251
|
+
}
|
|
1252
|
+
function documentBehaviors(ast) {
|
|
1253
|
+
const behaviors = findNodes(ast, node => node.type === 'behavior');
|
|
1254
|
+
return behaviors.map(behavior => {
|
|
1255
|
+
const data = behavior;
|
|
1256
|
+
return {
|
|
1257
|
+
name: data.name || 'unnamed',
|
|
1258
|
+
parameters: data.parameters || [],
|
|
1259
|
+
description: generateBehaviorDescription(data),
|
|
1260
|
+
eventHandlers: documentEventHandlers(behavior),
|
|
1261
|
+
source: generate(behavior),
|
|
1262
|
+
};
|
|
1263
|
+
});
|
|
1264
|
+
}
|
|
1265
|
+
function documentFunctions(ast) {
|
|
1266
|
+
const functions = findNodes(ast, node => node.type === 'function' || node.type === 'def');
|
|
1267
|
+
return functions.map(fn => {
|
|
1268
|
+
const data = fn;
|
|
1269
|
+
return {
|
|
1270
|
+
name: data.name || 'unnamed',
|
|
1271
|
+
parameters: data.parameters || data.params || [],
|
|
1272
|
+
returns: inferReturnType(data),
|
|
1273
|
+
description: generateFunctionDescription(data),
|
|
1274
|
+
source: generate(fn),
|
|
1275
|
+
};
|
|
1276
|
+
});
|
|
1277
|
+
}
|
|
1278
|
+
function documentCommands(commands) {
|
|
1279
|
+
return commands.map(cmd => {
|
|
1280
|
+
const data = cmd;
|
|
1281
|
+
return {
|
|
1282
|
+
name: data.name || 'unknown',
|
|
1283
|
+
description: getCommandDescription(data.name),
|
|
1284
|
+
target: data.target ? generate(data.target) : undefined,
|
|
1285
|
+
};
|
|
1286
|
+
});
|
|
1287
|
+
}
|
|
1288
|
+
function generateEventHandlerDescription(handler) {
|
|
1289
|
+
const event = handler.event || 'event';
|
|
1290
|
+
const selector = handler.selector;
|
|
1291
|
+
const commandCount = (handler.commands || []).length;
|
|
1292
|
+
let desc = `Handles the '${event}' event`;
|
|
1293
|
+
if (selector)
|
|
1294
|
+
desc += ` from elements matching '${selector}'`;
|
|
1295
|
+
desc += `. Executes ${commandCount} command${commandCount !== 1 ? 's' : ''}.`;
|
|
1296
|
+
return desc;
|
|
1297
|
+
}
|
|
1298
|
+
function generateBehaviorDescription(behavior) {
|
|
1299
|
+
const name = behavior.name || 'This behavior';
|
|
1300
|
+
const params = behavior.parameters || [];
|
|
1301
|
+
const body = behavior.body || behavior.eventHandlers || [];
|
|
1302
|
+
let desc = `${name} is a reusable behavior`;
|
|
1303
|
+
if (params.length > 0)
|
|
1304
|
+
desc += ` that takes ${params.length} parameter${params.length !== 1 ? 's' : ''} (${params.join(', ')})`;
|
|
1305
|
+
if (body.length > 0)
|
|
1306
|
+
desc += ` and defines ${body.length} event handler${body.length !== 1 ? 's' : ''}`;
|
|
1307
|
+
desc += '.';
|
|
1308
|
+
return desc;
|
|
1309
|
+
}
|
|
1310
|
+
function generateFunctionDescription(fn) {
|
|
1311
|
+
const name = fn.name || 'This function';
|
|
1312
|
+
const params = fn.parameters || fn.params || [];
|
|
1313
|
+
let desc = `${name} is a custom function`;
|
|
1314
|
+
if (params.length > 0)
|
|
1315
|
+
desc += ` that takes ${params.length} parameter${params.length !== 1 ? 's' : ''} (${params.join(', ')})`;
|
|
1316
|
+
desc += '.';
|
|
1317
|
+
return desc;
|
|
1318
|
+
}
|
|
1319
|
+
function getCommandDescription(name) {
|
|
1320
|
+
const descriptions = {
|
|
1321
|
+
add: 'Adds a class or attribute to an element',
|
|
1322
|
+
remove: 'Removes a class or attribute from an element',
|
|
1323
|
+
toggle: 'Toggles a class on an element',
|
|
1324
|
+
put: 'Puts content into an element',
|
|
1325
|
+
set: 'Sets a variable or property value',
|
|
1326
|
+
fetch: 'Makes an HTTP request',
|
|
1327
|
+
send: 'Sends a custom event',
|
|
1328
|
+
trigger: 'Triggers an event on an element',
|
|
1329
|
+
wait: 'Pauses execution for a duration',
|
|
1330
|
+
show: 'Shows an element',
|
|
1331
|
+
hide: 'Hides an element',
|
|
1332
|
+
log: 'Logs a message to the console',
|
|
1333
|
+
call: 'Calls a function',
|
|
1334
|
+
go: 'Navigates to a URL',
|
|
1335
|
+
take: 'Removes a class from other elements and adds it to this one',
|
|
1336
|
+
};
|
|
1337
|
+
return descriptions[name] || `Executes the '${name}' command`;
|
|
1338
|
+
}
|
|
1339
|
+
function inferReturnType(fn) {
|
|
1340
|
+
if (fn.body && fn.body.type === 'returnStatement') {
|
|
1341
|
+
const arg = fn.body.argument;
|
|
1342
|
+
if (arg)
|
|
1343
|
+
return arg.type === 'literal' ? typeof arg.value : 'value';
|
|
1344
|
+
}
|
|
1345
|
+
return undefined;
|
|
1346
|
+
}
|
|
1347
|
+
function generateOverallDescription(handlers, behaviors, functions) {
|
|
1348
|
+
const parts = [];
|
|
1349
|
+
if (handlers.length > 0) {
|
|
1350
|
+
const events = handlers.map(h => h.event).filter((e, i, arr) => arr.indexOf(e) === i);
|
|
1351
|
+
parts.push(`Responds to ${events.join(', ')} event${events.length > 1 ? 's' : ''}`);
|
|
1352
|
+
}
|
|
1353
|
+
if (behaviors.length > 0)
|
|
1354
|
+
parts.push(`defines ${behaviors.length} reusable behavior${behaviors.length > 1 ? 's' : ''}`);
|
|
1355
|
+
if (functions.length > 0)
|
|
1356
|
+
parts.push(`includes ${functions.length} custom function${functions.length > 1 ? 's' : ''}`);
|
|
1357
|
+
if (parts.length === 0)
|
|
1358
|
+
return 'No documented features.';
|
|
1359
|
+
return parts.join(', ') + '.';
|
|
1360
|
+
}
|
|
1361
|
+
function calculateCodeMetrics(ast) {
|
|
1362
|
+
const eventHandlers = findNodes(ast, node => node.type === 'eventHandler');
|
|
1363
|
+
const behaviors = findNodes(ast, node => node.type === 'behavior');
|
|
1364
|
+
const functions = findNodes(ast, node => node.type === 'function' || node.type === 'def');
|
|
1365
|
+
const commands = findNodes(ast, node => node.type === 'command');
|
|
1366
|
+
const complexity = calculateComplexity(ast);
|
|
1367
|
+
const cyclomatic = complexity.cyclomatic;
|
|
1368
|
+
let complexityRating;
|
|
1369
|
+
if (cyclomatic <= 3)
|
|
1370
|
+
complexityRating = 'simple';
|
|
1371
|
+
else if (cyclomatic <= 7)
|
|
1372
|
+
complexityRating = 'moderate';
|
|
1373
|
+
else if (cyclomatic <= 10)
|
|
1374
|
+
complexityRating = 'complex';
|
|
1375
|
+
else
|
|
1376
|
+
complexityRating = 'very complex';
|
|
1377
|
+
return {
|
|
1378
|
+
eventHandlerCount: eventHandlers.length,
|
|
1379
|
+
behaviorCount: behaviors.length,
|
|
1380
|
+
functionCount: functions.length,
|
|
1381
|
+
commandCount: commands.length,
|
|
1382
|
+
complexity: complexityRating,
|
|
1383
|
+
cyclomaticComplexity: cyclomatic,
|
|
1384
|
+
};
|
|
1385
|
+
}
|
|
1386
|
+
function slugify(text) {
|
|
1387
|
+
return text
|
|
1388
|
+
.toLowerCase()
|
|
1389
|
+
.replace(/[^\w\s-]/g, '')
|
|
1390
|
+
.replace(/\s+/g, '-')
|
|
1391
|
+
.trim();
|
|
1392
|
+
}
|
|
1393
|
+
function escapeHtml(text) {
|
|
1394
|
+
return text
|
|
1395
|
+
.replace(/&/g, '&')
|
|
1396
|
+
.replace(/</g, '<')
|
|
1397
|
+
.replace(/>/g, '>')
|
|
1398
|
+
.replace(/"/g, '"')
|
|
1399
|
+
.replace(/'/g, ''');
|
|
1400
|
+
}
|
|
1401
|
+
|
|
1402
|
+
function query(ast, selector) {
|
|
1403
|
+
const matches = queryAll(ast, selector);
|
|
1404
|
+
return matches.length > 0 ? matches[0] : null;
|
|
1405
|
+
}
|
|
1406
|
+
function queryAll(ast, selector) {
|
|
1407
|
+
if (!ast)
|
|
1408
|
+
return [];
|
|
1409
|
+
const selectorParts = selector.split(',').map(s => s.trim());
|
|
1410
|
+
const allMatches = [];
|
|
1411
|
+
for (const selectorPart of selectorParts) {
|
|
1412
|
+
const parsedSelector = parseSingleSelector(selectorPart);
|
|
1413
|
+
const matches = [];
|
|
1414
|
+
const ancestorStack = [];
|
|
1415
|
+
function traverse(node, path, parent, siblingIndex, siblings) {
|
|
1416
|
+
const queryContext = {
|
|
1417
|
+
getParent: () => parent,
|
|
1418
|
+
getPath: () => path,
|
|
1419
|
+
getAncestors: () => [...ancestorStack],
|
|
1420
|
+
getSiblingIndex: () => siblingIndex,
|
|
1421
|
+
getSiblings: () => siblings,
|
|
1422
|
+
};
|
|
1423
|
+
if (matchesSelector(node, parsedSelector, queryContext)) {
|
|
1424
|
+
matches.push({
|
|
1425
|
+
node,
|
|
1426
|
+
path: [...path],
|
|
1427
|
+
matches: extractCaptures(node, parsedSelector, queryContext),
|
|
1428
|
+
});
|
|
1429
|
+
}
|
|
1430
|
+
ancestorStack.push(node);
|
|
1431
|
+
for (const [key, value] of Object.entries(node)) {
|
|
1432
|
+
if (key === 'type' ||
|
|
1433
|
+
key === 'start' ||
|
|
1434
|
+
key === 'end' ||
|
|
1435
|
+
key === 'line' ||
|
|
1436
|
+
key === 'column') {
|
|
1437
|
+
continue;
|
|
1438
|
+
}
|
|
1439
|
+
if (Array.isArray(value)) {
|
|
1440
|
+
const childNodes = value.filter((v) => v && typeof v === 'object' && typeof v.type === 'string');
|
|
1441
|
+
for (let i = 0; i < value.length; i++) {
|
|
1442
|
+
const item = value[i];
|
|
1443
|
+
if (item && typeof item === 'object' && typeof item.type === 'string') {
|
|
1444
|
+
traverse(item, [...path, `${key}/${i}`], node, i, childNodes);
|
|
1445
|
+
}
|
|
1446
|
+
}
|
|
1447
|
+
}
|
|
1448
|
+
else if (value && typeof value === 'object' && typeof value.type === 'string') {
|
|
1449
|
+
traverse(value, [...path, key], node, 0, [value]);
|
|
1450
|
+
}
|
|
1451
|
+
}
|
|
1452
|
+
ancestorStack.pop();
|
|
1453
|
+
}
|
|
1454
|
+
traverse(ast, [], null, 0, [ast]);
|
|
1455
|
+
allMatches.push(...matches);
|
|
1456
|
+
}
|
|
1457
|
+
return allMatches;
|
|
1458
|
+
}
|
|
1459
|
+
function parseSelector(selector) {
|
|
1460
|
+
return parseSingleSelector(selector.trim());
|
|
1461
|
+
}
|
|
1462
|
+
function parseSingleSelector(selector) {
|
|
1463
|
+
const result = {
|
|
1464
|
+
type: null,
|
|
1465
|
+
attributes: [],
|
|
1466
|
+
pseudos: [],
|
|
1467
|
+
combinator: null,
|
|
1468
|
+
};
|
|
1469
|
+
selector = selector.trim();
|
|
1470
|
+
if (/\[\[/.test(selector)) {
|
|
1471
|
+
throw new Error(`Invalid selector syntax: "${selector}" - unexpected [[`);
|
|
1472
|
+
}
|
|
1473
|
+
if (/([>+~])\s*([>+~])/.test(selector)) {
|
|
1474
|
+
throw new Error(`Invalid selector syntax: "${selector}" - consecutive combinators`);
|
|
1475
|
+
}
|
|
1476
|
+
const combinatorMatch = selector.match(/^([^\s>+~]+(?:\[[^\]]*\])*(?::[a-zA-Z-]+(?:\([^)]*\))?)*)\s*([>+~]|\s)\s*(.+)$/);
|
|
1477
|
+
if (combinatorMatch) {
|
|
1478
|
+
const [, left, combinator, right] = combinatorMatch;
|
|
1479
|
+
if (combinator && left && right) {
|
|
1480
|
+
result.combinator = {
|
|
1481
|
+
type: (combinator.trim() || ' '),
|
|
1482
|
+
right: parseSingleSelector(right),
|
|
1483
|
+
};
|
|
1484
|
+
selector = left.trim();
|
|
1485
|
+
}
|
|
1486
|
+
}
|
|
1487
|
+
const typeMatch = selector.match(/^([a-zA-Z_][a-zA-Z0-9_-]*)/);
|
|
1488
|
+
if (typeMatch) {
|
|
1489
|
+
result.type = typeMatch[1] ?? null;
|
|
1490
|
+
selector = selector.substring(typeMatch[0].length);
|
|
1491
|
+
}
|
|
1492
|
+
const pseudoRegex = /:([a-zA-Z-]+)(?:\(([^)]*(?:\([^)]*\))*[^)]*)\))?/g;
|
|
1493
|
+
let pseudoMatch;
|
|
1494
|
+
const pseudoRanges = [];
|
|
1495
|
+
while ((pseudoMatch = pseudoRegex.exec(selector)) !== null) {
|
|
1496
|
+
const [fullMatch, name, argument] = pseudoMatch;
|
|
1497
|
+
result.pseudos.push({
|
|
1498
|
+
name: name ?? '',
|
|
1499
|
+
argument: argument || null,
|
|
1500
|
+
});
|
|
1501
|
+
pseudoRanges.push([pseudoMatch.index, pseudoMatch.index + fullMatch.length]);
|
|
1502
|
+
}
|
|
1503
|
+
const attributeRegex = /\[([a-zA-Z_][a-zA-Z0-9_-]*)(?:(\^=|\$=|\*=|\|=|~=|!=|=)([^\]]*))?\]/g;
|
|
1504
|
+
let attributeMatch;
|
|
1505
|
+
while ((attributeMatch = attributeRegex.exec(selector)) !== null) {
|
|
1506
|
+
const matchStart = attributeMatch.index;
|
|
1507
|
+
const matchEnd = matchStart + attributeMatch[0].length;
|
|
1508
|
+
const insidePseudo = pseudoRanges.some(([start, end]) => matchStart >= start && matchEnd <= end);
|
|
1509
|
+
if (insidePseudo)
|
|
1510
|
+
continue;
|
|
1511
|
+
const [, name, operator, value] = attributeMatch;
|
|
1512
|
+
if (name) {
|
|
1513
|
+
result.attributes.push({
|
|
1514
|
+
name: name.trim(),
|
|
1515
|
+
operator: (operator || 'exists'),
|
|
1516
|
+
value: value !== undefined ? parseAttributeValue(value.trim()) : null,
|
|
1517
|
+
});
|
|
1518
|
+
}
|
|
1519
|
+
}
|
|
1520
|
+
return result;
|
|
1521
|
+
}
|
|
1522
|
+
function parseAttributeValue(value) {
|
|
1523
|
+
if ((value.startsWith('"') && value.endsWith('"')) ||
|
|
1524
|
+
(value.startsWith("'") && value.endsWith("'"))) {
|
|
1525
|
+
return value.slice(1, -1);
|
|
1526
|
+
}
|
|
1527
|
+
const num = Number(value);
|
|
1528
|
+
if (!isNaN(num)) {
|
|
1529
|
+
return num;
|
|
1530
|
+
}
|
|
1531
|
+
if (value === 'true')
|
|
1532
|
+
return true;
|
|
1533
|
+
if (value === 'false')
|
|
1534
|
+
return false;
|
|
1535
|
+
if (value === 'null')
|
|
1536
|
+
return null;
|
|
1537
|
+
if (value === 'undefined')
|
|
1538
|
+
return undefined;
|
|
1539
|
+
return value;
|
|
1540
|
+
}
|
|
1541
|
+
function matchesSelector(node, selector, context) {
|
|
1542
|
+
if (selector.combinator) {
|
|
1543
|
+
const rightmost = getRightmostSelector(selector);
|
|
1544
|
+
if (!matchesSimpleSelector(node, rightmost)) {
|
|
1545
|
+
return false;
|
|
1546
|
+
}
|
|
1547
|
+
return checkCombinatorChain(node, selector, context);
|
|
1548
|
+
}
|
|
1549
|
+
return matchesSimpleSelector(node, selector, context);
|
|
1550
|
+
}
|
|
1551
|
+
function getRightmostSelector(selector) {
|
|
1552
|
+
if (selector.combinator) {
|
|
1553
|
+
return getRightmostSelector(selector.combinator.right);
|
|
1554
|
+
}
|
|
1555
|
+
return selector;
|
|
1556
|
+
}
|
|
1557
|
+
function checkCombinatorChain(node, selector, context) {
|
|
1558
|
+
if (!selector.combinator) {
|
|
1559
|
+
return true;
|
|
1560
|
+
}
|
|
1561
|
+
const combinatorType = selector.combinator.type;
|
|
1562
|
+
const rightSelector = selector.combinator.right;
|
|
1563
|
+
const leftSelector = {
|
|
1564
|
+
type: selector.type,
|
|
1565
|
+
attributes: selector.attributes,
|
|
1566
|
+
pseudos: selector.pseudos};
|
|
1567
|
+
if (rightSelector.combinator) {
|
|
1568
|
+
if (!checkCombinatorChain(node, rightSelector, context)) {
|
|
1569
|
+
return false;
|
|
1570
|
+
}
|
|
1571
|
+
const immediateRight = {
|
|
1572
|
+
type: rightSelector.type,
|
|
1573
|
+
attributes: rightSelector.attributes,
|
|
1574
|
+
pseudos: rightSelector.pseudos};
|
|
1575
|
+
return checkCombinatorRelationship(node, combinatorType, leftSelector, context, immediateRight);
|
|
1576
|
+
}
|
|
1577
|
+
return checkCombinatorRelationship(node, combinatorType, leftSelector, context);
|
|
1578
|
+
}
|
|
1579
|
+
function matchesSimpleSelector(node, selector, context) {
|
|
1580
|
+
if (selector.type && node.type !== selector.type) {
|
|
1581
|
+
return false;
|
|
1582
|
+
}
|
|
1583
|
+
for (const attr of selector.attributes) {
|
|
1584
|
+
if (!matchesAttribute(node, attr)) {
|
|
1585
|
+
return false;
|
|
1586
|
+
}
|
|
1587
|
+
}
|
|
1588
|
+
for (const pseudo of selector.pseudos) {
|
|
1589
|
+
if (context) {
|
|
1590
|
+
if (!matchesPseudo(node, pseudo, context)) {
|
|
1591
|
+
return false;
|
|
1592
|
+
}
|
|
1593
|
+
}
|
|
1594
|
+
else {
|
|
1595
|
+
if (!matchesPseudoSimple(node, pseudo)) {
|
|
1596
|
+
return false;
|
|
1597
|
+
}
|
|
1598
|
+
}
|
|
1599
|
+
}
|
|
1600
|
+
return true;
|
|
1601
|
+
}
|
|
1602
|
+
function matchesAttribute(node, attr) {
|
|
1603
|
+
const value = node[attr.name];
|
|
1604
|
+
switch (attr.operator) {
|
|
1605
|
+
case 'exists':
|
|
1606
|
+
return value !== undefined;
|
|
1607
|
+
case '=':
|
|
1608
|
+
return value === attr.value;
|
|
1609
|
+
case '!=':
|
|
1610
|
+
return value !== attr.value;
|
|
1611
|
+
case '^=':
|
|
1612
|
+
return typeof value === 'string' && value.startsWith(String(attr.value));
|
|
1613
|
+
case '$=':
|
|
1614
|
+
return typeof value === 'string' && value.endsWith(String(attr.value));
|
|
1615
|
+
case '*=':
|
|
1616
|
+
return typeof value === 'string' && value.includes(String(attr.value));
|
|
1617
|
+
case '|=':
|
|
1618
|
+
return (typeof value === 'string' && (value === attr.value || value.startsWith(attr.value + '-')));
|
|
1619
|
+
case '~=':
|
|
1620
|
+
return typeof value === 'string' && value.split(/\s+/).includes(String(attr.value));
|
|
1621
|
+
default:
|
|
1622
|
+
return false;
|
|
1623
|
+
}
|
|
1624
|
+
}
|
|
1625
|
+
function matchesPseudo(node, pseudo, context) {
|
|
1626
|
+
switch (pseudo.name) {
|
|
1627
|
+
case 'first-child':
|
|
1628
|
+
return isFirstChild(node, context);
|
|
1629
|
+
case 'last-child':
|
|
1630
|
+
return isLastChild(node, context);
|
|
1631
|
+
case 'has':
|
|
1632
|
+
return hasDescendant(node, pseudo.argument);
|
|
1633
|
+
case 'not':
|
|
1634
|
+
return !matchesSelector(node, parseSelector(pseudo.argument), context);
|
|
1635
|
+
case 'contains':
|
|
1636
|
+
return containsText(node, pseudo.argument);
|
|
1637
|
+
default:
|
|
1638
|
+
return false;
|
|
1639
|
+
}
|
|
1640
|
+
}
|
|
1641
|
+
function matchesPseudoSimple(node, pseudo) {
|
|
1642
|
+
switch (pseudo.name) {
|
|
1643
|
+
case 'has':
|
|
1644
|
+
return hasDescendant(node, pseudo.argument);
|
|
1645
|
+
case 'not':
|
|
1646
|
+
return !matchesSimpleSelector(node, parseSelector(pseudo.argument));
|
|
1647
|
+
case 'contains':
|
|
1648
|
+
return containsText(node, pseudo.argument);
|
|
1649
|
+
default:
|
|
1650
|
+
return false;
|
|
1651
|
+
}
|
|
1652
|
+
}
|
|
1653
|
+
function checkCombinatorRelationship(node, combinatorType, leftSelector, context, intermediateSelector) {
|
|
1654
|
+
switch (combinatorType) {
|
|
1655
|
+
case '>': {
|
|
1656
|
+
const parent = context.getParent();
|
|
1657
|
+
if (!parent)
|
|
1658
|
+
return false;
|
|
1659
|
+
if (intermediateSelector) {
|
|
1660
|
+
return (matchesSimpleSelector(parent, intermediateSelector) &&
|
|
1661
|
+
hasAncestorMatching(parent, context, leftSelector));
|
|
1662
|
+
}
|
|
1663
|
+
return matchesSimpleSelector(parent, leftSelector);
|
|
1664
|
+
}
|
|
1665
|
+
case '+':
|
|
1666
|
+
return hasPreviousSiblingMatching(node, context, leftSelector);
|
|
1667
|
+
case '~':
|
|
1668
|
+
return hasAnyPreviousSiblingMatching(node, context, leftSelector);
|
|
1669
|
+
case ' ':
|
|
1670
|
+
if (intermediateSelector) {
|
|
1671
|
+
return hasAncestorMatchingWithFurtherAncestor(node, context, intermediateSelector, leftSelector);
|
|
1672
|
+
}
|
|
1673
|
+
return hasAncestorMatching(node, context, leftSelector);
|
|
1674
|
+
default:
|
|
1675
|
+
return false;
|
|
1676
|
+
}
|
|
1677
|
+
}
|
|
1678
|
+
function hasAncestorMatchingWithFurtherAncestor(node, context, intermediateSelector, leftSelector) {
|
|
1679
|
+
if (!context.getAncestors) {
|
|
1680
|
+
return false;
|
|
1681
|
+
}
|
|
1682
|
+
const ancestors = context.getAncestors();
|
|
1683
|
+
for (let i = ancestors.length - 1; i >= 0; i--) {
|
|
1684
|
+
const ancestor = ancestors[i];
|
|
1685
|
+
if (matchesSimpleSelector(ancestor, intermediateSelector)) {
|
|
1686
|
+
for (let j = i - 1; j >= 0; j--) {
|
|
1687
|
+
if (matchesSimpleSelector(ancestors[j], leftSelector)) {
|
|
1688
|
+
return true;
|
|
1689
|
+
}
|
|
1690
|
+
}
|
|
1691
|
+
}
|
|
1692
|
+
}
|
|
1693
|
+
return false;
|
|
1694
|
+
}
|
|
1695
|
+
function isFirstChild(node, context) {
|
|
1696
|
+
if (context.getSiblingIndex) {
|
|
1697
|
+
return context.getSiblingIndex() === 0;
|
|
1698
|
+
}
|
|
1699
|
+
const parent = context.getParent();
|
|
1700
|
+
if (!parent)
|
|
1701
|
+
return false;
|
|
1702
|
+
for (const [, value] of Object.entries(parent)) {
|
|
1703
|
+
if (Array.isArray(value) && value.length > 0 && value[0] === node) {
|
|
1704
|
+
return true;
|
|
1705
|
+
}
|
|
1706
|
+
}
|
|
1707
|
+
return false;
|
|
1708
|
+
}
|
|
1709
|
+
function isLastChild(node, context) {
|
|
1710
|
+
if (context.getSiblingIndex && context.getSiblings) {
|
|
1711
|
+
const siblings = context.getSiblings();
|
|
1712
|
+
return context.getSiblingIndex() === siblings.length - 1;
|
|
1713
|
+
}
|
|
1714
|
+
const parent = context.getParent();
|
|
1715
|
+
if (!parent)
|
|
1716
|
+
return false;
|
|
1717
|
+
for (const [, value] of Object.entries(parent)) {
|
|
1718
|
+
if (Array.isArray(value) && value.length > 0 && value[value.length - 1] === node) {
|
|
1719
|
+
return true;
|
|
1720
|
+
}
|
|
1721
|
+
}
|
|
1722
|
+
return false;
|
|
1723
|
+
}
|
|
1724
|
+
function hasDescendant(node, selector) {
|
|
1725
|
+
const descendants = findNodes(node, () => true);
|
|
1726
|
+
const parsedSelector = parseSelector(selector);
|
|
1727
|
+
return descendants.some(descendant => matchesSelector(descendant, parsedSelector, { getParent: () => null }));
|
|
1728
|
+
}
|
|
1729
|
+
function containsText(node, text) {
|
|
1730
|
+
let searchText = text;
|
|
1731
|
+
if ((text.startsWith('"') && text.endsWith('"')) ||
|
|
1732
|
+
(text.startsWith("'") && text.endsWith("'"))) {
|
|
1733
|
+
searchText = text.slice(1, -1);
|
|
1734
|
+
}
|
|
1735
|
+
const nodeStr = JSON.stringify(node);
|
|
1736
|
+
return nodeStr.includes(searchText);
|
|
1737
|
+
}
|
|
1738
|
+
function hasAncestorMatching(node, context, selector) {
|
|
1739
|
+
if (context.getAncestors) {
|
|
1740
|
+
const ancestors = context.getAncestors();
|
|
1741
|
+
for (const ancestor of ancestors) {
|
|
1742
|
+
if (matchesSimpleSelector(ancestor, selector)) {
|
|
1743
|
+
return true;
|
|
1744
|
+
}
|
|
1745
|
+
}
|
|
1746
|
+
return false;
|
|
1747
|
+
}
|
|
1748
|
+
const parent = context.getParent();
|
|
1749
|
+
return parent ? matchesSimpleSelector(parent, selector) : false;
|
|
1750
|
+
}
|
|
1751
|
+
function hasPreviousSiblingMatching(node, context, selector) {
|
|
1752
|
+
if (!context.getSiblings || !context.getSiblingIndex) {
|
|
1753
|
+
return false;
|
|
1754
|
+
}
|
|
1755
|
+
const siblings = context.getSiblings();
|
|
1756
|
+
const index = context.getSiblingIndex();
|
|
1757
|
+
if (index > 0) {
|
|
1758
|
+
const prevSibling = siblings[index - 1];
|
|
1759
|
+
return prevSibling ? matchesSimpleSelector(prevSibling, selector) : false;
|
|
1760
|
+
}
|
|
1761
|
+
return false;
|
|
1762
|
+
}
|
|
1763
|
+
function hasAnyPreviousSiblingMatching(node, context, selector) {
|
|
1764
|
+
if (!context.getSiblings || !context.getSiblingIndex) {
|
|
1765
|
+
return false;
|
|
1766
|
+
}
|
|
1767
|
+
const siblings = context.getSiblings();
|
|
1768
|
+
const index = context.getSiblingIndex();
|
|
1769
|
+
for (let i = 0; i < index; i++) {
|
|
1770
|
+
const sibling = siblings[i];
|
|
1771
|
+
if (sibling && matchesSimpleSelector(sibling, selector)) {
|
|
1772
|
+
return true;
|
|
1773
|
+
}
|
|
1774
|
+
}
|
|
1775
|
+
return false;
|
|
1776
|
+
}
|
|
1777
|
+
function extractCaptures(node, selector, context) {
|
|
1778
|
+
const captures = {};
|
|
1779
|
+
const rightmost = getRightmostSelector(selector);
|
|
1780
|
+
for (const attr of rightmost.attributes) {
|
|
1781
|
+
if (attr.operator === '=' || attr.operator === 'exists') {
|
|
1782
|
+
const key = rightmost.type ? `${rightmost.type}[${attr.name}]` : `[${attr.name}]`;
|
|
1783
|
+
captures[key] = node[attr.name];
|
|
1784
|
+
}
|
|
1785
|
+
}
|
|
1786
|
+
if (selector.combinator && context?.getAncestors) {
|
|
1787
|
+
const ancestors = context.getAncestors();
|
|
1788
|
+
extractCapturesFromCombinatorChain(selector, ancestors, captures);
|
|
1789
|
+
}
|
|
1790
|
+
return captures;
|
|
1791
|
+
}
|
|
1792
|
+
function extractCapturesFromCombinatorChain(selector, ancestors, captures) {
|
|
1793
|
+
if (!selector.combinator)
|
|
1794
|
+
return;
|
|
1795
|
+
const leftSelector = {
|
|
1796
|
+
type: selector.type,
|
|
1797
|
+
attributes: selector.attributes,
|
|
1798
|
+
pseudos: selector.pseudos};
|
|
1799
|
+
for (const ancestor of ancestors) {
|
|
1800
|
+
if (matchesSimpleSelector(ancestor, leftSelector)) {
|
|
1801
|
+
for (const attr of leftSelector.attributes) {
|
|
1802
|
+
if (attr.operator === '=' || attr.operator === 'exists') {
|
|
1803
|
+
const key = leftSelector.type ? `${leftSelector.type}[${attr.name}]` : `[${attr.name}]`;
|
|
1804
|
+
captures[key] = ancestor[attr.name];
|
|
1805
|
+
}
|
|
1806
|
+
}
|
|
1807
|
+
break;
|
|
1808
|
+
}
|
|
1809
|
+
}
|
|
1810
|
+
if (selector.combinator.right.combinator) {
|
|
1811
|
+
extractCapturesFromCombinatorChain(selector.combinator.right, ancestors, captures);
|
|
1812
|
+
}
|
|
1813
|
+
}
|
|
1814
|
+
function queryXPath(ast, xpath) {
|
|
1815
|
+
if (!ast)
|
|
1816
|
+
return [];
|
|
1817
|
+
if (xpath === '//*') {
|
|
1818
|
+
return findNodes(ast, () => true);
|
|
1819
|
+
}
|
|
1820
|
+
const descendantMatch = xpath.match(/^\/\/([a-zA-Z_][a-zA-Z0-9_]*)/);
|
|
1821
|
+
if (descendantMatch) {
|
|
1822
|
+
const nodeType = descendantMatch[1];
|
|
1823
|
+
return findNodes(ast, node => node.type === nodeType);
|
|
1824
|
+
}
|
|
1825
|
+
throw new Error(`XPath query "${xpath}" not supported in basic implementation`);
|
|
1826
|
+
}
|
|
1827
|
+
|
|
1828
|
+
function normalizeVisitResult(result, fallback) {
|
|
1829
|
+
if (result === null) {
|
|
1830
|
+
return fallback;
|
|
1831
|
+
}
|
|
1832
|
+
if (Array.isArray(result)) {
|
|
1833
|
+
return result.length > 0 ? result[0] : fallback;
|
|
1834
|
+
}
|
|
1835
|
+
return result;
|
|
1836
|
+
}
|
|
1837
|
+
function transform(ast, visitor, options = {}) {
|
|
1838
|
+
const transformVisitor = new ASTVisitor(visitor);
|
|
1839
|
+
const visitResult = transformVisitor.visit(ast, createVisitorContext());
|
|
1840
|
+
let result;
|
|
1841
|
+
if (visitResult === null) {
|
|
1842
|
+
result = ast;
|
|
1843
|
+
}
|
|
1844
|
+
else if (Array.isArray(visitResult)) {
|
|
1845
|
+
result = visitResult.length > 0 ? visitResult[0] : ast;
|
|
1846
|
+
}
|
|
1847
|
+
else {
|
|
1848
|
+
result = visitResult;
|
|
1849
|
+
}
|
|
1850
|
+
if (options.optimize) {
|
|
1851
|
+
result = optimize(result, options);
|
|
1852
|
+
}
|
|
1853
|
+
if (options.minify) {
|
|
1854
|
+
result = minifyAST(result);
|
|
1855
|
+
}
|
|
1856
|
+
return result;
|
|
1857
|
+
}
|
|
1858
|
+
function optimize(ast, options = {}) {
|
|
1859
|
+
const passes = [];
|
|
1860
|
+
if (options.batchOperations || options.batchSimilarOperations || options.optimize) {
|
|
1861
|
+
passes.push(createBatchOperationsPass());
|
|
1862
|
+
}
|
|
1863
|
+
if (options.redundantClassOperations || options.optimize) {
|
|
1864
|
+
passes.push(createRemoveRedundantOperationsPass());
|
|
1865
|
+
}
|
|
1866
|
+
if (options.optimize) {
|
|
1867
|
+
passes.push(createSimplifyConditionsPass());
|
|
1868
|
+
}
|
|
1869
|
+
return applyOptimizationPasses(ast, passes);
|
|
1870
|
+
}
|
|
1871
|
+
function applyOptimizationPasses(ast, passes) {
|
|
1872
|
+
let result = ast;
|
|
1873
|
+
for (const pass of passes) {
|
|
1874
|
+
result = applyOptimizationPass(result, pass);
|
|
1875
|
+
}
|
|
1876
|
+
return result;
|
|
1877
|
+
}
|
|
1878
|
+
function applyOptimizationPass(ast, pass) {
|
|
1879
|
+
const visitor = new ASTVisitor({
|
|
1880
|
+
enter(node, context) {
|
|
1881
|
+
if (!pass.shouldRun || pass.shouldRun(node)) {
|
|
1882
|
+
pass.transform(node, context);
|
|
1883
|
+
}
|
|
1884
|
+
},
|
|
1885
|
+
});
|
|
1886
|
+
return normalizeVisitResult(visitor.visit(ast, createVisitorContext()), ast);
|
|
1887
|
+
}
|
|
1888
|
+
function createBatchOperationsPass() {
|
|
1889
|
+
return {
|
|
1890
|
+
name: 'batch-operations',
|
|
1891
|
+
transform: (node, context) => {
|
|
1892
|
+
if (node.commands && Array.isArray(node.commands)) {
|
|
1893
|
+
const commands = node.commands;
|
|
1894
|
+
const batched = batchSimilarCommands(commands);
|
|
1895
|
+
if (batched.length !== commands.length) {
|
|
1896
|
+
context.replace({ ...node, commands: batched });
|
|
1897
|
+
}
|
|
1898
|
+
}
|
|
1899
|
+
return node;
|
|
1900
|
+
},
|
|
1901
|
+
};
|
|
1902
|
+
}
|
|
1903
|
+
function createRemoveRedundantOperationsPass() {
|
|
1904
|
+
return {
|
|
1905
|
+
name: 'remove-redundant',
|
|
1906
|
+
transform: (node, context) => {
|
|
1907
|
+
if (node.commands && Array.isArray(node.commands)) {
|
|
1908
|
+
const commands = node.commands;
|
|
1909
|
+
const optimized = removeRedundantOperations(commands);
|
|
1910
|
+
if (optimized.length !== commands.length) {
|
|
1911
|
+
context.replace({ ...node, commands: optimized });
|
|
1912
|
+
}
|
|
1913
|
+
}
|
|
1914
|
+
return node;
|
|
1915
|
+
},
|
|
1916
|
+
};
|
|
1917
|
+
}
|
|
1918
|
+
function createSimplifyConditionsPass() {
|
|
1919
|
+
return {
|
|
1920
|
+
name: 'simplify-conditions',
|
|
1921
|
+
transform: (node, context) => {
|
|
1922
|
+
if (node.type === 'conditional' && node.condition) {
|
|
1923
|
+
const simplified = simplifyCondition(node.condition);
|
|
1924
|
+
if (simplified !== node.condition) {
|
|
1925
|
+
context.replace({ ...node, condition: simplified });
|
|
1926
|
+
}
|
|
1927
|
+
}
|
|
1928
|
+
return node;
|
|
1929
|
+
},
|
|
1930
|
+
};
|
|
1931
|
+
}
|
|
1932
|
+
function normalize(ast) {
|
|
1933
|
+
const visitor = new ASTVisitor({
|
|
1934
|
+
exit(node, context) {
|
|
1935
|
+
const normalized = {
|
|
1936
|
+
type: node.type,
|
|
1937
|
+
start: node.start,
|
|
1938
|
+
end: node.end,
|
|
1939
|
+
line: node.line,
|
|
1940
|
+
column: node.column,
|
|
1941
|
+
};
|
|
1942
|
+
if (node.type === 'command') {
|
|
1943
|
+
normalized.name = node.name;
|
|
1944
|
+
normalized.args = node.args;
|
|
1945
|
+
normalized.target = node.target;
|
|
1946
|
+
}
|
|
1947
|
+
else if (node.type === 'eventHandler') {
|
|
1948
|
+
normalized.event = node.event;
|
|
1949
|
+
normalized.commands = node.commands;
|
|
1950
|
+
}
|
|
1951
|
+
else if (node.type === 'selector') {
|
|
1952
|
+
normalized.value = node.value;
|
|
1953
|
+
}
|
|
1954
|
+
else if (node.type === 'identifier') {
|
|
1955
|
+
normalized.name = node.name;
|
|
1956
|
+
}
|
|
1957
|
+
else if (node.type === 'literal') {
|
|
1958
|
+
normalized.value = node.value;
|
|
1959
|
+
}
|
|
1960
|
+
context.replace(normalized);
|
|
1961
|
+
},
|
|
1962
|
+
});
|
|
1963
|
+
return normalizeVisitResult(visitor.visit(ast, createVisitorContext()), ast);
|
|
1964
|
+
}
|
|
1965
|
+
function inlineVariables(ast) {
|
|
1966
|
+
const variables = new Map();
|
|
1967
|
+
const collectVisitor = new ASTVisitor({
|
|
1968
|
+
enter(node) {
|
|
1969
|
+
if (node.name === 'set' &&
|
|
1970
|
+
node.variable &&
|
|
1971
|
+
node.value &&
|
|
1972
|
+
isSimpleValue(node.value)) {
|
|
1973
|
+
const varName = node.variable.name;
|
|
1974
|
+
variables.set(varName, node.value);
|
|
1975
|
+
}
|
|
1976
|
+
},
|
|
1977
|
+
});
|
|
1978
|
+
visit(ast, collectVisitor);
|
|
1979
|
+
const inlineVisitor = new ASTVisitor({
|
|
1980
|
+
enter(node, context) {
|
|
1981
|
+
if (node.type === 'identifier' && variables.has(node.name)) {
|
|
1982
|
+
context.replace(variables.get(node.name));
|
|
1983
|
+
}
|
|
1984
|
+
},
|
|
1985
|
+
});
|
|
1986
|
+
return normalizeVisitResult(inlineVisitor.visit(ast, createVisitorContext()), ast);
|
|
1987
|
+
}
|
|
1988
|
+
function extractCommonExpressions(ast) {
|
|
1989
|
+
const expressionCounts = new Map();
|
|
1990
|
+
const countVisitor = new ASTVisitor({
|
|
1991
|
+
enter(node) {
|
|
1992
|
+
if (node.type === 'binaryExpression' && isComplexExpression(node)) {
|
|
1993
|
+
const expr = JSON.stringify(node, ['type', 'operator', 'left', 'right', 'name', 'value']);
|
|
1994
|
+
if (!expressionCounts.has(expr)) {
|
|
1995
|
+
expressionCounts.set(expr, { count: 0, node, nodes: [] });
|
|
1996
|
+
}
|
|
1997
|
+
const entry = expressionCounts.get(expr);
|
|
1998
|
+
entry.count++;
|
|
1999
|
+
entry.nodes.push(node);
|
|
2000
|
+
}
|
|
2001
|
+
},
|
|
2002
|
+
});
|
|
2003
|
+
visit(ast, countVisitor);
|
|
2004
|
+
let varCounter = 0;
|
|
2005
|
+
const extractions = new Map();
|
|
2006
|
+
const newCommands = [];
|
|
2007
|
+
for (const [expr, data] of expressionCounts) {
|
|
2008
|
+
if (data.count > 1) {
|
|
2009
|
+
const varName = `temp${++varCounter}`;
|
|
2010
|
+
extractions.set(expr, varName);
|
|
2011
|
+
newCommands.push({
|
|
2012
|
+
type: 'command',
|
|
2013
|
+
name: 'set',
|
|
2014
|
+
start: 0,
|
|
2015
|
+
end: 0,
|
|
2016
|
+
line: 1,
|
|
2017
|
+
column: 1,
|
|
2018
|
+
variable: { type: 'identifier', name: varName, start: 0, end: 0, line: 1, column: 1 },
|
|
2019
|
+
value: data.node,
|
|
2020
|
+
});
|
|
2021
|
+
}
|
|
2022
|
+
}
|
|
2023
|
+
const replaceVisitor = new ASTVisitor({
|
|
2024
|
+
enter(node, context) {
|
|
2025
|
+
if (node.type === 'binaryExpression') {
|
|
2026
|
+
const expr = JSON.stringify(node, ['type', 'operator', 'left', 'right', 'name', 'value']);
|
|
2027
|
+
if (extractions.has(expr)) {
|
|
2028
|
+
context.replace({
|
|
2029
|
+
type: 'identifier',
|
|
2030
|
+
name: extractions.get(expr),
|
|
2031
|
+
start: node.start ?? 0,
|
|
2032
|
+
end: node.end ?? 0,
|
|
2033
|
+
line: node.line ?? 1,
|
|
2034
|
+
column: node.column ?? 1,
|
|
2035
|
+
});
|
|
2036
|
+
}
|
|
2037
|
+
}
|
|
2038
|
+
},
|
|
2039
|
+
});
|
|
2040
|
+
let result = normalizeVisitResult(replaceVisitor.visit(ast, createVisitorContext()), ast);
|
|
2041
|
+
if (newCommands.length > 0 && result.commands) {
|
|
2042
|
+
result = {
|
|
2043
|
+
...result,
|
|
2044
|
+
commands: [...newCommands, ...result.commands],
|
|
2045
|
+
};
|
|
2046
|
+
}
|
|
2047
|
+
return result;
|
|
2048
|
+
}
|
|
2049
|
+
function createOptimizationPass(config) {
|
|
2050
|
+
return {
|
|
2051
|
+
name: config.name,
|
|
2052
|
+
transform: config.transform,
|
|
2053
|
+
shouldRun: config.shouldRun,
|
|
2054
|
+
};
|
|
2055
|
+
}
|
|
2056
|
+
function batchSimilarCommands(commands) {
|
|
2057
|
+
const groups = new Map();
|
|
2058
|
+
const result = [];
|
|
2059
|
+
for (const cmd of commands) {
|
|
2060
|
+
if (cmd.name === 'add' || cmd.name === 'remove') {
|
|
2061
|
+
const key = `${cmd.name}:${cmd.target?.name || 'default'}`;
|
|
2062
|
+
if (!groups.has(key)) {
|
|
2063
|
+
groups.set(key, []);
|
|
2064
|
+
}
|
|
2065
|
+
groups.get(key).push(cmd);
|
|
2066
|
+
}
|
|
2067
|
+
else {
|
|
2068
|
+
for (const [, group] of groups) {
|
|
2069
|
+
if (group.length > 1) {
|
|
2070
|
+
result.push(createBatchedCommand(group));
|
|
2071
|
+
}
|
|
2072
|
+
else {
|
|
2073
|
+
result.push(...group);
|
|
2074
|
+
}
|
|
2075
|
+
}
|
|
2076
|
+
groups.clear();
|
|
2077
|
+
result.push(cmd);
|
|
2078
|
+
}
|
|
2079
|
+
}
|
|
2080
|
+
for (const [, group] of groups) {
|
|
2081
|
+
if (group.length > 1) {
|
|
2082
|
+
result.push(createBatchedCommand(group));
|
|
2083
|
+
}
|
|
2084
|
+
else {
|
|
2085
|
+
result.push(...group);
|
|
2086
|
+
}
|
|
2087
|
+
}
|
|
2088
|
+
return result;
|
|
2089
|
+
}
|
|
2090
|
+
function createBatchedCommand(commands) {
|
|
2091
|
+
return {
|
|
2092
|
+
...commands[0],
|
|
2093
|
+
args: commands.map(cmd => cmd.args[0]).filter(Boolean),
|
|
2094
|
+
};
|
|
2095
|
+
}
|
|
2096
|
+
function removeRedundantOperations(commands) {
|
|
2097
|
+
const result = [];
|
|
2098
|
+
const operationTracker = new Map();
|
|
2099
|
+
for (let i = 0; i < commands.length; i++) {
|
|
2100
|
+
const cmd = commands[i];
|
|
2101
|
+
if (cmd.name === 'add' || cmd.name === 'remove') {
|
|
2102
|
+
const target = cmd.target?.name || 'default';
|
|
2103
|
+
const selector = cmd.args?.[0]?.value;
|
|
2104
|
+
const key = `${target}:${selector}`;
|
|
2105
|
+
const lastOp = operationTracker.get(key);
|
|
2106
|
+
if (lastOp) {
|
|
2107
|
+
if ((lastOp.operation === 'add' && cmd.name === 'remove') ||
|
|
2108
|
+
(lastOp.operation === 'remove' && cmd.name === 'add')) {
|
|
2109
|
+
result.splice(lastOp.index, 1);
|
|
2110
|
+
for (const [_, v] of operationTracker) {
|
|
2111
|
+
if (v.index > lastOp.index) {
|
|
2112
|
+
v.index--;
|
|
2113
|
+
}
|
|
2114
|
+
}
|
|
2115
|
+
}
|
|
2116
|
+
}
|
|
2117
|
+
operationTracker.set(key, { operation: cmd.name, index: result.length });
|
|
2118
|
+
result.push(cmd);
|
|
2119
|
+
}
|
|
2120
|
+
else {
|
|
2121
|
+
result.push(cmd);
|
|
2122
|
+
}
|
|
2123
|
+
}
|
|
2124
|
+
return result;
|
|
2125
|
+
}
|
|
2126
|
+
function simplifyCondition(condition) {
|
|
2127
|
+
if (condition.operator === '&&') {
|
|
2128
|
+
const left = condition.left;
|
|
2129
|
+
const right = condition.right;
|
|
2130
|
+
if (left.type === 'literal' && left.value === true)
|
|
2131
|
+
return right;
|
|
2132
|
+
if (right.type === 'literal' && right.value === true)
|
|
2133
|
+
return left;
|
|
2134
|
+
if (left.type === 'literal' && left.value === false)
|
|
2135
|
+
return left;
|
|
2136
|
+
if (right.type === 'literal' && right.value === false)
|
|
2137
|
+
return right;
|
|
2138
|
+
}
|
|
2139
|
+
return condition;
|
|
2140
|
+
}
|
|
2141
|
+
function isSimpleValue(node) {
|
|
2142
|
+
return node.type === 'literal' || node.type === 'identifier';
|
|
2143
|
+
}
|
|
2144
|
+
function isComplexExpression(node) {
|
|
2145
|
+
return (node.type === 'binaryExpression' &&
|
|
2146
|
+
(node.left?.type === 'binaryExpression' ||
|
|
2147
|
+
node.right?.type === 'binaryExpression'));
|
|
2148
|
+
}
|
|
2149
|
+
function minifyAST(ast) {
|
|
2150
|
+
const visitor = new ASTVisitor({
|
|
2151
|
+
enter(node, context) {
|
|
2152
|
+
const minified = {
|
|
2153
|
+
type: node.type,
|
|
2154
|
+
};
|
|
2155
|
+
for (const [key, value] of Object.entries(node)) {
|
|
2156
|
+
if (!['start', 'end', 'line', 'column'].includes(key)) {
|
|
2157
|
+
minified[key] = value;
|
|
2158
|
+
}
|
|
2159
|
+
}
|
|
2160
|
+
context.replace(minified);
|
|
2161
|
+
},
|
|
2162
|
+
});
|
|
2163
|
+
return normalizeVisitResult(visitor.visit(ast, createVisitorContext()), ast);
|
|
2164
|
+
}
|
|
2165
|
+
|
|
2166
|
+
function pos(node) {
|
|
2167
|
+
const p = {};
|
|
2168
|
+
if (typeof node.start === 'number')
|
|
2169
|
+
p.start = node.start;
|
|
2170
|
+
if (typeof node.end === 'number')
|
|
2171
|
+
p.end = node.end;
|
|
2172
|
+
if (typeof node.line === 'number')
|
|
2173
|
+
p.line = node.line;
|
|
2174
|
+
if (typeof node.column === 'number')
|
|
2175
|
+
p.column = node.column;
|
|
2176
|
+
return p;
|
|
2177
|
+
}
|
|
2178
|
+
function fromCoreAST(node) {
|
|
2179
|
+
if (!node)
|
|
2180
|
+
return { type: 'literal', value: null };
|
|
2181
|
+
switch (node.type) {
|
|
2182
|
+
case 'eventHandler':
|
|
2183
|
+
return convertEventHandler(node);
|
|
2184
|
+
case 'command':
|
|
2185
|
+
return convertCommand$1(node);
|
|
2186
|
+
case 'CommandSequence':
|
|
2187
|
+
return convertCommandSequence(node);
|
|
2188
|
+
case 'block':
|
|
2189
|
+
return convertBlock(node);
|
|
2190
|
+
case 'literal':
|
|
2191
|
+
return {
|
|
2192
|
+
type: 'literal',
|
|
2193
|
+
value: node.value,
|
|
2194
|
+
...pos(node),
|
|
2195
|
+
};
|
|
2196
|
+
case 'string':
|
|
2197
|
+
return { type: 'literal', value: node.value, ...pos(node) };
|
|
2198
|
+
case 'selector':
|
|
2199
|
+
return {
|
|
2200
|
+
type: 'selector',
|
|
2201
|
+
value: (node.value ?? node.selector ?? ''),
|
|
2202
|
+
...pos(node),
|
|
2203
|
+
};
|
|
2204
|
+
case 'contextReference':
|
|
2205
|
+
return {
|
|
2206
|
+
type: 'identifier',
|
|
2207
|
+
value: (node.name ?? node.contextType ?? ''),
|
|
2208
|
+
...pos(node),
|
|
2209
|
+
};
|
|
2210
|
+
case 'identifier':
|
|
2211
|
+
return {
|
|
2212
|
+
type: 'identifier',
|
|
2213
|
+
value: (node.name ?? node.value ?? ''),
|
|
2214
|
+
name: (node.name ?? ''),
|
|
2215
|
+
...pos(node),
|
|
2216
|
+
};
|
|
2217
|
+
case 'propertyAccess':
|
|
2218
|
+
case 'possessiveExpression':
|
|
2219
|
+
return convertPossessive(node);
|
|
2220
|
+
case 'memberExpression':
|
|
2221
|
+
return convertMember(node);
|
|
2222
|
+
case 'binaryExpression':
|
|
2223
|
+
return convertBinary(node);
|
|
2224
|
+
case 'callExpression':
|
|
2225
|
+
return convertCall(node);
|
|
2226
|
+
case 'unaryExpression':
|
|
2227
|
+
return {
|
|
2228
|
+
type: 'unary',
|
|
2229
|
+
operator: node.operator,
|
|
2230
|
+
operand: fromCoreAST(node.argument ?? node.operand),
|
|
2231
|
+
...pos(node),
|
|
2232
|
+
};
|
|
2233
|
+
case 'timeExpression':
|
|
2234
|
+
return { type: 'literal', value: node.value, ...pos(node) };
|
|
2235
|
+
case 'templateLiteral':
|
|
2236
|
+
return { type: 'literal', value: (node.raw ?? ''), ...pos(node) };
|
|
2237
|
+
case 'variable':
|
|
2238
|
+
return {
|
|
2239
|
+
type: 'variable',
|
|
2240
|
+
name: (node.name ?? ''),
|
|
2241
|
+
scope: (node.scope ?? 'local'),
|
|
2242
|
+
...pos(node),
|
|
2243
|
+
};
|
|
2244
|
+
case 'htmlSelector':
|
|
2245
|
+
return {
|
|
2246
|
+
type: 'selector',
|
|
2247
|
+
value: (node.value ?? node.selector ?? ''),
|
|
2248
|
+
...pos(node),
|
|
2249
|
+
};
|
|
2250
|
+
case 'positional':
|
|
2251
|
+
return {
|
|
2252
|
+
type: 'positional',
|
|
2253
|
+
position: node.position,
|
|
2254
|
+
...(node.target ? { target: fromCoreAST(node.target) } : {}),
|
|
2255
|
+
...pos(node),
|
|
2256
|
+
};
|
|
2257
|
+
case 'positionalExpression':
|
|
2258
|
+
return {
|
|
2259
|
+
type: 'positional',
|
|
2260
|
+
position: node.operator,
|
|
2261
|
+
...(node.argument ? { target: fromCoreAST(node.argument) } : {}),
|
|
2262
|
+
...pos(node),
|
|
2263
|
+
};
|
|
2264
|
+
default:
|
|
2265
|
+
return {
|
|
2266
|
+
type: 'literal',
|
|
2267
|
+
value: node.value ?? null,
|
|
2268
|
+
...pos(node),
|
|
2269
|
+
};
|
|
2270
|
+
}
|
|
2271
|
+
}
|
|
2272
|
+
function convertEventHandler(node) {
|
|
2273
|
+
const event = (node.event ?? 'click');
|
|
2274
|
+
const commands = (node.commands ?? node.body ?? []);
|
|
2275
|
+
const body = commands.map(cmd => fromCoreAST(cmd));
|
|
2276
|
+
const modifiers = buildEventModifiers(node);
|
|
2277
|
+
return { type: 'event', event, modifiers, body, ...pos(node) };
|
|
2278
|
+
}
|
|
2279
|
+
function convertCommand$1(node) {
|
|
2280
|
+
const name = node.name;
|
|
2281
|
+
if (name === 'if' || name === 'unless') {
|
|
2282
|
+
return convertIfCommand(node);
|
|
2283
|
+
}
|
|
2284
|
+
if (name === 'repeat') {
|
|
2285
|
+
return convertRepeatCommand(node);
|
|
2286
|
+
}
|
|
2287
|
+
const args = (node.args ?? []).map(arg => fromCoreAST(arg));
|
|
2288
|
+
const target = node.target ? fromCoreAST(node.target) : undefined;
|
|
2289
|
+
const modifiers = node.modifiers
|
|
2290
|
+
? convertModifiers(node.modifiers)
|
|
2291
|
+
: undefined;
|
|
2292
|
+
const roles = inferRoles(name, args, modifiers, target);
|
|
2293
|
+
return {
|
|
2294
|
+
type: 'command',
|
|
2295
|
+
name,
|
|
2296
|
+
args,
|
|
2297
|
+
...(target ? { target } : {}),
|
|
2298
|
+
...(modifiers && Object.keys(modifiers).length > 0 ? { modifiers } : {}),
|
|
2299
|
+
...(roles ? { roles } : {}),
|
|
2300
|
+
...pos(node),
|
|
2301
|
+
};
|
|
2302
|
+
}
|
|
2303
|
+
function convertIfCommand(node) {
|
|
2304
|
+
const args = (node.args ?? []);
|
|
2305
|
+
let condition;
|
|
2306
|
+
let thenBranch;
|
|
2307
|
+
let elseBranch;
|
|
2308
|
+
if (node.condition) {
|
|
2309
|
+
condition = fromCoreAST(node.condition);
|
|
2310
|
+
thenBranch = (node.thenBranch ?? node.then ?? []).map(n => fromCoreAST(n));
|
|
2311
|
+
elseBranch = node.elseBranch
|
|
2312
|
+
? node.elseBranch.map(n => fromCoreAST(n))
|
|
2313
|
+
: node.else
|
|
2314
|
+
? node.else.map(n => fromCoreAST(n))
|
|
2315
|
+
: undefined;
|
|
2316
|
+
}
|
|
2317
|
+
else {
|
|
2318
|
+
condition = args[0] ? fromCoreAST(args[0]) : { type: 'literal', value: true };
|
|
2319
|
+
thenBranch = extractBlockCommands(args[1]);
|
|
2320
|
+
elseBranch = args[2] ? extractBlockCommands(args[2]) : undefined;
|
|
2321
|
+
}
|
|
2322
|
+
if (node.name === 'unless') {
|
|
2323
|
+
condition = { type: 'unary', operator: 'not', operand: condition };
|
|
2324
|
+
}
|
|
2325
|
+
return {
|
|
2326
|
+
type: 'if',
|
|
2327
|
+
condition,
|
|
2328
|
+
thenBranch,
|
|
2329
|
+
...(elseBranch ? { elseBranch } : {}),
|
|
2330
|
+
...pos(node),
|
|
2331
|
+
};
|
|
2332
|
+
}
|
|
2333
|
+
function convertRepeatCommand(node) {
|
|
2334
|
+
const args = (node.args ?? []);
|
|
2335
|
+
if (args.length === 0) {
|
|
2336
|
+
return { type: 'repeat', body: [], ...pos(node) };
|
|
2337
|
+
}
|
|
2338
|
+
const loopTypeNode = args[0];
|
|
2339
|
+
const loopType = (loopTypeNode?.name ?? loopTypeNode?.value ?? 'forever');
|
|
2340
|
+
const bodyBlock = args[args.length - 1];
|
|
2341
|
+
switch (loopType) {
|
|
2342
|
+
case 'times': {
|
|
2343
|
+
const count = args[1] ? fromCoreAST(args[1]) : undefined;
|
|
2344
|
+
return {
|
|
2345
|
+
type: 'repeat',
|
|
2346
|
+
count,
|
|
2347
|
+
body: extractBlockCommands(bodyBlock),
|
|
2348
|
+
...pos(node),
|
|
2349
|
+
};
|
|
2350
|
+
}
|
|
2351
|
+
case 'for': {
|
|
2352
|
+
const itemName = (args[1]?.value ?? 'item');
|
|
2353
|
+
const collection = args[2]
|
|
2354
|
+
? fromCoreAST(args[2])
|
|
2355
|
+
: { type: 'identifier', value: '[]' };
|
|
2356
|
+
return {
|
|
2357
|
+
type: 'foreach',
|
|
2358
|
+
itemName,
|
|
2359
|
+
collection,
|
|
2360
|
+
body: extractBlockCommands(bodyBlock),
|
|
2361
|
+
...pos(node),
|
|
2362
|
+
};
|
|
2363
|
+
}
|
|
2364
|
+
case 'while': {
|
|
2365
|
+
const condition = args[1]
|
|
2366
|
+
? fromCoreAST(args[1])
|
|
2367
|
+
: { type: 'literal', value: true };
|
|
2368
|
+
return {
|
|
2369
|
+
type: 'while',
|
|
2370
|
+
condition,
|
|
2371
|
+
body: extractBlockCommands(bodyBlock),
|
|
2372
|
+
...pos(node),
|
|
2373
|
+
};
|
|
2374
|
+
}
|
|
2375
|
+
default:
|
|
2376
|
+
return { type: 'repeat', body: extractBlockCommands(bodyBlock), ...pos(node) };
|
|
2377
|
+
}
|
|
2378
|
+
}
|
|
2379
|
+
function convertCommandSequence(node) {
|
|
2380
|
+
const commands = (node.commands ?? []);
|
|
2381
|
+
if (commands.length === 1) {
|
|
2382
|
+
return fromCoreAST(commands[0]);
|
|
2383
|
+
}
|
|
2384
|
+
return {
|
|
2385
|
+
type: 'event',
|
|
2386
|
+
event: 'click',
|
|
2387
|
+
body: commands.map(cmd => fromCoreAST(cmd)),
|
|
2388
|
+
...pos(node),
|
|
2389
|
+
};
|
|
2390
|
+
}
|
|
2391
|
+
function convertBlock(node) {
|
|
2392
|
+
const commands = (node.commands ?? []);
|
|
2393
|
+
if (commands.length === 1) {
|
|
2394
|
+
return fromCoreAST(commands[0]);
|
|
2395
|
+
}
|
|
2396
|
+
return {
|
|
2397
|
+
type: 'event',
|
|
2398
|
+
event: 'click',
|
|
2399
|
+
body: commands.map(cmd => fromCoreAST(cmd)),
|
|
2400
|
+
...pos(node),
|
|
2401
|
+
};
|
|
2402
|
+
}
|
|
2403
|
+
function convertPossessive(node) {
|
|
2404
|
+
const object = node.object
|
|
2405
|
+
? fromCoreAST(node.object)
|
|
2406
|
+
: { type: 'identifier', value: 'me' };
|
|
2407
|
+
const property = typeof node.property === 'string'
|
|
2408
|
+
? node.property
|
|
2409
|
+
: (node.property?.name ?? node.property?.value ?? '');
|
|
2410
|
+
return { type: 'possessive', object, property, ...pos(node) };
|
|
2411
|
+
}
|
|
2412
|
+
function convertMember(node) {
|
|
2413
|
+
const object = node.object
|
|
2414
|
+
? fromCoreAST(node.object)
|
|
2415
|
+
: { type: 'identifier', value: 'me' };
|
|
2416
|
+
const property = typeof node.property === 'string'
|
|
2417
|
+
? node.property
|
|
2418
|
+
: node.property
|
|
2419
|
+
? fromCoreAST(node.property)
|
|
2420
|
+
: { type: 'literal', value: '' };
|
|
2421
|
+
return {
|
|
2422
|
+
type: 'member',
|
|
2423
|
+
object,
|
|
2424
|
+
property,
|
|
2425
|
+
computed: (node.computed ?? false),
|
|
2426
|
+
...pos(node),
|
|
2427
|
+
};
|
|
2428
|
+
}
|
|
2429
|
+
function convertBinary(node) {
|
|
2430
|
+
return {
|
|
2431
|
+
type: 'binary',
|
|
2432
|
+
operator: (node.operator ?? ''),
|
|
2433
|
+
left: fromCoreAST(node.left),
|
|
2434
|
+
right: fromCoreAST(node.right),
|
|
2435
|
+
...pos(node),
|
|
2436
|
+
};
|
|
2437
|
+
}
|
|
2438
|
+
function convertCall(node) {
|
|
2439
|
+
const callee = typeof node.callee === 'string'
|
|
2440
|
+
? { type: 'identifier', value: node.callee, name: node.callee }
|
|
2441
|
+
: fromCoreAST(node.callee);
|
|
2442
|
+
const args = (node.arguments ?? node.args ?? []).map(a => fromCoreAST(a));
|
|
2443
|
+
return { type: 'call', callee, args, ...pos(node) };
|
|
2444
|
+
}
|
|
2445
|
+
function convertModifiers(modifiers) {
|
|
2446
|
+
const result = {};
|
|
2447
|
+
for (const [key, value] of Object.entries(modifiers)) {
|
|
2448
|
+
result[key] = fromCoreAST(value);
|
|
2449
|
+
}
|
|
2450
|
+
return result;
|
|
2451
|
+
}
|
|
2452
|
+
function buildEventModifiers(node) {
|
|
2453
|
+
return {
|
|
2454
|
+
...(node.once ? { once: true } : {}),
|
|
2455
|
+
...(node.debounce ? { debounce: node.debounce } : {}),
|
|
2456
|
+
...(node.throttle ? { throttle: node.throttle } : {}),
|
|
2457
|
+
...(node.prevent ? { prevent: true } : {}),
|
|
2458
|
+
...(node.stop ? { stop: true } : {}),
|
|
2459
|
+
...(node.capture ? { capture: true } : {}),
|
|
2460
|
+
...(node.passive ? { passive: true } : {}),
|
|
2461
|
+
...(node.from ? { from: node.from } : {}),
|
|
2462
|
+
...(node.selector && !node.from ? { from: node.selector } : {}),
|
|
2463
|
+
};
|
|
2464
|
+
}
|
|
2465
|
+
function extractBlockCommands(block) {
|
|
2466
|
+
if (!block)
|
|
2467
|
+
return [];
|
|
2468
|
+
if (block.type === 'block') {
|
|
2469
|
+
return (block.commands ?? []).map(cmd => fromCoreAST(cmd));
|
|
2470
|
+
}
|
|
2471
|
+
return [fromCoreAST(block)];
|
|
2472
|
+
}
|
|
2473
|
+
function inferRoles(name, args, modifiers, target) {
|
|
2474
|
+
const roles = {};
|
|
2475
|
+
switch (name) {
|
|
2476
|
+
case 'set': {
|
|
2477
|
+
if (args[0])
|
|
2478
|
+
roles.destination = args[0];
|
|
2479
|
+
const toVal = modifiers?.to;
|
|
2480
|
+
if (toVal && typeof toVal === 'object' && 'type' in toVal) {
|
|
2481
|
+
roles.patient = toVal;
|
|
2482
|
+
}
|
|
2483
|
+
else if (args[1]) {
|
|
2484
|
+
roles.patient = args[1];
|
|
2485
|
+
}
|
|
2486
|
+
break;
|
|
2487
|
+
}
|
|
2488
|
+
case 'put': {
|
|
2489
|
+
if (args[0])
|
|
2490
|
+
roles.patient = args[0];
|
|
2491
|
+
if (modifiers) {
|
|
2492
|
+
for (const key of ['into', 'before', 'after']) {
|
|
2493
|
+
const val = modifiers[key];
|
|
2494
|
+
if (val && typeof val === 'object' && 'type' in val) {
|
|
2495
|
+
roles.destination = val;
|
|
2496
|
+
roles.method = { type: 'literal', value: key };
|
|
2497
|
+
break;
|
|
2498
|
+
}
|
|
2499
|
+
}
|
|
2500
|
+
if (typeof modifiers.position === 'string') {
|
|
2501
|
+
roles.method = { type: 'literal', value: modifiers.position };
|
|
2502
|
+
}
|
|
2503
|
+
}
|
|
2504
|
+
if (!roles.destination && target) {
|
|
2505
|
+
roles.destination = target;
|
|
2506
|
+
}
|
|
2507
|
+
break;
|
|
2508
|
+
}
|
|
2509
|
+
case 'increment':
|
|
2510
|
+
case 'decrement': {
|
|
2511
|
+
if (args[0])
|
|
2512
|
+
roles.destination = args[0];
|
|
2513
|
+
const byVal = modifiers?.by;
|
|
2514
|
+
if (byVal && typeof byVal === 'object' && 'type' in byVal) {
|
|
2515
|
+
roles.quantity = byVal;
|
|
2516
|
+
}
|
|
2517
|
+
else if (args[1]) {
|
|
2518
|
+
roles.quantity = args[1];
|
|
2519
|
+
}
|
|
2520
|
+
break;
|
|
2521
|
+
}
|
|
2522
|
+
case 'fetch': {
|
|
2523
|
+
if (args[0])
|
|
2524
|
+
roles.source = args[0];
|
|
2525
|
+
const asVal = modifiers?.as;
|
|
2526
|
+
if (asVal && typeof asVal === 'object' && 'type' in asVal) {
|
|
2527
|
+
roles.responseType = asVal;
|
|
2528
|
+
}
|
|
2529
|
+
else if (typeof asVal === 'string') {
|
|
2530
|
+
roles.responseType = { type: 'identifier', value: asVal, name: asVal };
|
|
2531
|
+
}
|
|
2532
|
+
break;
|
|
2533
|
+
}
|
|
2534
|
+
case 'wait':
|
|
2535
|
+
case 'settle': {
|
|
2536
|
+
if (args[0])
|
|
2537
|
+
roles.duration = args[0];
|
|
2538
|
+
break;
|
|
2539
|
+
}
|
|
2540
|
+
case 'toggle': {
|
|
2541
|
+
if (args[0])
|
|
2542
|
+
roles.patient = args[0];
|
|
2543
|
+
if (target)
|
|
2544
|
+
roles.destination = target;
|
|
2545
|
+
break;
|
|
2546
|
+
}
|
|
2547
|
+
case 'add': {
|
|
2548
|
+
if (args[0])
|
|
2549
|
+
roles.patient = args[0];
|
|
2550
|
+
if (target)
|
|
2551
|
+
roles.destination = target;
|
|
2552
|
+
break;
|
|
2553
|
+
}
|
|
2554
|
+
case 'remove': {
|
|
2555
|
+
if (args[0])
|
|
2556
|
+
roles.patient = args[0];
|
|
2557
|
+
if (target)
|
|
2558
|
+
roles.source = target;
|
|
2559
|
+
break;
|
|
2560
|
+
}
|
|
2561
|
+
case 'send':
|
|
2562
|
+
case 'trigger': {
|
|
2563
|
+
if (args[0])
|
|
2564
|
+
roles.patient = args[0];
|
|
2565
|
+
if (target)
|
|
2566
|
+
roles.destination = target;
|
|
2567
|
+
break;
|
|
2568
|
+
}
|
|
2569
|
+
default:
|
|
2570
|
+
return null;
|
|
2571
|
+
}
|
|
2572
|
+
return Object.keys(roles).length > 0 ? roles : null;
|
|
2573
|
+
}
|
|
2574
|
+
|
|
2575
|
+
const POS = { start: 0, end: 0, line: 1, column: 0 };
|
|
2576
|
+
function nodePos(node) {
|
|
2577
|
+
return {
|
|
2578
|
+
start: node.start ?? POS.start,
|
|
2579
|
+
end: node.end ?? POS.end,
|
|
2580
|
+
line: node.line ?? POS.line,
|
|
2581
|
+
column: node.column ?? POS.column,
|
|
2582
|
+
};
|
|
2583
|
+
}
|
|
2584
|
+
function toCoreAST(node) {
|
|
2585
|
+
if (!node)
|
|
2586
|
+
return { type: 'literal', value: null, raw: 'null', ...POS };
|
|
2587
|
+
switch (node.type) {
|
|
2588
|
+
case 'event':
|
|
2589
|
+
return convertEvent(node);
|
|
2590
|
+
case 'command':
|
|
2591
|
+
return convertCommand(node);
|
|
2592
|
+
case 'if':
|
|
2593
|
+
return convertIf(node);
|
|
2594
|
+
case 'repeat':
|
|
2595
|
+
return convertRepeat(node);
|
|
2596
|
+
case 'foreach':
|
|
2597
|
+
return convertForEach(node);
|
|
2598
|
+
case 'while':
|
|
2599
|
+
return convertWhile(node);
|
|
2600
|
+
case 'literal':
|
|
2601
|
+
return {
|
|
2602
|
+
type: 'literal',
|
|
2603
|
+
value: node.value,
|
|
2604
|
+
raw: node.value === null ? 'null' : node.value === undefined ? '' : String(node.value),
|
|
2605
|
+
...nodePos(node),
|
|
2606
|
+
};
|
|
2607
|
+
case 'identifier':
|
|
2608
|
+
return { type: 'identifier', name: node.name ?? node.value, ...nodePos(node) };
|
|
2609
|
+
case 'selector':
|
|
2610
|
+
return { type: 'selector', value: node.value, ...nodePos(node) };
|
|
2611
|
+
case 'variable':
|
|
2612
|
+
return { type: 'identifier', name: node.name, scope: node.scope, ...nodePos(node) };
|
|
2613
|
+
case 'binary':
|
|
2614
|
+
return {
|
|
2615
|
+
type: 'binaryExpression',
|
|
2616
|
+
operator: node.operator,
|
|
2617
|
+
left: toCoreAST(node.left),
|
|
2618
|
+
right: toCoreAST(node.right),
|
|
2619
|
+
...nodePos(node),
|
|
2620
|
+
};
|
|
2621
|
+
case 'unary':
|
|
2622
|
+
return {
|
|
2623
|
+
type: 'unaryExpression',
|
|
2624
|
+
operator: node.operator,
|
|
2625
|
+
argument: toCoreAST(node.operand),
|
|
2626
|
+
prefix: true,
|
|
2627
|
+
...nodePos(node),
|
|
2628
|
+
};
|
|
2629
|
+
case 'member':
|
|
2630
|
+
return {
|
|
2631
|
+
type: 'memberExpression',
|
|
2632
|
+
object: toCoreAST(node.object),
|
|
2633
|
+
property: typeof node.property === 'string'
|
|
2634
|
+
? { type: 'identifier', name: node.property, ...POS }
|
|
2635
|
+
: toCoreAST(node.property),
|
|
2636
|
+
computed: node.computed ?? false,
|
|
2637
|
+
...nodePos(node),
|
|
2638
|
+
};
|
|
2639
|
+
case 'possessive':
|
|
2640
|
+
return {
|
|
2641
|
+
type: 'possessiveExpression',
|
|
2642
|
+
object: toCoreAST(node.object),
|
|
2643
|
+
property: { type: 'identifier', name: node.property, ...POS },
|
|
2644
|
+
...nodePos(node),
|
|
2645
|
+
};
|
|
2646
|
+
case 'call':
|
|
2647
|
+
return {
|
|
2648
|
+
type: 'callExpression',
|
|
2649
|
+
callee: toCoreAST(node.callee),
|
|
2650
|
+
arguments: (node.args ?? []).map(a => toCoreAST(a)),
|
|
2651
|
+
...nodePos(node),
|
|
2652
|
+
};
|
|
2653
|
+
case 'positional':
|
|
2654
|
+
return {
|
|
2655
|
+
type: 'callExpression',
|
|
2656
|
+
callee: { type: 'identifier', name: node.position, ...POS },
|
|
2657
|
+
arguments: node.target ? [toCoreAST(node.target)] : [],
|
|
2658
|
+
...nodePos(node),
|
|
2659
|
+
};
|
|
2660
|
+
default: {
|
|
2661
|
+
return { type: 'literal', value: null, raw: 'null', ...POS };
|
|
2662
|
+
}
|
|
2663
|
+
}
|
|
2664
|
+
}
|
|
2665
|
+
function convertEvent(node) {
|
|
2666
|
+
const commands = (node.body ?? []).map(cmd => toCoreAST(cmd));
|
|
2667
|
+
const modifiers = flattenEventModifiers(node.modifiers);
|
|
2668
|
+
return {
|
|
2669
|
+
type: 'eventHandler',
|
|
2670
|
+
event: node.event,
|
|
2671
|
+
commands,
|
|
2672
|
+
...modifiers,
|
|
2673
|
+
...nodePos(node),
|
|
2674
|
+
};
|
|
2675
|
+
}
|
|
2676
|
+
function convertCommand(node) {
|
|
2677
|
+
const result = {
|
|
2678
|
+
type: 'command',
|
|
2679
|
+
name: node.name,
|
|
2680
|
+
args: (node.args ?? []).map(a => toCoreAST(a)),
|
|
2681
|
+
isBlocking: false,
|
|
2682
|
+
...nodePos(node),
|
|
2683
|
+
};
|
|
2684
|
+
if (node.target) {
|
|
2685
|
+
result.target = toCoreAST(node.target);
|
|
2686
|
+
}
|
|
2687
|
+
if (node.modifiers && Object.keys(node.modifiers).length > 0) {
|
|
2688
|
+
const convertedModifiers = {};
|
|
2689
|
+
for (const [key, value] of Object.entries(node.modifiers)) {
|
|
2690
|
+
convertedModifiers[key] =
|
|
2691
|
+
value && typeof value === 'object' && 'type' in value
|
|
2692
|
+
? toCoreAST(value)
|
|
2693
|
+
: value;
|
|
2694
|
+
}
|
|
2695
|
+
result.modifiers = convertedModifiers;
|
|
2696
|
+
}
|
|
2697
|
+
return result;
|
|
2698
|
+
}
|
|
2699
|
+
function convertIf(node) {
|
|
2700
|
+
const result = {
|
|
2701
|
+
type: 'command',
|
|
2702
|
+
name: 'if',
|
|
2703
|
+
isBlocking: true,
|
|
2704
|
+
condition: toCoreAST(node.condition),
|
|
2705
|
+
thenBranch: node.thenBranch.map(n => toCoreAST(n)),
|
|
2706
|
+
args: [],
|
|
2707
|
+
...nodePos(node),
|
|
2708
|
+
};
|
|
2709
|
+
if (node.elseBranch) {
|
|
2710
|
+
result.elseBranch = node.elseBranch.map(n => toCoreAST(n));
|
|
2711
|
+
}
|
|
2712
|
+
return result;
|
|
2713
|
+
}
|
|
2714
|
+
function convertRepeat(node) {
|
|
2715
|
+
const result = {
|
|
2716
|
+
type: 'command',
|
|
2717
|
+
name: 'repeat',
|
|
2718
|
+
isBlocking: true,
|
|
2719
|
+
body: node.body.map(n => toCoreAST(n)),
|
|
2720
|
+
args: [],
|
|
2721
|
+
...nodePos(node),
|
|
2722
|
+
};
|
|
2723
|
+
if (node.count !== undefined) {
|
|
2724
|
+
result.count =
|
|
2725
|
+
typeof node.count === 'number'
|
|
2726
|
+
? { type: 'literal', value: node.count, raw: String(node.count), ...POS }
|
|
2727
|
+
: toCoreAST(node.count);
|
|
2728
|
+
result.loopVariant = 'times';
|
|
2729
|
+
}
|
|
2730
|
+
return result;
|
|
2731
|
+
}
|
|
2732
|
+
function convertForEach(node) {
|
|
2733
|
+
return {
|
|
2734
|
+
type: 'command',
|
|
2735
|
+
name: 'repeat',
|
|
2736
|
+
isBlocking: true,
|
|
2737
|
+
loopVariant: 'for',
|
|
2738
|
+
itemName: node.itemName,
|
|
2739
|
+
...(node.indexName ? { indexName: node.indexName } : {}),
|
|
2740
|
+
collection: toCoreAST(node.collection),
|
|
2741
|
+
body: node.body.map(n => toCoreAST(n)),
|
|
2742
|
+
args: [],
|
|
2743
|
+
...nodePos(node),
|
|
2744
|
+
};
|
|
2745
|
+
}
|
|
2746
|
+
function convertWhile(node) {
|
|
2747
|
+
return {
|
|
2748
|
+
type: 'command',
|
|
2749
|
+
name: 'repeat',
|
|
2750
|
+
isBlocking: true,
|
|
2751
|
+
loopVariant: 'while',
|
|
2752
|
+
condition: toCoreAST(node.condition),
|
|
2753
|
+
body: node.body.map(n => toCoreAST(n)),
|
|
2754
|
+
args: [],
|
|
2755
|
+
...nodePos(node),
|
|
2756
|
+
};
|
|
2757
|
+
}
|
|
2758
|
+
function flattenEventModifiers(modifiers) {
|
|
2759
|
+
if (!modifiers)
|
|
2760
|
+
return {};
|
|
2761
|
+
const flat = {};
|
|
2762
|
+
if (modifiers.once)
|
|
2763
|
+
flat.once = true;
|
|
2764
|
+
if (modifiers.prevent)
|
|
2765
|
+
flat.prevent = true;
|
|
2766
|
+
if (modifiers.stop)
|
|
2767
|
+
flat.stop = true;
|
|
2768
|
+
if (modifiers.capture)
|
|
2769
|
+
flat.capture = true;
|
|
2770
|
+
if (modifiers.passive)
|
|
2771
|
+
flat.passive = true;
|
|
2772
|
+
if (modifiers.debounce)
|
|
2773
|
+
flat.debounce = modifiers.debounce;
|
|
2774
|
+
if (modifiers.throttle)
|
|
2775
|
+
flat.throttle = modifiers.throttle;
|
|
2776
|
+
if (modifiers.from)
|
|
2777
|
+
flat.from = modifiers.from;
|
|
2778
|
+
return flat;
|
|
2779
|
+
}
|
|
2780
|
+
|
|
2781
|
+
var DiagnosticSeverity;
|
|
2782
|
+
(function (DiagnosticSeverity) {
|
|
2783
|
+
DiagnosticSeverity[DiagnosticSeverity["Error"] = 1] = "Error";
|
|
2784
|
+
DiagnosticSeverity[DiagnosticSeverity["Warning"] = 2] = "Warning";
|
|
2785
|
+
DiagnosticSeverity[DiagnosticSeverity["Information"] = 3] = "Information";
|
|
2786
|
+
DiagnosticSeverity[DiagnosticSeverity["Hint"] = 4] = "Hint";
|
|
2787
|
+
})(DiagnosticSeverity || (DiagnosticSeverity = {}));
|
|
2788
|
+
var SymbolKind;
|
|
2789
|
+
(function (SymbolKind) {
|
|
2790
|
+
SymbolKind[SymbolKind["File"] = 1] = "File";
|
|
2791
|
+
SymbolKind[SymbolKind["Module"] = 2] = "Module";
|
|
2792
|
+
SymbolKind[SymbolKind["Namespace"] = 3] = "Namespace";
|
|
2793
|
+
SymbolKind[SymbolKind["Package"] = 4] = "Package";
|
|
2794
|
+
SymbolKind[SymbolKind["Class"] = 5] = "Class";
|
|
2795
|
+
SymbolKind[SymbolKind["Method"] = 6] = "Method";
|
|
2796
|
+
SymbolKind[SymbolKind["Property"] = 7] = "Property";
|
|
2797
|
+
SymbolKind[SymbolKind["Field"] = 8] = "Field";
|
|
2798
|
+
SymbolKind[SymbolKind["Constructor"] = 9] = "Constructor";
|
|
2799
|
+
SymbolKind[SymbolKind["Enum"] = 10] = "Enum";
|
|
2800
|
+
SymbolKind[SymbolKind["Interface"] = 11] = "Interface";
|
|
2801
|
+
SymbolKind[SymbolKind["Function"] = 12] = "Function";
|
|
2802
|
+
SymbolKind[SymbolKind["Variable"] = 13] = "Variable";
|
|
2803
|
+
SymbolKind[SymbolKind["Constant"] = 14] = "Constant";
|
|
2804
|
+
SymbolKind[SymbolKind["String"] = 15] = "String";
|
|
2805
|
+
SymbolKind[SymbolKind["Number"] = 16] = "Number";
|
|
2806
|
+
SymbolKind[SymbolKind["Boolean"] = 17] = "Boolean";
|
|
2807
|
+
SymbolKind[SymbolKind["Array"] = 18] = "Array";
|
|
2808
|
+
SymbolKind[SymbolKind["Object"] = 19] = "Object";
|
|
2809
|
+
SymbolKind[SymbolKind["Key"] = 20] = "Key";
|
|
2810
|
+
SymbolKind[SymbolKind["Null"] = 21] = "Null";
|
|
2811
|
+
SymbolKind[SymbolKind["EnumMember"] = 22] = "EnumMember";
|
|
2812
|
+
SymbolKind[SymbolKind["Struct"] = 23] = "Struct";
|
|
2813
|
+
SymbolKind[SymbolKind["Event"] = 24] = "Event";
|
|
2814
|
+
SymbolKind[SymbolKind["Operator"] = 25] = "Operator";
|
|
2815
|
+
SymbolKind[SymbolKind["TypeParameter"] = 26] = "TypeParameter";
|
|
2816
|
+
})(SymbolKind || (SymbolKind = {}));
|
|
2817
|
+
var CompletionItemKind;
|
|
2818
|
+
(function (CompletionItemKind) {
|
|
2819
|
+
CompletionItemKind[CompletionItemKind["Text"] = 1] = "Text";
|
|
2820
|
+
CompletionItemKind[CompletionItemKind["Method"] = 2] = "Method";
|
|
2821
|
+
CompletionItemKind[CompletionItemKind["Function"] = 3] = "Function";
|
|
2822
|
+
CompletionItemKind[CompletionItemKind["Constructor"] = 4] = "Constructor";
|
|
2823
|
+
CompletionItemKind[CompletionItemKind["Field"] = 5] = "Field";
|
|
2824
|
+
CompletionItemKind[CompletionItemKind["Variable"] = 6] = "Variable";
|
|
2825
|
+
CompletionItemKind[CompletionItemKind["Class"] = 7] = "Class";
|
|
2826
|
+
CompletionItemKind[CompletionItemKind["Interface"] = 8] = "Interface";
|
|
2827
|
+
CompletionItemKind[CompletionItemKind["Module"] = 9] = "Module";
|
|
2828
|
+
CompletionItemKind[CompletionItemKind["Property"] = 10] = "Property";
|
|
2829
|
+
CompletionItemKind[CompletionItemKind["Unit"] = 11] = "Unit";
|
|
2830
|
+
CompletionItemKind[CompletionItemKind["Value"] = 12] = "Value";
|
|
2831
|
+
CompletionItemKind[CompletionItemKind["Enum"] = 13] = "Enum";
|
|
2832
|
+
CompletionItemKind[CompletionItemKind["Keyword"] = 14] = "Keyword";
|
|
2833
|
+
CompletionItemKind[CompletionItemKind["Snippet"] = 15] = "Snippet";
|
|
2834
|
+
CompletionItemKind[CompletionItemKind["Color"] = 16] = "Color";
|
|
2835
|
+
CompletionItemKind[CompletionItemKind["File"] = 17] = "File";
|
|
2836
|
+
CompletionItemKind[CompletionItemKind["Reference"] = 18] = "Reference";
|
|
2837
|
+
CompletionItemKind[CompletionItemKind["Folder"] = 19] = "Folder";
|
|
2838
|
+
CompletionItemKind[CompletionItemKind["EnumMember"] = 20] = "EnumMember";
|
|
2839
|
+
CompletionItemKind[CompletionItemKind["Constant"] = 21] = "Constant";
|
|
2840
|
+
CompletionItemKind[CompletionItemKind["Struct"] = 22] = "Struct";
|
|
2841
|
+
CompletionItemKind[CompletionItemKind["Event"] = 23] = "Event";
|
|
2842
|
+
CompletionItemKind[CompletionItemKind["Operator"] = 24] = "Operator";
|
|
2843
|
+
CompletionItemKind[CompletionItemKind["TypeParameter"] = 25] = "TypeParameter";
|
|
2844
|
+
})(CompletionItemKind || (CompletionItemKind = {}));
|
|
2845
|
+
function interchangeToLSPDiagnostics(nodes, options) {
|
|
2846
|
+
const source = options?.source ?? 'lokascript';
|
|
2847
|
+
const cyclomaticThreshold = options?.cyclomaticThreshold ?? 10;
|
|
2848
|
+
const cognitiveThreshold = options?.cognitiveThreshold ?? 15;
|
|
2849
|
+
const diagnostics = [];
|
|
2850
|
+
for (const node of nodes) {
|
|
2851
|
+
const cyclomatic = calculateCyclomatic(node);
|
|
2852
|
+
const cognitive = calculateCognitive(node);
|
|
2853
|
+
const smells = detectSmells(node);
|
|
2854
|
+
for (const smell of smells) {
|
|
2855
|
+
diagnostics.push({
|
|
2856
|
+
range: nodeToRange(smell.node),
|
|
2857
|
+
severity: smell.severity,
|
|
2858
|
+
code: smell.code,
|
|
2859
|
+
source,
|
|
2860
|
+
message: smell.message,
|
|
2861
|
+
});
|
|
2862
|
+
}
|
|
2863
|
+
if (cyclomatic > cyclomaticThreshold) {
|
|
2864
|
+
diagnostics.push({
|
|
2865
|
+
range: nodeToRange(node),
|
|
2866
|
+
severity: DiagnosticSeverity.Warning,
|
|
2867
|
+
code: 'high-cyclomatic-complexity',
|
|
2868
|
+
source,
|
|
2869
|
+
message: `High cyclomatic complexity: ${cyclomatic} (recommended: <= ${cyclomaticThreshold})`,
|
|
2870
|
+
});
|
|
2871
|
+
}
|
|
2872
|
+
if (cognitive > cognitiveThreshold) {
|
|
2873
|
+
diagnostics.push({
|
|
2874
|
+
range: nodeToRange(node),
|
|
2875
|
+
severity: DiagnosticSeverity.Information,
|
|
2876
|
+
code: 'high-cognitive-complexity',
|
|
2877
|
+
source,
|
|
2878
|
+
message: `High cognitive complexity: ${cognitive} (recommended: <= ${cognitiveThreshold})`,
|
|
2879
|
+
});
|
|
2880
|
+
}
|
|
2881
|
+
}
|
|
2882
|
+
return diagnostics;
|
|
2883
|
+
}
|
|
2884
|
+
function interchangeToLSPSymbols(nodes) {
|
|
2885
|
+
const symbols = [];
|
|
2886
|
+
for (const node of nodes) {
|
|
2887
|
+
const symbol = nodeToSymbol(node);
|
|
2888
|
+
if (symbol)
|
|
2889
|
+
symbols.push(symbol);
|
|
2890
|
+
}
|
|
2891
|
+
return symbols;
|
|
2892
|
+
}
|
|
2893
|
+
function interchangeToLSPHover(nodes, position) {
|
|
2894
|
+
const node = findNodeAtPosition(nodes, position);
|
|
2895
|
+
if (!node)
|
|
2896
|
+
return null;
|
|
2897
|
+
let contents = `**${friendlyTypeName(node)}**\n\n`;
|
|
2898
|
+
switch (node.type) {
|
|
2899
|
+
case 'event':
|
|
2900
|
+
contents += `Event: \`${node.event}\`\n\n`;
|
|
2901
|
+
break;
|
|
2902
|
+
case 'command':
|
|
2903
|
+
contents += `Command: \`${node.name}\`\n\n`;
|
|
2904
|
+
break;
|
|
2905
|
+
case 'if':
|
|
2906
|
+
contents += 'Conditional branch\n\n';
|
|
2907
|
+
break;
|
|
2908
|
+
case 'repeat':
|
|
2909
|
+
case 'foreach':
|
|
2910
|
+
case 'while':
|
|
2911
|
+
contents += 'Loop construct\n\n';
|
|
2912
|
+
break;
|
|
2913
|
+
}
|
|
2914
|
+
if (['event', 'if', 'repeat', 'foreach', 'while'].includes(node.type)) {
|
|
2915
|
+
const cyclomatic = calculateCyclomatic(node);
|
|
2916
|
+
const cognitive = calculateCognitive(node);
|
|
2917
|
+
contents += `**Complexity:** cyclomatic ${cyclomatic}, cognitive ${cognitive}\n`;
|
|
2918
|
+
}
|
|
2919
|
+
return {
|
|
2920
|
+
contents,
|
|
2921
|
+
range: nodeToRange(node),
|
|
2922
|
+
};
|
|
2923
|
+
}
|
|
2924
|
+
function interchangeToLSPCompletions(nodes, position) {
|
|
2925
|
+
const node = findNodeAtPosition(nodes, position);
|
|
2926
|
+
if (!node) {
|
|
2927
|
+
return [
|
|
2928
|
+
{ label: 'on', kind: CompletionItemKind.Keyword, detail: 'Event handler' },
|
|
2929
|
+
{ label: 'init', kind: CompletionItemKind.Keyword, detail: 'Initialization' },
|
|
2930
|
+
{ label: 'behavior', kind: CompletionItemKind.Keyword, detail: 'Behavior definition' },
|
|
2931
|
+
{ label: 'def', kind: CompletionItemKind.Keyword, detail: 'Function definition' },
|
|
2932
|
+
];
|
|
2933
|
+
}
|
|
2934
|
+
switch (node.type) {
|
|
2935
|
+
case 'event':
|
|
2936
|
+
return [
|
|
2937
|
+
{ label: 'add', kind: CompletionItemKind.Method, detail: 'Add class/attribute' },
|
|
2938
|
+
{ label: 'remove', kind: CompletionItemKind.Method, detail: 'Remove class/attribute' },
|
|
2939
|
+
{ label: 'toggle', kind: CompletionItemKind.Method, detail: 'Toggle class/attribute' },
|
|
2940
|
+
{ label: 'put', kind: CompletionItemKind.Method, detail: 'Set content/value' },
|
|
2941
|
+
{ label: 'set', kind: CompletionItemKind.Method, detail: 'Set variable/property' },
|
|
2942
|
+
{ label: 'fetch', kind: CompletionItemKind.Method, detail: 'HTTP request' },
|
|
2943
|
+
{ label: 'if', kind: CompletionItemKind.Keyword, detail: 'Conditional' },
|
|
2944
|
+
{ label: 'repeat', kind: CompletionItemKind.Keyword, detail: 'Loop' },
|
|
2945
|
+
];
|
|
2946
|
+
case 'if':
|
|
2947
|
+
return [
|
|
2948
|
+
{ label: 'then', kind: CompletionItemKind.Keyword, detail: 'Then clause' },
|
|
2949
|
+
{ label: 'else', kind: CompletionItemKind.Keyword, detail: 'Else clause' },
|
|
2950
|
+
{ label: 'end', kind: CompletionItemKind.Keyword, detail: 'End conditional' },
|
|
2951
|
+
];
|
|
2952
|
+
case 'repeat':
|
|
2953
|
+
case 'foreach':
|
|
2954
|
+
case 'while':
|
|
2955
|
+
return [{ label: 'end', kind: CompletionItemKind.Keyword, detail: 'End loop' }];
|
|
2956
|
+
default:
|
|
2957
|
+
return [
|
|
2958
|
+
{ label: 'me', kind: CompletionItemKind.Variable, detail: 'Current element' },
|
|
2959
|
+
{ label: 'it', kind: CompletionItemKind.Variable, detail: 'Current context' },
|
|
2960
|
+
{ label: 'you', kind: CompletionItemKind.Variable, detail: 'Event target' },
|
|
2961
|
+
];
|
|
2962
|
+
}
|
|
2963
|
+
}
|
|
2964
|
+
function calculateCyclomatic(node) {
|
|
2965
|
+
let complexity = 1;
|
|
2966
|
+
walkInterchange(node, n => {
|
|
2967
|
+
if (isDecisionPoint(n))
|
|
2968
|
+
complexity++;
|
|
2969
|
+
});
|
|
2970
|
+
return complexity;
|
|
2971
|
+
}
|
|
2972
|
+
function calculateCognitive(node) {
|
|
2973
|
+
let complexity = 0;
|
|
2974
|
+
function walk(n, depth) {
|
|
2975
|
+
const nests = isNestingConstruct(n);
|
|
2976
|
+
const newDepth = nests ? depth + 1 : depth;
|
|
2977
|
+
if (isDecisionPoint(n)) {
|
|
2978
|
+
complexity += 1 + depth;
|
|
2979
|
+
}
|
|
2980
|
+
for (const child of getChildren(n)) {
|
|
2981
|
+
walk(child, newDepth);
|
|
2982
|
+
}
|
|
2983
|
+
}
|
|
2984
|
+
walk(node, 0);
|
|
2985
|
+
return complexity;
|
|
2986
|
+
}
|
|
2987
|
+
function detectSmells(root) {
|
|
2988
|
+
const smells = [];
|
|
2989
|
+
checkNesting(root, 0, smells);
|
|
2990
|
+
if (root.type === 'event') {
|
|
2991
|
+
const body = root.body ?? [];
|
|
2992
|
+
if (body.length > 15) {
|
|
2993
|
+
smells.push({
|
|
2994
|
+
node: root,
|
|
2995
|
+
code: 'long-event-handler',
|
|
2996
|
+
message: `Event handler has ${body.length} commands (consider extracting behavior)`,
|
|
2997
|
+
severity: DiagnosticSeverity.Information,
|
|
2998
|
+
});
|
|
2999
|
+
}
|
|
3000
|
+
}
|
|
3001
|
+
return smells;
|
|
3002
|
+
}
|
|
3003
|
+
function checkNesting(node, depth, smells) {
|
|
3004
|
+
if (depth > 4 && isNestingConstruct(node)) {
|
|
3005
|
+
smells.push({
|
|
3006
|
+
node,
|
|
3007
|
+
code: 'deep-nesting',
|
|
3008
|
+
message: `Deeply nested ${node.type} (depth ${depth}, recommended: <= 4)`,
|
|
3009
|
+
severity: DiagnosticSeverity.Warning,
|
|
3010
|
+
});
|
|
3011
|
+
}
|
|
3012
|
+
const children = getChildren(node);
|
|
3013
|
+
const newDepth = isNestingConstruct(node) ? depth + 1 : depth;
|
|
3014
|
+
for (const child of children) {
|
|
3015
|
+
checkNesting(child, newDepth, smells);
|
|
3016
|
+
}
|
|
3017
|
+
}
|
|
3018
|
+
function walkInterchange(node, visitor) {
|
|
3019
|
+
visitor(node);
|
|
3020
|
+
for (const child of getChildren(node)) {
|
|
3021
|
+
walkInterchange(child, visitor);
|
|
3022
|
+
}
|
|
3023
|
+
}
|
|
3024
|
+
function getChildren(node) {
|
|
3025
|
+
const children = [];
|
|
3026
|
+
switch (node.type) {
|
|
3027
|
+
case 'event': {
|
|
3028
|
+
const e = node;
|
|
3029
|
+
if (e.body)
|
|
3030
|
+
children.push(...e.body);
|
|
3031
|
+
if (e.target)
|
|
3032
|
+
children.push(e.target);
|
|
3033
|
+
break;
|
|
3034
|
+
}
|
|
3035
|
+
case 'command': {
|
|
3036
|
+
const c = node;
|
|
3037
|
+
if (c.args)
|
|
3038
|
+
children.push(...c.args);
|
|
3039
|
+
if (c.target)
|
|
3040
|
+
children.push(c.target);
|
|
3041
|
+
break;
|
|
3042
|
+
}
|
|
3043
|
+
case 'if': {
|
|
3044
|
+
const i = node;
|
|
3045
|
+
children.push(i.condition);
|
|
3046
|
+
children.push(...i.thenBranch);
|
|
3047
|
+
if (i.elseBranch)
|
|
3048
|
+
children.push(...i.elseBranch);
|
|
3049
|
+
if (i.elseIfBranches) {
|
|
3050
|
+
for (const branch of i.elseIfBranches) {
|
|
3051
|
+
children.push(branch.condition);
|
|
3052
|
+
children.push(...branch.body);
|
|
3053
|
+
}
|
|
3054
|
+
}
|
|
3055
|
+
break;
|
|
3056
|
+
}
|
|
3057
|
+
case 'repeat': {
|
|
3058
|
+
const r = node;
|
|
3059
|
+
if (r.count && typeof r.count !== 'number')
|
|
3060
|
+
children.push(r.count);
|
|
3061
|
+
if (r.whileCondition)
|
|
3062
|
+
children.push(r.whileCondition);
|
|
3063
|
+
children.push(...r.body);
|
|
3064
|
+
break;
|
|
3065
|
+
}
|
|
3066
|
+
case 'foreach': {
|
|
3067
|
+
const f = node;
|
|
3068
|
+
children.push(f.collection);
|
|
3069
|
+
children.push(...f.body);
|
|
3070
|
+
break;
|
|
3071
|
+
}
|
|
3072
|
+
case 'while': {
|
|
3073
|
+
const w = node;
|
|
3074
|
+
children.push(w.condition);
|
|
3075
|
+
children.push(...w.body);
|
|
3076
|
+
break;
|
|
3077
|
+
}
|
|
3078
|
+
case 'binary':
|
|
3079
|
+
children.push(node.left, node.right);
|
|
3080
|
+
break;
|
|
3081
|
+
case 'unary':
|
|
3082
|
+
children.push(node.operand);
|
|
3083
|
+
break;
|
|
3084
|
+
case 'member':
|
|
3085
|
+
children.push(node.object);
|
|
3086
|
+
if (typeof node.property !== 'string')
|
|
3087
|
+
children.push(node.property);
|
|
3088
|
+
break;
|
|
3089
|
+
case 'possessive':
|
|
3090
|
+
children.push(node.object);
|
|
3091
|
+
break;
|
|
3092
|
+
case 'call':
|
|
3093
|
+
children.push(node.callee);
|
|
3094
|
+
if (node.args)
|
|
3095
|
+
children.push(...node.args);
|
|
3096
|
+
break;
|
|
3097
|
+
}
|
|
3098
|
+
return children;
|
|
3099
|
+
}
|
|
3100
|
+
function isDecisionPoint(node) {
|
|
3101
|
+
return node.type === 'if' || node.type === 'while' || node.type === 'foreach';
|
|
3102
|
+
}
|
|
3103
|
+
function isNestingConstruct(node) {
|
|
3104
|
+
return (node.type === 'if' ||
|
|
3105
|
+
node.type === 'repeat' ||
|
|
3106
|
+
node.type === 'foreach' ||
|
|
3107
|
+
node.type === 'while' ||
|
|
3108
|
+
node.type === 'event');
|
|
3109
|
+
}
|
|
3110
|
+
function nodeToRange(node) {
|
|
3111
|
+
const startLine = (node.line ?? 1) - 1;
|
|
3112
|
+
const startChar = node.column ?? 0;
|
|
3113
|
+
if (node.end !== undefined && node.start !== undefined) {
|
|
3114
|
+
const length = node.end - node.start;
|
|
3115
|
+
return {
|
|
3116
|
+
start: { line: startLine, character: startChar },
|
|
3117
|
+
end: { line: startLine, character: startChar + length },
|
|
3118
|
+
};
|
|
3119
|
+
}
|
|
3120
|
+
const estimatedLength = estimateLength(node);
|
|
3121
|
+
return {
|
|
3122
|
+
start: { line: startLine, character: startChar },
|
|
3123
|
+
end: { line: startLine, character: startChar + estimatedLength },
|
|
3124
|
+
};
|
|
3125
|
+
}
|
|
3126
|
+
function estimateLength(node) {
|
|
3127
|
+
switch (node.type) {
|
|
3128
|
+
case 'event':
|
|
3129
|
+
return 3 + (node.event?.length ?? 5);
|
|
3130
|
+
case 'command':
|
|
3131
|
+
return node.name?.length ?? 5;
|
|
3132
|
+
case 'selector':
|
|
3133
|
+
return (node.value?.length ?? 5) + 1;
|
|
3134
|
+
case 'identifier':
|
|
3135
|
+
return node.value?.length ?? 2;
|
|
3136
|
+
case 'literal':
|
|
3137
|
+
return String(node.value ?? '').length + 2;
|
|
3138
|
+
default:
|
|
3139
|
+
return 10;
|
|
3140
|
+
}
|
|
3141
|
+
}
|
|
3142
|
+
function findNodeAtPosition(nodes, position) {
|
|
3143
|
+
const targetLine = position.line + 1;
|
|
3144
|
+
const targetChar = position.character;
|
|
3145
|
+
let best = null;
|
|
3146
|
+
let bestSize = Infinity;
|
|
3147
|
+
for (const root of nodes) {
|
|
3148
|
+
walkInterchange(root, node => {
|
|
3149
|
+
const nodeLine = node.line ?? 1;
|
|
3150
|
+
if (nodeLine !== targetLine)
|
|
3151
|
+
return;
|
|
3152
|
+
const nodeStart = node.column ?? 0;
|
|
3153
|
+
let nodeEnd;
|
|
3154
|
+
if (node.end !== undefined && node.start !== undefined) {
|
|
3155
|
+
nodeEnd = nodeStart + (node.end - node.start);
|
|
3156
|
+
}
|
|
3157
|
+
else {
|
|
3158
|
+
nodeEnd = nodeStart + estimateLength(node);
|
|
3159
|
+
}
|
|
3160
|
+
if (targetChar >= nodeStart && targetChar <= nodeEnd) {
|
|
3161
|
+
const size = nodeEnd - nodeStart;
|
|
3162
|
+
if (size < bestSize) {
|
|
3163
|
+
best = node;
|
|
3164
|
+
bestSize = size;
|
|
3165
|
+
}
|
|
3166
|
+
}
|
|
3167
|
+
});
|
|
3168
|
+
}
|
|
3169
|
+
if (!best) {
|
|
3170
|
+
for (const root of nodes) {
|
|
3171
|
+
walkInterchange(root, node => {
|
|
3172
|
+
if ((node.line ?? 1) === targetLine) {
|
|
3173
|
+
const priority = getNodePriority(node.type);
|
|
3174
|
+
if (!best || priority > getNodePriority(best.type)) {
|
|
3175
|
+
best = node;
|
|
3176
|
+
}
|
|
3177
|
+
}
|
|
3178
|
+
});
|
|
3179
|
+
}
|
|
3180
|
+
}
|
|
3181
|
+
return best;
|
|
3182
|
+
}
|
|
3183
|
+
function getNodePriority(type) {
|
|
3184
|
+
switch (type) {
|
|
3185
|
+
case 'event':
|
|
3186
|
+
return 3;
|
|
3187
|
+
case 'command':
|
|
3188
|
+
return 2;
|
|
3189
|
+
case 'if':
|
|
3190
|
+
return 2;
|
|
3191
|
+
case 'selector':
|
|
3192
|
+
return 1;
|
|
3193
|
+
case 'identifier':
|
|
3194
|
+
return 1;
|
|
3195
|
+
default:
|
|
3196
|
+
return 0;
|
|
3197
|
+
}
|
|
3198
|
+
}
|
|
3199
|
+
function friendlyTypeName(node) {
|
|
3200
|
+
switch (node.type) {
|
|
3201
|
+
case 'event':
|
|
3202
|
+
return 'Event Handler';
|
|
3203
|
+
case 'command':
|
|
3204
|
+
return `Command (${node.name})`;
|
|
3205
|
+
case 'if':
|
|
3206
|
+
return 'Conditional';
|
|
3207
|
+
case 'repeat':
|
|
3208
|
+
return 'Repeat Loop';
|
|
3209
|
+
case 'foreach':
|
|
3210
|
+
return 'For Each Loop';
|
|
3211
|
+
case 'while':
|
|
3212
|
+
return 'While Loop';
|
|
3213
|
+
case 'binary':
|
|
3214
|
+
return `Binary Expression (${node.operator})`;
|
|
3215
|
+
case 'unary':
|
|
3216
|
+
return `Unary Expression (${node.operator})`;
|
|
3217
|
+
case 'literal':
|
|
3218
|
+
return 'Literal';
|
|
3219
|
+
case 'identifier':
|
|
3220
|
+
return 'Identifier';
|
|
3221
|
+
case 'selector':
|
|
3222
|
+
return 'CSS Selector';
|
|
3223
|
+
case 'variable':
|
|
3224
|
+
return 'Variable';
|
|
3225
|
+
case 'member':
|
|
3226
|
+
return 'Member Access';
|
|
3227
|
+
case 'possessive':
|
|
3228
|
+
return 'Possessive Expression';
|
|
3229
|
+
case 'call':
|
|
3230
|
+
return 'Function Call';
|
|
3231
|
+
default:
|
|
3232
|
+
return node.type;
|
|
3233
|
+
}
|
|
3234
|
+
}
|
|
3235
|
+
function nodeToSymbol(node) {
|
|
3236
|
+
switch (node.type) {
|
|
3237
|
+
case 'event': {
|
|
3238
|
+
const e = node;
|
|
3239
|
+
const range = nodeToRange(node);
|
|
3240
|
+
return {
|
|
3241
|
+
name: `on ${e.event}`,
|
|
3242
|
+
detail: 'Event Handler',
|
|
3243
|
+
kind: SymbolKind.Event,
|
|
3244
|
+
range,
|
|
3245
|
+
selectionRange: range,
|
|
3246
|
+
children: extractBodySymbols(e.body ?? []),
|
|
3247
|
+
};
|
|
3248
|
+
}
|
|
3249
|
+
case 'command': {
|
|
3250
|
+
const c = node;
|
|
3251
|
+
const range = nodeToRange(node);
|
|
3252
|
+
return {
|
|
3253
|
+
name: c.name,
|
|
3254
|
+
detail: 'Command',
|
|
3255
|
+
kind: SymbolKind.Method,
|
|
3256
|
+
range,
|
|
3257
|
+
selectionRange: range,
|
|
3258
|
+
};
|
|
3259
|
+
}
|
|
3260
|
+
case 'if': {
|
|
3261
|
+
const range = nodeToRange(node);
|
|
3262
|
+
return {
|
|
3263
|
+
name: 'if',
|
|
3264
|
+
detail: 'Conditional',
|
|
3265
|
+
kind: SymbolKind.Struct,
|
|
3266
|
+
range,
|
|
3267
|
+
selectionRange: range,
|
|
3268
|
+
children: extractBodySymbols(node.thenBranch),
|
|
3269
|
+
};
|
|
3270
|
+
}
|
|
3271
|
+
case 'repeat': {
|
|
3272
|
+
const range = nodeToRange(node);
|
|
3273
|
+
return {
|
|
3274
|
+
name: 'repeat',
|
|
3275
|
+
detail: 'Loop',
|
|
3276
|
+
kind: SymbolKind.Enum,
|
|
3277
|
+
range,
|
|
3278
|
+
selectionRange: range,
|
|
3279
|
+
children: extractBodySymbols(node.body),
|
|
3280
|
+
};
|
|
3281
|
+
}
|
|
3282
|
+
case 'foreach': {
|
|
3283
|
+
const f = node;
|
|
3284
|
+
const range = nodeToRange(node);
|
|
3285
|
+
return {
|
|
3286
|
+
name: `for each ${f.itemName}`,
|
|
3287
|
+
detail: 'Loop',
|
|
3288
|
+
kind: SymbolKind.Enum,
|
|
3289
|
+
range,
|
|
3290
|
+
selectionRange: range,
|
|
3291
|
+
children: extractBodySymbols(f.body),
|
|
3292
|
+
};
|
|
3293
|
+
}
|
|
3294
|
+
case 'while': {
|
|
3295
|
+
const range = nodeToRange(node);
|
|
3296
|
+
return {
|
|
3297
|
+
name: 'while',
|
|
3298
|
+
detail: 'Loop',
|
|
3299
|
+
kind: SymbolKind.Enum,
|
|
3300
|
+
range,
|
|
3301
|
+
selectionRange: range,
|
|
3302
|
+
children: extractBodySymbols(node.body),
|
|
3303
|
+
};
|
|
3304
|
+
}
|
|
3305
|
+
default:
|
|
3306
|
+
return null;
|
|
3307
|
+
}
|
|
3308
|
+
}
|
|
3309
|
+
function extractBodySymbols(body) {
|
|
3310
|
+
const symbols = [];
|
|
3311
|
+
for (const node of body) {
|
|
3312
|
+
const symbol = nodeToSymbol(node);
|
|
3313
|
+
if (symbol)
|
|
3314
|
+
symbols.push(symbol);
|
|
3315
|
+
}
|
|
3316
|
+
return symbols;
|
|
3317
|
+
}
|
|
3318
|
+
|
|
3319
|
+
export { ASTVisitor, analyzeDependencies, analyzeMetrics, analyzePatterns, applyOptimizationPasses, calculateCognitive, calculateComplexity, calculateCyclomatic, countNodeTypes, createOptimizationPass, createTypeCollector, createVisitorContext, detectCodeSmells, extractCommonExpressions, findDeadCode, findFirst, findNodes, format, fromCoreAST, generate, generateCommand, generateDocumentation, generateExpression, generateHTML, generateJSON, generateMarkdown, generateWithMetadata, getAncestors, inlineVariables, interchangeToLSPCompletions, interchangeToLSPDiagnostics, interchangeToLSPHover, interchangeToLSPSymbols, measureDepth, minify, normalize, optimize, parseSelector, query, queryAll, queryXPath, suggestOptimizations, toCoreAST, transform, visit };
|
|
3320
|
+
//# sourceMappingURL=index.mjs.map
|