├── .circleci └── config.yml ├── .editorconfig ├── .eslintignore ├── .eslintrc.cjs ├── .gitattributes ├── .github └── ISSUE_TEMPLATE.md ├── .gitignore ├── .prettierrc.json ├── .vscode ├── launch.json ├── settings.json └── tasks.json ├── .vscodeignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── _code-dep └── index.ts ├── client ├── .eslintrc.cjs ├── package-lock.json ├── package.json ├── src │ ├── extension.ts │ ├── feature │ │ ├── relatedFiles.ts │ │ └── userInput │ │ │ ├── multiStepInput.ts │ │ │ └── userInput.ts │ └── shared │ │ ├── constants.ts │ │ └── types │ │ └── types.ts └── tsconfig.json ├── docs ├── developer │ ├── adding-new-command.md │ ├── architechture.md │ ├── architecture-graph.md │ ├── corner-cases.md │ ├── decisions.md │ ├── design.md │ ├── developers.md │ ├── how-to-use.md │ ├── new-features.md │ ├── performance.md │ ├── term-and-abbreviations.md │ └── testing.md └── user │ ├── README.md │ ├── codeActions.readme.md │ ├── commands.readme.md │ ├── completions.readme.md │ ├── definitions.readme.md │ ├── rename.readme.md │ └── symbols.readme.md ├── images ├── architecture.png ├── aurelia-logo.png ├── completions-complex.png ├── completions.gif ├── completions.png ├── definitions.png ├── dependencygraph.png ├── logo.png └── symbols.png ├── jest.config.js ├── language-configuration.json ├── package-lock.json ├── package.json ├── server ├── .eslintrc.cjs ├── package-lock.json ├── package.json ├── src │ ├── aot │ │ ├── AureliaComponents.ts │ │ ├── AureliaProgram.ts │ │ ├── aotTypes.ts │ │ ├── getAureliaComponentList.ts │ │ ├── parser │ │ │ ├── parser-types.ts │ │ │ └── regions │ │ │ │ ├── RegionParser.ts │ │ │ │ ├── ViewRegions.ts │ │ │ │ ├── ViewRegionsVisitor.ts │ │ │ │ ├── findSpecificRegion.ts │ │ │ │ ├── languageServer │ │ │ │ ├── AbstractRegionLanguageService.ts │ │ │ │ ├── AttributeInterpolationLanguageService.ts │ │ │ │ ├── AttributeLanguageService.ts │ │ │ │ ├── AureliaHtmlLanguageService.ts │ │ │ │ ├── BindableAttributeLanguageService.ts │ │ │ │ ├── CustomElementLanguageService.ts │ │ │ │ ├── ImportLanguageService.ts │ │ │ │ ├── RepeatForLanguageService.ts │ │ │ │ ├── TextInterpolationLanguageService.ts │ │ │ │ └── ValueConverterLanguageService.ts │ │ │ │ ├── rangeFromRegion.ts │ │ │ │ └── visitors │ │ │ │ └── RegionLanguageServerVisitor.ts │ │ ├── staticAnalysis │ │ │ ├── ConventionService.ts │ │ │ ├── CustomElementAnalyser.ts │ │ │ └── ValueConverterAnalyser.ts │ │ └── tsMorph │ │ │ ├── AureliaTsMorph.ts │ │ │ └── tsMorphClass.ts │ ├── common │ │ ├── @aurelia-runtime-patch │ │ │ └── src │ │ │ │ ├── README.md │ │ │ │ ├── alias.ts │ │ │ │ ├── binding-behavior.ts │ │ │ │ ├── binding │ │ │ │ ├── ast.ts │ │ │ │ ├── connectable.ts │ │ │ │ └── expression-parser.ts │ │ │ │ ├── index.ts │ │ │ │ ├── observation.ts │ │ │ │ ├── observation │ │ │ │ ├── array-observer.ts │ │ │ │ ├── binding-context.ts │ │ │ │ ├── collection-length-observer.ts │ │ │ │ ├── computed-observer.ts │ │ │ │ ├── connectable-switcher.ts │ │ │ │ ├── dirty-checker.ts │ │ │ │ ├── flush-queue.ts │ │ │ │ ├── map-observer.ts │ │ │ │ ├── observable.ts │ │ │ │ ├── observation.ts │ │ │ │ ├── observer-locator.ts │ │ │ │ ├── primitive-observer.ts │ │ │ │ ├── property-accessor.ts │ │ │ │ ├── proxy-observation.ts │ │ │ │ ├── set-observer.ts │ │ │ │ ├── setter-observer.ts │ │ │ │ ├── signaler.ts │ │ │ │ └── subscriber-collection.ts │ │ │ │ ├── startup-sequence.md │ │ │ │ ├── utilities-objects.ts │ │ │ │ └── value-converter.ts │ │ ├── AureliaUtils.ts │ │ ├── MyLodash.ts │ │ ├── _save.ts │ │ ├── className.ts │ │ ├── client │ │ │ └── client.ts │ │ ├── constants.ts │ │ ├── debugging │ │ │ ├── debuggingSandbox.ts │ │ │ └── saveDataToDisk.ts │ │ ├── diagnosticMessages │ │ │ ├── DiagnosticMessages.ts │ │ │ └── diagnosticMessagesData.ts │ │ ├── documens │ │ │ ├── OffsetUtils.ts │ │ │ ├── PositionUtils.ts │ │ │ ├── TextDocumentUtils.ts │ │ │ ├── find-source-word.ts │ │ │ ├── related.ts │ │ │ ├── selections.ts │ │ │ ├── split-by-word-separators.ts │ │ │ └── xScopeUtils.ts │ │ ├── global.d.ts │ │ ├── logging │ │ │ ├── WallabyUtils.ts │ │ │ ├── errorStackLogging.ts │ │ │ ├── logger.ts │ │ │ └── performance-measure.ts │ │ ├── object │ │ │ └── ObjectUtils.ts │ │ ├── parseExpression │ │ │ └── ParseExpressionUtil.ts │ │ ├── services │ │ │ ├── AnalyzerService.ts │ │ │ └── RegionService.ts │ │ ├── string │ │ │ └── StringUtils.ts │ │ ├── template │ │ │ └── aurelia-attributes.ts │ │ ├── types │ │ │ └── types.ts │ │ └── view │ │ │ ├── document-parsing.ts │ │ │ ├── project-position.ts │ │ │ └── uri-utils.ts │ ├── configuration │ │ └── DocumentSettings.ts │ ├── core │ │ ├── AureliaProjects.ts │ │ ├── aureliaServer.ts │ │ ├── container.ts │ │ └── depdencenyInjection.ts │ ├── feature │ │ ├── codeAction │ │ │ └── onCodeAction.ts │ │ ├── commands │ │ │ ├── declareViewModelVariable │ │ │ │ ├── README.md │ │ │ │ └── declareViewModelVariable.ts │ │ │ ├── extractComponent │ │ │ │ ├── README.md │ │ │ │ └── extractComponent.ts │ │ │ └── onExecuteCommand.ts │ │ ├── completions │ │ │ ├── aureliaKeyWordCompletions.ts │ │ │ ├── completions.ts │ │ │ ├── createAureliaTemplateAttributeCompletions.ts │ │ │ ├── onCompletions.ts │ │ │ ├── virtualCompletion.ts │ │ │ └── virtualCompletion2.ts │ │ ├── content │ │ │ └── changeContent.ts │ │ ├── definition │ │ │ ├── accessScopeDefinition.ts │ │ │ ├── aureliaDefintion.ts │ │ │ ├── onDefinitions.ts │ │ │ └── virtualDefinition.ts │ │ ├── diagnostics │ │ │ └── diagnostics.ts │ │ ├── hover │ │ │ ├── accessScopeHover.ts │ │ │ └── onHover.ts │ │ ├── initialization │ │ │ └── initialization.ts │ │ ├── rename │ │ │ ├── aureliaRename.ts │ │ │ ├── onRenameRequest.ts │ │ │ └── workspaceEdits.ts │ │ ├── save │ │ │ └── saveContent.ts │ │ ├── symbols │ │ │ ├── onDocumentSymbol.ts │ │ │ └── onWorkspaceSymbol.ts │ │ └── virtual │ │ │ ├── README.md │ │ │ └── virtualSourceFile.ts │ ├── project.config.example.ts │ ├── project.config.ts │ └── server.ts └── tsconfig.json ├── syntaxes └── html.json ├── tests ├── .eslintrc.cjs ├── common │ ├── errors │ │ └── TestErrors.ts │ ├── file-path-mocks.ts │ ├── files │ │ └── get-test-dir.ts │ ├── find-project-root.ts │ ├── fixtures │ │ └── get-fixture-dir.ts │ ├── gherkin │ │ └── gherkin-step-table.ts │ └── mock-server │ │ ├── mock-server.ts │ │ └── text-documents.ts ├── dev-test-helpers │ └── cache-cucumber-features.ts ├── features │ ├── capabilities │ │ ├── codeActions │ │ │ └── code-actions.feature │ │ ├── completions │ │ │ ├── completions-aurelia-keywords.feature │ │ │ ├── completions-methods.feature │ │ │ ├── completions-performance.feature │ │ │ ├── completions-value-converters.feature │ │ │ ├── completions-when-to-trigger.feature │ │ │ └── completions.feature │ │ ├── definitions │ │ │ ├── definitions-import-tag.feature │ │ │ ├── definitions-index.feature │ │ │ ├── definitions-upon-file-change.feature │ │ │ ├── definitions-view-model.feature │ │ │ └── definitions.feature │ │ ├── diagnostics │ │ │ └── diagnostics.feature │ │ ├── hover │ │ │ └── hover.feature │ │ ├── rename │ │ │ ├── rename-upon-file-change.feature │ │ │ ├── rename-view-model.feature │ │ │ └── rename-view.feature │ │ └── symbols │ │ │ └── on-document-symbol.feature │ ├── content │ │ └── content-change.feature │ ├── core │ │ ├── AureliaProjects │ │ │ └── AureliaProjects.feature │ │ └── viewmodel │ │ │ └── AureliaComponents.feature │ ├── detecting-on-init.feature │ └── hydrate-on-init.feature ├── jest-cucumber-setup.spec.ts ├── minimal-jest │ ├── index.spec.ts │ ├── index.ts │ ├── minimal.feature │ └── minimal.spec.ts ├── step-definitions │ ├── capabilities │ │ ├── codeAction │ │ │ └── codeAction.spec.ts │ │ ├── common │ │ │ └── common-capabilities.spec.ts │ │ ├── completions.spec.ts │ │ ├── completions │ │ │ └── completions-value-converters.spec.ts │ │ ├── definitions.spec.ts │ │ ├── diagnostics │ │ │ └── diagnostics.spec.ts │ │ ├── hover │ │ │ └── hover.spec.ts │ │ ├── new-common │ │ │ ├── file.step.ts │ │ │ └── project.step.ts │ │ ├── rename │ │ │ └── rename.spec.ts │ │ └── symbols │ │ │ └── documentSymbols.spec.ts │ ├── content │ │ └── content-change.spec.ts │ ├── core │ │ ├── AureliaComponents.spec.ts │ │ └── AureliaProjects.spec.ts │ └── initialization │ │ └── on-initialized │ │ ├── detecting-on-init.spec.ts │ │ └── hydrate-on-init.spec.ts ├── testFixture │ ├── .vscode │ │ └── settings.json │ ├── cli-generated │ │ ├── package.json │ │ ├── src │ │ │ ├── compo-user │ │ │ │ ├── compo-user.html │ │ │ │ └── compo-user.ts │ │ │ ├── just-class-name │ │ │ │ ├── just-class-name.html │ │ │ │ └── just-class-name.ts │ │ │ ├── minimal-component │ │ │ │ ├── minimal-component.html │ │ │ │ └── minimal-component.ts │ │ │ ├── my-compo │ │ │ │ ├── my-compo.html │ │ │ │ └── my-compo.ts │ │ │ ├── realdworld-advanced │ │ │ │ ├── api.ts │ │ │ │ ├── home │ │ │ │ │ ├── index.html │ │ │ │ │ └── index.ts │ │ │ │ ├── resources.d.ts │ │ │ │ ├── settings │ │ │ │ │ ├── index.html │ │ │ │ │ └── index.ts │ │ │ │ └── state.ts │ │ │ ├── value-converters │ │ │ │ ├── sort-value-converter.ts │ │ │ │ └── take-value-converter.ts │ │ │ └── view-model-test │ │ │ │ ├── view-model-test.html │ │ │ │ └── view-model-test.ts │ │ └── tsconfig.json │ ├── monorepo │ │ ├── package-aurelia │ │ │ ├── package.json │ │ │ └── src │ │ │ │ ├── aurelia.html │ │ │ │ └── aurelia.ts │ │ ├── package-burelia │ │ │ ├── package.json │ │ │ └── src │ │ │ │ ├── burelia.html │ │ │ │ └── burelia.ts │ │ └── package-c │ │ │ └── package.json │ ├── non-aurelia-project │ │ └── package.json │ └── scoped-for-testing │ │ ├── .aurelia │ │ └── extension │ │ │ └── templates.js │ │ ├── package.json │ │ ├── src │ │ ├── index.html │ │ ├── index.ts │ │ ├── specific │ │ │ ├── numericInput │ │ │ │ ├── numericInput.html │ │ │ │ └── numericInput.ts │ │ │ └── stage4 │ │ │ │ ├── stage4.html │ │ │ │ └── stage4.ts │ │ └── view │ │ │ ├── custom-element │ │ │ ├── custom-element-user.html │ │ │ ├── custom-element-user.ts │ │ │ ├── custom-element.html │ │ │ ├── custom-element.ts │ │ │ ├── other-custom-element-user.html │ │ │ └── other-custom-element-user.ts │ │ │ ├── diagnostics │ │ │ ├── view-diagnostics.html │ │ │ └── view-diagnostics.ts │ │ │ ├── empty-view.html │ │ │ └── empty-view.ts │ │ └── tsconfig.json ├── testLauncher │ ├── cliGenerated.spec.ts │ ├── core.spec.ts │ ├── monorepo.spec.ts │ ├── scopedForTesting.spec.ts │ └── withWallaby.spec.ts ├── tsconfig.json └── unit │ ├── common │ ├── documens │ │ └── find-source-word.spec.ts │ ├── logging │ │ └── errorStackLogging.spec.ts │ └── testTemplate.ts │ └── core │ ├── RegionParser.spec.ts │ └── expression-parser.spec.ts ├── tsconfig.eslint.json ├── tsconfig.json └── wallaby.config.js /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | 3 | references: 4 | npm_install_update: &npm_install_update 5 | run: 6 | name: npm-update-and-install 7 | command: | 8 | sudo npm install -g npm@8.5.0 9 | npm i 10 | npm run postinstall 11 | 12 | jobs: 13 | build: 14 | docker: 15 | - image: circleci/node:gallium 16 | steps: 17 | - checkout 18 | - *npm_install_update 19 | - run: 20 | name: pre-publish 21 | command: npm run vscode:prepublish 22 | test: 23 | docker: 24 | - image: circleci/node:gallium 25 | steps: 26 | - checkout 27 | - *npm_install_update 28 | - run: 29 | name: build 30 | command: npm run build 31 | - run: 32 | name: Test 33 | command: npm run test:ci 34 | publish: 35 | docker: 36 | - image: circleci/node:gallium 37 | steps: 38 | - checkout 39 | - *npm_install_update 40 | - run: 41 | name: install VS Code Extension Manager 42 | command: npm i vsce 43 | - run: 44 | name: publish to marketplace 45 | command: npx vsce publish -p ${VSTOKEN} 46 | prerelease: 47 | docker: 48 | - image: circleci/node:gallium 49 | steps: 50 | - checkout 51 | - *npm_install_update 52 | - run: 53 | name: install VS Code Extension Manager 54 | command: npm i vsce 55 | - run: 56 | name: pre-release to marketplace 57 | command: npx vsce publish --pre-release -p ${VSTOKEN} 58 | 59 | workflows: 60 | version: 2 61 | main: 62 | jobs: 63 | - build: 64 | filters: 65 | tags: 66 | only: /^\d+\.\d+\.\d+$/ 67 | - test: 68 | requires: 69 | - build 70 | - prerelease: 71 | requires: 72 | - build 73 | - test 74 | filters: 75 | branches: 76 | ignore: /.*/ 77 | tags: 78 | only: /^\d+\.\d*[13579]\.\d+$/ 79 | - publish: 80 | requires: 81 | - build 82 | - test 83 | filters: 84 | branches: 85 | ignore: /.*/ 86 | tags: 87 | only: /^\d+\.\d*[02468]\.\d+$/ 88 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig is awesome: https://EditorConfig.org 2 | 3 | # top-most EditorConfig file 4 | root = true 5 | 6 | [*] 7 | indent_style = space 8 | indent_size = 2 9 | end_of_line = lf 10 | charset = utf-8 11 | trim_trailing_whitespace = true 12 | insert_final_newline = false -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules/** 2 | client/node_modules/** 3 | client/out/** 4 | 5 | server/node_modules/** 6 | server/out/** 7 | server/src/common/@aurelia-runtime-patch 8 | 9 | tests/testFixture 10 | tests/out/** 11 | tests/minimal-jest 12 | 13 | wallaby.config.js 14 | jest.config.js 15 | .eslintrc.cjs 16 | _code-dep -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | *.html text eol=lf 2 | *.ts text eol=lf 3 | *.js text eol=lf 4 | *.json text eol=lf 5 | *.scss text eol=lf 6 | *.md text eol=lf 7 | *.ejs text eol=lf 8 | *.svg text eol=lf 9 | .* text eol=lf 10 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 7 | **I'm submitting a bug report** 8 | **I'm submitting a feature request** 9 | 10 | * **Extension Version:** 11 | 12 | 13 | **Please tell us about your environment:** 14 | * **Operating System:** 15 | 16 | 17 | * **Visual studio code version:** 18 | 19 | 20 | **Current behavior:** 21 | 22 | **Expected/desired behavior:** 23 | 33 | 34 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | out 2 | node_modules 3 | client/server 4 | .vscode-test 5 | 6 | client/testFixture/src/gitignore 7 | 8 | server/src/common/debugging/debugging-data.json 9 | 10 | tests/dev-test-helpers/features-cache.json -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true 3 | } -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | // A launch configuration that compiles the extension and then opens it inside a new window 2 | { 3 | "version": "0.2.0", 4 | "configurations": [ 5 | { 6 | "type": "extensionHost", 7 | "request": "launch", 8 | "name": "Launch Client", 9 | "runtimeExecutable": "${execPath}", 10 | "args": [ 11 | "--extensionDevelopmentPath=${workspaceRoot}", 12 | // "--disable-extensions" 13 | ], 14 | "outFiles": ["${workspaceRoot}/client/out/**/*.js"], 15 | "preLaunchTask": { 16 | "type": "npm", 17 | "script": "watch" 18 | } 19 | }, 20 | { 21 | "type": "node", 22 | "request": "attach", 23 | "name": "Attach to Server", 24 | "port": 6009, 25 | "restart": true, 26 | "outFiles": ["${workspaceRoot}/server/out/**/*.js"] 27 | }, 28 | { 29 | "name": "Language Server E2E Test", 30 | "type": "extensionHost", 31 | "request": "launch", 32 | "runtimeExecutable": "${execPath}", 33 | "args": [ 34 | "--extensionDevelopmentPath=${workspaceRoot}", 35 | "--extensionTestsPath=${workspaceRoot}/client/out/client/src/test/index", 36 | "${workspaceRoot}/client/testFixture" 37 | ], 38 | "outFiles": ["${workspaceRoot}/client/out/client/src/test/**/*.js"] 39 | }, 40 | // https://www.hossambarakat.net/2017/06/01/running-mocha-with-typescript/ 41 | { 42 | "name": "Run mocha", 43 | "type": "node", 44 | "request": "launch", 45 | "env": { 46 | "TS_NODE_PROJECT": "./tsconfig.json" 47 | }, 48 | "program": "${workspaceRoot}/node_modules/mocha/bin/_mocha", 49 | "stopOnEntry": false, 50 | "args": [ 51 | "--require", 52 | "ts-node/register", 53 | "'${workspaceRoot}/tests/unit/**/*.spec.ts'" 54 | ], 55 | "cwd": "${workspaceRoot}", 56 | "protocol": "inspector" 57 | } 58 | ], 59 | "compounds": [ 60 | { 61 | "name": "Client + Server", 62 | "configurations": ["Launch Client", "Attach to Server"] 63 | } 64 | ] 65 | } 66 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.insertSpaces": false, 3 | "files.eol": "\n", 4 | "tslint.enable": true, 5 | "typescript.tsc.autoDetect": "off", 6 | "typescript.preferences.quoteStyle": "single", 7 | "editor.codeActionsOnSave": { 8 | "source.fixAll.eslint": true 9 | }, 10 | "html.format.wrapAttributes": "force-expand-multiline" 11 | } 12 | -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "tasks": [ 4 | { 5 | "type": "npm", 6 | "script": "compile", 7 | "group": "build", 8 | "presentation": { 9 | "panel": "dedicated", 10 | "reveal": "never" 11 | }, 12 | "problemMatcher": [ 13 | "$tsc" 14 | ] 15 | }, 16 | { 17 | "type": "npm", 18 | "script": "watch", 19 | "isBackground": true, 20 | "group": { 21 | "kind": "build", 22 | "isDefault": true 23 | }, 24 | "presentation": { 25 | "panel": "dedicated", 26 | "reveal": "never" 27 | }, 28 | "problemMatcher": [ 29 | "$tsc-watch" 30 | ] 31 | } 32 | ] 33 | } 34 | -------------------------------------------------------------------------------- /.vscodeignore: -------------------------------------------------------------------------------- 1 | .vscode/** 2 | **/*.ts 3 | **/*.map 4 | .gitignore 5 | **/tsconfig.json 6 | **/tsconfig.base.json 7 | contributing.md 8 | .travis.yml 9 | client/node_modules/** 10 | !client/node_modules/vscode-jsonrpc/** 11 | !client/node_modules/vscode-languageclient/** 12 | !client/node_modules/vscode-languageserver-protocol/** 13 | !client/node_modules/vscode-languageserver-types/** 14 | !client/node_modules/semver/** 15 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2010 - 2016 Blue Spire Inc. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /_code-dep/index.ts: -------------------------------------------------------------------------------- 1 | import * as server from '../server/src/server'; 2 | import * as client from '../client/src/extension'; 3 | 4 | // Command to run (from root dir) to get deps graph 5 | // ./node_modules/.bin/code-dependency --exclude "node_modules" --source ./_code-dep 6 | // or run 7 | // `npm run start:depTree` 8 | // `yarn start:depTree` -------------------------------------------------------------------------------- /client/.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const thisDir = path.resolve(__dirname); 3 | 4 | module.exports = { 5 | extends: [ 6 | '../.eslintrc.cjs', 7 | ], 8 | parserOptions: { 9 | project: path.join(thisDir, 'tsconfig.json'), 10 | tsconfigRootDir: thisDir, 11 | }, 12 | }; -------------------------------------------------------------------------------- /client/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "aurelia-client", 3 | "description": "A VSCode extension for Aurelia", 4 | "author": "AureliaEffect", 5 | "license": "MIT", 6 | "version": "2.3.9", 7 | "publisher": "AureliaEffect", 8 | "repository": { 9 | "type": "git", 10 | "url": "https://github.com/aurelia/vscode-extension" 11 | }, 12 | "engines": { 13 | "vscode": "^1.63.0" 14 | }, 15 | "dependencies": { 16 | "lodash": "^4.17.21", 17 | "vscode-languageclient": "^6.1.3" 18 | }, 19 | "devDependencies": { 20 | "@types/lodash": "^4.14.168", 21 | "@types/vscode": "1.51.0" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /client/src/feature/userInput/userInput.ts: -------------------------------------------------------------------------------- 1 | import { window, ExtensionContext } from 'vscode'; 2 | 3 | export async function getUserInputCommand(context: ExtensionContext) { 4 | const result = await window.showInputBox({ 5 | // value: 'abcdef', 6 | // valueSelection: [2, 4], 7 | placeHolder: 'Enter component name', 8 | validateInput: (text) => { 9 | const isEmpty = text === ''; 10 | const isTooShort = text.length <= 2; 11 | if (isEmpty || isTooShort) return 'Must contain at least 3 characters'; 12 | }, 13 | }); 14 | return result; 15 | } 16 | -------------------------------------------------------------------------------- /client/src/shared/constants.ts: -------------------------------------------------------------------------------- 1 | export const CREATE_COMPONEN_TEXT = 'Create Component'; 2 | -------------------------------------------------------------------------------- /client/src/shared/types/types.ts: -------------------------------------------------------------------------------- 1 | import { QuickPickItem } from 'vscode'; 2 | 3 | export interface GetComponentState { 4 | title: string; 5 | step: number; 6 | totalSteps: number; 7 | resourceGroup: QuickPickItem | string; 8 | name: string; 9 | runtime: QuickPickItem; 10 | } 11 | -------------------------------------------------------------------------------- /client/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "experimentalDecorators": true, 4 | "target": "es2019", 5 | "outDir": "out", 6 | "rootDir": "src", 7 | "module": "commonjs", 8 | "sourceMap": true, 9 | "strictPropertyInitialization": false, 10 | "skipLibCheck": true, 11 | "strictNullChecks": true 12 | }, 13 | "include": ["src"], 14 | "exclude": ["node_modules", ".vscode-test"] 15 | } 16 | -------------------------------------------------------------------------------- /docs/developer/adding-new-command.md: -------------------------------------------------------------------------------- 1 | 2 | # Adding a new Command 3 | 4 | 1. `package.json` [link](../../package.json) 5 | - `contributes.commands` 6 | ```json 7 | { 8 | "command": "extension.au.", 9 | "title": "Command Name", 10 | "category": "Aurelia" 11 | } 12 | ``` 13 | 14 | 2. `server.ts` [link](../../server/src/server.ts) 15 | - `connection.onExecuteCommand` 16 | 17 | 3. `constants.ts` [link](../../server/src/common/constants.ts) 18 | -------------------------------------------------------------------------------- /docs/developer/architecture-graph.md: -------------------------------------------------------------------------------- 1 | ```mermaid 2 | flowchart TD 3 | AureliaServer --> AureliaProjects 4 | AureliaServer --> DocumentSettings 5 | AureliaProjects --> hydrateAureliaProjectList 6 | hydrateAureliaProjectList --> AureliaProgram 7 | 8 | AureliaServer 9 | 10 | DocumentSettings 11 | 12 | AureliaProjects 13 | hydrateAureliaProjectList 14 | 15 | AureliaProgram 16 | 17 | ``` 18 | 19 | --- 20 | 21 | ```mermaid 22 | sequenceDiagram 23 | autonumber 24 | participant AureliaServer 25 | participant AureliaProjects 26 | participant WatcherProgram 27 | participant AureliaProgram 28 | 29 | AureliaServer->>AureliaProjects: onConnectionInitialized 30 | 31 | loop For each project 32 | AureliaProjects ->> WatcherProgram: hydrateAureliaProjectList 33 | end 34 | WatcherProgram ->> WatcherProgram: createAureliaWatchProgram 35 | WatcherProgram ->> AureliaProgram: updateAureliaComponents 36 | AureliaProgram ->> AureliaProgram: setComponentList 37 | AureliaProgram ->> AureliaProjects: targetAureliaProject.aureliaProgram = aureliaProgram 38 | 39 | AureliaServer->>AureliaProjects: onContentChanged 40 | ``` -------------------------------------------------------------------------------- /docs/developer/corner-cases.md: -------------------------------------------------------------------------------- 1 | 2 | # Template / View 3 | - .html file with only this content does not gets parsed (issue with 3rd party html-parser?) 4 | ```html 5 | ${foo} 6 | ``` 7 | -------------------------------------------------------------------------------- /docs/developer/decisions.md: -------------------------------------------------------------------------------- 1 | 2 | # Testing 3 | 4 | ## Verification 5 | - . 6 | In tests, I need to pass in file names, the question here is 7 | Should I verify, that a file exists (1) only in the test or (2) in the business logic? 8 | 9 | [DECISION] (1) 10 | - Reason: Not in tests, one would always only open file, that exist -------------------------------------------------------------------------------- /docs/developer/developers.md: -------------------------------------------------------------------------------- 1 | - [Debugging](#debugging) 2 | - [Run and Debug](#run-and-debug) 3 | - [Troubleshooting](#troubleshooting) 4 | - [Launch Client](#launch-client) 5 | - [Attach server](#attach-server) 6 | - [Coding style](#coding-style) 7 | - [Prefer only one return in function with if/else/switch](#prefer-only-one-return-in-function-with-ifelseswitch) 8 | - [Testing](#testing) 9 | - [Assumptions](#assumptions) 10 | - [In tests, one does not need a .ts/.html file for the extension to active](#in-tests-one-does-not-need-a-tshtml-file-for-the-extension-to-active) 11 | # Debugging 12 | 13 | ## Run and Debug 14 | 15 | 1. Select Run and Debug in VSCode 16 | 2. Select and run the launch task "Launch Client" 17 | 1. Select a folder/workspace you want to debug 18 | 2. [important] The extension only activates, when .ts/.html file is open 19 | 3. Select and run launch task "Attach Server" 20 | 21 | ## Troubleshooting 22 | 23 | ### Launch Client 24 | - When I click on "Launch Client" nothing happens 25 | - Reload VSCode and try again 26 | - The terminal process "C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe -Command yarn run watch" terminated with exit code: 1. 27 | - change your default terminal (security issues with powershell, the default shell) 28 | - Recommendation: Bash 29 | 30 | ### Attach server 31 | - "Attach Server" results in Could not connect to debug target at http://localhost:6009: 32 | - Make sure to have step 2.2 in [Run and Debug](#run-and-debug) 33 | 34 | # Coding style 35 | 36 | ## Prefer only one return in function with if/else/switch 37 | Reason: For logging, I then only have to log at the end of the function. 38 | 39 | # Testing 40 | (check dedicated testing.md file) 41 | 42 | ## Assumptions 43 | 44 | ### In tests, one does not need a .ts/.html file for the extension to active 45 | Compare 2.2 in [Run and Debug](#run-and-debug). 46 | In our BDD tests, "open VSCode" activates the extension -------------------------------------------------------------------------------- /docs/developer/how-to-use.md: -------------------------------------------------------------------------------- 1 | 2 | # Rename 3 | - Why can't I rename the same variable again? 4 | --> You have to save the project for the extension to update the component list. -------------------------------------------------------------------------------- /docs/developer/new-features.md: -------------------------------------------------------------------------------- 1 | 2 | # Adding new features 3 | With this doc, I'm trying to track how easy it is to add a new feature. 4 | Thus, have a (subject) measure of how complex the extension architecture is. 5 | 6 | ## Adding diagnostics 7 | - somewhere the parsing is happening 8 | - parsing <- init 9 | - aureliaServer.ts -> onConnectionInitialized 10 | - AureliaProjects 11 | - [hydrate](..\..\server\src\core\AureliaProjects.ts) 12 | - [addAureliaProgramToEachProject](..\..\server\src\core\AureliaProjects.ts) 13 | - --> AureliaProgram 14 | - [initAureliaComponents] 15 | - AureliaComponents 16 | 17 | 18 | ## TODO 19 | - Static analysis is part of core 20 | --> Should be split up 21 | - Goal of the "extension core" should not be handling aurelia related stuff 22 | -------------------------------------------------------------------------------- /docs/developer/performance.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## AureliaProgram 4 | Want to reduce calls, because we want to reuse typechecker 5 | Currently: 6 | 1. this.container.get() 7 | 2. new AureliaProgram() in AureliaProject.hydrate 8 | 9 | ## AureliaProject 10 | - [ ] ~0.7s updateAureliaComponents 11 | 12 | 13 | - [ ] ~0.25 parsing 14 | Perf: 15 | >> (6.) - onCompletion 0 << 16 | >> (7.) - onCompletion 1 << 17 | >> 0.2472254550009966 sec 18 | 19 | ## Ts-morph 20 | - [ ] ~2s compilerObject = program.compilerObject; -------------------------------------------------------------------------------- /docs/developer/term-and-abbreviations.md: -------------------------------------------------------------------------------- 1 | | Business Logic | Implementation | 2 | | -------------- | -------------- | 3 | | Files | Documents | 4 | | Start up | Initialization | 5 | -------------------------------------------------------------------------------- /docs/developer/testing.md: -------------------------------------------------------------------------------- 1 | # Strategy 2 | 1. Focus on unit tests for Aurelia Server. 3 | 2. Minimal e2e setup 4 | Reason for "minimal": Mainly, ease of development, because for unit tesets, one can use Wallaby. An E2e test "cycle" (in development) takes too long. 5 | 6 | 7 | # Missing 8 | 9 | - [ ] detecting-on-init.feature 10 | Think of a way, to make this more accurate 11 | - [ ] the Perf todo -------------------------------------------------------------------------------- /docs/user/codeActions.readme.md: -------------------------------------------------------------------------------- 1 | # Code Action 2 | 3 | ## General information 4 | https://code.visualstudio.com/docs/editor/refactoring 5 | Aka: Refactoring, Quick Fix 6 | 7 | ## How to trigger in VSCode 8 | Shortcut: `Ctrl+.` or `Cmd+.` 9 | Command: Quick Fix 10 | 11 | ## Feature list 12 | - Turn `` into `` 13 | - Why: In the absence of extension like [Path intellisense](https://github.com/ChristianKohler/PathIntellisense) one could leverage the completions capabilities of the a tag, then turn it into an `` tag 14 | 15 | ## Development 16 | 17 | ### Backlog 18 | - Turn `` into `` 19 | - ~~Select code in view -> refactor into own component~~ 20 | - Available in the command palette: "[Au] Extract Component" 21 | - Turn into Code Action eventually 22 | -------------------------------------------------------------------------------- /docs/user/commands.readme.md: -------------------------------------------------------------------------------- 1 | # Commands 2 | 3 | ## General information 4 | 5 | ## How to trigger in VSCode 6 | 7 | Shortcut: - 8 | Command: - 9 | Command Palette -> Then type in the command name 10 | 11 | ## Feature list 12 | 13 | - Declare View Model Variable/Method ("[Au] Declare View Model Variable from Selection") 14 | 1. Select code in your view 15 | 2. Run command "[Au] Declare View Model Variable from Selection" 16 | 3. In your view model, the selected text gets added 17 | 18 | - Extract Component ("[Au] Extract Component") 19 | 1. Select code in your view file 20 | 2. Triggre command 21 | 3. Input name 22 | 4. Components gets created 23 | - Currently `//` 24 | 25 | ## Development 26 | 27 | ### Backlog 28 | -------------------------------------------------------------------------------- /docs/user/definitions.readme.md: -------------------------------------------------------------------------------- 1 | # Definition 2 | 3 | ## General information 4 | https://code.visualstudio.com/docs/editor/editingevolved#_go-to-definition 5 | 6 | ## How to trigger in VSCode 7 | Shortcut: `F12` 8 | Command: Go to Definition 9 | 10 | ## Feature list 11 | Note: The `|` indicates your cursor position, where you trigger Defintions. 12 | 13 | ### Definition in View 14 | Will go to definition in View model 15 | - ```html 16 |

17 | ``` 18 | - ```html 19 |

20 | ``` 21 | - ```html 22 |

23 | ``` 24 | - ```html 25 | 26 | ``` 27 | - ```html 28 | 29 | ``` 30 | - Should land in View model following one of the conventions 31 | - .js and .html are named custom-element and 32 | ViewModel is named CustomElement (or CustomElementCustomElement) 33 | - Class has `@customElement` decorator 34 | - [Official docs on convention](https://docs.aurelia.io/getting-to-know-aurelia/components/creating-components#convention-less-components) 35 | - ```html 36 | 37 | ``` 38 | - Should land in View model 39 | 40 | ### Definition in View model 41 | - ```js 42 | class CustomElement| {} 43 | ``` 44 | - Should show all view references, where `` is used 45 | - ```js 46 | class CustomElement { 47 | @bindable foo| 48 | } 49 | ``` 50 | - Should show all view references, where `` is used 51 | 52 | ## Limitations 53 | - HTML-only Custom Element not supported yet 54 | - Router related views not supported. More specifically, you cannot find usage places of components, that are the "base" of a route. Still works for imported components inside route view. 55 | - Access Members are not supported, eg `foo.|member` does not work. (Only works for "Access Scopes". `foo` would is the Access Scope in `foo.member` or `foo[0]` or `foo(arg)`. In the last case `foo` is th Call Scope, which is supported too.) 56 | 57 | ## Development 58 | 59 | ### Backlog 60 | - Turn `
` into `` 61 | - Select code in view -> refactor into own component 62 | -------------------------------------------------------------------------------- /docs/user/rename.readme.md: -------------------------------------------------------------------------------- 1 | # Rename 2 | 3 | ## ⚠️ Warning ⚠️ 4 | **TLDR**: Feature may be unstable, use at own risk. 5 | 6 | When triggering Rename, a lot of places can potentially change, thus making it hard to verify, if the changes were correct. 7 | Thus, it is *highly* recommended, to eg. commit all the current changes, or any other means, that allow your diff view to just see changes from the Rename operation. 8 | *(Imo, that's generally true for all kinds of Renames, not only specific to Aurelia.)* 9 | The Extension tries to catch all th relevant places for Renaming, but due to the low iteration count of this feature, we unfortunately cannot ensure, that really *all* places changed, which can lead to a faulty application. 10 | We are really sorry for the inconvenience, but still believe, that *if* you have to rename, this feature can at least some amount of work. 11 | 12 | 13 | ## General information 14 | https://code.visualstudio.com/docs/editor/editingevolved#_rename-symbol 15 | 16 | ### How to trigger in VSCode 17 | Shortcut: `F2` 18 | Command: Rename Symbol 19 | 20 | ## Usage inside the Aurelia extension 21 | 22 | ## Feature list 23 | Note: The `|` indicates your cursor position, where you trigger Rename. 24 | 25 | ### Rename in View 26 | Will rename variables in View model. For Custom Elements and Bindables will also rename in other components. 27 | - ```html 28 |

29 | ``` 30 | - ```html 31 |

32 | ``` 33 | - ```html 34 |

35 | ``` 36 | - ```html 37 | 38 | ``` 39 | - ```html 40 | 41 | ``` 42 | 43 | ### Rename in View model 44 | - ```js 45 | class CustomElement| {} 46 | ``` 47 | - Should rename all places in all Views, where `` is used 48 | - ```js 49 | class CustomElement { 50 | @bindable foo| 51 | } 52 | ``` 53 | - Should rename all places in all Views, where `` is used 54 | 55 | ### Limitations 56 | - HTML-only Custom Element not supported yet 57 | - Router related views not supported. More specifically, you cannot find usage places of components, that are the "base" of a route. Still works for imported components inside route view. 58 | 59 | ## Development 60 | 61 | ### Backlog 62 | -------------------------------------------------------------------------------- /docs/user/symbols.readme.md: -------------------------------------------------------------------------------- 1 | # Symbols 2 | 3 | ## General information 4 | https://code.visualstudio.com/docs/editor/editingevolved#_go-to-symbol 5 | https://code.visualstudio.com/docs/editor/editingevolved#_open-symbol-by-name 6 | 7 | ## How to trigger in VSCode 8 | Shortcut: 9 | - Go to Symbol in Editor `Ctrl+Shift+o` or `Cmd+Shift+o` 10 | - Go to Symbol in Workspace `Ctrl+t` or `Cmd+t` 11 | 12 | ## Feature list 13 | - Display *all* Aurelia related regions 14 | - in Editor 15 | - in Workspace 16 | 17 | ## Development 18 | 19 | ### Backlog 20 | -------------------------------------------------------------------------------- /images/architecture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aurelia/vscode-extension/897308feeab78167bec80b1e242aa58f30d2e770/images/architecture.png -------------------------------------------------------------------------------- /images/aurelia-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aurelia/vscode-extension/897308feeab78167bec80b1e242aa58f30d2e770/images/aurelia-logo.png -------------------------------------------------------------------------------- /images/completions-complex.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aurelia/vscode-extension/897308feeab78167bec80b1e242aa58f30d2e770/images/completions-complex.png -------------------------------------------------------------------------------- /images/completions.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aurelia/vscode-extension/897308feeab78167bec80b1e242aa58f30d2e770/images/completions.gif -------------------------------------------------------------------------------- /images/completions.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aurelia/vscode-extension/897308feeab78167bec80b1e242aa58f30d2e770/images/completions.png -------------------------------------------------------------------------------- /images/definitions.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aurelia/vscode-extension/897308feeab78167bec80b1e242aa58f30d2e770/images/definitions.png -------------------------------------------------------------------------------- /images/dependencygraph.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aurelia/vscode-extension/897308feeab78167bec80b1e242aa58f30d2e770/images/dependencygraph.png -------------------------------------------------------------------------------- /images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aurelia/vscode-extension/897308feeab78167bec80b1e242aa58f30d2e770/images/logo.png -------------------------------------------------------------------------------- /images/symbols.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aurelia/vscode-extension/897308feeab78167bec80b1e242aa58f30d2e770/images/symbols.png -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | roots: [''], 3 | // modulePaths: [''], 4 | moduleDirectories: ['node_modules', '/server/node_modules'], // So server files also get watched 5 | testEnvironment: 'node', 6 | transform: { 7 | '^.+\\.ts$': '@sucrase/jest-plugin', 8 | // '^.+\\.ts$': '@swc/jest', 9 | // '^.+\\.ts$': 'esbuild-jest', 10 | }, 11 | // preset: 'ts-jest', 12 | // verbose: true, 13 | // testRegex: '.spec.[j,t]s$', 14 | // testMatch: ['*.spec.ts'], 15 | // testMatch: ['**/unit/core/**/*.spec.ts'], 16 | // coverageDirectory: '.coverage', 17 | // coverageReporters: ['text', 'text-summary'], 18 | // coverageThreshold: { 19 | // global: { statements: 90, lines: 90, functions: 90 }, 20 | // }, 21 | testPathIgnorePatterns: ['/build/', '/node_modules/', '/testFixture/', '/server/out', '/tests/out'], 22 | // globals: { 23 | // 'ts-jest': { 24 | // tsconfig: 'tests/tsconfig.json', 25 | // }, 26 | // }, 27 | }; 28 | -------------------------------------------------------------------------------- /language-configuration.json: -------------------------------------------------------------------------------- 1 | { 2 | "comments": { 3 | "blockComment": [ "" ] 4 | }, 5 | "brackets": [ 6 | [""], 7 | ["<", ">"], 8 | ["{", "}"], 9 | ["(", ")"] 10 | ], 11 | "autoClosingPairs": [ 12 | { "open": "{", "close": "}"}, 13 | { "open": "[", "close": "]"}, 14 | { "open": "(", "close": ")" }, 15 | { "open": "'", "close": "'" }, 16 | { "open": "\"", "close": "\"" }, 17 | { "open": "${", "close": "}" } 18 | ], 19 | "surroundingPairs": [ 20 | { "open": "'", "close": "'" }, 21 | { "open": "\"", "close": "\"" }, 22 | { "open": "{", "close": "}"}, 23 | { "open": "[", "close": "]"}, 24 | { "open": "(", "close": ")" }, 25 | { "open": "<", "close": ">" }, 26 | { "open": "${", "close": "}" } 27 | ] 28 | } 29 | -------------------------------------------------------------------------------- /server/.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const thisDir = path.resolve(__dirname); 3 | 4 | module.exports = { 5 | extends: ['../.eslintrc.cjs'], 6 | parserOptions: { 7 | project: path.join(thisDir, 'tsconfig.json'), 8 | tsconfigRootDir: thisDir, 9 | }, 10 | }; 11 | -------------------------------------------------------------------------------- /server/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "aurelia", 3 | "description": "A VSCode extension for Aurelia", 4 | "author": "AureliaEffect", 5 | "license": "MIT", 6 | "version": "2.3.9", 7 | "repository": { 8 | "type": "git", 9 | "url": "https://github.com/aurelia/vscode-extension" 10 | }, 11 | "engines": { 12 | "node": "*" 13 | }, 14 | "dependencies": { 15 | "@aurelia/kernel": "^2.0.0-alpha.23", 16 | "fast-glob": "^3.2.7", 17 | "lodash": "^4.17.21", 18 | "parse5": "^6.0.1", 19 | "parse5-sax-parser": "^6.0.1" 20 | }, 21 | "scripts": {}, 22 | "devDependencies": { 23 | "@types/lodash": "^4.14.168", 24 | "@types/parse5": "^5.0.3", 25 | "@types/parse5-sax-parser": "^5.0.1" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /server/src/aot/aotTypes.ts: -------------------------------------------------------------------------------- 1 | import { ts } from 'ts-morph'; 2 | 3 | import { AureliaClassTypes } from '../common/constants'; 4 | import { AbstractRegion } from './parser/regions/ViewRegions'; 5 | 6 | export interface IAureliaClassMember { 7 | name: string; 8 | memberType: string; 9 | documentation: string; 10 | isBindable: boolean; 11 | syntaxKind: ts.SyntaxKind; 12 | start: number; 13 | end: number; 14 | } 15 | 16 | export interface IAureliaComponent { 17 | documentation: string; 18 | sourceFile?: ts.SourceFile; 19 | version?: number; 20 | /** export class >ComponentName< {} */ 21 | className: string; 22 | /** component-name */ 23 | baseViewModelFileName: string; 24 | /** path/to/component-name.ts */ 25 | viewModelFilePath: string; 26 | /** 27 | * export class >Sort sort 28 | */ 29 | valueConverterName?: string; 30 | /** 31 | * Kebab case of eg. 32 | * \@customElement(">component-name<") 33 | * export class >ComponentName< {} --> component-name 34 | */ 35 | componentName?: string; 36 | decoratorComponentName?: string; 37 | decoratorStartOffset?: number; 38 | decoratorEndOffset?: number; 39 | viewFilePath?: string; 40 | type: AureliaClassTypes; 41 | /** Class Members */ 42 | classMembers?: IAureliaClassMember[]; 43 | /** View */ 44 | viewRegions: AbstractRegion[]; 45 | } 46 | 47 | export interface IAureliaBindable { 48 | componentName: string; 49 | /** 50 | * Class member information of bindable. 51 | * 52 | * Reason for structure: 53 | * Before, the interface was like `export interface IAureliaBindable extends IAureliaClassMember`, 54 | * but due to further processing hardship (creating actual CompletionItem), that interface was hard to work with. 55 | */ 56 | classMember: IAureliaClassMember; 57 | } 58 | -------------------------------------------------------------------------------- /server/src/aot/parser/parser-types.ts: -------------------------------------------------------------------------------- 1 | import { ts } from 'ts-morph'; 2 | 3 | export interface DefinitionResult { 4 | lineAndCharacter: ts.LineAndCharacter; 5 | viewModelFilePath?: string; 6 | viewFilePath?: string; 7 | } 8 | -------------------------------------------------------------------------------- /server/src/aot/parser/regions/ViewRegionsVisitor.ts: -------------------------------------------------------------------------------- 1 | import { 2 | AttributeInterpolationRegion, 3 | AttributeRegion, 4 | AureliaHtmlRegion, 5 | BindableAttributeRegion, 6 | CustomElementRegion, 7 | ImportRegion, 8 | RepeatForRegion, 9 | TextInterpolationRegion, 10 | ValueConverterRegion, 11 | } from './ViewRegions'; 12 | 13 | export interface IVisitor { 14 | accept: () => {}; 15 | } 16 | 17 | export interface IViewRegionsVisitor { 18 | visitAttribute(region: AttributeRegion): T; 19 | visitAttributeInterpolation(region: AttributeInterpolationRegion): T; 20 | visitAureliaHtmlInterpolation(region: AureliaHtmlRegion): T; 21 | visitBindableAttribute(region: BindableAttributeRegion): T; 22 | visitCustomElement(region: CustomElementRegion): T; 23 | visitImport(region: ImportRegion): T; 24 | visitRepeatFor(region: RepeatForRegion): T; 25 | visitTextInterpolation(region: TextInterpolationRegion): T; 26 | visitValueConverter(region: ValueConverterRegion): T; 27 | } 28 | -------------------------------------------------------------------------------- /server/src/aot/parser/regions/languageServer/AbstractRegionLanguageService.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Diagnostic, 3 | CompletionList, 4 | Position, 5 | WorkspaceEdit, 6 | CodeAction, 7 | CompletionParams, 8 | } from 'vscode-languageserver'; 9 | import { TextDocument } from 'vscode-languageserver-textdocument'; 10 | 11 | import { Container } from '../../../../core/container'; 12 | import { AureliaCompletionItem } from '../../../../feature/completions/virtualCompletion'; 13 | import { CustomHover } from '../../../../feature/virtual/virtualSourceFile'; 14 | import { AureliaProgram } from '../../../AureliaProgram'; 15 | import { DefinitionResult } from '../../parser-types'; 16 | import { AbstractRegion } from '../ViewRegions'; 17 | 18 | export interface AbstractRegionLanguageService { 19 | doValidation?( 20 | aureliaProgram: AureliaProgram, 21 | document: TextDocument 22 | ): Promise; 23 | doCodeAction?( 24 | aureliaProgram: AureliaProgram, 25 | document: TextDocument, 26 | start: Position, 27 | region?: AbstractRegion 28 | ): Promise; 29 | doComplete?: ( 30 | aureliaProgram: AureliaProgram, 31 | document: TextDocument, 32 | triggerCharacter?: string, 33 | region?: AbstractRegion, 34 | offset?: number, 35 | /** 36 | * This was introduced when introducing TriggerKind (eg. invoked). 37 | * We need to handle the previous trigger character depending on 38 | * 1. Triggered (invoked) 39 | * 2. On typed 40 | * 41 | * for 1. No trigger character is passed 42 | * for 2. the trigger character is substring from offset - 1. 43 | */ 44 | insertTriggerCharacter?: boolean, 45 | completionParams?: CompletionParams 46 | ) => Promise; 47 | doDefinition?: ( 48 | aureliaProgram: AureliaProgram, 49 | document: TextDocument, 50 | position: Position, 51 | region: AbstractRegion 52 | ) => Promise; 53 | doHover?: ( 54 | aureliaProgram: AureliaProgram, 55 | document: TextDocument, 56 | position: Position, 57 | goToSourceWord: string, 58 | region: AbstractRegion 59 | ) => Promise; 60 | doRename?: ( 61 | container: Container, 62 | aureliaProgram: AureliaProgram, 63 | document: TextDocument, 64 | position: Position, 65 | newName: string, 66 | region: AbstractRegion 67 | ) => Promise; 68 | } 69 | -------------------------------------------------------------------------------- /server/src/aot/parser/regions/languageServer/AttributeInterpolationLanguageService.ts: -------------------------------------------------------------------------------- 1 | import { 2 | CompletionParams, 3 | Position, 4 | } from 'vscode-languageserver'; 5 | import { TextDocument } from 'vscode-languageserver-textdocument'; 6 | 7 | import { Container } from '../../../../core/container'; 8 | import { aureliaVirtualComplete_vNext } from '../../../../feature/completions/virtualCompletion2'; 9 | import { getAccessScopeDefinition } from '../../../../feature/definition/accessScopeDefinition'; 10 | import { getAccessScopeHover } from '../../../../feature/hover/accessScopeHover'; 11 | import { aureliaRenameFromView } from '../../../../feature/rename/aureliaRename'; 12 | import { VirtualLanguageService } from '../../../../feature/virtual/virtualSourceFile'; 13 | import { AureliaProgram } from '../../../AureliaProgram'; 14 | import { DefinitionResult } from '../../parser-types'; 15 | import { AbstractRegion } from '../ViewRegions'; 16 | import { AbstractRegionLanguageService } from './AbstractRegionLanguageService'; 17 | 18 | export class AttributeInterpolationLanguageService 19 | implements AbstractRegionLanguageService 20 | { 21 | public async doComplete( 22 | aureliaProgram: AureliaProgram, 23 | document: TextDocument, 24 | triggerCharacter?: string, 25 | region?: AbstractRegion, 26 | offset?: number, 27 | insertTriggerCharacter?: boolean, 28 | completionParams?: CompletionParams 29 | ) { 30 | const completions = aureliaVirtualComplete_vNext( 31 | aureliaProgram, 32 | document, 33 | region, 34 | triggerCharacter, 35 | offset, 36 | insertTriggerCharacter, 37 | completionParams 38 | ); 39 | return completions; 40 | } 41 | public async doDefinition( 42 | aureliaProgram: AureliaProgram, 43 | document: TextDocument, 44 | position: Position, 45 | region: AbstractRegion 46 | ): Promise { 47 | const regions = 48 | aureliaProgram.aureliaComponents.getOneByFromDocument( 49 | document 50 | )?.viewRegions; 51 | 52 | return getAccessScopeDefinition( 53 | aureliaProgram, 54 | document, 55 | position, 56 | region, 57 | regions 58 | ); 59 | } 60 | public async doHover( 61 | aureliaProgram: AureliaProgram, 62 | document: TextDocument, 63 | position: Position, 64 | goToSourceWord: string, 65 | attributeRegion: AbstractRegion 66 | ): Promise> { 67 | return getAccessScopeHover( 68 | aureliaProgram, 69 | document, 70 | position, 71 | goToSourceWord, 72 | attributeRegion 73 | ); 74 | } 75 | 76 | public async doRename( 77 | container: Container, 78 | aureliaProgram: AureliaProgram, 79 | document: TextDocument, 80 | position: Position, 81 | newName: string, 82 | region: AbstractRegion 83 | ) { 84 | const renames = aureliaRenameFromView( 85 | container, 86 | aureliaProgram, 87 | document, 88 | position, 89 | newName, 90 | region 91 | ); 92 | return renames; 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /server/src/aot/parser/regions/languageServer/AttributeLanguageService.ts: -------------------------------------------------------------------------------- 1 | import { Container } from 'aurelia-dependency-injection'; 2 | import { CompletionParams, Position } from 'vscode-languageserver'; 3 | import { TextDocument } from 'vscode-languageserver-textdocument'; 4 | 5 | import { aureliaVirtualComplete_vNext } from '../../../../feature/completions/virtualCompletion2'; 6 | import { getAccessScopeDefinition } from '../../../../feature/definition/accessScopeDefinition'; 7 | import { getAccessScopeHover } from '../../../../feature/hover/accessScopeHover'; 8 | import { aureliaRenameFromView } from '../../../../feature/rename/aureliaRename'; 9 | import { VirtualLanguageService } from '../../../../feature/virtual/virtualSourceFile'; 10 | import { AureliaProgram } from '../../../AureliaProgram'; 11 | import { DefinitionResult } from '../../parser-types'; 12 | import { AbstractRegion } from '../ViewRegions'; 13 | import { AbstractRegionLanguageService } from './AbstractRegionLanguageService'; 14 | 15 | export class AttributeLanguageService implements AbstractRegionLanguageService { 16 | public async doComplete( 17 | aureliaProgram: AureliaProgram, 18 | document: TextDocument, 19 | triggerCharacter?: string, 20 | region?: AbstractRegion, 21 | offset?: number, 22 | insertTriggerCharacter?: boolean, 23 | completionParams?: CompletionParams 24 | ) { 25 | const completions = aureliaVirtualComplete_vNext( 26 | aureliaProgram, 27 | document, 28 | region, 29 | triggerCharacter, 30 | offset, 31 | insertTriggerCharacter, 32 | completionParams 33 | ); 34 | return completions; 35 | } 36 | 37 | public async doDefinition( 38 | aureliaProgram: AureliaProgram, 39 | document: TextDocument, 40 | position: Position, 41 | region: AbstractRegion 42 | ): Promise { 43 | const regions = 44 | aureliaProgram.aureliaComponents.getOneByFromDocument( 45 | document 46 | )?.viewRegions; 47 | 48 | return getAccessScopeDefinition( 49 | aureliaProgram, 50 | document, 51 | position, 52 | region, 53 | regions 54 | ); 55 | } 56 | 57 | public async doHover( 58 | aureliaProgram: AureliaProgram, 59 | document: TextDocument, 60 | position: Position, 61 | goToSourceWord: string, 62 | attributeRegion: AbstractRegion 63 | ): Promise> { 64 | return getAccessScopeHover( 65 | aureliaProgram, 66 | document, 67 | position, 68 | goToSourceWord, 69 | attributeRegion 70 | ); 71 | } 72 | 73 | public async doRename( 74 | container: Container, 75 | aureliaProgram: AureliaProgram, 76 | document: TextDocument, 77 | position: Position, 78 | newName: string, 79 | region: AbstractRegion 80 | ) { 81 | const renames = aureliaRenameFromView( 82 | container, 83 | aureliaProgram, 84 | document, 85 | position, 86 | newName, 87 | region 88 | ); 89 | return renames; 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /server/src/aot/parser/regions/languageServer/BindableAttributeLanguageService.ts: -------------------------------------------------------------------------------- 1 | import { kebabCase } from '@aurelia/kernel'; 2 | import { Position, TextDocument } from 'vscode-languageserver-textdocument'; 3 | 4 | import { TextDocumentUtils } from '../../../../common/documens/TextDocumentUtils'; 5 | import { UriUtils } from '../../../../common/view/uri-utils'; 6 | import { Container } from '../../../../core/container'; 7 | import { aureliaRenameFromView } from '../../../../feature/rename/aureliaRename'; 8 | import { AureliaProgram } from '../../../AureliaProgram'; 9 | import { DefinitionResult } from '../../parser-types'; 10 | import { AbstractRegion } from '../ViewRegions'; 11 | import { AbstractRegionLanguageService } from './AbstractRegionLanguageService'; 12 | 13 | // const logger = new Logger('getBindableAttributeMode'); 14 | 15 | export class BindableAttributeLanguageService 16 | implements AbstractRegionLanguageService 17 | { 18 | public async doDefinition( 19 | aureliaProgram: AureliaProgram, 20 | document: TextDocument, 21 | position: Position, 22 | region: AbstractRegion 23 | ): Promise { 24 | const targetComponent = aureliaProgram.aureliaComponents.getOneBy( 25 | 'componentName', 26 | region.tagName 27 | ); 28 | if (!targetComponent) return; 29 | 30 | const targetMember = targetComponent?.classMembers?.find((member) => { 31 | const correctNamingConvetion = 32 | kebabCase(member.name) === kebabCase(region.regionValue ?? ''); 33 | const is = correctNamingConvetion && member.isBindable; 34 | return is; 35 | }); 36 | const viewModelDocument = TextDocumentUtils.createFromPath( 37 | targetComponent.viewModelFilePath 38 | ); 39 | if (targetMember == null) return; 40 | const { line, character } = viewModelDocument.positionAt( 41 | targetMember.start 42 | ); 43 | 44 | const result = { 45 | lineAndCharacter: { 46 | line: line + 1, // + 1client is 1-based index 47 | character: character + 1, // + 1client is 1-based index 48 | } /** TODO: Find class declaration position. Currently default to top of file */, 49 | viewModelFilePath: UriUtils.toSysPath(targetComponent.viewModelFilePath), 50 | }; 51 | return result; 52 | } 53 | 54 | public async doRename( 55 | container: Container, 56 | aureliaProgram: AureliaProgram, 57 | document: TextDocument, 58 | position: Position, 59 | newName: string, 60 | region: AbstractRegion 61 | ) { 62 | const renames = aureliaRenameFromView( 63 | container, 64 | aureliaProgram, 65 | document, 66 | position, 67 | newName, 68 | region 69 | ); 70 | return renames; 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /server/src/aot/parser/regions/languageServer/CustomElementLanguageService.ts: -------------------------------------------------------------------------------- 1 | import { Position } from 'vscode-languageserver'; 2 | import { TextDocument } from 'vscode-languageserver-textdocument'; 3 | 4 | import { findSourceWord } from '../../../../common/documens/find-source-word'; 5 | import { UriUtils } from '../../../../common/view/uri-utils'; 6 | import { getBindablesCompletion } from '../../../../feature/completions/completions'; 7 | import { AureliaProgram } from '../../../AureliaProgram'; 8 | import { DefinitionResult } from '../../parser-types'; 9 | import { AbstractRegion } from '../ViewRegions'; 10 | import { AbstractRegionLanguageService } from './AbstractRegionLanguageService'; 11 | 12 | export class CustomElementLanguageService 13 | implements AbstractRegionLanguageService 14 | { 15 | public async doComplete( 16 | aureliaProgram: AureliaProgram, 17 | document: TextDocument, 18 | triggerCharacter: string | undefined, 19 | region?: AbstractRegion 20 | ) { 21 | if (triggerCharacter === ' ') { 22 | const bindablesCompletion = await getBindablesCompletion( 23 | aureliaProgram, 24 | document, 25 | region 26 | ); 27 | if (bindablesCompletion.length > 0) return bindablesCompletion; 28 | } 29 | return []; 30 | } 31 | 32 | public async doDefinition( 33 | aureliaProgram: AureliaProgram, 34 | document: TextDocument, 35 | position: Position, 36 | customElementRegion: AbstractRegion 37 | ): Promise { 38 | const offset = document.offsetAt(position); 39 | const goToSourceWord = findSourceWord(customElementRegion, offset); 40 | 41 | const targetComponent = aureliaProgram.aureliaComponents.getOneBy( 42 | 'componentName', 43 | goToSourceWord 44 | ); 45 | if (targetComponent == null) return; 46 | 47 | /** 48 | * 1. Triggered on <|my-component> 49 | */ 50 | return { 51 | lineAndCharacter: { 52 | line: 1, 53 | character: 1, 54 | } /** TODO: Find class declaration position. Currently default to top of file */, 55 | viewModelFilePath: UriUtils.toSysPath(targetComponent.viewModelFilePath), 56 | }; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /server/src/aot/parser/regions/languageServer/ImportLanguageService.ts: -------------------------------------------------------------------------------- 1 | import * as fs from 'fs'; 2 | import * as path from 'path'; 3 | 4 | import { Position, TextDocument } from 'vscode-languageserver-textdocument'; 5 | 6 | import { UriUtils } from '../../../../common/view/uri-utils'; 7 | import { AureliaProgram } from '../../../AureliaProgram'; 8 | import { DefinitionResult } from '../../parser-types'; 9 | import { AbstractRegion } from '../ViewRegions'; 10 | import { AbstractRegionLanguageService } from './AbstractRegionLanguageService'; 11 | 12 | export class ImportLanguageService implements AbstractRegionLanguageService { 13 | public async doDefinition( 14 | aureliaProgram: AureliaProgram, 15 | document: TextDocument, 16 | position: Position, 17 | importRegion: AbstractRegion 18 | ) { 19 | const components = aureliaProgram.aureliaComponents.getAll(); 20 | const targetRelativePath = importRegion.regionValue; 21 | if (targetRelativePath === undefined) return; 22 | 23 | const sourceDirName = path.dirname(UriUtils.toSysPath(document.uri)); 24 | const resolvedPath = path.resolve(sourceDirName, targetRelativePath); 25 | 26 | // View 27 | let viewPath: string | undefined; 28 | if (fs.existsSync(resolvedPath)) { 29 | viewPath = resolvedPath; 30 | } 31 | 32 | // View model 33 | // Note: We could have gone the simple `fs.existsSync` way, but with this approach 34 | // we could check for component info. 35 | // Probably rather needed for hover, so 36 | // TODO: check like View, but only when you implement hover to reuse the below code. 37 | let viewModelPath: string | undefined; 38 | components.find((component) => { 39 | const { dir, name } = path.parse(component.viewModelFilePath); 40 | const viewModelWithoutExt = `${dir}${path.sep}${name}`; 41 | const isTargetViewModel = viewModelWithoutExt === resolvedPath; 42 | if (isTargetViewModel) { 43 | viewModelPath = component.viewModelFilePath; 44 | return true; 45 | } 46 | 47 | return false; 48 | }); 49 | 50 | const result: DefinitionResult = { 51 | lineAndCharacter: { line: 1, character: 0 }, 52 | viewModelFilePath: viewModelPath, 53 | viewFilePath: viewPath, 54 | }; 55 | return result; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /server/src/aot/parser/regions/languageServer/RepeatForLanguageService.ts: -------------------------------------------------------------------------------- 1 | import { CompletionParams, Position } from 'vscode-languageserver'; 2 | import { TextDocument } from 'vscode-languageserver-textdocument'; 3 | 4 | import { Container } from '../../../../core/container'; 5 | import { aureliaVirtualComplete_vNext } from '../../../../feature/completions/virtualCompletion2'; 6 | import { getAccessScopeViewModelDefinition } from '../../../../feature/definition/accessScopeDefinition'; 7 | import { aureliaRenameFromView } from '../../../../feature/rename/aureliaRename'; 8 | import { AureliaProgram } from '../../../AureliaProgram'; 9 | import { DefinitionResult } from '../../parser-types'; 10 | import { AbstractRegion } from '../ViewRegions'; 11 | import { AbstractRegionLanguageService } from './AbstractRegionLanguageService'; 12 | 13 | export class RepeatForLanguageService implements AbstractRegionLanguageService { 14 | public async doComplete( 15 | aureliaProgram: AureliaProgram, 16 | document: TextDocument, 17 | triggerCharacter?: string, 18 | region?: AbstractRegion, 19 | offset?: number, 20 | insertTriggerCharacter?: boolean, 21 | completionParams?: CompletionParams 22 | ) { 23 | const completions = aureliaVirtualComplete_vNext( 24 | aureliaProgram, 25 | document, 26 | region, 27 | triggerCharacter, 28 | offset, 29 | insertTriggerCharacter, 30 | completionParams 31 | ); 32 | return completions; 33 | } 34 | public async doDefinition( 35 | aureliaProgram: AureliaProgram, 36 | document: TextDocument, 37 | position: Position, 38 | region: AbstractRegion 39 | ): Promise { 40 | return getAccessScopeViewModelDefinition( 41 | document, 42 | position, 43 | region, 44 | aureliaProgram 45 | ); 46 | } 47 | 48 | public async doRename( 49 | container: Container, 50 | aureliaProgram: AureliaProgram, 51 | document: TextDocument, 52 | position: Position, 53 | newName: string, 54 | region: AbstractRegion 55 | ) { 56 | const renames = aureliaRenameFromView( 57 | container, 58 | aureliaProgram, 59 | document, 60 | position, 61 | newName, 62 | region 63 | ); 64 | return renames; 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /server/src/aot/parser/regions/visitors/RegionLanguageServerVisitor.ts: -------------------------------------------------------------------------------- 1 | // import { AureliaProgram } from '../../viewModel/AureliaProgram'; 2 | // import { BindableAttributeLanguageService } from '../languageServer/BindableAttributeLanguageService'; 3 | // import { 4 | // AttributeRegion, 5 | // AttributeInterpolationRegion, 6 | // BindableAttributeRegion, 7 | // CustomElementRegion, 8 | // RepeatForRegion, 9 | // TextInterpolationRegion, 10 | // ValueConverterRegion, 11 | // AureliaHtmlRegion, 12 | // } from '../ViewRegions'; 13 | // import { IViewRegionsVisitor } from '../ViewRegionsVisitor'; 14 | 15 | // export class RegionLanguageServerVisitor implements IViewRegionsVisitor { 16 | // constructor(public aureliaProgram: AureliaProgram) {} 17 | 18 | // visitAttribute(region: AttributeRegion): T {} 19 | // visitAttributeInterpolation(region: AttributeInterpolationRegion): T {} 20 | // visitAureliaHtmlInterpolation(region: AureliaHtmlRegion): T {} 21 | // visitBindableAttribute(region: BindableAttributeRegion): T {} 22 | // visitCustomElement(region: CustomElementRegion): T {} 23 | // visitRepeatFor(region: RepeatForRegion): T {} 24 | // visitTextInterpolation(region: TextInterpolationRegion): T {} 25 | // visitValueConverter(region: ValueConverterRegion): T {} 26 | // } 27 | -------------------------------------------------------------------------------- /server/src/aot/staticAnalysis/ConventionService.ts: -------------------------------------------------------------------------------- 1 | import * as Path from 'path'; 2 | 3 | import { kebabCase } from 'lodash'; 4 | import { ts } from 'ts-morph'; 5 | 6 | import { AureliaDecorator, AureliaClassTypes } from '../../common/constants'; 7 | 8 | export class ConventionService { 9 | public static fulfillsAureliaConventions(node: ts.ClassDeclaration) { 10 | const fulfillsAureliaConventions = 11 | classDeclarationHasUseViewOrNoView(node) || 12 | hasCustomElementNamingConvention(node) || 13 | hasValueConverterNamingConvention(node); 14 | 15 | return fulfillsAureliaConventions; 16 | } 17 | } 18 | 19 | /** 20 | * checks whether a classDeclaration has a useView or noView 21 | * 22 | * @param classDeclaration - ClassDeclaration to check 23 | */ 24 | function classDeclarationHasUseViewOrNoView( 25 | classDeclaration: ts.ClassDeclaration 26 | ): boolean { 27 | if (!classDeclaration.decorators) return false; 28 | 29 | const hasViewDecorator = classDeclaration.decorators.some((decorator) => { 30 | const result = 31 | decorator.getText().includes('@useView') || 32 | decorator.getText().includes('@noView'); 33 | return result; 34 | }); 35 | 36 | return hasViewDecorator; 37 | } 38 | 39 | /** 40 | * MyClassCustomelement 41 | * 42 | * \@customElement(...) 43 | * MyClass 44 | */ 45 | function hasCustomElementNamingConvention( 46 | classDeclaration: ts.ClassDeclaration 47 | ): boolean { 48 | const hasCustomElementDecorator = 49 | classDeclaration.decorators?.some((decorator) => { 50 | const decoratorName = decorator.getText(); 51 | const result = 52 | decoratorName.includes(AureliaDecorator.CUSTOM_ELEMENT) || 53 | decoratorName.includes('name'); 54 | return result; 55 | }) ?? false; 56 | 57 | const className = classDeclaration.name?.getText(); 58 | const hasCustomElementNamingConvention = Boolean( 59 | className?.includes(AureliaClassTypes.CUSTOM_ELEMENT) 60 | ); 61 | 62 | const { fileName } = classDeclaration.getSourceFile(); 63 | const baseName = Path.parse(fileName).name; 64 | const isCorrectFileAndClassConvention = 65 | kebabCase(baseName) === kebabCase(className); 66 | 67 | return ( 68 | hasCustomElementDecorator || 69 | hasCustomElementNamingConvention || 70 | isCorrectFileAndClassConvention 71 | ); 72 | } 73 | 74 | /** 75 | * MyClassValueConverter 76 | * 77 | * \@valueConverter(...) 78 | * MyClass 79 | */ 80 | function hasValueConverterNamingConvention( 81 | classDeclaration: ts.ClassDeclaration 82 | ): boolean { 83 | const hasValueConverterDecorator = 84 | classDeclaration.decorators?.some((decorator) => { 85 | const result = decorator 86 | .getText() 87 | .includes(AureliaDecorator.VALUE_CONVERTER); 88 | return result; 89 | }) ?? false; 90 | 91 | const hasValueConverterNamingConvention = Boolean( 92 | classDeclaration.name?.getText().includes(AureliaClassTypes.VALUE_CONVERTER) 93 | ); 94 | 95 | return hasValueConverterDecorator || hasValueConverterNamingConvention; 96 | } 97 | -------------------------------------------------------------------------------- /server/src/aot/staticAnalysis/ValueConverterAnalyser.ts: -------------------------------------------------------------------------------- 1 | import * as Path from 'path'; 2 | 3 | import { ts } from 'ts-morph'; 4 | 5 | import { 6 | VALUE_CONVERTER_SUFFIX, 7 | AureliaClassTypes, 8 | } from '../../common/constants'; 9 | import { UriUtils } from '../../common/view/uri-utils'; 10 | import { IAureliaComponent } from '../aotTypes'; 11 | import { Optional } from '../parser/regions/ViewRegions'; 12 | 13 | export class ValueConverterAnalyser { 14 | public static getComponentInfo( 15 | targetClassDeclaration: ts.ClassDeclaration, 16 | sourceFile: ts.SourceFile, 17 | documentation: string 18 | ): Optional { 19 | const valueConverterName = targetClassDeclaration.name 20 | ?.getText() 21 | .replace(VALUE_CONVERTER_SUFFIX, '') 22 | .toLocaleLowerCase(); 23 | 24 | const result: Optional = { 25 | documentation, 26 | className: targetClassDeclaration.name?.getText() ?? '', 27 | valueConverterName, 28 | baseViewModelFileName: Path.parse(sourceFile.fileName).name, 29 | viewModelFilePath: UriUtils.toSysPath(sourceFile.fileName), 30 | type: AureliaClassTypes.VALUE_CONVERTER, 31 | sourceFile, 32 | }; 33 | 34 | return result; 35 | } 36 | 37 | public static checkValueConverter( 38 | targetClassDeclaration: ts.ClassDeclaration 39 | ) { 40 | const isValueConverterName = targetClassDeclaration.name 41 | ?.getText() 42 | .includes(VALUE_CONVERTER_SUFFIX); 43 | 44 | return Boolean(isValueConverterName); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /server/src/aot/tsMorph/tsMorphClass.ts: -------------------------------------------------------------------------------- 1 | import { ClassDeclaration, SourceFile, SyntaxKind } from 'ts-morph'; 2 | 3 | export function getClass( 4 | sourceFile: SourceFile | undefined, 5 | className: string 6 | ) { 7 | if (!sourceFile) throw new Error('No Source file'); 8 | 9 | const classNode = sourceFile.getClass(className)!; 10 | return classNode; 11 | } 12 | 13 | /** 14 | * @example 15 | * getClassMember(sourceFile, 'foo') 16 | */ 17 | export function getClassMember(classNode: ClassDeclaration, name: string) { 18 | classNode.getFirstDescendantByKind(SyntaxKind.Identifier)?.getText(); 19 | const target = classNode 20 | .getDescendantsOfKind(SyntaxKind.Identifier) 21 | .find((descendant) => { 22 | return descendant.getText() === name; 23 | }); 24 | 25 | return target; 26 | } 27 | -------------------------------------------------------------------------------- /server/src/common/@aurelia-runtime-patch/src/README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 9 | 10 | 11 | 27 | 33 | 34 | 35 | 41 | 42 |
7 |

Old School Table Layout

8 |
12 | 26 | 28 |

Welcome to our Old School site

29 |

Look at all these horrific table, tr tags and inline styles.

30 |

This is very clean in terms of code too. Some table vased layouts had nested tables, within nested tables 31 | within nested tables! 32 |

39 |

copyright © OLDSCHOOL ltd

40 |
-------------------------------------------------------------------------------- /server/src/common/@aurelia-runtime-patch/src/alias.ts: -------------------------------------------------------------------------------- 1 | import { Registration } from '@aurelia/kernel'; 2 | import { defineMetadata, getAnnotationKeyFor, getOwnMetadata } from './utilities-objects'; 3 | import type { Constructable, IResourceKind, ResourceDefinition, IContainer } from '@aurelia/kernel'; 4 | 5 | export function alias(...aliases: readonly string[]) { 6 | return function (target: Constructable) { 7 | const key = getAnnotationKeyFor('aliases'); 8 | const existing = getOwnMetadata(key, target); 9 | if (existing === void 0) { 10 | defineMetadata(key, aliases, target); 11 | } else { 12 | existing.push(...aliases); 13 | } 14 | }; 15 | } 16 | 17 | export function registerAliases(aliases: readonly string[], resource: IResourceKind, key: string, container: IContainer) { 18 | for (let i = 0, ii = aliases.length; i < ii; ++i) { 19 | Registration.aliasTo(key, resource.keyFrom(aliases[i])).register(container); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /server/src/common/@aurelia-runtime-patch/src/observation/connectable-switcher.ts: -------------------------------------------------------------------------------- 1 | import type { IConnectable } from '../observation'; 2 | 3 | /** 4 | * Current subscription collector 5 | */ 6 | let _connectable: IConnectable | null = null; 7 | const connectables: IConnectable[] = []; 8 | // eslint-disable-next-line 9 | export let connecting = false; 10 | 11 | // todo: layer based collection pause/resume? 12 | export function pauseConnecting() { 13 | connecting = false; 14 | } 15 | 16 | export function resumeConnecting() { 17 | connecting = true; 18 | } 19 | 20 | export function currentConnectable(): IConnectable | null { 21 | return _connectable; 22 | } 23 | 24 | export function enterConnectable(connectable: IConnectable): void { 25 | if (connectable == null) { 26 | if (true /**/) 27 | throw new Error('Connectable cannot be null/undefined'); 28 | else 29 | throw new Error('AUR0206'); 30 | } 31 | if (_connectable == null) { 32 | _connectable = connectable; 33 | connectables[0] = _connectable; 34 | connecting = true; 35 | return; 36 | } 37 | if (_connectable === connectable) { 38 | if (true /**/) 39 | throw new Error(`Trying to enter an active connectable`); 40 | else 41 | throw new Error('AUR0207'); 42 | } 43 | connectables.push(_connectable); 44 | _connectable = connectable; 45 | connecting = true; 46 | } 47 | 48 | export function exitConnectable(connectable: IConnectable): void { 49 | if (connectable == null) { 50 | if (true /**/) 51 | throw new Error('Connectable cannot be null/undefined'); 52 | else 53 | throw new Error('AUR0208'); 54 | } 55 | if (_connectable !== connectable) { 56 | if (true /**/) 57 | throw new Error(`Trying to exit an unactive connectable`); 58 | else 59 | throw new Error('AUR0209'); 60 | } 61 | 62 | connectables.pop(); 63 | _connectable = connectables.length > 0 ? connectables[connectables.length - 1] : null; 64 | connecting = _connectable != null; 65 | } 66 | 67 | export const ConnectableSwitcher = Object.freeze({ 68 | get current() { 69 | return _connectable; 70 | }, 71 | get connecting() { 72 | return connecting; 73 | }, 74 | enter: enterConnectable, 75 | exit: exitConnectable, 76 | pause: pauseConnecting, 77 | resume: resumeConnecting, 78 | }); 79 | -------------------------------------------------------------------------------- /server/src/common/@aurelia-runtime-patch/src/observation/flush-queue.ts: -------------------------------------------------------------------------------- 1 | import { def } from '../utilities-objects'; 2 | 3 | /* eslint-disable @typescript-eslint/ban-types */ 4 | /** 5 | * Add a readonly 'queue' property on the target class to return the default FlushQueue 6 | * implementation 7 | */ 8 | export function withFlushQueue(): ClassDecorator; 9 | export function withFlushQueue(target: Function): void; 10 | export function withFlushQueue(target?: Function): ClassDecorator | void { 11 | return target == null ? queueableDeco : queueableDeco(target); 12 | } 13 | 14 | function queueableDeco(target: Function): void { 15 | const proto = target.prototype as IWithFlushQueue; 16 | def(proto, 'queue', { get: getFlushQueue }); 17 | } 18 | /* eslint-enable @typescript-eslint/ban-types */ 19 | 20 | export interface IFlushable { 21 | flush(): void; 22 | } 23 | 24 | export interface IWithFlushQueue { 25 | queue: FlushQueue; 26 | } 27 | 28 | export class FlushQueue { 29 | public static readonly instance: FlushQueue = new FlushQueue(); 30 | 31 | /** @internal */ 32 | private _flushing: boolean = false; 33 | /** @internal */ 34 | private readonly _items: Set = new Set(); 35 | 36 | public get count(): number { 37 | return this._items.size; 38 | } 39 | 40 | public add(callable: IFlushable): void { 41 | this._items.add(callable); 42 | if (this._flushing) { 43 | return; 44 | } 45 | this._flushing = true; 46 | try { 47 | this._items.forEach(flushItem); 48 | } finally { 49 | this._flushing = false; 50 | } 51 | } 52 | 53 | public clear(): void { 54 | this._items.clear(); 55 | this._flushing = false; 56 | } 57 | } 58 | 59 | function getFlushQueue() { 60 | return FlushQueue.instance; 61 | } 62 | 63 | function flushItem(item: IFlushable, _: IFlushable, items: Set) { 64 | items.delete(item); 65 | item.flush(); 66 | } 67 | -------------------------------------------------------------------------------- /server/src/common/@aurelia-runtime-patch/src/observation/primitive-observer.ts: -------------------------------------------------------------------------------- 1 | import { Primitive } from '@aurelia/kernel'; 2 | import { AccessorType } from '../observation'; 3 | 4 | import type { IAccessor, ISubscribable } from '../observation'; 5 | 6 | export class PrimitiveObserver implements IAccessor, ISubscribable { 7 | public get doNotCache(): true { return true; } 8 | public type: AccessorType = AccessorType.None; 9 | /** @internal */ 10 | private readonly _obj: Primitive; 11 | /** @internal */ 12 | private readonly _key: PropertyKey; 13 | 14 | public constructor( 15 | obj: Primitive, 16 | key: PropertyKey, 17 | ) { 18 | this._obj = obj; 19 | this._key = key; 20 | } 21 | 22 | public getValue(): unknown { 23 | // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access,@typescript-eslint/no-explicit-any 24 | return (this._obj as any)[this._key]; 25 | } 26 | public setValue(): void { /* do nothing */ } 27 | public subscribe(): void { /* do nothing */ } 28 | public unsubscribe(): void { /* do nothing */ } 29 | } 30 | -------------------------------------------------------------------------------- /server/src/common/@aurelia-runtime-patch/src/observation/property-accessor.ts: -------------------------------------------------------------------------------- 1 | import { AccessorType, LifecycleFlags } from '../observation'; 2 | import type { IAccessor, IObservable } from '../observation'; 3 | 4 | export class PropertyAccessor implements IAccessor { 5 | // the only thing can be guaranteed is it's an object 6 | // even if this property accessor is used to access an element 7 | public type: AccessorType = AccessorType.None; 8 | 9 | public getValue(obj: object, key: string): unknown { 10 | return (obj as IObservable)[key]; 11 | } 12 | 13 | public setValue(value: unknown, flags: LifecycleFlags, obj: object, key: string): void { 14 | (obj as IObservable)[key] = value; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /server/src/common/@aurelia-runtime-patch/src/observation/signaler.ts: -------------------------------------------------------------------------------- 1 | import { DI } from '@aurelia/kernel'; 2 | import { createLookup } from '../utilities-objects'; 3 | import type { ISubscriber, LifecycleFlags } from '../observation'; 4 | 5 | type Signal = string; 6 | 7 | export interface ISignaler extends Signaler {} 8 | export const ISignaler = DI.createInterface('ISignaler', x => x.singleton(Signaler)); 9 | 10 | export class Signaler { 11 | public signals: Record> = createLookup(); 12 | 13 | public dispatchSignal(name: Signal, flags?: LifecycleFlags): void { 14 | const listeners = this.signals[name]; 15 | if (listeners === undefined) { 16 | return; 17 | } 18 | let listener: ISubscriber; 19 | for (listener of listeners.keys()) { 20 | listener.handleChange(undefined, undefined, flags!); 21 | } 22 | } 23 | 24 | public addSignalListener(name: Signal, listener: ISubscriber): void { 25 | const signals = this.signals; 26 | const listeners = signals[name]; 27 | if (listeners === undefined) { 28 | signals[name] = new Set([listener]); 29 | } else { 30 | listeners.add(listener); 31 | } 32 | } 33 | 34 | public removeSignalListener(name: Signal, listener: ISubscriber): void { 35 | const listeners = this.signals[name]; 36 | if (listeners) { 37 | listeners.delete(listener); 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /server/src/common/@aurelia-runtime-patch/src/startup-sequence.md: -------------------------------------------------------------------------------- 1 | ### Preliminary high-level overview of the startup sequence from a lifecycle perspective: 2 | 3 | ![](https://www.lucidchart.com/publicSegments/view/54a2c1be-6e40-48a3-898c-a7ff815b4bdc/image.png) 4 | -------------------------------------------------------------------------------- /server/src/common/@aurelia-runtime-patch/src/utilities-objects.ts: -------------------------------------------------------------------------------- 1 | import { Metadata, Protocol } from '@aurelia/kernel'; 2 | 3 | /** 4 | * A shortcut to Object.prototype.hasOwnProperty 5 | * Needs to do explicit .call 6 | * 7 | * @internal 8 | */ 9 | export const hasOwnProp = Object.prototype.hasOwnProperty; 10 | 11 | /** @internal */ export const def = Reflect.defineProperty; 12 | 13 | // eslint-disable-next-line @typescript-eslint/ban-types 14 | // @ts-ignore 15 | /** @internal */ export const isFunction = (v: unknown): v is K => typeof v === 'function'; 16 | 17 | /** @internal */ export const isString = (v: unknown): v is string => typeof v === 'string'; 18 | 19 | /** @internal */ export function defineHiddenProp(obj: object, key: PropertyKey, value: T): T { 20 | def(obj, key, { 21 | enumerable: false, 22 | configurable: true, 23 | writable: true, 24 | value 25 | }); 26 | return value; 27 | } 28 | 29 | /** @internal */ export function ensureProto( 30 | proto: T, 31 | key: K, 32 | defaultValue: unknown, 33 | force: boolean = false 34 | ): void { 35 | if (force || !hasOwnProp.call(proto, key)) { 36 | defineHiddenProp(proto, key, defaultValue); 37 | } 38 | } 39 | 40 | /** @internal */ export const createLookup = (): Record => Object.create(null); 41 | 42 | /** @internal */ export const getOwnMetadata = Metadata.getOwn; 43 | /** @internal */ export const hasOwnMetadata = Metadata.hasOwn; 44 | /** @internal */ export const defineMetadata = Metadata.define; 45 | /** @internal */ export const getAnnotationKeyFor = Protocol.annotation.keyFor; 46 | /** @internal */ export const getResourceKeyFor = Protocol.resource.keyFor; 47 | /** @internal */ export const appendResourceKey = Protocol.resource.appendTo; 48 | -------------------------------------------------------------------------------- /server/src/common/AureliaUtils.ts: -------------------------------------------------------------------------------- 1 | import { camelCase } from '@aurelia/kernel'; 2 | import { AureliaVersion } from './constants'; 3 | 4 | export class AureliaUtils { 5 | public static normalizeVariable(varName: string): string { 6 | return camelCase(varName); 7 | } 8 | 9 | public static isSameVariableName( 10 | name1: string | undefined, 11 | name2: string | undefined 12 | ): boolean { 13 | if (name1 === undefined) return false; 14 | if (name2 === undefined) return false; 15 | 16 | const normalized1 = this.normalizeVariable(name1); 17 | const normalized2 = this.normalizeVariable(name2); 18 | const isSame = normalized1 === normalized2; 19 | 20 | return isSame; 21 | } 22 | 23 | public static isAuV1(auVersion: AureliaVersion): boolean { 24 | const is = auVersion === AureliaVersion.V1; 25 | return is; 26 | } 27 | 28 | public static isAuV2(auVersion: AureliaVersion): boolean { 29 | const is = auVersion === AureliaVersion.V1; 30 | return is; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /server/src/common/MyLodash.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-unsafe-call */ 2 | /* eslint-disable @typescript-eslint/no-unsafe-assignment */ 3 | /* eslint-disable @typescript-eslint/no-unsafe-argument */ 4 | /* eslint-disable @typescript-eslint/ban-ts-comment */ 5 | // @ts-nocheck 6 | 7 | export class MyLodash { 8 | /** https://stackoverflow.com/questions/35228052/debounce-function-implemented-with-promises */ 9 | public static debouncePromise(inner, ms = 0) { 10 | let timer = null; 11 | let resolves = []; 12 | 13 | return function (...args) { 14 | // Run the function after a certain amount of time 15 | clearTimeout(timer); 16 | timer = setTimeout(() => { 17 | // Get the result of the inner function, then apply it to the resolve function of 18 | // each promise that has been created since the last time the inner function was run 19 | const result = inner(...args); 20 | // eslint-disable-next-line @typescript-eslint/no-unsafe-return 21 | resolves.forEach((r) => r(result)); 22 | resolves = []; 23 | }, ms); 24 | 25 | return new Promise((r) => resolves.push(r)); 26 | }; 27 | } 28 | 29 | /** https://gist.github.com/nmsdvid/8807205#gistcomment-3939848 */ 30 | public static debounce(func: (...args: T[]) => unknown, delay = 200) { 31 | let timeout: number | NodeJS.Timeout; 32 | 33 | return function (...args: T[]) { 34 | clearTimeout(timeout as number); 35 | timeout = setTimeout(() => func(...args), delay); 36 | }; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /server/src/common/className.ts: -------------------------------------------------------------------------------- 1 | import { kebabCase } from 'lodash'; 2 | import { ts } from 'ts-morph'; 3 | 4 | import { getClassDecoratorInfos } from '../aot/getAureliaComponentList'; 5 | import { CUSTOM_ELEMENT_SUFFIX } from './constants'; 6 | 7 | /** 8 | * Fetches the equivalent component name based on the given class declaration 9 | * 10 | * @param sourceFile - The class declaration to map a component name from 11 | */ 12 | export function getElementNameFromClassDeclaration( 13 | classDeclaration: ts.ClassDeclaration 14 | ): string { 15 | const className = classDeclaration.name?.getText() ?? ''; 16 | const classDecoratorInfos = getClassDecoratorInfos(classDeclaration); 17 | 18 | const customElementDecoratorName = classDecoratorInfos.find( 19 | (info) => info.decoratorName === 'customElement' 20 | )?.decoratorArgument; 21 | 22 | // Prioritize decorator name over class name convention 23 | if (customElementDecoratorName) { 24 | return customElementDecoratorName; 25 | } 26 | 27 | const withoutCustomElementSuffix = className.replace( 28 | CUSTOM_ELEMENT_SUFFIX, 29 | '' 30 | ); 31 | return kebabCase(withoutCustomElementSuffix); 32 | } 33 | -------------------------------------------------------------------------------- /server/src/common/debugging/debuggingSandbox.ts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aurelia/vscode-extension/897308feeab78167bec80b1e242aa58f30d2e770/server/src/common/debugging/debuggingSandbox.ts -------------------------------------------------------------------------------- /server/src/common/debugging/saveDataToDisk.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-unsafe-return */ 2 | /* eslint-disable @typescript-eslint/no-explicit-any */ 3 | /* eslint-disable @typescript-eslint/no-unsafe-assignment */ 4 | import * as fs from 'fs'; 5 | 6 | const SAVE_FILE_PATH = 7 | '/Users/hdn/Desktop/aurelia-vscode-extension/vscode-extension/server/src/common/debugging/debugging-data.json'; 8 | 9 | export function saveDataToDisk(data: Record): void { 10 | let finalData = {}; 11 | 12 | if (fs.existsSync(SAVE_FILE_PATH)) { 13 | const existingSave = fs.readFileSync(SAVE_FILE_PATH, 'utf-8'); 14 | const asJson = JSON.parse(existingSave || '{}'); 15 | finalData = asJson; 16 | } 17 | 18 | finalData = { 19 | ...finalData, 20 | ...data, 21 | }; 22 | const asJsonString = JSON.stringify(finalData, null, 4); 23 | fs.writeFileSync(SAVE_FILE_PATH, asJsonString); 24 | } 25 | 26 | export function getSaveData(): Record { 27 | if (fs.existsSync(SAVE_FILE_PATH)) { 28 | const existingSave = fs.readFileSync(SAVE_FILE_PATH, 'utf-8'); 29 | const asJson = JSON.parse(existingSave); 30 | return asJson; 31 | } 32 | 33 | throw new Error('No Save data'); 34 | } 35 | -------------------------------------------------------------------------------- /server/src/common/diagnosticMessages/DiagnosticMessages.ts: -------------------------------------------------------------------------------- 1 | import { Logger } from '../logging/logger'; 2 | import { diagnosticMessagesData } from './diagnosticMessagesData'; 3 | 4 | const logger = new Logger('Diagnostics'); 5 | 6 | export class DiagnosticMessages { 7 | private readonly aureliaCode = 'auvsc'; 8 | 9 | private readonly diagnosticCodeForMessage: string; 10 | 11 | constructor(private readonly message: keyof typeof diagnosticMessagesData) { 12 | this.message = message; 13 | this.diagnosticCodeForMessage = `${this.aureliaCode}(${diagnosticMessagesData[message].code})`; 14 | } 15 | 16 | public log(): void { 17 | const targetMessage = diagnosticMessagesData[this.message]; 18 | const consoleMessage = `[${targetMessage.category}] ${this.message} ${this.diagnosticCodeForMessage}`; 19 | 20 | // logger.log(consoleMessage); 21 | 22 | } 23 | 24 | public additionalLog(message: string, data: unknown): void { 25 | 26 | // logger.log(`${message}: ${data} ${this.diagnosticCodeForMessage}`); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /server/src/common/diagnosticMessages/diagnosticMessagesData.ts: -------------------------------------------------------------------------------- 1 | export const diagnosticMessagesData = { 2 | 'Chained value converters not supported yet.': { 3 | category: 'Unsupported', 4 | code: 7001, 5 | }, 6 | }; 7 | -------------------------------------------------------------------------------- /server/src/common/documens/OffsetUtils.ts: -------------------------------------------------------------------------------- 1 | export class OffsetUtils { 2 | public static isIncluded( 3 | startOffset: number | undefined, 4 | endOffset: number | undefined, 5 | targetOffset: number | undefined 6 | ) { 7 | if (startOffset == null) return false; 8 | if (endOffset == null) return false; 9 | if (targetOffset == null) return false; 10 | 11 | const isStart = startOffset <= targetOffset; 12 | const isEnd = targetOffset <= endOffset; 13 | const result = isStart && isEnd; 14 | return result; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /server/src/common/documens/PositionUtils.ts: -------------------------------------------------------------------------------- 1 | import { Position } from 'vscode-languageserver'; 2 | 3 | import { projectPosition } from '../view/project-position'; 4 | 5 | export class PositionUtils { 6 | public static convertToZeroIndexed(line: number, character: number) { 7 | const position = Position.create(line - 1, character - 1); 8 | return position; 9 | } 10 | 11 | public static isIncluded(start: Position, end: Position, target: Position) { 12 | const projectedStart = projectPosition(start); 13 | const projectedEnd = projectPosition(end); 14 | const projectedSources = projectPosition(target); 15 | const isIncluded = 16 | projectedStart <= projectedSources && projectedSources <= projectedEnd; 17 | 18 | return isIncluded; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /server/src/common/documens/TextDocumentUtils.ts: -------------------------------------------------------------------------------- 1 | import * as fs from 'fs'; 2 | 3 | import { TextDocuments } from 'vscode-languageserver'; 4 | import { TextDocument } from 'vscode-languageserver-textdocument'; 5 | 6 | import { 7 | DocumentSettings, 8 | ExtensionSettings, 9 | } from '../../configuration/DocumentSettings'; 10 | import { UriUtils } from '../view/uri-utils'; 11 | 12 | export class TextDocumentUtils { 13 | public static createFromPath( 14 | path: string, 15 | languageId: 'html' | 'typescript' | 'javascript' = 'html' 16 | ): TextDocument { 17 | const content = fs.readFileSync(path, 'utf-8'); 18 | const document = TextDocument.create( 19 | UriUtils.toVscodeUri(path), 20 | languageId, 21 | 0, 22 | content 23 | ); 24 | 25 | return document; 26 | } 27 | 28 | public static createViewModelFromPath( 29 | path: string 30 | ): TextDocument | undefined { 31 | const pathStat = fs.statSync(path); 32 | if (!pathStat.isFile()) return; 33 | 34 | const content = fs.readFileSync(path, 'utf-8'); 35 | const document = TextDocument.create( 36 | UriUtils.toVscodeUri(path), 37 | 'html', 38 | 0, 39 | content 40 | ); 41 | 42 | return document; 43 | } 44 | 45 | public static createHtmlFromUri( 46 | { uri }: { uri: string }, 47 | allDocuments?: TextDocuments 48 | ): TextDocument { 49 | const openDocument = allDocuments?.get(uri); 50 | if (openDocument) { 51 | return openDocument; 52 | } 53 | 54 | const content = fs.readFileSync(UriUtils.toSysPath(uri), 'utf-8'); 55 | const document = TextDocument.create(uri, 'html', 0, content); 56 | 57 | return document; 58 | } 59 | 60 | public static createHtmlFromPath(path: string): TextDocument | undefined { 61 | const pathStat = fs.statSync(path); 62 | if (!pathStat.isFile()) return; 63 | 64 | const content = fs.readFileSync(path, 'utf-8'); 65 | const document = TextDocument.create( 66 | UriUtils.toVscodeUri(path), 67 | 'html', 68 | 0, 69 | content 70 | ); 71 | 72 | return document; 73 | } 74 | } 75 | 76 | export function isViewModelDocument( 77 | document: { uri: string }, 78 | documentSettings: DocumentSettings | ExtensionSettings 79 | ) { 80 | let settings: ExtensionSettings; 81 | if (documentSettings instanceof DocumentSettings) { 82 | settings = documentSettings.getSettings(); 83 | } else { 84 | settings = documentSettings; 85 | } 86 | 87 | const scriptExtensions = settings?.relatedFiles?.script; 88 | const isScript = scriptExtensions?.find((extension) => 89 | document.uri.endsWith(extension) 90 | ); 91 | return Boolean(isScript); 92 | } 93 | -------------------------------------------------------------------------------- /server/src/common/documens/related.ts: -------------------------------------------------------------------------------- 1 | import * as fs from 'fs'; 2 | import * as path from 'path'; 3 | 4 | /** 5 | * @param fullPath - Full path of the file, which triggered the command 6 | * @param relatedExts - Possible extensions, for target file 7 | * @returns targetFile 8 | */ 9 | export function getRelatedFilePath( 10 | fullPath: string, 11 | relatedExts: string[] 12 | ): string { 13 | let targetFile: string = ''; 14 | try { 15 | relatedExts.forEach((ext) => { 16 | const fileName = `${path.basename( 17 | fullPath, 18 | path.extname(fullPath) 19 | )}${ext}`.replace('.spec.spec', '.spec'); // Quick fix because we are appending eg. '.spec.ts' to 'file.spec' 20 | fullPath = path.join(path.dirname(fullPath), fileName); 21 | if (!fs.existsSync(fullPath)) return; 22 | 23 | targetFile = fullPath; 24 | }); 25 | } catch (error) { 26 | console.log(error); 27 | } 28 | return targetFile; 29 | } 30 | -------------------------------------------------------------------------------- /server/src/common/documens/selections.ts: -------------------------------------------------------------------------------- 1 | import { AllDocuments, GetEditorSelectionResponse } from '../types/types'; 2 | import { Range } from 'vscode-languageserver'; 3 | 4 | export function extractSelectedTexts( 5 | getEditorSelectionResponse: GetEditorSelectionResponse, 6 | allDocuments: AllDocuments, 7 | ) { 8 | const { documentUri, selections } = getEditorSelectionResponse; 9 | const document = allDocuments.get(documentUri); 10 | 11 | let rawTexts = selections.map((selection) => { 12 | const range = Range.create(selection.start, selection.end); 13 | const text = document?.getText(range); 14 | return text; 15 | }); 16 | const selectedTexts = rawTexts.filter( 17 | (text) => text !== undefined 18 | ) as string[]; 19 | 20 | return selectedTexts; 21 | } 22 | -------------------------------------------------------------------------------- /server/src/common/documens/split-by-word-separators.ts: -------------------------------------------------------------------------------- 1 | import { WORD_SEPARATORS_REGEX_STRING } from '../constants'; 2 | 3 | export function splitByWordSeparators(input: string) { 4 | const whiteSpaceRegex = '\\s\\r\\n\\t'; 5 | const myRegex = new RegExp( 6 | `[${WORD_SEPARATORS_REGEX_STRING}${whiteSpaceRegex}]`, 7 | 'i' 8 | ); 9 | const split = input.split(myRegex).filter((_string) => _string !== ''); 10 | return split; 11 | } 12 | -------------------------------------------------------------------------------- /server/src/common/documens/xScopeUtils.ts: -------------------------------------------------------------------------------- 1 | import { ViewRegionInfoV2 } from '../../aot/parser/regions/ViewRegions'; 2 | 3 | export class XScopeUtils { 4 | public static getScopeByOffset( 5 | scopes: ViewRegionInfoV2['accessScopes'], 6 | offset: number | undefined 7 | ) { 8 | if (scopes == null) return; 9 | if (offset == null) return; 10 | 11 | const result = scopes.find((scope) => { 12 | const { start, end } = scope.nameLocation; 13 | const afterStart = start <= offset; 14 | const beforeEnd = offset <= end; 15 | const inBetween = afterStart && beforeEnd; 16 | 17 | return inBetween; 18 | }); 19 | 20 | return result; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /server/src/common/global.d.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-explicit-any */ 2 | export type AsyncReturnType any> = T extends ( 3 | ...args: any 4 | ) => Promise 5 | ? U 6 | : T extends (...args: any) => infer U 7 | ? U 8 | : any; 9 | -------------------------------------------------------------------------------- /server/src/common/object/ObjectUtils.ts: -------------------------------------------------------------------------------- 1 | export class ObjectUtils { 2 | /** 3 | * @example 4 | * const obj = { 5 | * a: { 6 | * b: { 7 | * c: 'arrived', 8 | * d: 'well', 9 | * }, 10 | * }, 11 | * }; 12 | * -- atPath -> 13 | * { a: { b: { c: 'arrived' } } } 14 | */ 15 | public static atPath( 16 | obj: Record, 17 | keys: (string | undefined)[], 18 | result: Record 19 | ) { 20 | if (obj == null) return; 21 | 22 | keys.forEach((key) => { 23 | if (key == null) return; 24 | if (obj[key] == null) return; 25 | if (typeof obj[key] === 'object') { 26 | result[key] = {}; 27 | } else { 28 | result[key] = obj[key]; 29 | } 30 | // eslint-disable-next-line @typescript-eslint/ban-ts-comment 31 | // @ts-ignore 32 | this.atPath(obj[key], keys, result[key]); 33 | }); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /server/src/common/services/AnalyzerService.ts: -------------------------------------------------------------------------------- 1 | import { TextDocument } from 'vscode-languageserver-textdocument'; 2 | 3 | import { IAureliaComponent } from '../../aot/aotTypes'; 4 | import { AureliaProjects } from '../../core/AureliaProjects'; 5 | import { Container } from '../../core/container'; 6 | 7 | export class AnalyzerService { 8 | public static getComponentByDocumennt( 9 | container: Container, 10 | document: TextDocument 11 | ): IAureliaComponent | undefined { 12 | const aureliaProjects = container.get(AureliaProjects); 13 | const targetProject = aureliaProjects.getFromUri(document.uri); 14 | 15 | if (!targetProject) return; 16 | const aureliaProgram = targetProject?.aureliaProgram; 17 | if (!aureliaProgram) return; 18 | 19 | const component = 20 | aureliaProgram.aureliaComponents.getOneByFromDocument(document); 21 | if (!component) return; 22 | 23 | return component; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /server/src/common/string/StringUtils.ts: -------------------------------------------------------------------------------- 1 | export class StringUtils { 2 | public static replaceAll( 3 | input: string, 4 | searchValue: string, 5 | replaceValue: string 6 | ) { 7 | const searchRegex = new RegExp(`\\b${searchValue}\\b`, 'g'); 8 | const result = input.replace(searchRegex, () => { 9 | return replaceValue; 10 | }); 11 | return result; 12 | } 13 | 14 | public static insert( 15 | str: string | undefined, 16 | index: number, 17 | value: string | undefined 18 | ): string { 19 | if (str == null) return ''; 20 | if (value == null) return ''; 21 | 22 | const ind = index < 0 ? this.length + index : index; 23 | return str.substr(0, ind) + value + str.substr(ind); 24 | } 25 | 26 | static removeQuotes(rawPackageRoot: string) { 27 | const withOutQuotes = rawPackageRoot.replace(/['"]/g, ''); 28 | return withOutQuotes; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /server/src/common/template/aurelia-attributes.ts: -------------------------------------------------------------------------------- 1 | import { AURELIA_TEMPLATE_ATTRIBUTE_KEYWORD_LIST } from '../constants'; 2 | 3 | /** 4 | * @example 5 | * const input = 'inter-bindable.bind' 6 | * ^ 7 | * getAureliaAttributeKeywordIndex(input) // 14 8 | */ 9 | export function getAureliaAttributeKeywordIndex(input: string): number { 10 | let index = NaN; 11 | 12 | AURELIA_TEMPLATE_ATTRIBUTE_KEYWORD_LIST.find((keyword) => { 13 | const withDot = `.${keyword}`; 14 | const match = input.indexOf(withDot); 15 | if (match >= 0) { 16 | index = match; 17 | return true; 18 | } 19 | return false; 20 | }); 21 | 22 | return index; 23 | } 24 | 25 | /** 26 | * @example 27 | * const input = 'inter-bindable.bind' 28 | * 29 | * getBindableNameFromAttritute(input) // inter-bindable 30 | */ 31 | export function getBindableNameFromAttritute(input: string): string { 32 | // Sth like: .(bind|call)$ 33 | const asRegex = new RegExp( 34 | `.(${AURELIA_TEMPLATE_ATTRIBUTE_KEYWORD_LIST.join('|')})$` 35 | ); 36 | const result = input.replace(asRegex, ''); 37 | return result; 38 | } 39 | 40 | // getBindableNameFromAttritute('inter-bindable.bind'); 41 | -------------------------------------------------------------------------------- /server/src/common/types/types.ts: -------------------------------------------------------------------------------- 1 | import { TextDocuments } from 'vscode-languageserver'; 2 | import { Position, TextDocument } from 'vscode-languageserver-textdocument'; 3 | 4 | export type AllDocuments = TextDocuments; 5 | 6 | /** 7 | * Type from the client: vscode.TextEditor.Selection 8 | */ 9 | export interface ClientEditorSelection { 10 | /** 11 | * The position at which the selection starts. 12 | * This position might be before or after [active](#Selection.active). 13 | */ 14 | anchor: Position; 15 | /** 16 | * The position of the cursor. 17 | * This position might be before or after [anchor](#Selection.anchor). 18 | */ 19 | active: Position; 20 | start: Position; 21 | end: Position; 22 | 23 | /** 24 | * A selection is reversed if [active](#Selection.active).isBefore([anchor](#Selection.anchor)). 25 | */ 26 | // isReversed: boolean; 27 | } 28 | 29 | export interface GetEditorSelectionResponse { 30 | documentText: string; 31 | documentUri: string; 32 | documentPath: string; 33 | selections: ClientEditorSelection[]; 34 | } 35 | 36 | export interface UserSuppliedTemplatesFunctions { 37 | createViewModelTemplate: () => string 38 | createViewTemplate: () => string 39 | } 40 | -------------------------------------------------------------------------------- /server/src/common/view/project-position.ts: -------------------------------------------------------------------------------- 1 | import { Position } from 'vscode-languageserver-textdocument'; 2 | 3 | interface LocationLike { 4 | startLine?: number; 5 | startCol?: number; 6 | endLine?: number; 7 | endCol?: number; 8 | } 9 | 10 | /** 11 | * Project 2dim line x character to a 1dim value 12 | */ 13 | export function projectLocation(location: LocationLike): number | undefined { 14 | const line = location.startLine ?? location.endLine; 15 | const character = location.startCol ?? location.endCol; 16 | 17 | if (line == null) return; 18 | if (character == null) return; 19 | 20 | const projection = projectPosition({ line, character }); 21 | return projection; 22 | } 23 | 24 | /** 25 | * Project 2dim line x character to a 1dim value 26 | */ 27 | export function projectPosition(position: Position) { 28 | const projection = position.line * 100000 + position.character; 29 | return projection; 30 | } 31 | -------------------------------------------------------------------------------- /server/src/common/view/uri-utils.ts: -------------------------------------------------------------------------------- 1 | import * as nodePath from 'path'; 2 | 3 | export class UriUtils { 4 | /** 5 | * Convert path to how your system would use it. 6 | * 7 | * Re. Naming: Similar to `nodePath#normalize`, but I wanted to stress the vscode-uri vs. window-path fact. 8 | */ 9 | public static toSysPath(path: string) { 10 | if (path.includes('file:')) { 11 | path = this.removeFileProtocol(path); 12 | } 13 | 14 | if (this.isWin()) { 15 | path = this.decodeWinPath(path); 16 | } 17 | 18 | path = nodePath.normalize(path); 19 | 20 | return path; 21 | } 22 | 23 | public static toSysPathMany(uris: string[]): string[] { 24 | const asPaths = uris.map((uri) => this.toSysPath(uri)); 25 | return asPaths; 26 | } 27 | 28 | /** 29 | * Linux/macOS just prefix `file://` 30 | * Windows: convert backslash to forwardslash and encode 31 | */ 32 | public static toVscodeUri(filePath: string): string { 33 | // filePath; /*?*/ 34 | // const uri = pathToFileURL(filePath).href; 35 | let uri = filePath; 36 | if (this.isWin()) { 37 | uri = this.encodeWinPath(filePath); 38 | uri = `file:///${uri}`; 39 | } else { 40 | uri = `file://${uri}`; 41 | } 42 | 43 | return uri; 44 | } 45 | 46 | public static isWin(): boolean { 47 | return nodePath.sep === '\\'; 48 | } 49 | 50 | public static encodeWinPath(path: string): string { 51 | // C%3A/Users/hdn%20local/.vscode/extensions/wallabyjs.wallaby-vscode-1.0.318/projects/01d527eb4e87d260/instrumented/tests/testFixture/scoped-for-testing/src/view/custom-element/other-custom-element-user.html 52 | let encodePath = path.replace(/\\\\?/g, () => '/'); 53 | // fix colon 54 | encodePath = encodePath.replace(':', '%3A'); 55 | // fix whitespace 56 | encodePath = encodePath.replace(' ', '%20'); 57 | 58 | return encodePath; 59 | } 60 | 61 | private static decodeWinPath(path: string): string { 62 | // C%3A/Users/hdn%20local/.vscode/extensions/wallabyjs.wallaby-vscode-1.0.318/projects/01d527eb4e87d260/instrumented/tests/testFixture/scoped-for-testing/src/view/custom-element/other-custom-element-user.html 63 | let winPath = path; 64 | if (winPath.startsWith('/')) { 65 | winPath = winPath.replace('/', ''); 66 | } 67 | winPath = winPath.replace(/\//g, '\\'); 68 | winPath = winPath.replace(/%3A/g, ':'); 69 | winPath = winPath.replace(/%20/g, ' '); 70 | 71 | return winPath; 72 | } 73 | private static removeFileProtocol(fileUri: string): string { 74 | const removed = fileUri.replace(/^file:\/\/?\/?/g, ''); 75 | if (this.isWin()) { 76 | return removed; 77 | } 78 | 79 | const addSlashAtStart = `/${removed}`; 80 | return addSlashAtStart; 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /server/src/core/container.ts: -------------------------------------------------------------------------------- 1 | import { Container } from 'aurelia-dependency-injection'; 2 | 3 | export * from 'aurelia-dependency-injection'; 4 | 5 | export const globalContainer = new Container(); 6 | -------------------------------------------------------------------------------- /server/src/core/depdencenyInjection.ts: -------------------------------------------------------------------------------- 1 | import { Container } from 'aurelia-dependency-injection'; 2 | import { Connection, TextDocuments } from 'vscode-languageserver'; 3 | import { TextDocument } from 'vscode-languageserver-textdocument'; 4 | 5 | import { 6 | ExtensionSettings, 7 | DocumentSettings, 8 | } from '../configuration/DocumentSettings'; 9 | import { DeclareViewModelVariable } from '../feature/commands/declareViewModelVariable/declareViewModelVariable'; 10 | import { ExtractComponent } from '../feature/commands/extractComponent/extractComponent'; 11 | import { AureliaProjects } from './AureliaProjects'; 12 | 13 | export const ConnectionInjection = 'Connection'; 14 | export const AllDocumentsInjection = 'AllDocuments'; 15 | 16 | export function initDependencyInjection( 17 | container: Container, 18 | connection: Connection, 19 | extensionSettings: ExtensionSettings, 20 | allDocuments: TextDocuments 21 | ) { 22 | container.registerInstance(Container); 23 | container.registerInstance(ConnectionInjection, connection); 24 | container.registerInstance(AllDocumentsInjection, allDocuments); 25 | 26 | container.registerInstance( 27 | DocumentSettings, 28 | new DocumentSettings(extensionSettings) 29 | ); 30 | const settings = container.get(DocumentSettings); 31 | container.registerInstance(AureliaProjects, new AureliaProjects(settings)); 32 | const aureliaProjects = container.get(AureliaProjects); 33 | 34 | container.registerInstance( 35 | ExtractComponent, 36 | new ExtractComponent(container, connection, allDocuments, aureliaProjects) 37 | ); 38 | 39 | container.registerInstance( 40 | DeclareViewModelVariable, 41 | new DeclareViewModelVariable(container, connection, allDocuments, aureliaProjects) 42 | ); 43 | } 44 | -------------------------------------------------------------------------------- /server/src/feature/codeAction/onCodeAction.ts: -------------------------------------------------------------------------------- 1 | import { TextDocuments } from 'vscode-languageserver'; 2 | import { CodeActionParams } from 'vscode-languageserver-protocol'; 3 | import { TextDocument } from 'vscode-languageserver-textdocument'; 4 | 5 | import { AbstractRegionLanguageService } from '../../aot/parser/regions/languageServer/AbstractRegionLanguageService'; 6 | import { AureliaHtmlLanguageService } from '../../aot/parser/regions/languageServer/AureliaHtmlLanguageService'; 7 | import { TextDocumentUtils } from '../../common/documens/TextDocumentUtils'; 8 | import { RegionService } from '../../common/services/RegionService'; 9 | import { UriUtils } from '../../common/view/uri-utils'; 10 | import { AureliaProjects } from '../../core/AureliaProjects'; 11 | import { Container } from '../../core/container'; 12 | 13 | export async function onCodeAction( 14 | container: Container, 15 | { textDocument, range }: CodeActionParams, 16 | allDocuments: TextDocuments 17 | ) { 18 | // We need some kind of code action map 19 | // Since, eg. the aHref tag should only trigger "rename to import tag" code action 20 | 21 | const aureliaProjects = container.get(AureliaProjects); 22 | const targetProject = aureliaProjects.getFromUri(textDocument.uri); 23 | if (!targetProject) return []; 24 | const aureliaProgram = targetProject?.aureliaProgram; 25 | if (!aureliaProgram) return []; 26 | 27 | const targetComponent = aureliaProgram.aureliaComponents.getOneBy( 28 | 'viewFilePath', 29 | UriUtils.toSysPath(textDocument.uri) 30 | ); 31 | const regions = targetComponent?.viewRegions; 32 | if (!regions) return []; 33 | 34 | const region = RegionService.findRegionAtPosition(regions, range.start); 35 | 36 | let languageService: AbstractRegionLanguageService; 37 | if (region === undefined) { 38 | languageService = new AureliaHtmlLanguageService(); 39 | } else { 40 | languageService = region.languageService; 41 | } 42 | const doCodeAction = languageService.doCodeAction; 43 | 44 | if (doCodeAction) { 45 | const document = TextDocumentUtils.createHtmlFromUri( 46 | textDocument, 47 | allDocuments 48 | ); 49 | const codeAction = await doCodeAction( 50 | aureliaProgram, 51 | document, 52 | range.start, 53 | region 54 | ); 55 | return codeAction; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /server/src/feature/commands/declareViewModelVariable/README.md: -------------------------------------------------------------------------------- 1 | ## Description 2 | - Select code in your view 3 | - Run command "[Au] Declare View Model Variable from Selection" 4 | - In your view model, the selected text gets added 5 | 6 | ## Limitations 7 | - View needs View Model 8 | - Class needs at least one member 9 | -------------------------------------------------------------------------------- /server/src/feature/commands/extractComponent/README.md: -------------------------------------------------------------------------------- 1 | - create file in `/.aurelia/extension/templates.js` 2 | - has to be a js file 3 | - access variables from within extension 4 | - 5 | -------------------------------------------------------------------------------- /server/src/feature/commands/onExecuteCommand.ts: -------------------------------------------------------------------------------- 1 | // import { ExecuteCommandParams } from 'vscode-languageserver'; 2 | // import { AureliaProjects } from '../../core/AureliaProjects'; 3 | // import { Container } from '../../core/container'; 4 | 5 | // export function onExecuteCommand( 6 | // container: Container, 7 | // executeCommandParams: ExecuteCommandParams 8 | // ) { 9 | // // const aureliaProjects = container.get(AureliaProjects); 10 | // // const targetProject = aureliaProjects.getFromUri(document.uri); 11 | // // if (!targetProject) return; 12 | // // const aureliaProgram = targetProject?.aureliaProgram; 13 | // // if (!aureliaProgram) return; 14 | 15 | // // const targetComponent = 16 | // // aureliaProgram.aureliaComponents.getOneByFromDocument(document); 17 | // // const regions = targetComponent?.viewRegions; 18 | 19 | // // if (!regions) return; 20 | 21 | // // const region = ViewRegionUtils.findRegionAtPosition(regions, position); 22 | // // if (!region) return normalRename(position, document, newName); 23 | // // const doRename = region.languageService.doRename; 24 | // } 25 | -------------------------------------------------------------------------------- /server/src/feature/content/changeContent.ts: -------------------------------------------------------------------------------- 1 | import { Container } from 'aurelia-dependency-injection'; 2 | import { TextDocumentChangeEvent } from 'vscode-languageserver'; 3 | import { TextDocument } from 'vscode-languageserver-textdocument'; 4 | 5 | import { Logger } from '../../common/logging/logger'; 6 | import { AureliaProjects } from '../../core/AureliaProjects'; 7 | 8 | const logger = new Logger('changeContent'); 9 | 10 | export async function onConnectionDidChangeContent( 11 | container: Container, 12 | { document }: TextDocumentChangeEvent 13 | ) { 14 | const aureliaProjects = container.get(AureliaProjects); 15 | 16 | // Keep track of changed map 17 | aureliaProjects.addDocumentToEditingTracker(document); 18 | 19 | // Hydration 20 | if (!aureliaProjects.isHydrated()) { 21 | /* prettier-ignore */ logger.log('Initilization started.',{logMs:true,msStart:true}); 22 | await aureliaProjects.hydrate([document]); 23 | /* prettier-ignore */ logger.log('Initilization done. Aurelia Extension is ready to use. 🚀',{logMs:true,msEnd:true}); 24 | return; 25 | } 26 | if (aureliaProjects.preventHydration(document)) return; 27 | 28 | // Updating 29 | switch (document.languageId) { 30 | case 'javascript': 31 | case 'typescript': { 32 | aureliaProjects.updateManyViewModel([document]); 33 | logger.log('View model updated.'); 34 | break; 35 | } 36 | case 'html': { 37 | aureliaProjects.updateManyView([document]); 38 | logger.log('View updated'); 39 | } } 40 | } 41 | -------------------------------------------------------------------------------- /server/src/feature/definition/onDefinitions.ts: -------------------------------------------------------------------------------- 1 | import { pathToFileURL } from 'url'; 2 | 3 | import { Position, TextDocument } from 'vscode-html-languageservice'; 4 | import { LocationLink, Range } from 'vscode-languageserver'; 5 | 6 | import { RegionParser } from '../../aot/parser/regions/RegionParser'; 7 | import { AbstractRegion } from '../../aot/parser/regions/ViewRegions'; 8 | import { isViewModelDocument } from '../../common/documens/TextDocumentUtils'; 9 | import { RegionService } from '../../common/services/RegionService'; 10 | import { ParseHtml } from '../../common/view/document-parsing'; 11 | import { DocumentSettings } from '../../configuration/DocumentSettings'; 12 | import { AureliaProjects } from '../../core/AureliaProjects'; 13 | import { Container } from '../../core/container'; 14 | import { aureliaDefinitionFromViewModel } from './aureliaDefintion'; 15 | 16 | export async function onDefintion( 17 | document: TextDocument, 18 | position: Position, 19 | container: Container 20 | ): Promise { 21 | const aureliaProjects = container.get(AureliaProjects); 22 | const targetProject = aureliaProjects.getFromUri(document.uri); 23 | if (!targetProject) return; 24 | const aureliaProgram = targetProject?.aureliaProgram; 25 | if (!aureliaProgram) return; 26 | 27 | const targetComponent = 28 | aureliaProgram.aureliaComponents.getOneByFromDocument(document); 29 | 30 | const documentSettings = container.get(DocumentSettings); 31 | const isViewModel = isViewModelDocument(document, documentSettings); 32 | 33 | if (isViewModel) { 34 | const defintion = await aureliaDefinitionFromViewModel( 35 | container, 36 | document, 37 | position 38 | ); 39 | return defintion; 40 | } 41 | 42 | let regions: AbstractRegion[] = []; 43 | if (targetComponent) { 44 | regions = targetComponent?.viewRegions; 45 | } else { 46 | // Quickfix for Html-only Custom Elements 47 | if (!ParseHtml.isHtmlWithRootTemplate(document.getText())) return; 48 | regions = RegionParser.parse( 49 | document, 50 | aureliaProgram.aureliaComponents.getAll() 51 | ); 52 | } 53 | 54 | if (regions.length === 0) return; 55 | 56 | const offset = document.offsetAt(position); 57 | const region = RegionService.findRegionAtOffset(regions, offset); 58 | if (region === undefined) return; 59 | const doDefinition = region.languageService.doDefinition; 60 | 61 | if (doDefinition) { 62 | const definition = await doDefinition( 63 | aureliaProgram, 64 | document, 65 | position, 66 | region 67 | ); 68 | 69 | if (!definition) return; 70 | 71 | const { line, character } = definition.lineAndCharacter; 72 | const targetPath = 73 | definition.viewFilePath ?? definition.viewModelFilePath ?? ''; 74 | 75 | const range = Range.create( 76 | Position.create(line - 1, character), 77 | Position.create(line, character) 78 | ); 79 | 80 | return [ 81 | LocationLink.create(pathToFileURL(targetPath).toString(), range, range), 82 | ]; 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /server/src/feature/definition/virtualDefinition.ts: -------------------------------------------------------------------------------- 1 | import { AureliaProgram } from '../../aot/AureliaProgram'; 2 | import { DefinitionResult } from '../../aot/parser/parser-types'; 3 | import { UriUtils } from '../../common/view/uri-utils'; 4 | import { 5 | createVirtualFileWithContent, 6 | getVirtualLangagueService, 7 | } from '../virtual/virtualSourceFile'; 8 | 9 | /** 10 | * 1. Create virtual file 11 | * 2. with goToSourceWord 12 | * 3. return definition 13 | */ 14 | export function getVirtualDefinition( 15 | filePath: string, 16 | aureliaProgram: AureliaProgram, 17 | goToSourceWord: string 18 | ): DefinitionResult | undefined { 19 | const virtualFileWithContent = createVirtualFileWithContent( 20 | aureliaProgram, 21 | filePath, 22 | goToSourceWord 23 | ); 24 | if (virtualFileWithContent === undefined) return; 25 | 26 | const { virtualSourcefile, virtualCursorIndex, viewModelFilePath } = 27 | virtualFileWithContent; 28 | 29 | const program = aureliaProgram.getProgram(); 30 | if (program === undefined) return; 31 | 32 | const virtualCls = getVirtualLangagueService(virtualSourcefile, program); 33 | 34 | const result = virtualCls.getDefinitionAtPosition( 35 | UriUtils.toSysPath(virtualSourcefile.fileName), 36 | virtualCursorIndex 37 | ); 38 | 39 | if (result?.length === 0) return; 40 | 41 | return { 42 | lineAndCharacter: virtualSourcefile.getLineAndCharacterOfPosition( 43 | result![0].contextSpan?.start ?? 0 44 | ), 45 | viewModelFilePath, 46 | }; 47 | } 48 | -------------------------------------------------------------------------------- /server/src/feature/diagnostics/diagnostics.ts: -------------------------------------------------------------------------------- 1 | import { Diagnostic } from 'vscode-languageserver-protocol'; 2 | import { TextDocument } from 'vscode-languageserver-textdocument'; 3 | 4 | import { RegionService } from '../../common/services/RegionService'; 5 | import { Container } from '../../core/container'; 6 | 7 | export function createDiagnostics( 8 | container: Container, 9 | document: TextDocument 10 | ): Diagnostic[] { 11 | const regions = RegionService.getRegionsInDocument(container, document); 12 | 13 | return []; 14 | } 15 | -------------------------------------------------------------------------------- /server/src/feature/hover/accessScopeHover.ts: -------------------------------------------------------------------------------- 1 | import { TextDocument, Position } from 'vscode-languageserver'; 2 | 3 | import { AureliaProgram } from '../../aot/AureliaProgram'; 4 | import { AbstractRegion } from '../../aot/parser/regions/ViewRegions'; 5 | import { 6 | createVirtualLanguageService, 7 | CustomHover, 8 | } from '../virtual/virtualSourceFile'; 9 | 10 | export async function getAccessScopeHover( 11 | aureliaProgram: AureliaProgram, 12 | document: TextDocument, 13 | position: Position, 14 | goToSourceWord: string, 15 | attributeRegion: AbstractRegion 16 | ): Promise { 17 | const virtualLanguageService = await createVirtualLanguageService( 18 | aureliaProgram, 19 | position, 20 | document, 21 | { 22 | region: attributeRegion, 23 | startAtBeginningOfMethodInVirtualFile: true, 24 | } 25 | ); 26 | 27 | if (!virtualLanguageService) return; 28 | 29 | const quickInfo = virtualLanguageService.getQuickInfoAtPosition(); 30 | 31 | if (!quickInfo) return; 32 | return quickInfo; 33 | } 34 | -------------------------------------------------------------------------------- /server/src/feature/hover/onHover.ts: -------------------------------------------------------------------------------- 1 | // import { Position, TextDocument } from 'vscode-languageserver'; 2 | 3 | // import { findSourceWord } from '../../common/documens/find-source-word'; 4 | 5 | export async function onHover( 6 | // documentContent: string, 7 | // position: Position, 8 | // filePath: string 9 | ) { 10 | return; 11 | // const document = TextDocument.create(filePath, 'html', 0, documentContent); 12 | 13 | // const doHover = mode.doHover; 14 | 15 | // const offset = document.offsetAt(position); 16 | // const goToSourceWord = findSourceWord(region, offset); 17 | 18 | // if (doHover) { 19 | // try { 20 | // const hoverResult = await doHover( 21 | // document, 22 | // position, 23 | // goToSourceWord, 24 | // region 25 | // ); 26 | // return hoverResult; 27 | // } catch (error) { 28 | // console.log('TCL: error', error); 29 | // return; 30 | // } 31 | // } 32 | } 33 | -------------------------------------------------------------------------------- /server/src/feature/initialization/initialization.ts: -------------------------------------------------------------------------------- 1 | import { Container } from 'aurelia-dependency-injection'; 2 | import { TextDocument } from 'vscode-languageserver-textdocument'; 3 | 4 | import { Logger } from '../../common/logging/logger'; 5 | import { ExtensionSettings } from '../../configuration/DocumentSettings'; 6 | import { AureliaProjects } from '../../core/AureliaProjects'; 7 | 8 | const logger = new Logger('initialization'); 9 | 10 | /** 11 | * 1. Init DI 12 | * 2. Detect Aurelia project 13 | * 3. Hydrate Project map 14 | */ 15 | export async function onConnectionInitialized( 16 | container: Container, 17 | extensionSettings: ExtensionSettings, 18 | activeDocuments: TextDocument[] = [], 19 | forceReinit: boolean = false 20 | ) { 21 | /* prettier-ignore */ logger.log('Initilization started.',{logMs:true,msStart:true}); 22 | 23 | const aureliaProjects = container.get(AureliaProjects); 24 | const isAureliaProject = await aureliaProjects.getAureliaProjectsOnly(extensionSettings); 25 | if (!isAureliaProject) return; 26 | 27 | const hydrated = await aureliaProjects.hydrate(activeDocuments, forceReinit); 28 | if (hydrated) { 29 | /* prettier-ignore */ logger.log('Initilization done. Aurelia Extension is ready to use. 🚀',{logMs:true,msEnd:true}); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /server/src/feature/save/saveContent.ts: -------------------------------------------------------------------------------- 1 | import { Container } from 'aurelia-dependency-injection'; 2 | import { TextDocumentChangeEvent } from 'vscode-languageserver'; 3 | import { TextDocument } from 'vscode-languageserver-textdocument'; 4 | 5 | import { Logger } from '../../common/logging/logger'; 6 | import { AureliaProjects } from '../../core/AureliaProjects'; 7 | 8 | const logger = new Logger('saveContent'); 9 | 10 | export async function onDidSave( 11 | container: Container, 12 | { document }: TextDocumentChangeEvent 13 | ) { 14 | const aureliaProjects = container.get(AureliaProjects); 15 | aureliaProjects.clearEditingTracker(); 16 | 17 | if (aureliaProjects.preventHydration(document)) return; 18 | 19 | switch (document.languageId) { 20 | case 'javascript': 21 | case 'typescript': { 22 | aureliaProjects.updateManyViewModel([document]); 23 | logger.log('View model saved and updated.'); 24 | break; 25 | } 26 | case 'html': { 27 | aureliaProjects.updateManyView([document]); 28 | logger.log('View saved and updated'); 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /server/src/feature/symbols/onWorkspaceSymbol.ts: -------------------------------------------------------------------------------- 1 | import { Container } from 'aurelia-dependency-injection'; 2 | import { Range } from 'vscode-languageserver-protocol'; 3 | import { Position, SymbolInformation } from 'vscode-languageserver-types'; 4 | 5 | import { AbstractRegion } from '../../aot/parser/regions/ViewRegions'; 6 | import { UriUtils } from '../../common/view/uri-utils'; 7 | import { AureliaProjects } from '../../core/AureliaProjects'; 8 | import { convertToSymbolName } from './onDocumentSymbol'; 9 | 10 | // export function onWorkspaceSymbol(container: Container, query: string) { 11 | export function onWorkspaceSymbol(container: Container) { 12 | const finalWorkspaceSymbols: SymbolInformation[] = []; 13 | const aureliaProjects = container.get(AureliaProjects); 14 | aureliaProjects.getAll().forEach((aureliaProject) => { 15 | aureliaProject.aureliaProgram?.aureliaComponents 16 | .getAll() 17 | .forEach((component) => { 18 | component.viewRegions?.forEach((region) => { 19 | if (component.viewFilePath === undefined) return; 20 | 21 | const viewUri = UriUtils.toVscodeUri(component.viewFilePath); 22 | const symbol = createWorkspaceSymbol(viewUri, region); 23 | if (!symbol) return; 24 | 25 | finalWorkspaceSymbols.push(symbol); 26 | }); 27 | }); 28 | }); 29 | 30 | return finalWorkspaceSymbols; 31 | } 32 | 33 | function createWorkspaceSymbol( 34 | uri: string, 35 | region: AbstractRegion 36 | ): SymbolInformation | undefined { 37 | const converted = convertToSymbolName(region); 38 | if (!converted) return; 39 | 40 | const symbolName = `Au: ${converted.value}`; 41 | const start: Position = { 42 | line: region.sourceCodeLocation.startLine, 43 | character: region.sourceCodeLocation.startCol, 44 | }; 45 | const end: Position = { 46 | line: region.sourceCodeLocation.endLine, 47 | character: region.sourceCodeLocation.endCol, 48 | }; 49 | const range = Range.create(start, end); 50 | const symbolInformation = SymbolInformation.create( 51 | symbolName, 52 | converted.icon, 53 | range, 54 | uri 55 | ); 56 | 57 | return symbolInformation; 58 | } 59 | -------------------------------------------------------------------------------- /server/src/project.config.example.ts: -------------------------------------------------------------------------------- 1 | export const PROJECT_CONFIG = { 2 | /** 3 | * Path to the vscode-extension project 4 | * 5 | * @example ~/coding/repos/vscode-extension 6 | */ 7 | projectPath: '', 8 | }; 9 | -------------------------------------------------------------------------------- /server/src/project.config.ts: -------------------------------------------------------------------------------- 1 | export const PROJECT_CONFIG = { 2 | /** 3 | * Path to the vscode-extension project 4 | * 5 | * @example ~/coding/repos/vscode-extension 6 | */ 7 | // projectPath: 'C:\\Users\\hdn local\\Desktop\\dev\\aurelia\\vscode-extension', 8 | projectPath: '~/coding/repos/vscode-extension', 9 | }; 10 | -------------------------------------------------------------------------------- /server/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "experimentalDecorators": true, 4 | "target": "es6", 5 | "module": "commonjs", 6 | "moduleResolution": "node", 7 | "sourceMap": true, 8 | "strict": true, 9 | "outDir": "out", 10 | "rootDir": "src", 11 | "strictPropertyInitialization": false, 12 | "resolveJsonModule": true, 13 | "skipLibCheck": true, 14 | "esModuleInterop": true 15 | }, 16 | "include": ["src"], 17 | "exclude": ["node_modules", ".vscode-test"] 18 | } 19 | -------------------------------------------------------------------------------- /tests/.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const thisDir = path.resolve(__dirname); 3 | 4 | module.exports = { 5 | extends: ['../.eslintrc.cjs'], 6 | parserOptions: { 7 | project: path.join(thisDir, 'tsconfig.json'), 8 | tsconfigRootDir: thisDir, 9 | }, 10 | }; 11 | -------------------------------------------------------------------------------- /tests/common/errors/TestErrors.ts: -------------------------------------------------------------------------------- 1 | import { getPathsFromFileNames } from '../file-path-mocks'; 2 | import { FixtureNames, FIXTURE_NAMES } from '../fixtures/get-fixture-dir'; 3 | 4 | export class TestError extends Error { 5 | public message: string; 6 | 7 | constructor(message?: string) { 8 | const finalMessage = `[TestError] ${message ?? ''}`; 9 | super(finalMessage); 10 | } 11 | 12 | public log(message: string): void { 13 | console.log(`[TestError] ${message}`); 14 | } 15 | 16 | public verifyProjectName(projectName: FixtureNames): void { 17 | if (!FIXTURE_NAMES.includes(projectName)) { 18 | this.logWrongProjectName(projectName); 19 | } 20 | } 21 | 22 | public verifyFileInProject(uri: string, fileName: string): void { 23 | const paths = getPathsFromFileNames(uri, [fileName]); 24 | const fileDoesNotExist = paths.some((path) => path === undefined); 25 | 26 | if (fileDoesNotExist) { 27 | this.logFileNotExistInProject(fileName); 28 | } 29 | } 30 | 31 | private logWrongProjectName(projectName: FixtureNames): void { 32 | throw new TestError(`Wrong project name: ${projectName}`); 33 | } 34 | 35 | private logFileNotExistInProject(fileName: string): void { 36 | throw new TestError(`Wrong project name: ${fileName}`); 37 | } 38 | } 39 | 40 | export const testError = new TestError(); 41 | -------------------------------------------------------------------------------- /tests/common/files/get-test-dir.ts: -------------------------------------------------------------------------------- 1 | import * as path from 'path'; 2 | 3 | import { findProjectRoot } from '../find-project-root'; 4 | 5 | export function getTestDir(): string { 6 | const testDir = path.resolve(findProjectRoot(), 'tests'); /* ? */ 7 | return testDir; 8 | } 9 | -------------------------------------------------------------------------------- /tests/common/find-project-root.ts: -------------------------------------------------------------------------------- 1 | import * as fs from 'fs'; 2 | import * as path from 'path'; 3 | 4 | /** 5 | * 6 | */ 7 | export function findProjectRoot(targetPath = __dirname): string { 8 | const parentPath = path.dirname(targetPath); 9 | 10 | try { 11 | const clientPath = `${parentPath}/client`; 12 | fs.accessSync(clientPath); 13 | const serverPath = `${parentPath}/server`; 14 | fs.accessSync(serverPath); 15 | 16 | return parentPath; 17 | } catch (error) { 18 | return findProjectRoot(parentPath); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /tests/common/fixtures/get-fixture-dir.ts: -------------------------------------------------------------------------------- 1 | import * as path from 'path'; 2 | 3 | import { UriUtils } from '../../../server/src/common/view/uri-utils'; 4 | import { findProjectRoot } from '../find-project-root'; 5 | 6 | const projectRoot = findProjectRoot(); 7 | const testFixtureDir = path.resolve(projectRoot, 'tests/testFixture'); 8 | // const testFixtureDir = path.resolve(projectRoot, getTestDir(), 'testFixture'); 9 | 10 | export const FIXTURE_NAMES = [ 11 | 'cli-generated', 12 | 'monorepo', 13 | 'non-aurelia-project', 14 | 'scoped-for-testing', 15 | ] as const; 16 | export type FixtureNames = typeof FIXTURE_NAMES[number]; 17 | 18 | export function getFixtureDir(fixtureName: FixtureNames): string { 19 | const cliGeneratedFixtureDir = path.resolve( 20 | projectRoot, 21 | `${testFixtureDir}/${fixtureName}` 22 | ); 23 | return cliGeneratedFixtureDir; 24 | } 25 | 26 | export function getFixtureUri(fixtureName: FixtureNames): string { 27 | const fixtureUri = `file:/${getFixtureDir(fixtureName)}`; 28 | 29 | return fixtureUri; 30 | } 31 | 32 | export const getAbsPathFromFixtureDir = 33 | (fixtureName: FixtureNames) => 34 | (relPath: string): string => { 35 | let absPath = path.resolve(testFixtureDir, fixtureName, relPath); 36 | if (path.sep === '\\') { 37 | // absPath = UriUtils.normalize(absPath); 38 | absPath = UriUtils.toSysPath(absPath); 39 | // absPath = UriUtils.toUri(absPath); 40 | } 41 | return absPath; 42 | }; 43 | -------------------------------------------------------------------------------- /tests/common/gherkin/gherkin-step-table.ts: -------------------------------------------------------------------------------- 1 | export type StepTable = Record< 2 | HeaderName, 3 | string 4 | >[]; 5 | 6 | export type FileNameStepTable = StepTable<'fileName'>; 7 | 8 | export function getTableValues(stepTable: StepTable): string[] { 9 | const values = stepTable.flatMap(Object.values) as string[]; 10 | return values; 11 | } 12 | -------------------------------------------------------------------------------- /tests/dev-test-helpers/cache-cucumber-features.ts: -------------------------------------------------------------------------------- 1 | import * as fs from 'fs'; 2 | import * as path from 'path'; 3 | 4 | import { loadFeatures } from 'jest-cucumber'; 5 | 6 | import { getTestDir } from '../common/files/get-test-dir'; 7 | 8 | const TEST_PATH = getTestDir(); 9 | const CACHE_FILE_PATH = path.resolve( 10 | TEST_PATH, 11 | 'dev-test-helpers/features-cache.json' 12 | ); 13 | 14 | export type ParsedFeatures = ReturnType; 15 | 16 | export function createFeatureCache(features: ParsedFeatures): void { 17 | fs.writeFileSync(CACHE_FILE_PATH, JSON.stringify(features)); 18 | } 19 | 20 | export function readFeatureCache(): ParsedFeatures | undefined { 21 | const data = fs.readFileSync(CACHE_FILE_PATH, 'utf-8'); 22 | if (!data || data === '{}') return; 23 | 24 | const parsedFeatures = (JSON.parse(data) as unknown) as ParsedFeatures; 25 | return parsedFeatures; 26 | } 27 | 28 | export function resetFeatureCache(): void { 29 | fs.writeFileSync(CACHE_FILE_PATH, '{}'); 30 | } 31 | -------------------------------------------------------------------------------- /tests/features/capabilities/codeActions/code-actions.feature: -------------------------------------------------------------------------------- 1 | @scoped_for_testing 2 | Feature: Code Actions. 3 | Background: 4 | Given the project is named "scoped-for-testing" 5 | 6 | Scenario Outline: View Refactor. 7 | And I open VSCode with the following file "custom-element.html" 8 | And I'm on the line at character 9 | When I trigger Code Action 10 | Then the the refactor Code Action should have been performed 11 | 12 | Examples: 13 | | DESCRIPTION | CODE_ACTION | LINE | CODE | NEW_CODE | 14 | | Import | refactor.aTag | 7 | `<\|a href="foo">
` | import | 15 | 16 | Scenario Outline: View Refactor - on change content. 17 | Given I open VSCode with the following file "empty-view.html" 18 | And I'm replacing the file content with 19 | And I'm on the line at character 20 | When I trigger Code Action 21 | Then the the refactor Code Action should have been performed 22 | 23 | Examples: 24 | | DESCRIPTION | CODE_ACTION | LINE | CODE | NEW_CODE | 25 | | Import | refactor.aTag | 0 | `<\|a href="foo">` | import | -------------------------------------------------------------------------------- /tests/features/capabilities/completions/completions-aurelia-keywords.feature: -------------------------------------------------------------------------------- 1 | @scoped_for_testing 2 | Feature: Completions. 3 | Background: 4 | Given the project is named "scoped-for-testing" 5 | And I open VSCode with the following file "empty-view.html" 6 | 7 | Scenario Outline: Aurelia key words. 8 | Given I'm replacing the file content with 9 | And I'm on the line at character 10 | When I trigger Suggestions with 11 | Then I should get the correct suggestions 12 | 13 | Examples: 14 | | DESCRIPTION | LINE | CODE | SUGGESTION | TRIGGER_CHARACTER | 15 | | import | 0 | `<\|` | import | < | 16 | 17 | Scenario Outline: Constructor arguments 18 | Given I'm replacing the file content with 19 | And I'm on the line at character 20 | When I trigger Suggestions with '' 21 | Then I should get the correct suggestions 22 | 23 | Examples: 24 | | DESCRIPTION | LINE | CODE | SUGGESTION | 25 | | Private | 0 | `${\|}

` | pri | 26 | | Public | 0 | `${\|}

` | pub | 27 | | Protected | 0 | `${\|}

` | prot | 28 | | Readonly | 0 | `${\|}

` | readOnly | -------------------------------------------------------------------------------- /tests/features/capabilities/completions/completions-methods.feature: -------------------------------------------------------------------------------- 1 | @cli_generated 2 | Feature: Completions - Methods. 3 | Background: 4 | Given the project is named "cli-generated" 5 | And I open VSCode with the following file "view-model-test.html" 6 | 7 | Scenario Outline: Empty brackets. 8 | Given I'm replacing the file content with 9 | And I'm on the line at character 10 | When I trigger Suggestions with '' 11 | Then I should get the correct method with brackets 12 | 13 | Examples: 14 | | LINE | CODE | METHOD_NAME | 15 | | 0 | `
` | functionVariable | 16 | 17 | Scenario Outline: Method Argument completion. 18 | Given I'm replacing the file content with 19 | And I'm on the line at character 20 | When I trigger Suggestions with '' 21 | Then I should get the correct method with its arguments 22 | 23 | Examples: 24 | | LINE | CODE | METHOD_NAME | 25 | | 0 | `
` | methodWithArgs | -------------------------------------------------------------------------------- /tests/features/capabilities/completions/completions-performance.feature: -------------------------------------------------------------------------------- 1 | @scoped_for_testing 2 | Feature: Completions performance. 3 | Background: 4 | Given the project is named "scoped-for-testing" 5 | And I open VSCode with the following file "empty-view.html" 6 | 7 | Scenario Outline: Completions performance. 8 | Given I'm replacing the file content with 9 | And I'm on the line at character 10 | When I trigger Suggestions by typing 11 | Then I should get the correct suggestions 12 | 13 | Examples: 14 | | DESCRIPTION | LINE | CODE | SUGGESTION | TRIGGER_CHARACTER | 15 | | Text should trigger Custom Element | 0 | `

${\|${pub}

` | pri | { | 16 | # | Text should trigger Custom Element | 0 | `

${\|${pub}

` | | { | 17 | # ^ TODO Should be 0 suggestions 18 | -------------------------------------------------------------------------------- /tests/features/capabilities/completions/completions-value-converters.feature: -------------------------------------------------------------------------------- 1 | @cli_generated 2 | Feature: Completions - Value converters 3 | Background: 4 | Given the project is named "cli-generated" 5 | And I open VSCode with the following file "minimal-component.html" 6 | 7 | Scenario Outline: Completions for Value converters 8 | Given I'm replacing the file content with 9 | And I'm on the line at character 10 | When I trigger Suggestions with '' 11 | Then I should get the correct Value converters suggestions 12 | 13 | Examples: 14 | | LINE | CODE | 15 | | 0 | `
` | 16 | -------------------------------------------------------------------------------- /tests/features/capabilities/completions/completions-when-to-trigger.feature: -------------------------------------------------------------------------------- 1 | @scoped_for_testing 2 | Feature: When completions should trigger. 3 | Background: 4 | Given the project is named "scoped-for-testing" 5 | And I open VSCode with the following file "empty-view.html" 6 | 7 | Scenario Outline: When completions should trigger. 8 | Given I'm replacing the file content with 9 | And I'm on the line at character 10 | When I trigger Suggestions with 11 | Then I should get the correct suggestions 12 | 13 | Examples: 14 | | DESCRIPTION | LINE | CODE | SUGGESTION | TRIGGER_CHARACTER | 15 | | HTML Text trigger CE (not) | 0 | `

<\|${pub}

` | | | 16 | | HTML Text trigger CE | 0 | `

<\|_${pub}

` | empty-view | | 17 | | HTML Text trigger CE | 0 | `

${pub}<\|

` | empty-view | | 18 | | HTML Text trigger CE | 0 | `<\|` | empty-view | | 19 | | Aurelia Keyword | 0 | `

` | if.bind | ' ' | 20 | | Aurelia Keyword | 0 | `

` | show.bind | s | 21 | | Attribute Value | 0 | `

` | | | 22 | | Attribute Value (not) | 0 | `

` | | ' ' | 23 | | Attribute Interpolation | 0 | `

` | readOnly | | 24 | | Attribute - .bind | 0 | `

` | readOnly | | 25 | | Bindable Attr - Empty "" | 0 | `` | | | 26 | | Bindable Attr - .bind | 0 | `` | readOnly | | 27 | | Bindable Attr - Interpol | 0 | `` | readOnly | | 28 | # TODO | Bindable Attr - Interpol | 0 | `` | readOnly | ' ' | 29 | | Bindable Attr - Interpol | 0 | `` | readOnly | | 30 | | Comment | 0 | `` | | | 31 | | HTML Attributes | 0 | `

` | aria-checked | ' ' | 32 | | Text Interpol (not) | 0 | `

${} \|

` | | ' ' | 33 | -------------------------------------------------------------------------------- /tests/features/capabilities/definitions/definitions-import-tag.feature: -------------------------------------------------------------------------------- 1 | @scoped_for_testing 2 | Feature: Definitions in View. 3 | Background: 4 | Given the project is named "scoped-for-testing" 5 | 6 | Scenario Outline: Bindable Attribute 7 | And I open VSCode with the following file "stage4.html" 8 | And I'm on the line at character 9 | When I execute Go To Definition 10 | Then I should land in the file 11 | And the number of definitions should be 12 | 13 | Examples: 14 | | DESCRIPTION | LINE | CODE | NUM_DEFINTIONS | TARGET_FILE_NAME | 15 | | Import | 2 | ` \|output-as-string="true"` | 1 | numericInput.ts | 16 | | Import | 4 | ` value.bind="\|launchConfig.launchDetails.pricePerToken"` | 1 | stage4.ts | 17 | 18 | Scenario Outline: Import tag. 19 | And I open VSCode with the following file "other-custom-element-user.html" 20 | And I'm on the line at character 21 | When I execute Go To Definition 22 | Then I should land in the file 23 | And the number of definitions should be 24 | 25 | Examples: 26 | | DESCRIPTION | LINE | CODE | NUM_DEFINTIONS | TARGET_FILE_NAME | 27 | | Import | 1 | ` ` | 1 | custom-element.ts | 28 | # | Import | 2 | ` ` | 1 | custom-element.html | 29 | -------------------------------------------------------------------------------- /tests/features/capabilities/definitions/definitions-index.feature: -------------------------------------------------------------------------------- 1 | @cli_generated 2 | Feature: Definition in View - index.ts. 3 | Background: 4 | Given the project is named "cli-generated" 5 | And I open VSCode with the following file "realdworld-advanced/settings/index.html" 6 | 7 | Scenario Outline: View model of components with index.ts. 8 | Given I'm on the line at character 9 | When I execute Go To Definition 10 | Then I should land in the file 11 | 12 | Examples: 13 | | LINE | CODE | TARGET_FILE_NAME | 14 | | 3 | `

Your Settings

` | realdworld-advanced/settings/index.ts | 15 | 16 | -------------------------------------------------------------------------------- /tests/features/capabilities/definitions/definitions-upon-file-change.feature: -------------------------------------------------------------------------------- 1 | @scoped_for_testing 2 | Feature: Definition after file change 3 | 4 | Background: 5 | Given the project is named "scoped-for-testing" 6 | And I open VSCode with the following file "custom-element.ts" 7 | And I'm on the line at character 8 | And I open VSCode with the following file "custom-element-user.ts" 9 | 10 | Scenario Outline: Definition after file change 11 | When I change the file "custom-element-user.ts" by adding a new line 12 | And I execute Go To Definition in the file "custom-element.ts" 13 | Then the defintion in "custom-element-user.ts" should be correct 14 | 15 | Examples: 16 | | DESCRIPTION | LINE | CODE | 17 | | Import | 1 | `export class \|CustomElementCustomElement {` | 18 | -------------------------------------------------------------------------------- /tests/features/capabilities/definitions/definitions-view-model.feature: -------------------------------------------------------------------------------- 1 | @scoped_for_testing 2 | Feature: Definition in View Model. 3 | Background: 4 | Given the project is named "scoped-for-testing" 5 | 6 | Scenario Outline: Variable in View Model. 7 | And I open VSCode with the following file "custom-element.ts" 8 | And I'm on the line at character 9 | When I execute Go To Definition 10 | Then I should land in the file 11 | And the number of definitions should be 12 | 13 | Examples: 14 | | DESCRIPTION | LINE | CODE | NUM_DEFINTIONS | TARGET_FILE_NAME | 15 | | Bindable | 1 | `export class \|CustomElementCustomElement {` | 9 | custom-element-user.html | 16 | | Bindable | 2 | ` @bindable \|foo;` | 10 | custom-element.html | 17 | | Bindable | 3 | ` @bindable \|barBaz;` | 5 | custom-element.html | 18 | | Class Variable | 4 | ` \|qux;` | 4 | custom-element.html | 19 | 20 | Scenario Outline: Not triggering 21 | And I open VSCode with the following file "custom-element-user.ts" 22 | And I'm on the line at character 23 | When I execute Go To Definition 24 | And the number of definitions should be 25 | 26 | Examples: 27 | | DESCRIPTION | LINE | CODE | NUM_DEFINTIONS | 28 | | Bindable | 0 | `import { CustomElementCustomElement } from './\|custom-element';` | 0 | 29 | | Bindable | 10 | ` this.\|quxUser` | 0 | 30 | -------------------------------------------------------------------------------- /tests/features/capabilities/definitions/definitions.feature: -------------------------------------------------------------------------------- 1 | @cli_generated 2 | Feature: Definition in View. 3 | Background: 4 | Given the project is named "cli-generated" 5 | And I open VSCode with the following file "compo-user.html" 6 | 7 | Scenario Outline: Custom Element. 8 | Given I'm on the line at character 9 | When I execute Go To Definition 10 | Then I should land in the file 11 | 12 | Examples: 13 | | DESCRIPTION | LINE | CODE | TARGET_FILE_NAME | 14 | | Attribute | 7 | `
` | compo-user.ts | 15 | | Attribute interpolation | 11 | `
` | compo-user.ts | 16 | | Custom Element | 15 | ` <\|my-compo>` | my-compo.ts | 17 | | Text interpolation | 27 | ` ${} ${\|grammarRules.length}` | compo-user.ts | 18 | | Attribute | 30 | `