├── .eslintignore ├── .eslintrc.js ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.yml │ └── feature_request.yml └── workflows │ ├── pr-gated.yml │ └── rc.yml ├── .gitignore ├── .pipelines ├── 1loc.yml └── pr-pipeline.yml ├── .prettierrc ├── .vscode-test.mjs ├── .vscode ├── extensions.json ├── launch.json ├── settings.json └── tasks.json ├── .vscodeignore ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── LICENSE ├── README.md ├── SECURITY.md ├── SUPPORT.md ├── build.md ├── etc └── PQIcon_128.png ├── localize ├── .gitignore ├── LocProject.json ├── i18n │ ├── cs │ │ ├── extension.json │ │ ├── package.nls.json │ │ └── pq-test-result-view.json │ ├── de │ │ ├── extension.json │ │ ├── package.nls.json │ │ └── pq-test-result-view.json │ ├── es │ │ ├── extension.json │ │ ├── package.nls.json │ │ └── pq-test-result-view.json │ ├── fr │ │ ├── extension.json │ │ ├── package.nls.json │ │ └── pq-test-result-view.json │ ├── it │ │ ├── extension.json │ │ ├── package.nls.json │ │ └── pq-test-result-view.json │ ├── ja │ │ ├── extension.json │ │ ├── package.nls.json │ │ └── pq-test-result-view.json │ ├── ko │ │ ├── extension.json │ │ ├── package.nls.json │ │ └── pq-test-result-view.json │ ├── pl │ │ ├── extension.json │ │ ├── package.nls.json │ │ └── pq-test-result-view.json │ ├── pt-br │ │ ├── extension.json │ │ ├── package.nls.json │ │ └── pq-test-result-view.json │ ├── ru │ │ ├── extension.json │ │ ├── package.nls.json │ │ └── pq-test-result-view.json │ ├── tr │ │ ├── extension.json │ │ ├── package.nls.json │ │ └── pq-test-result-view.json │ ├── zh-cn │ │ ├── extension.json │ │ ├── package.nls.json │ │ └── pq-test-result-view.json │ └── zh-tw │ │ ├── extension.json │ │ ├── package.nls.json │ │ └── pq-test-result-view.json └── loc │ ├── extension │ ├── cs │ │ └── extension.json.lcl │ ├── de │ │ └── extension.json.lcl │ ├── es │ │ └── extension.json.lcl │ ├── fr │ │ └── extension.json.lcl │ ├── it │ │ └── extension.json.lcl │ ├── ja │ │ └── extension.json.lcl │ ├── ko │ │ └── extension.json.lcl │ ├── pl │ │ └── extension.json.lcl │ ├── pt-br │ │ └── extension.json.lcl │ ├── ru │ │ └── extension.json.lcl │ ├── tr │ │ └── extension.json.lcl │ ├── zh-cn │ │ └── extension.json.lcl │ └── zh-tw │ │ └── extension.json.lcl │ ├── package │ ├── cs │ │ └── package.nls.json.lcl │ ├── de │ │ └── package.nls.json.lcl │ ├── es │ │ └── package.nls.json.lcl │ ├── fr │ │ └── package.nls.json.lcl │ ├── it │ │ └── package.nls.json.lcl │ ├── ja │ │ └── package.nls.json.lcl │ ├── ko │ │ └── package.nls.json.lcl │ ├── pl │ │ └── package.nls.json.lcl │ ├── pt-br │ │ └── package.nls.json.lcl │ ├── ru │ │ └── package.nls.json.lcl │ ├── tr │ │ └── package.nls.json.lcl │ ├── zh-cn │ │ └── package.nls.json.lcl │ └── zh-tw │ │ └── package.nls.json.lcl │ └── pq-test-result-view │ ├── cs │ └── pq-test-result-view.json.lcl │ ├── de │ └── pq-test-result-view.json.lcl │ ├── es │ └── pq-test-result-view.json.lcl │ ├── fr │ └── pq-test-result-view.json.lcl │ ├── it │ └── pq-test-result-view.json.lcl │ ├── ja │ └── pq-test-result-view.json.lcl │ ├── ko │ └── pq-test-result-view.json.lcl │ ├── pl │ └── pq-test-result-view.json.lcl │ ├── pt-br │ └── pq-test-result-view.json.lcl │ ├── ru │ └── pq-test-result-view.json.lcl │ ├── tr │ └── pq-test-result-view.json.lcl │ ├── zh-cn │ └── pq-test-result-view.json.lcl │ └── zh-tw │ └── pq-test-result-view.json.lcl ├── media └── VSCodeSDK.gif ├── mochaReporterConfig.json ├── package-lock.json ├── package.json ├── package.nls.json ├── resources └── license-header.js ├── scripts ├── addI18n.ts ├── clean.ts ├── install.ts ├── test-e2e.ts └── utils │ └── vsixs.ts ├── src ├── GlobalEventBus.ts ├── commands │ └── LifecycleCommands.ts ├── common │ ├── DeferredValue.ts │ ├── Disposable.ts │ ├── DisposableEventEmitter.ts │ ├── MultiStepInput.ts │ ├── OperationType.ts │ ├── PQTestService.ts │ ├── PowerQueryTask.ts │ ├── PqSdkNugetPackageService.ts │ ├── SpawnedProcess.ts │ ├── ValueEventEmitter.ts │ ├── WaitNotify.ts │ ├── errors.ts │ ├── iterables │ │ ├── FibonacciNumbers.ts │ │ └── NumberIterator.ts │ ├── nuget │ │ ├── NugetCommandService.ts │ │ ├── NugetHttpService.ts │ │ └── NugetLiteHttpService.ts │ ├── promises │ │ ├── CancellationToken.ts │ │ ├── cancelable.ts │ │ ├── defer.ts │ │ ├── doResolve.ts │ │ ├── fromEvent.ts │ │ ├── fromEvents.ts │ │ ├── isPromise.ts │ │ ├── noop.ts │ │ ├── once.ts │ │ ├── promisifyTry.ts │ │ ├── setFunctionNameAndLength.ts │ │ ├── symbols.ts │ │ └── types.ts │ ├── sockets │ │ ├── JsonRpcSocketClient.ts │ │ └── SocketClient.ts │ └── vscode-powerquery.api.d.ts ├── constants │ ├── PowerQuerySdkConfiguration.ts │ └── PowerQuerySdkExtension.ts ├── debugAdapter.ts ├── debugAdaptor │ ├── MQueryDebugSession.ts │ └── activateMQueryDebug.ts ├── extension.ts ├── features │ ├── LifeCycleTaskTreeView.ts │ ├── PowerQueryTaskProvider.ts │ ├── PqSdkOutputChannel.ts │ └── PqSdkTaskTerminal.ts ├── i18n │ ├── extension.json │ └── extension.ts ├── panels │ └── PqTestResultViewPanel.ts ├── pqTestConnector │ ├── PqServiceHostClient.ts │ ├── PqServiceHostClientLite.ts │ ├── PqTestExecutableOnceTask.ts │ ├── PqTestExecutableTaskQueue.ts │ └── PqTestTaskUtils.ts ├── test │ ├── .eslintrc.js │ ├── TestUtils.ts │ ├── common.ts │ ├── commonSuite │ │ ├── NewProject.spec.ts │ │ └── PqSdkToolAcquisition.spec.ts │ ├── connectors │ │ ├── .gitignore │ │ ├── AadTestConnector │ │ │ ├── .vscode │ │ │ │ ├── launch.json │ │ │ │ └── settings.json │ │ │ ├── AadTestConnector.pq │ │ │ ├── AadTestConnector.query.pq │ │ │ └── resources.resx │ │ ├── MultipleAuthKindConnector │ │ │ ├── .vscode │ │ │ │ └── settings.json │ │ │ ├── MultipleAuthKindConnector.pq │ │ │ ├── MultipleAuthKindConnector.proj │ │ │ └── MultipleAuthKindConnector.query.pq │ │ └── OAuthConnector │ │ │ ├── .vscode │ │ │ └── settings.json │ │ │ ├── OAuthConnector.pq │ │ │ └── OAuthConnector.query.pq │ ├── runTest.ts │ ├── suite │ │ ├── extension.test.ts │ │ ├── index.ts │ │ └── project.test.ts │ ├── tsconfig.json │ └── utils │ │ ├── connectorProjects.ts │ │ ├── editorUtils.ts │ │ ├── index.ts │ │ ├── notificationUtils.ts │ │ ├── outputChannelUtils.ts │ │ ├── pqSdkNugetPackageUtils.ts │ │ ├── pqSdkTestOutputChannel.ts │ │ ├── settingUtils.ts │ │ ├── sideBarUtils.ts │ │ └── titleBarUtils.ts └── utils │ ├── NugetVersions.ts │ ├── assertUtils.ts │ ├── debounce.ts │ ├── executables.ts │ ├── files.ts │ ├── ids.ts │ ├── numbers.ts │ ├── osUtils.ts │ ├── pids.ts │ ├── strings.ts │ ├── types.ts │ └── vscodes.ts ├── templates ├── PQConn.pq ├── PQConn.proj ├── PQConn.query.pq ├── PQConn16.png ├── PQConn20.png ├── PQConn24.png ├── PQConn32.png ├── PQConn40.png ├── PQConn48.png ├── PQConn64.png ├── PQConn80.png ├── resources.resx └── settings.json ├── tsconfig.json ├── unit-tests ├── .eslintrc ├── common │ ├── Utils.spec.ts │ ├── iterables.spec.ts │ ├── nuget │ │ ├── NugetCommandService.spec.ts │ │ ├── NugetHttpService.spec.ts │ │ └── NugetVersions.spec.ts │ ├── promises │ │ ├── cancelable.spec.ts │ │ ├── cancellationToken.spec.ts │ │ ├── fromEvent.spec.ts │ │ └── fromEvents.spec.ts │ ├── sockets │ │ └── JsonRpcSocketClient.spec.ts │ └── testConstants.ts └── tsconfig.json ├── webpack.config.js └── webviews └── pq-test-result-view ├── .eslintignore ├── .eslintrc.json ├── config ├── env.js ├── paths.js ├── webpack.common.js ├── webpack.dev.js └── webpack.prod.js ├── package-lock.json ├── package.json ├── public ├── i18n │ └── pq-test-result-view.json └── index.html ├── src ├── App.scss ├── App.tsx ├── bootstrap.tsx ├── components │ ├── MessageBoxComp.tsx │ └── TestBatteryGeneralGrid.tsx ├── contexts │ └── VscodeContexts.tsx ├── i18n.ts ├── index.ts ├── themes.ts ├── utils │ ├── jsons.ts │ └── types.ts └── views │ └── TestBatteryResultView.tsx └── tsconfig.json /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | out 4 | scripts 5 | **/*.js -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.yml: -------------------------------------------------------------------------------- 1 | name: Request a Feature 2 | description: Suggest an idea for this project 3 | body: 4 | - type: checkboxes 5 | id: pre-flight 6 | attributes: 7 | label: Preflight Checklist 8 | description: "Before submitting a bug report, please make sure you've done the following:" 9 | options: 10 | - label: I have installed the [latest version of Power Query SDK](https://github.com/microsoft/vscode-powerquery-sdk/releases/latest). 11 | required: true 12 | - label: I have checked existing resources, including the [common issues](https://learn.microsoft.com/en-us/power-query/commonissues) and the [release notes](https://github.com/microsoft/vscode-powerquery-sdk/releases). 13 | required: true 14 | - label: I have searched for [similar issues](https://github.com/microsoft/vscode-powerquery-sdk/issues). 15 | required: true 16 | 17 | - type: markdown 18 | attributes: 19 | value: --- 20 | 21 | - type: textarea 22 | id: goal 23 | attributes: 24 | label: Problem 25 | description: Is your feature request related to a problem? Please describe 26 | placeholder: A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 27 | validations: 28 | required: true 29 | 30 | - type: textarea 31 | id: solution 32 | attributes: 33 | label: Desired Solution 34 | description: Describe the solution you'd like 35 | placeholder: A clear and concise description of what you want to happen. 36 | validations: 37 | required: true 38 | 39 | - type: textarea 40 | id: alternative 41 | attributes: 42 | label: Alternatives and Workarounds 43 | description: Describe alternatives you've considered 44 | placeholder: A clear and concise description of any alternative solutions or features you've considered. 45 | 46 | - type: textarea 47 | id: context 48 | attributes: 49 | label: Additional Context 50 | description: Add any other context or screenshots that can help us understand your feature request better. 51 | -------------------------------------------------------------------------------- /.github/workflows/pr-gated.yml: -------------------------------------------------------------------------------- 1 | name: Gated pull request 2 | on: 3 | workflow_dispatch: 4 | pull_request: 5 | branches: [main] 6 | 7 | jobs: 8 | build-and-test: 9 | runs-on: windows-latest 10 | steps: 11 | - name: checkout 12 | uses: actions/checkout@v4 13 | - name: setup node 14 | uses: actions/setup-node@v4 15 | with: 16 | node-version: "20" 17 | - run: node -v 18 | - run: npm ci 19 | - run: npm run audit 20 | - run: npm run test:unit-test 21 | - run: npm run vsix 22 | - name: retry 5 times e2e test cases 23 | uses: nick-fields/retry@v3 24 | with: 25 | timeout_minutes: 10 26 | max_attempts: 5 27 | command: npm run test:e2e 28 | - name: upload VSIX to artifactory 29 | uses: actions/upload-artifact@v4 30 | with: 31 | name: vsix-artifact 32 | path: | 33 | *.vsix 34 | -------------------------------------------------------------------------------- /.pipelines/1loc.yml: -------------------------------------------------------------------------------- 1 | # Starter pipeline 2 | # Start with a minimal pipeline that you can customize to build and deploy your code. 3 | # Add steps that build, run tests, deploy, and more: 4 | # https://aka.ms/yaml 5 | name: 1loc_$(SourceBranchName)_$(Date:yyyyMMdd)$(Rev:.r) 6 | trigger: none 7 | 8 | pr: none 9 | 10 | pool: 11 | vmImage: 'windows-latest' 12 | 13 | schedules: 14 | - cron: "0 0 * * *" 15 | displayName: Daily midnight build 16 | branches: 17 | include: 18 | - main 19 | always: true 20 | 21 | steps: 22 | - checkout: self 23 | - task: OneLocBuild@2 24 | displayName: "Localization Build: localize/LocProject.json" 25 | env: 26 | SYSTEM_ACCESSTOKEN: $(System.AccessToken) 27 | inputs: 28 | locProj: 'localize/LocProject.json' 29 | outDir: '$(Build.ArtifactStagingDirectory)' 30 | isCreatePrSelected: true 31 | repoType: gitHub 32 | gitHubPatVariable: "$(GitHubPat)" 33 | prSourceBranchPrefix: 'locfiles' 34 | packageSourceAuth: 'patAuth' 35 | patVariable: '$(OneLocBuildPat)' 36 | 37 | - task: PublishBuildArtifacts@1 38 | inputs: 39 | PathtoPublish: '$(Build.ArtifactStagingDirectory)' 40 | ArtifactName: 'drop' 41 | publishLocation: 'Container' 42 | -------------------------------------------------------------------------------- /.pipelines/pr-pipeline.yml: -------------------------------------------------------------------------------- 1 | # Node.js 2 | # Build a general Node.js project with npm. 3 | # Add steps that analyze code, save build artifacts, deploy, and more: 4 | # https://docs.microsoft.com/azure/devops/pipelines/languages/javascript 5 | 6 | trigger: 7 | - main 8 | 9 | pr: 10 | - main 11 | 12 | pool: 13 | vmImage: "windows-latest" 14 | variables: 15 | CI: "true" 16 | 17 | steps: 18 | - task: NodeTool@0 19 | inputs: 20 | versionSpec: "18.17.0" 21 | displayName: "Install Node.js" 22 | 23 | - task: Npm@1 24 | displayName: "npm clean-install" 25 | inputs: 26 | command: "ci" 27 | 28 | - task: Npm@1 29 | displayName: "audit" 30 | inputs: 31 | command: "custom" 32 | customCommand: "run audit" 33 | 34 | - task: Npm@1 35 | displayName: "npm unit test" 36 | inputs: 37 | command: "custom" 38 | customCommand: "run test:unit-test" 39 | 40 | - task: Npm@1 41 | displayName: "build VSIX" 42 | inputs: 43 | command: "custom" 44 | customCommand: "run vsix" 45 | 46 | # - task: Npm@1 47 | # displayName: "npm e2e test" 48 | # enabled: 'true' 49 | # retryCountOnTaskFailure: 5 50 | # continueOnError: 'true' 51 | # inputs: 52 | # command: "custom" 53 | # customCommand: "run test:e2e" 54 | 55 | - task: PoliCheck@1 56 | inputs: 57 | inputType: "Basic" 58 | targetType: "F" 59 | targetArgument: "src" 60 | result: "PoliCheck.xml" 61 | 62 | - task: CopyFiles@2 63 | displayName: "copy VSIX to ArtifactStagingDirectory" 64 | inputs: 65 | contents: "$(System.DefaultWorkingDirectory)/*.vsix" 66 | targetFolder: $(Build.ArtifactStagingDirectory) 67 | 68 | - task: PublishBuildArtifacts@1 69 | displayName: "drop ArtifactStagingDirectory" 70 | inputs: 71 | PathtoPublish: $(Build.ArtifactStagingDirectory) 72 | ArtifactName: VSIX 73 | 74 | - task: PublishBuildArtifacts@1 75 | displayName: "publish policheck results" 76 | inputs: 77 | PathtoPublish: "$(System.DefaultWorkingDirectory)/../_sdt/logs/PoliCheck" 78 | ArtifactName: PoliCheck 79 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "arrowParens": "avoid", 3 | "endOfLine": "auto", 4 | "printWidth": 120, 5 | "tabWidth": 4, 6 | "trailingComma": "all", 7 | "useTabs": false 8 | } 9 | -------------------------------------------------------------------------------- /.vscode-test.mjs: -------------------------------------------------------------------------------- 1 | import { defineConfig } from '@vscode/test-cli'; 2 | 3 | export default defineConfig({ 4 | files: 'out/src/test/**/*.test.js', 5 | }); 6 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | // See http://go.microsoft.com/fwlink/?LinkId=827846 3 | // for the documentation about the extensions.json format 4 | "recommendations": [ 5 | "dbaeumer.vscode-eslint", 6 | "amodio.tsl-problem-matcher", 7 | "esbenp.prettier-vscode", 8 | "ms-vscode.extension-test-runner" 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | // A launch configuration that compiles the extension and then opens it inside a new window 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | { 6 | "version": "0.2.0", 7 | "configurations": [ 8 | { 9 | "type": "node", 10 | "request": "launch", 11 | "name": "Debug current .ts file", 12 | "runtimeArgs": ["-r", "ts-node/register"], 13 | "args": ["${file}"] 14 | }, 15 | { 16 | "name": "Run Extension", 17 | "type": "extensionHost", 18 | "request": "launch", 19 | "args": ["--extensionDevelopmentPath=${workspaceFolder}"], 20 | "outFiles": ["${workspaceFolder}/dist/**/*.js"], 21 | "preLaunchTask": "${defaultBuildTask}" 22 | }, 23 | { 24 | "name": "Run Extension [Dev Mode]", 25 | "type": "extensionHost", 26 | "request": "launch", 27 | "args": ["--extensionDevelopmentPath=${workspaceFolder}"], 28 | "outFiles": ["${workspaceFolder}/dist/**/*.js"], 29 | "preLaunchTask": "Develop_Extensions", 30 | "sourceMaps": true, 31 | "env": { 32 | "WEBVIEW_DEV_MODE": "true" 33 | } 34 | }, 35 | { 36 | "name": "Extension UI Tests", 37 | "type": "extensionHost", 38 | "request": "launch", 39 | "runtimeExecutable": "${execPath}", 40 | "args": [ 41 | "--extensionDevelopmentPath=${workspaceFolder}", 42 | "--extensionTestsPath=${workspaceFolder}/out/src/test/suite/index", 43 | "--extensions-dir=${workspaceFolder}/.vscode-test/extensions" 44 | ], 45 | "outFiles": ["${workspaceFolder}/out/**/*.js"], 46 | "preLaunchTask": "tasks: watch-tests" 47 | } 48 | ] 49 | } 50 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | // Place your settings in this file to overwrite default and user settings. 2 | { 3 | "files.exclude": { 4 | "out": false, // set this to true to hide the "out" folder with the compiled JS files 5 | "dist": false // set this to true to hide the "dist" folder with the compiled JS files 6 | }, 7 | "search.exclude": { 8 | "out": true, // set this to false to include "out" folder in search results 9 | "dist": true // set this to false to include "dist" folder in search results 10 | }, 11 | // Turn off tsc task auto detection since we have the necessary tasks as npm scripts 12 | "typescript.tsc.autoDetect": "off", 13 | "editor.formatOnSave": true, 14 | "editor.defaultFormatter": "esbenp.prettier-vscode", 15 | } 16 | -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | // See https://go.microsoft.com/fwlink/?LinkId=733558 2 | // for the documentation about the tasks.json format 3 | { 4 | "version": "2.0.0", 5 | "tasks": [ 6 | { 7 | "label": "Watch_VSC_PQ_SDK", 8 | "type": "npm", 9 | "script": "watch", 10 | "problemMatcher": ["$ts-webpack-watch", "$tslint-webpack-watch"], 11 | "isBackground": true, 12 | "presentation": { 13 | "reveal": "never", 14 | "group": "watchers" 15 | }, 16 | "group": { 17 | "kind": "build", 18 | "isDefault": true 19 | } 20 | }, 21 | { 22 | "type": "npm", 23 | "script": "watch-tests", 24 | "problemMatcher": "$tsc-watch", 25 | "isBackground": true, 26 | "presentation": { 27 | "reveal": "never", 28 | "group": "watchers" 29 | }, 30 | "group": "build" 31 | }, 32 | { 33 | "label": "Dev_PQTest_Result_WebView", 34 | "type": "npm", 35 | "script": "start", 36 | "promptOnClose": true, 37 | "isBackground": true, 38 | "problemMatcher": { 39 | "owner": "webpack", 40 | "severity": "error", 41 | "fileLocation": "absolute", 42 | "pattern": [ 43 | { 44 | "regexp": "ERROR in (.*)", 45 | "file": 1 46 | }, 47 | { 48 | "regexp": "\\((\\d+),(\\d+)\\):(.*)", 49 | "line": 1, 50 | "column": 2, 51 | "message": 3 52 | } 53 | ], 54 | "background": { 55 | "activeOnStart": true, 56 | "beginsPattern": "Compiling\\.\\.\\.", 57 | "endsPattern": "compiled successfully in" 58 | } 59 | }, 60 | "options": { 61 | "cwd": "${workspaceFolder}/webviews/pq-test-result-view" 62 | } 63 | }, 64 | { 65 | "label": "Develop_Extensions", 66 | "dependsOn": ["Watch_VSC_PQ_SDK", "Dev_PQTest_Result_WebView"] 67 | }, 68 | { 69 | "label": "tasks: watch-tests", 70 | "dependsOn": ["Watch_VSC_PQ_SDK", "Dev_PQTest_Result_WebView", "npm: watch-tests"], 71 | "problemMatcher": [] 72 | } 73 | ] 74 | } 75 | -------------------------------------------------------------------------------- /.vscodeignore: -------------------------------------------------------------------------------- 1 | .github/ 2 | .pipelines/ 3 | .nuget/ 4 | .idea/ 5 | .vscode/** 6 | .vscode-test/** 7 | out/** 8 | node_modules/** 9 | resources/** 10 | localize/** 11 | src/** 12 | test-resources/** 13 | unit-tests/** 14 | webviews/** 15 | media/** 16 | .eslintignore 17 | .gitignore 18 | .yarnrc 19 | webpack.config.js 20 | vsc-extension-quickstart.md 21 | **/tsconfig.json 22 | **/.eslintrc.json 23 | **/*.map 24 | **/*.ts 25 | pqTest.pid 26 | **/test-results.xml 27 | **/*.mjs 28 | mochaReporterConfig.json -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | All notable changes to the "vscode-powerquery-sdk" extension will be documented in this file. 4 | 5 | Check [Keep a Changelog](http://keepachangelog.com/) for recommendations on how to structure this file. 6 | 7 | ## [Unreleased] 8 | 9 | - Initial release 10 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Microsoft Open Source Code of Conduct 2 | 3 | This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). 4 | 5 | Resources: 6 | 7 | - [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/) 8 | - [Microsoft Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) 9 | - Contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with questions or concerns 10 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) Microsoft Corporation. 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Power Query Connector SDK for Visual Studio Code 2 | 3 | > **_NOTE:_** The Power Query SDK extension for Visual Studio Code is currently in Public Preview. You can learn more about this extension and the development of Power Query data connectors from the link [aka.ms/PowerQuerySDKDocs](https://aka.ms/PowerQuerySDKDocs). 4 | 5 | Provides functionality related to the development and testing of Custom Connectors for Power Query and Power BI. 6 | 7 | Install the latest version of the [Power Query SDK through the Visual Studio Code Marketplace](https://aka.ms/PowerQuerySDK). 8 | 9 | ![Animated demonstration GIF of the Power Query SDK for Visual Studio Code](media/VSCodeSDK.gif) 10 | 11 | > **_NOTE:_** You can learn how to manually build and install this project from the article on [build and install](build.md). 12 | 13 | ## Features 14 | 15 | What you can do with this extension: 16 | 17 | * Create a new extension project using a custom connector template 18 | * Build connector file (.mez) 19 | * Set and manage credentials 20 | * Run test queries 21 | * Test your TestConnection function for refresh on the cloud 22 | * View query results 23 | * Leverage syntax highlighting and intellisense for writing M script 24 | * Manage your workspace settings and other project-level configurations 25 | 26 | ## Related projects 27 | 28 | [vscode-powerquery](https://github.com/microsoft/vscode-powerquery) 29 | 30 | 31 | ## Contributing 32 | 33 | This project welcomes contributions and suggestions. Most contributions require you to agree to a 34 | Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us 35 | the rights to use your contribution. For details, visit . 36 | 37 | When you submit a pull request, a CLA bot will automatically determine whether you need to provide 38 | a CLA and decorate the PR appropriately (e.g., status check, comment). Simply follow the instructions 39 | provided by the bot. You will only need to do this once across all repos using our CLA. 40 | 41 | This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). 42 | For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or 43 | contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments. 44 | 45 | ## Trademarks 46 | 47 | This project may contain trademarks or logos for projects, products, or services. Authorized use of Microsoft 48 | trademarks or logos is subject to and must follow 49 | [Microsoft's Trademark & Brand Guidelines](https://www.microsoft.com/en-us/legal/intellectualproperty/trademarks/usage/general). 50 | Use of Microsoft trademarks or logos in modified versions of this project must not cause confusion or imply Microsoft sponsorship. 51 | Any use of third-party trademarks or logos are subject to those third-party's policies. 52 | 53 | ## Support and issues 54 | 55 | Before creating a new issue or discussion, please make sure to the read our [support](SUPPORT.md) article for guidelines. 56 | 57 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## Security 4 | 5 | Microsoft takes the security of our software products and services seriously, which includes all source code repositories managed through our GitHub organizations, which include [Microsoft](https://github.com/Microsoft), [Azure](https://github.com/Azure), [DotNet](https://github.com/dotnet), [AspNet](https://github.com/aspnet), [Xamarin](https://github.com/xamarin), and [our GitHub organizations](https://opensource.microsoft.com/). 6 | 7 | If you believe you have found a security vulnerability in any Microsoft-owned repository that meets [Microsoft's definition of a security vulnerability](https://docs.microsoft.com/en-us/previous-versions/tn-archive/cc751383(v=technet.10)), please report it to us as described below. 8 | 9 | ## Reporting Security Issues 10 | 11 | **Please do not report security vulnerabilities through public GitHub issues.** 12 | 13 | Instead, please report them to the Microsoft Security Response Center (MSRC) at [https://msrc.microsoft.com/create-report](https://msrc.microsoft.com/create-report). 14 | 15 | If you prefer to submit without logging in, send email to [secure@microsoft.com](mailto:secure@microsoft.com). If possible, encrypt your message with our PGP key; please download it from the [Microsoft Security Response Center PGP Key page](https://www.microsoft.com/en-us/msrc/pgp-key-msrc). 16 | 17 | You should receive a response within 24 hours. If for some reason you do not, please follow up via email to ensure we received your original message. Additional information can be found at [microsoft.com/msrc](https://www.microsoft.com/msrc). 18 | 19 | Please include the requested information listed below (as much as you can provide) to help us better understand the nature and scope of the possible issue: 20 | 21 | * Type of issue (e.g. buffer overflow, SQL injection, cross-site scripting, etc.) 22 | * Full paths of source file(s) related to the manifestation of the issue 23 | * The location of the affected source code (tag/branch/commit or direct URL) 24 | * Any special configuration required to reproduce the issue 25 | * Step-by-step instructions to reproduce the issue 26 | * Proof-of-concept or exploit code (if possible) 27 | * Impact of the issue, including how an attacker might exploit the issue 28 | 29 | This information will help us triage your report more quickly. 30 | 31 | If you are reporting for a bug bounty, more complete reports can contribute to a higher bounty award. Please visit our [Microsoft Bug Bounty Program](https://microsoft.com/msrc/bounty) page for more details about our active programs. 32 | 33 | ## Preferred Languages 34 | 35 | We prefer all communications to be in English. 36 | 37 | ## Policy 38 | 39 | Microsoft follows the principle of [Coordinated Vulnerability Disclosure](https://www.microsoft.com/en-us/msrc/cvd). 40 | 41 | -------------------------------------------------------------------------------- /SUPPORT.md: -------------------------------------------------------------------------------- 1 | # Support 2 | 3 | ## How to file issues and get help 4 | 5 | This project uses GitHub Issues to track bugs and feature requests. Please search the existing 6 | issues before filing new issues to avoid duplicates. For new issues, file your bug or 7 | feature request as a new Issue. 8 | 9 | > Issues posted in this GitHub repository should be related to the vscode-powerquery-sdk extension. 10 | > You can post general questions regarding Custom Connector development in the [discussions section of this repository](https://github.com/microsoft/vscode-powerquery-sdk/discussions) to get help from the community and the Microsoft team. 11 | > Microsoft makes no guarantees or promises to resolve questions or scenarios raised through the discussion section. The support on the discussion section is provided on a best-effort approach to help users of the Power Query SDK. 12 | 13 | ## Microsoft Support Policy 14 | 15 | Support for this project is limited to the resources listed above. 16 | -------------------------------------------------------------------------------- /build.md: -------------------------------------------------------------------------------- 1 | # Build and install from source 2 | 3 | Install project dependencies: 4 | 5 | ```msdos 6 | npm install 7 | ``` 8 | 9 | Build the project and create the vsix installer (`npx vsce package`): 10 | 11 | ```msdos 12 | npm run vsix 13 | ``` 14 | 15 | Install the extension from the command line (`code --install-extension `): 16 | 17 | ```msdos 18 | npm run code-install 19 | ``` -------------------------------------------------------------------------------- /etc/PQIcon_128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/vscode-powerquery-sdk/cc03dafadd188d5442c64efd6f0885d1b2390c8e/etc/PQIcon_128.png -------------------------------------------------------------------------------- /localize/.gitignore: -------------------------------------------------------------------------------- 1 | i18nDist/ -------------------------------------------------------------------------------- /localize/LocProject.json: -------------------------------------------------------------------------------- 1 | { 2 | "Projects": [ 3 | { 4 | "LanguageSet": "Azure_Languages", 5 | "LocItems": [ 6 | { 7 | "SourceFile": "package.nls.json", 8 | "Languages": "fr;de;it;es;ja;ko;ru;pt-br;tr;pl;cs;zh-cn;zh-tw", 9 | "LclFile": "localize\\loc\\package\\{Lang}\\package.nls.json.lcl", 10 | "CopyOption": "LangIDOnPath", 11 | "OutputPath": "localize\\i18n" 12 | }, 13 | { 14 | "SourceFile": "src\\i18n\\extension.json", 15 | "Languages": "fr;de;it;es;ja;ko;ru;pt-br;tr;pl;cs;zh-cn;zh-tw", 16 | "LclFile": "localize\\loc\\extension\\{Lang}\\extension.json.lcl", 17 | "CopyOption": "LangIDOnPath", 18 | "OutputPath": "localize\\i18n" 19 | }, 20 | { 21 | "SourceFile": "webviews\\pq-test-result-view\\public\\i18n\\pq-test-result-view.json", 22 | "Languages": "fr;de;it;es;ja;ko;ru;pt-br;tr;pl;cs;zh-cn;zh-tw", 23 | "LclFile": "localize\\loc\\pq-test-result-view\\{Lang}\\pq-test-result-view.json.lcl", 24 | "CopyOption": "LangIDOnPath", 25 | "OutputPath": "localize\\i18n" 26 | } 27 | ] 28 | } 29 | ] 30 | } 31 | -------------------------------------------------------------------------------- /localize/i18n/cs/pq-test-result-view.json: -------------------------------------------------------------------------------- 1 | { 2 | "common.dismiss.label": "Zavřít", 3 | "common.error.label": "Chyba", 4 | "testBatteryResView.Table.Output.Title": "Výstup", 5 | "testBatteryResView.Table.Output.Summary": "Souhrn", 6 | "testBatteryResView.Table.Output.DataSource": "Zdroj dat" 7 | } -------------------------------------------------------------------------------- /localize/i18n/de/pq-test-result-view.json: -------------------------------------------------------------------------------- 1 | { 2 | "common.dismiss.label": "Schließen", 3 | "common.error.label": "Fehler", 4 | "testBatteryResView.Table.Output.Title": "Ausgabe", 5 | "testBatteryResView.Table.Output.Summary": "Zusammenfassung", 6 | "testBatteryResView.Table.Output.DataSource": "DataSource" 7 | } -------------------------------------------------------------------------------- /localize/i18n/es/pq-test-result-view.json: -------------------------------------------------------------------------------- 1 | { 2 | "common.dismiss.label": "Cerrar", 3 | "common.error.label": "Error", 4 | "testBatteryResView.Table.Output.Title": "Salida", 5 | "testBatteryResView.Table.Output.Summary": "Resumen", 6 | "testBatteryResView.Table.Output.DataSource": "Origen de datos" 7 | } -------------------------------------------------------------------------------- /localize/i18n/fr/pq-test-result-view.json: -------------------------------------------------------------------------------- 1 | { 2 | "common.dismiss.label": "Fermer", 3 | "common.error.label": "Erreur", 4 | "testBatteryResView.Table.Output.Title": "Sortie", 5 | "testBatteryResView.Table.Output.Summary": "Résumé", 6 | "testBatteryResView.Table.Output.DataSource": "Source de données" 7 | } -------------------------------------------------------------------------------- /localize/i18n/it/pq-test-result-view.json: -------------------------------------------------------------------------------- 1 | { 2 | "common.dismiss.label": "Chiudi", 3 | "common.error.label": "Errore", 4 | "testBatteryResView.Table.Output.Title": "Output", 5 | "testBatteryResView.Table.Output.Summary": "Riepilogo", 6 | "testBatteryResView.Table.Output.DataSource": "DataSource" 7 | } -------------------------------------------------------------------------------- /localize/i18n/ja/package.nls.json: -------------------------------------------------------------------------------- 1 | { 2 | "extension.pqtest.CreateNewProjectCommand.title": "拡張プロジェクトを作成する", 3 | "extension.pqtest.SeizePqTestCommand.title": "SDK ツールを更新する", 4 | "extension.pqtest.SetupCurrentWorkspaceCommand.title": "ワークスペースをセットアップする", 5 | "extension.pqtest.DeleteCredentialCommand.title": "すべての資格情報をクリアする", 6 | "extension.pqtest.DisplayExtensionInfoCommand.title": "コネクタ情報", 7 | "extension.pqtest.ListCredentialCommand.title": "資格情報を一覧表示する", 8 | "extension.pqtest.GenerateAndSetCredentialCommand.title": "資格情報を設定する", 9 | "extension.pqtest.RefreshCredentialCommand.title": "資格情報を更新する", 10 | "extension.pqtest.RunTestBatteryCommand.title": "現在の Power Query ファイルを評価する", 11 | "extension.pqtest.TestConnectionCommand.title": "テスト接続", 12 | "extension.pqtest.config.externals.msbuildPath.description": "msbuild.exe インストール フォルダーへのローカル パス。", 13 | "extension.pqtest.config.externals.nugetPath.description": "nuget.exe インストール フォルダーへのローカル パス。", 14 | "extension.pqtest.config.externals.nugetFeed.description": "NuGet フィードの URL の候補。", 15 | "extension.pqtest.config.externals.versionTag.description": "PQ SDK ツールのバージョン タグがダウンロードされます。", 16 | "extension.pqtest.config.externals.versionTag.recommended.description": "安定したバージョン", 17 | "extension.pqtest.config.externals.versionTag.latest.description": "最新バージョン", 18 | "extension.pqtest.config.externals.versionTag.customized.description": "ユーザー指定のバージョン値を使用する", 19 | "extension.pqtest.config.features.autoDetection.description": "false に設定すると、SDK はコネクタ ワークスペースを自動的に検出せず、設定ファイルを作成するように求めます。", 20 | "extension.pqtest.config.features.useServiceHost": "コマンド ライン以外の再利用可能なエンジン サービス ホストを使用して、新機能を試す", 21 | "extension.pqtest.config.pqtest.location.description": "PQTest インストール フォルダーへのローカル パス。", 22 | "extension.pqtest.config.pqtest.version.description": "ローカル PQ SDK ツールのバージョン。", 23 | "extension.pqtest.config.pqtest.extension.description": "コネクタ拡張ソース モジュール (.mez/.pqm) を指定します。
このオプションは複数指定できます。", 24 | "extension.pqtest.config.pqtest.queryFile.description": "セクション ドキュメントまたは M 式 (.m/.pq) を含むクエリ ファイル。", 25 | "extension.pqtest.taskDefinitions.properties.operation.description": "実行する操作", 26 | "extension.pqtest.taskDefinitions.properties.additionalArgs.description": "操作のための追加のコマンドライン引数", 27 | "extension.pqtest.taskDefinitions.properties.pathToConnector.description": "コネクタ ファイルへのパス (--extension)", 28 | "extension.pqtest.taskDefinitions.properties.pathToQueryFile.description": "クエリ ファイルへのパス (--queryFile)", 29 | "extension.pqtest.taskDefinitions.properties.credentialTemplate.description": "資格情報テンプレート オブジェクト。", 30 | "extension.pqtest.explorer.name": "Power Query SDK", 31 | "extension.pqtest.welcome.contents": "拡張機能を使用するには、Power Query 拡張プロジェクトを作成する必要があります。\n[拡張プロジェクトの作成](command:powerquery.sdk.tools.CreateNewProjectCommand)\n拡張機能の作成方法の詳細については、[ドキュメントをお読みください](https://aka.ms/PowerQuerySDKDocs)。", 32 | "extension.pqtest.debugger.properties.program.description": "Power Query ファイルへの絶対パス。", 33 | "extension.pqtest.debugger.properties.trace.description": "デバッグのログ記録を有効にする", 34 | "extension.pqtest.debugger.properties.operation.description": "PQTest 操作文字列", 35 | "extension.pqtest.debugger.properties.operation.info.description": "info: すべてのモジュール情報を返す", 36 | "extension.pqtest.debugger.properties.operation.runTest.description": "run-test: テスト接続", 37 | "extension.pqtest.debugger.properties.operation.testConnection.description": "test-connection: 現在のコネクタの接続をテストする", 38 | "extension.pqtest.debugger.properties.additionalArgs.description": "デバッグ タスクの追加引数" 39 | } -------------------------------------------------------------------------------- /localize/i18n/ja/pq-test-result-view.json: -------------------------------------------------------------------------------- 1 | { 2 | "common.dismiss.label": "閉じる", 3 | "common.error.label": "エラー", 4 | "testBatteryResView.Table.Output.Title": "出力", 5 | "testBatteryResView.Table.Output.Summary": "概要", 6 | "testBatteryResView.Table.Output.DataSource": "データソース" 7 | } -------------------------------------------------------------------------------- /localize/i18n/ko/package.nls.json: -------------------------------------------------------------------------------- 1 | { 2 | "extension.pqtest.CreateNewProjectCommand.title": "확장 프로젝트 만들기", 3 | "extension.pqtest.SeizePqTestCommand.title": "SDK 도구 업데이트", 4 | "extension.pqtest.SetupCurrentWorkspaceCommand.title": "작업 영역 설정", 5 | "extension.pqtest.DeleteCredentialCommand.title": "모든 자격 증명 지우기", 6 | "extension.pqtest.DisplayExtensionInfoCommand.title": "커넥터 정보", 7 | "extension.pqtest.ListCredentialCommand.title": "자격 증명 나열", 8 | "extension.pqtest.GenerateAndSetCredentialCommand.title": "자격 증명 설정", 9 | "extension.pqtest.RefreshCredentialCommand.title": "자격 증명 새로 고침", 10 | "extension.pqtest.RunTestBatteryCommand.title": "현재 Power Query 파일 평가", 11 | "extension.pqtest.TestConnectionCommand.title": "연결 테스트", 12 | "extension.pqtest.config.externals.msbuildPath.description": "msbuild.exe 설치 폴더의 로컬 경로입니다.", 13 | "extension.pqtest.config.externals.nugetPath.description": "nuget.exe 설치 폴더의 로컬 경로입니다.", 14 | "extension.pqtest.config.externals.nugetFeed.description": "추천 NuGet 피드 URL입니다.", 15 | "extension.pqtest.config.externals.versionTag.description": "다운로드할 PQ SDK 도구의 버전 태그입니다.", 16 | "extension.pqtest.config.externals.versionTag.recommended.description": "안정적인 버전", 17 | "extension.pqtest.config.externals.versionTag.latest.description": "최신 버전", 18 | "extension.pqtest.config.externals.versionTag.customized.description": "사용자가 제공한 버전 값 사용", 19 | "extension.pqtest.config.features.autoDetection.description": "false로 설정하면 SDK에서 커넥터 작업 영역을 자동으로 검색하고 설정 파일을 만들라는 메시지를 표시하지 않습니다.", 20 | "extension.pqtest.config.features.useServiceHost": "명령줄 외 재사용 가능한 엔진 서비스 호스트를 사용하여 새 기능을 사용해 보세요.", 21 | "extension.pqtest.config.pqtest.location.description": "PQTest 설치 폴더의 로컬 경로입니다.", 22 | "extension.pqtest.config.pqtest.version.description": "로컬 PQ SDK 도구 버전입니다.", 23 | "extension.pqtest.config.pqtest.extension.description": "커넥터 확장 원본 모듈(.mez/.pqm)을 지정합니다.
이 옵션은 두 번 이상 지정할 수 있습니다.", 24 | "extension.pqtest.config.pqtest.queryFile.description": "섹션 문서 또는 M 식(.m/.pq)을 포함하는 쿼리 파일입니다.", 25 | "extension.pqtest.taskDefinitions.properties.operation.description": "실행할 작업", 26 | "extension.pqtest.taskDefinitions.properties.additionalArgs.description": "작업에 대한 추가 명령줄 인수", 27 | "extension.pqtest.taskDefinitions.properties.pathToConnector.description": "커넥터 파일 경로(--extension)", 28 | "extension.pqtest.taskDefinitions.properties.pathToQueryFile.description": "쿼리 파일 경로(--queryFile)", 29 | "extension.pqtest.taskDefinitions.properties.credentialTemplate.description": "자격 증명 템플릿 개체입니다.", 30 | "extension.pqtest.explorer.name": "Power Query SDK", 31 | "extension.pqtest.welcome.contents": "확장 기능을 사용하려면 Power Query 확장 프로젝트를 만들어야 합니다.\n[확장 프로젝트 만들기](command:powerquery.sdk.tools.CreateNewProjectCommand)\n확장을 만드는 방법을 자세히 알아보려면 [우리 문서를 읽으세요](https://aka.ms/PowerQuerySDKDocs).", 32 | "extension.pqtest.debugger.properties.program.description": "Power Query 파일의 절대 경로입니다.", 33 | "extension.pqtest.debugger.properties.trace.description": "디버그 로깅 사용", 34 | "extension.pqtest.debugger.properties.operation.description": "PQTest 작업 문자열", 35 | "extension.pqtest.debugger.properties.operation.info.description": "정보: 모든 모듈 정보를 반환합니다.", 36 | "extension.pqtest.debugger.properties.operation.runTest.description": "run-test: 연결 테스트", 37 | "extension.pqtest.debugger.properties.operation.testConnection.description": "test-connection: 현재 커넥터의 연결을 테스트합니다.", 38 | "extension.pqtest.debugger.properties.additionalArgs.description": "디버깅 작업에 대한 추가 인수" 39 | } -------------------------------------------------------------------------------- /localize/i18n/ko/pq-test-result-view.json: -------------------------------------------------------------------------------- 1 | { 2 | "common.dismiss.label": "닫기", 3 | "common.error.label": "오류", 4 | "testBatteryResView.Table.Output.Title": "출력", 5 | "testBatteryResView.Table.Output.Summary": "요약", 6 | "testBatteryResView.Table.Output.DataSource": "DataSource" 7 | } -------------------------------------------------------------------------------- /localize/i18n/pl/pq-test-result-view.json: -------------------------------------------------------------------------------- 1 | { 2 | "common.dismiss.label": "Zamknij", 3 | "common.error.label": "Błąd", 4 | "testBatteryResView.Table.Output.Title": "Dane wyjściowe", 5 | "testBatteryResView.Table.Output.Summary": "Podsumowanie", 6 | "testBatteryResView.Table.Output.DataSource": "DataSource" 7 | } -------------------------------------------------------------------------------- /localize/i18n/pt-br/pq-test-result-view.json: -------------------------------------------------------------------------------- 1 | { 2 | "common.dismiss.label": "Fechar", 3 | "common.error.label": "Erro", 4 | "testBatteryResView.Table.Output.Title": "Saída", 5 | "testBatteryResView.Table.Output.Summary": "Resumo", 6 | "testBatteryResView.Table.Output.DataSource": "DataSource" 7 | } -------------------------------------------------------------------------------- /localize/i18n/ru/pq-test-result-view.json: -------------------------------------------------------------------------------- 1 | { 2 | "common.dismiss.label": "Закрыть", 3 | "common.error.label": "Ошибка", 4 | "testBatteryResView.Table.Output.Title": "Выходные данные", 5 | "testBatteryResView.Table.Output.Summary": "Сводка", 6 | "testBatteryResView.Table.Output.DataSource": "DataSource" 7 | } -------------------------------------------------------------------------------- /localize/i18n/tr/pq-test-result-view.json: -------------------------------------------------------------------------------- 1 | { 2 | "common.dismiss.label": "Kapat", 3 | "common.error.label": "Hata", 4 | "testBatteryResView.Table.Output.Title": "Çıktı", 5 | "testBatteryResView.Table.Output.Summary": "Özet", 6 | "testBatteryResView.Table.Output.DataSource": "DataSource" 7 | } -------------------------------------------------------------------------------- /localize/i18n/zh-cn/package.nls.json: -------------------------------------------------------------------------------- 1 | { 2 | "extension.pqtest.CreateNewProjectCommand.title": "创建扩展项目", 3 | "extension.pqtest.SeizePqTestCommand.title": "更新 SDK 工具", 4 | "extension.pqtest.SetupCurrentWorkspaceCommand.title": "安装工作区", 5 | "extension.pqtest.DeleteCredentialCommand.title": "清除所有凭据", 6 | "extension.pqtest.DisplayExtensionInfoCommand.title": "连接器信息", 7 | "extension.pqtest.ListCredentialCommand.title": "列出凭据", 8 | "extension.pqtest.GenerateAndSetCredentialCommand.title": "设置凭据", 9 | "extension.pqtest.RefreshCredentialCommand.title": "刷新凭据", 10 | "extension.pqtest.RunTestBatteryCommand.title": "评估当前 Power Query 文件", 11 | "extension.pqtest.TestConnectionCommand.title": "测试连接", 12 | "extension.pqtest.config.externals.msbuildPath.description": "msbuild.exe 安装文件夹的本地路径。", 13 | "extension.pqtest.config.externals.nugetPath.description": "nuget.exe 安装文件夹的本地路径。", 14 | "extension.pqtest.config.externals.nugetFeed.description": "建议的 nuget 源 URL。", 15 | "extension.pqtest.config.externals.versionTag.description": "要下载的 PQ SDK 工具的版本标记。", 16 | "extension.pqtest.config.externals.versionTag.recommended.description": "稳定版本", 17 | "extension.pqtest.config.externals.versionTag.latest.description": "最新版本", 18 | "extension.pqtest.config.externals.versionTag.customized.description": "使用用户提供的版本值", 19 | "extension.pqtest.config.features.autoDetection.description": "设置为 false 时,SDK 不会尝试自动检测连接器工作区并提示创建设置文件。", 20 | "extension.pqtest.config.features.useServiceHost": "使用命令行以外的可重用引擎服务主机试用新功能", 21 | "extension.pqtest.config.pqtest.location.description": "PQTest 安装文件夹的本地路径。", 22 | "extension.pqtest.config.pqtest.version.description": "本地 PQ SDK 工具版本。", 23 | "extension.pqtest.config.pqtest.extension.description": "指定连接器扩展源模块(.mez/.pqm)。
此选项可以多次指定。", 24 | "extension.pqtest.config.pqtest.queryFile.description": "包含节文档或 M 表达式 (.m/.pq) 的查询文件。", 25 | "extension.pqtest.taskDefinitions.properties.operation.description": "要运行的操作", 26 | "extension.pqtest.taskDefinitions.properties.additionalArgs.description": "操作的其他命令行参数", 27 | "extension.pqtest.taskDefinitions.properties.pathToConnector.description": "连接器文件的路径(--扩展)", 28 | "extension.pqtest.taskDefinitions.properties.pathToQueryFile.description": "查询文件的路径 (--queryFile)", 29 | "extension.pqtest.taskDefinitions.properties.credentialTemplate.description": "凭据模板对象。", 30 | "extension.pqtest.explorer.name": "Power Query SDK", 31 | "extension.pqtest.welcome.contents": "若要使用扩展功能,需要创建 Power query 扩展项目。\n[创建扩展项目](command:powerquery.sdk.tools.CreateNewProjectCommand)\n若要详细了解如何创建扩展,[请阅读我们的文档](https://aka.ms/PowerQuerySDKDocs)。", 32 | "extension.pqtest.debugger.properties.program.description": "Power Query 文件的绝对路径。", 33 | "extension.pqtest.debugger.properties.trace.description": "启用调试日志记录", 34 | "extension.pqtest.debugger.properties.operation.description": "PQTest 操作字符串", 35 | "extension.pqtest.debugger.properties.operation.info.description": "信息: 返回所有模块信息", 36 | "extension.pqtest.debugger.properties.operation.runTest.description": "run-test: 测试连接", 37 | "extension.pqtest.debugger.properties.operation.testConnection.description": "test-connection: 测试当前连接器的连接", 38 | "extension.pqtest.debugger.properties.additionalArgs.description": "调试任务的其他参数" 39 | } -------------------------------------------------------------------------------- /localize/i18n/zh-cn/pq-test-result-view.json: -------------------------------------------------------------------------------- 1 | { 2 | "common.dismiss.label": "关闭", 3 | "common.error.label": "错误", 4 | "testBatteryResView.Table.Output.Title": "输出", 5 | "testBatteryResView.Table.Output.Summary": "摘要", 6 | "testBatteryResView.Table.Output.DataSource": "DataSource" 7 | } -------------------------------------------------------------------------------- /localize/i18n/zh-tw/package.nls.json: -------------------------------------------------------------------------------- 1 | { 2 | "extension.pqtest.CreateNewProjectCommand.title": "建立延伸模組專案", 3 | "extension.pqtest.SeizePqTestCommand.title": "更新 SDK 工具", 4 | "extension.pqtest.SetupCurrentWorkspaceCommand.title": "設定工作區", 5 | "extension.pqtest.DeleteCredentialCommand.title": "清除所有認證", 6 | "extension.pqtest.DisplayExtensionInfoCommand.title": "連接器資訊", 7 | "extension.pqtest.ListCredentialCommand.title": "列出認證", 8 | "extension.pqtest.GenerateAndSetCredentialCommand.title": "設定認證", 9 | "extension.pqtest.RefreshCredentialCommand.title": "重新整理認證", 10 | "extension.pqtest.RunTestBatteryCommand.title": "評估目前的 Power Query 檔案", 11 | "extension.pqtest.TestConnectionCommand.title": "測試連線", 12 | "extension.pqtest.config.externals.msbuildPath.description": "msbuild.exe 安裝資料夾的本機路徑。", 13 | "extension.pqtest.config.externals.nugetPath.description": "nuget.exe 安裝資料夾的本機路徑。", 14 | "extension.pqtest.config.externals.nugetFeed.description": "建議的 NuGet 摘要 URL。", 15 | "extension.pqtest.config.externals.versionTag.description": "要下載之 PQ SDK 工具的版本標籤。", 16 | "extension.pqtest.config.externals.versionTag.recommended.description": "穩定版本", 17 | "extension.pqtest.config.externals.versionTag.latest.description": "最新版本", 18 | "extension.pqtest.config.externals.versionTag.customized.description": "使用使用者提供的版本值", 19 | "extension.pqtest.config.features.autoDetection.description": "設為 false 時,SDK 不會嘗試自動偵測連接器工作區,並提示您建立設定檔案。", 20 | "extension.pqtest.config.features.useServiceHost": "使用命令列以外可重複使用的引擎服務主機來嘗試新功能", 21 | "extension.pqtest.config.pqtest.location.description": "PQTest 安裝資料夾的本機路徑。", 22 | "extension.pqtest.config.pqtest.version.description": "本機 PQ SDK 工具版本。", 23 | "extension.pqtest.config.pqtest.extension.description": "指定連接器延伸來源模組 (.mez/.pqm)。
此選項可以指定一次以上。", 24 | "extension.pqtest.config.pqtest.queryFile.description": "包含區段文件或 M 運算式的查詢檔案 (.m/.pq)。", 25 | "extension.pqtest.taskDefinitions.properties.operation.description": "要執行的作業", 26 | "extension.pqtest.taskDefinitions.properties.additionalArgs.description": "作業的其他命令列引數", 27 | "extension.pqtest.taskDefinitions.properties.pathToConnector.description": "連接器檔案的路徑 (--extension)", 28 | "extension.pqtest.taskDefinitions.properties.pathToQueryFile.description": "查詢檔案的路徑 (--queryFile)", 29 | "extension.pqtest.taskDefinitions.properties.credentialTemplate.description": "認證範本物件。", 30 | "extension.pqtest.explorer.name": "Power Query SDK", 31 | "extension.pqtest.welcome.contents": "若要使用延伸模組功能,您需要建立 Power Query 延伸模組專案。\n[建立延伸模組專案](command:powerquery.sdk.tools.CreateNewProjectCommand)\n若要深入了解如何建立延伸模組,請 [閱讀我們的文件](https://aka.ms/PowerQuerySDKDocs)。", 32 | "extension.pqtest.debugger.properties.program.description": "Power Query 檔案的絕對路徑。", 33 | "extension.pqtest.debugger.properties.trace.description": "啟用偵錯記錄", 34 | "extension.pqtest.debugger.properties.operation.description": "PQTest 作業字串", 35 | "extension.pqtest.debugger.properties.operation.info.description": "資訊: 傳回所有模組資訊", 36 | "extension.pqtest.debugger.properties.operation.runTest.description": "run-test: 測試連線", 37 | "extension.pqtest.debugger.properties.operation.testConnection.description": "test-connection: 測試目前連接器的連線", 38 | "extension.pqtest.debugger.properties.additionalArgs.description": "偵錯工作的其他引數" 39 | } -------------------------------------------------------------------------------- /localize/i18n/zh-tw/pq-test-result-view.json: -------------------------------------------------------------------------------- 1 | { 2 | "common.dismiss.label": "關閉", 3 | "common.error.label": "錯誤", 4 | "testBatteryResView.Table.Output.Title": "輸出", 5 | "testBatteryResView.Table.Output.Summary": "摘要", 6 | "testBatteryResView.Table.Output.DataSource": "DataSource" 7 | } -------------------------------------------------------------------------------- /localize/loc/pq-test-result-view/cs/pq-test-result-view.json.lcl: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /localize/loc/pq-test-result-view/de/pq-test-result-view.json.lcl: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /localize/loc/pq-test-result-view/es/pq-test-result-view.json.lcl: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /localize/loc/pq-test-result-view/fr/pq-test-result-view.json.lcl: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /localize/loc/pq-test-result-view/it/pq-test-result-view.json.lcl: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /localize/loc/pq-test-result-view/ja/pq-test-result-view.json.lcl: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /localize/loc/pq-test-result-view/ko/pq-test-result-view.json.lcl: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /localize/loc/pq-test-result-view/pl/pq-test-result-view.json.lcl: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /localize/loc/pq-test-result-view/pt-br/pq-test-result-view.json.lcl: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /localize/loc/pq-test-result-view/ru/pq-test-result-view.json.lcl: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /localize/loc/pq-test-result-view/tr/pq-test-result-view.json.lcl: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /localize/loc/pq-test-result-view/zh-cn/pq-test-result-view.json.lcl: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /localize/loc/pq-test-result-view/zh-tw/pq-test-result-view.json.lcl: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /media/VSCodeSDK.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/vscode-powerquery-sdk/cc03dafadd188d5442c64efd6f0885d1b2390c8e/media/VSCodeSDK.gif -------------------------------------------------------------------------------- /mochaReporterConfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "reporterEnabled": "spec, mocha-junit-reporter" 3 | } 4 | -------------------------------------------------------------------------------- /resources/license-header.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Microsoft Corporation. 3 | * 4 | * Licensed under the MIT license found in the 5 | * LICENSE file in the root of this projects source tree. 6 | */ 7 | -------------------------------------------------------------------------------- /scripts/addI18n.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Microsoft Corporation. 3 | * 4 | * Licensed under the MIT license found in the 5 | * LICENSE file in the root of this projects source tree. 6 | */ 7 | 8 | import * as fs from "fs"; 9 | // import * as gulp from "gulp"; 10 | import * as nls from "vscode-nls-dev"; 11 | import * as path from "path"; 12 | import * as process from "process"; 13 | import * as vfs from "vinyl-fs"; 14 | 15 | const projectDirectory: string = process.cwd(); 16 | const i18nDirectory: string = path.join(projectDirectory, "localize", "i18n"); 17 | const i18nDistDirectory: string = path.join(projectDirectory, "localize", "i18nDist"); 18 | 19 | const distDirectory: string = path.join(projectDirectory, "dist"); 20 | 21 | const webviewDistPqTestResI18nDirectory: string = path.join( 22 | projectDirectory, 23 | "webviewDist", 24 | "pq-test-result-view", 25 | "i18n", 26 | ); 27 | 28 | if (fs.existsSync(i18nDistDirectory)) { 29 | fs.rmSync(i18nDistDirectory, { force: true, recursive: true }); 30 | } 31 | 32 | fs.mkdirSync(i18nDistDirectory); 33 | 34 | const allLangDirectories: string[] = fs 35 | .readdirSync(i18nDirectory, { withFileTypes: true }) 36 | .filter((dirent: fs.Dirent) => dirent.isDirectory()) 37 | .map((dirent: fs.Dirent) => dirent.name); 38 | 39 | // for each i18n json, first we need to copy them into the corresponding folders beneath lang's dist folder 40 | // package.nls.json -> i118nDist/{langId}/package.i18n.json 41 | // extension.json -> dist/extension.{langId}.json 42 | // pq-test-result-view.json -> webviewDist/pq-test-result-view/i18n/pq-test-result-view.{langId}.json 43 | 44 | for (const oneLangId of allLangDirectories) { 45 | const currentLangIdDirectory: string = path.join(i18nDistDirectory, oneLangId); 46 | 47 | fs.mkdirSync(currentLangIdDirectory); 48 | 49 | fs.copyFileSync( 50 | path.join(i18nDirectory, oneLangId, "package.nls.json"), 51 | path.join(currentLangIdDirectory, "package.i18n.json"), 52 | ); 53 | 54 | fs.copyFileSync( 55 | path.join(i18nDirectory, oneLangId, "extension.json"), 56 | path.join(distDirectory, `extension.${oneLangId}.json`), 57 | ); 58 | 59 | fs.copyFileSync( 60 | path.join(i18nDirectory, oneLangId, "pq-test-result-view.json"), 61 | path.join(webviewDistPqTestResI18nDirectory, `pq-test-result-view.${oneLangId}.json`), 62 | ); 63 | 64 | console.log(`[AddI18n] create ${currentLangIdDirectory} directory`); 65 | } 66 | 67 | const supportedLanguages: nls.Language[] = allLangDirectories.map((id: string) => ({ id, folderName: id })); 68 | 69 | vfs.src(["package.nls.json"], { cwd: projectDirectory }) 70 | .pipe(nls.createAdditionalLanguageFiles(supportedLanguages, path.join("localize", "i18nDist"), ".")) 71 | .pipe(vfs.dest(".", { cwd: projectDirectory })) 72 | -------------------------------------------------------------------------------- /scripts/clean.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Microsoft Corporation. 3 | * 4 | * Licensed under the MIT license found in the 5 | * LICENSE file in the root of this projects source tree. 6 | */ 7 | 8 | import * as glob from "glob"; 9 | import * as fs from "fs"; 10 | import * as path from "path"; 11 | import * as process from "process"; 12 | 13 | const projectDirectory: string = process.cwd(); 14 | 15 | const outDirectory: string = path.join(projectDirectory, "out"); 16 | const distDirectory: string = path.join(projectDirectory, "dist"); 17 | const webviewDistDirectory: string = path.join(projectDirectory, "webviewDist"); 18 | 19 | [outDirectory, distDirectory, webviewDistDirectory].forEach((oneDirectory: string) => { 20 | if (fs.existsSync(oneDirectory)) { 21 | fs.rmSync(oneDirectory, { force: true, recursive: true }); 22 | } 23 | }); 24 | 25 | const otherPackageNlsJsonsGlob: string[] = glob.globSync("package.nls.**.json", { cwd: projectDirectory }); 26 | 27 | otherPackageNlsJsonsGlob.forEach((oneNlsJson: string) => { 28 | fs.unlinkSync(path.join(projectDirectory, oneNlsJson)); 29 | }); 30 | -------------------------------------------------------------------------------- /scripts/install.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Microsoft Corporation. 3 | * 4 | * Licensed under the MIT license found in the 5 | * LICENSE file in the root of this projects source tree. 6 | */ 7 | 8 | import * as cp from "child_process"; 9 | import { getFirstVsixFileDirectlyBeneathOneDirectory } from "./utils/vsixs"; 10 | 11 | const cwd = process.cwd(); 12 | 13 | let oneVsixFile: string = getFirstVsixFileDirectlyBeneathOneDirectory(process.cwd()); 14 | 15 | if (oneVsixFile) { 16 | cp.execSync(`code --install-extension ${oneVsixFile}`, { cwd }); 17 | } else { 18 | console.error('Cannot find one vsix file, please run "npm run vsix" before install.'); 19 | } 20 | -------------------------------------------------------------------------------- /scripts/test-e2e.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Microsoft Corporation. 3 | * 4 | * Licensed under the MIT license found in the 5 | * LICENSE file in the root of this projects source tree. 6 | */ 7 | import * as path from "path"; 8 | import * as os from "os"; 9 | 10 | import { ExTester } from "vscode-extension-tester"; 11 | import { getFirstVsixFileDirectlyBeneathOneDirectory } from "./utils/vsixs"; 12 | 13 | const theVsixFilePath: string = getFirstVsixFileDirectlyBeneathOneDirectory(process.cwd()); 14 | 15 | // v5.10.0 selects the CWD rather than TMPDIR as test-resources folder, so we'll set it explicitly. 16 | const testerResourceFolder:string = path.resolve(os.tmpdir(), "test-resources"); 17 | 18 | async function doE2eTest() { 19 | const extTest = new ExTester(testerResourceFolder); 20 | 21 | // Performs all necessary setup: getting VSCode + ChromeDriver into the test instance 22 | await extTest.downloadCode(); 23 | await extTest.downloadChromeDriver(); 24 | 25 | // Install the extension into the test instance of VS Code 26 | await extTest.installVsix({ vsixFile: path.resolve(process.cwd(), theVsixFilePath), installDependencies: true }); 27 | 28 | // Runs the selected test files in VS Code using mocha and webdriver 29 | await extTest.runTests("out/src/test/**/*.spec.js", { cleanup: true, resources: [] }); 30 | } 31 | 32 | void doE2eTest(); 33 | -------------------------------------------------------------------------------- /scripts/utils/vsixs.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Microsoft Corporation. 3 | * 4 | * Licensed under the MIT license found in the 5 | * LICENSE file in the root of this projects source tree. 6 | */ 7 | 8 | import fs from "fs"; 9 | 10 | export function getFirstVsixFileDirectlyBeneathOneDirectory(targetDirectory: string) { 11 | const dirents: fs.Dirent[] = fs.readdirSync(targetDirectory, { withFileTypes: true }); 12 | 13 | let oneVsixFile: string = ""; 14 | 15 | dirents.some((dirent: fs.Dirent) => { 16 | if (!dirent.isDirectory() && dirent.name.endsWith(".vsix")) { 17 | oneVsixFile = dirent.name; 18 | return true; 19 | } 20 | return false; 21 | }); 22 | 23 | return oneVsixFile; 24 | } 25 | -------------------------------------------------------------------------------- /src/common/DeferredValue.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Microsoft Corporation. 3 | * 4 | * Licensed under the MIT license found in the 5 | * LICENSE file in the root of this projects source tree. 6 | */ 7 | 8 | type ResolveHandler = (value: T | PromiseLike) => void; 9 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 10 | type RejectHandler = (reason: any) => void; 11 | 12 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 13 | export class DeferredValue { 14 | public readonly deferred$: Promise; 15 | private _value: T; 16 | private _resolve: ResolveHandler | undefined = undefined; 17 | private _reject: RejectHandler | undefined = undefined; 18 | private _timeoutHandler: NodeJS.Timeout | undefined = undefined; 19 | 20 | private cleanUpHandlers(): void { 21 | this._resolve = undefined; 22 | this._resolve = undefined; 23 | 24 | if (this._timeoutHandler) { 25 | clearTimeout(this._timeoutHandler); 26 | this._timeoutHandler = undefined; 27 | } 28 | } 29 | 30 | public resolve(value: T): void { 31 | if (this._resolve) { 32 | this._resolve(value); 33 | this.cleanUpHandlers(); 34 | } 35 | } 36 | 37 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 38 | public reject(reason: any): void { 39 | if (this._reject) { 40 | this._reject(reason); 41 | this.cleanUpHandlers(); 42 | } 43 | } 44 | 45 | get value(): T { 46 | return this._value; 47 | } 48 | 49 | set value(val: T) { 50 | if (this.resolve) { 51 | this._value = val; 52 | this.resolve(val); 53 | } 54 | } 55 | 56 | constructor(initValue: T, timeout: number = 0) { 57 | this._value = initValue; 58 | 59 | this.deferred$ = new Promise((resolve: ResolveHandler, reject: RejectHandler) => { 60 | this.resolve = resolve; 61 | this.reject = reject; 62 | }); 63 | 64 | if (timeout) { 65 | this._timeoutHandler = setTimeout(() => { 66 | this._timeoutHandler = undefined; 67 | this.reject(new Error("DeferredValue timeout")); 68 | }); 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/common/Disposable.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Microsoft Corporation. 3 | * 4 | * Licensed under the MIT license found in the 5 | * LICENSE file in the root of this projects source tree. 6 | */ 7 | 8 | export interface IDisposable { 9 | readonly dispose: () => void; 10 | } 11 | 12 | export class Disposable implements IDisposable { 13 | constructor(private readonly onDispose: { (): void }) { 14 | if (!onDispose) { 15 | throw new Error("onDispose cannot be null or empty."); 16 | } else { 17 | this.onDispose = onDispose; 18 | } 19 | } 20 | 21 | public dispose = (): void => { 22 | this.onDispose(); 23 | }; 24 | } 25 | -------------------------------------------------------------------------------- /src/common/DisposableEventEmitter.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Microsoft Corporation. 3 | * 4 | * Licensed under the MIT license found in the 5 | * LICENSE file in the root of this projects source tree. 6 | */ 7 | 8 | import { Disposable, IDisposable } from "../common/Disposable"; 9 | import { EventEmitter } from "events"; 10 | 11 | export type ExtractEventTypes = EvtObjOrEvtProp extends Record 12 | ? ExtractEventTypes 13 | : EvtObjOrEvtProp; 14 | 15 | export class DisposableEventEmitter extends EventEmitter implements IDisposable { 16 | protected readonly internalDisposables: IDisposable[] = []; 17 | 18 | constructor(options?: { 19 | /** 20 | * Enables automatic capturing of promise rejection. 21 | */ 22 | captureRejections?: boolean | undefined; 23 | }) { 24 | super(options); 25 | } 26 | 27 | subscribeOneEvent( 28 | eventName: Event, 29 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 30 | listener: (...args: any[]) => void, 31 | ): IDisposable { 32 | this.on(eventName, listener); 33 | 34 | return new Disposable(() => { 35 | this.off(eventName, listener); 36 | }); 37 | } 38 | 39 | dispose(): void { 40 | while (this.internalDisposables.length) { 41 | const disposable: IDisposable | undefined = this.internalDisposables.pop(); 42 | 43 | if (disposable) { 44 | disposable.dispose(); 45 | } 46 | } 47 | 48 | for (const evtName of this.eventNames()) { 49 | this.removeAllListeners(evtName); 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/common/OperationType.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Microsoft Corporation. 3 | * 4 | * Licensed under the MIT license found in the 5 | * LICENSE file in the root of this projects source tree. 6 | */ 7 | 8 | export type OperationType = BuildOperationType | PQTestOperationType; 9 | 10 | export type BuildOperationType = "msbuild"; 11 | 12 | export type PQTestOperationType = 13 | | "delete-credential" 14 | | "info" 15 | | "compile" 16 | | "list-credential" 17 | | "credential-template" 18 | | "set-credential" 19 | | "refresh-credential" 20 | | "run-test" 21 | | "test-connection" 22 | | string; 23 | -------------------------------------------------------------------------------- /src/common/PowerQueryTask.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Microsoft Corporation. 3 | * 4 | * Licensed under the MIT license found in the 5 | * LICENSE file in the root of this projects source tree. 6 | */ 7 | 8 | import * as vscode from "vscode"; 9 | import { OperationType } from "./OperationType"; 10 | 11 | // Properties that need to be persisted as part of the task definition should be 12 | // included in the taskDefinitions section of package.json. 13 | 14 | export interface PowerQueryTask { 15 | readonly operation: OperationType; 16 | readonly additionalArgs?: string[]; 17 | readonly label?: string; 18 | } 19 | 20 | export interface PQTestTask extends PowerQueryTask { 21 | // TODO: Are we using "Connector" or "Extension" terminology? 22 | readonly pathToConnector?: string; 23 | readonly pathToQueryFile?: string; 24 | readonly stdinStr?: string; 25 | readonly credentialTemplate?: object; 26 | } 27 | 28 | export interface PowerQueryTaskDefinition extends PQTestTask, vscode.TaskDefinition {} 29 | -------------------------------------------------------------------------------- /src/common/ValueEventEmitter.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Microsoft Corporation. 3 | * 4 | * Licensed under the MIT license found in the 5 | * LICENSE file in the root of this projects source tree. 6 | */ 7 | 8 | import { IDisposable } from "./Disposable"; 9 | 10 | export type ExtractValueEventEmitterTypes = EvtObjOrEvtProp extends Record 11 | ? Value extends ValueEventEmitter 12 | ? Key 13 | : ExtractValueEventEmitterTypes 14 | : unknown; 15 | 16 | type ValueUpdateListener = (value: T) => void; 17 | 18 | interface Options { 19 | initOnFirstEmit: boolean; 20 | } 21 | 22 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 23 | export class ValueEventEmitter implements IDisposable { 24 | private _listeners: ValueUpdateListener[] = []; 25 | private resolveInit: ((value: T) => void) | undefined = undefined; 26 | public readonly init: Promise; 27 | 28 | constructor(public value: T, private readonly options: Partial = {}) { 29 | if (this.options.initOnFirstEmit) { 30 | this.init = new Promise((resolve: (value: T) => void) => { 31 | this.resolveInit = resolve; 32 | }); 33 | } else { 34 | this.init = Promise.resolve(value); 35 | } 36 | } 37 | 38 | subscribe(listener: ValueUpdateListener): void { 39 | this._listeners.push(listener); 40 | } 41 | 42 | unsubscribe(listener: ValueUpdateListener): void { 43 | this._listeners = this._listeners.filter((l: ValueUpdateListener) => l !== listener); 44 | } 45 | 46 | emit(value?: T): void { 47 | this.value = value ?? this.value; 48 | 49 | if (this.resolveInit) { 50 | this.resolveInit(this.value); 51 | this.resolveInit = undefined; 52 | } 53 | 54 | this._listeners.forEach((l: ValueUpdateListener) => l(this.value)); 55 | } 56 | 57 | dispose(): void { 58 | this._listeners = []; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/common/WaitNotify.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Microsoft Corporation. 3 | * 4 | * Licensed under the MIT license found in the 5 | * LICENSE file in the root of this projects source tree. 6 | */ 7 | 8 | import * as crypto from "crypto"; 9 | import { EventEmitter } from "events"; 10 | 11 | type ResolveHandler = (value: T | PromiseLike) => void; 12 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 13 | type RejectHandler = (reason: any) => void; 14 | 15 | export class WaitNotify { 16 | private readonly eventEmitter: EventEmitter = new EventEmitter(); 17 | private readonly waitingIds: Array = []; 18 | 19 | wait(timeout: number = 0): Promise { 20 | return new Promise((resolve: ResolveHandler, reject: RejectHandler) => { 21 | const currentId: string = crypto.randomUUID(); 22 | this.waitingIds.push(currentId); 23 | let timeoutHandler: NodeJS.Timeout | undefined = undefined; 24 | 25 | this.eventEmitter.once(currentId, () => { 26 | if (timeoutHandler) { 27 | clearTimeout(timeoutHandler); 28 | } 29 | 30 | resolve(); 31 | }); 32 | 33 | if (timeout) { 34 | timeoutHandler = setTimeout(() => { 35 | const stillWaitingIdIndex: number = this.waitingIds.indexOf(currentId); 36 | 37 | if (stillWaitingIdIndex !== -1) { 38 | this.waitingIds.splice(stillWaitingIdIndex, 1); 39 | reject(new Error("WaitNotify timeout")); 40 | } 41 | }, timeout); 42 | } 43 | }); 44 | } 45 | 46 | notify(): void { 47 | this.notifyAll(); 48 | } 49 | 50 | notifyAll(): void { 51 | for (const waitingId of this.waitingIds) { 52 | this.eventEmitter.emit(waitingId); 53 | } 54 | 55 | this.waitingIds.length = 0; 56 | } 57 | 58 | notifyOne(): void { 59 | const maybeWaitingId: string | undefined = this.waitingIds.shift(); 60 | 61 | if (maybeWaitingId) { 62 | this.eventEmitter.emit(maybeWaitingId); 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/common/errors.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Microsoft Corporation. 3 | * 4 | * Licensed under the MIT license found in the 5 | * LICENSE file in the root of this projects source tree. 6 | */ 7 | 8 | export class BaseError extends Error { 9 | constructor(message: string) { 10 | super(message); 11 | } 12 | 13 | /** 14 | * Capture current stack trace of the caller 15 | */ 16 | captureStackTrace(): string | undefined { 17 | const container: Error = new Error(); 18 | this.stack = container.stack; 19 | 20 | return this.stack; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/common/iterables/FibonacciNumbers.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Microsoft Corporation. 3 | * 4 | * Licensed under the MIT license found in the 5 | * LICENSE file in the root of this projects source tree. 6 | */ 7 | 8 | import { NumberIterator } from "./NumberIterator"; 9 | 10 | export const fibonacciNumbers: () => NumberIterator = () => { 11 | let curVal: number = 1; 12 | let nextVal: number = 1; 13 | 14 | return new NumberIterator(() => { 15 | const value: number = curVal; 16 | curVal = nextVal; 17 | nextVal += value; 18 | 19 | return { 20 | done: false, 21 | value, 22 | }; 23 | }); 24 | }; 25 | -------------------------------------------------------------------------------- /src/common/iterables/NumberIterator.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Microsoft Corporation. 3 | * 4 | * Licensed under the MIT license found in the 5 | * LICENSE file in the root of this projects source tree. 6 | */ 7 | 8 | export const DONE: IteratorReturnResult = { done: true, value: undefined }; 9 | 10 | export type NumberIteratorResult = IteratorResult; 11 | export type IterableNumbers = () => NumberIteratorResult; 12 | export type NumberMapper = (para: number) => number; 13 | export type NumberGenerator = () => NumberIterator; 14 | 15 | const toMsMapper: NumberMapper = (x: number) => Math.floor(x * 1e3); 16 | 17 | export class NumberIterator implements Iterator { 18 | next: IterableNumbers; 19 | 20 | constructor(_next: IterableNumbers) { 21 | this.next = _next; 22 | } 23 | 24 | [Symbol.iterator](): Iterator { 25 | return this; 26 | } 27 | 28 | map(fn: NumberMapper): NumberIterator { 29 | return new NumberIterator(() => { 30 | const cursor: NumberIteratorResult = this.next(); 31 | 32 | if (cursor.done) { 33 | return cursor; 34 | } 35 | 36 | return { 37 | done: false, 38 | value: fn(cursor.value), 39 | }; 40 | }); 41 | } 42 | 43 | addNoise(factor: number = 0.1): NumberIterator { 44 | return this.map((value: number) => value * (1 + (Math.random() - 0.5) * factor)); 45 | } 46 | 47 | toMs(): NumberIterator { 48 | return this.map(toMsMapper); 49 | } 50 | 51 | clamp(min: number, max: number): NumberIterator { 52 | // eslint-disable-next-line no-nested-ternary 53 | return this.map((value: number) => (value < min ? min : value > max ? max : value)); 54 | } 55 | 56 | take(n: number): NumberIterator { 57 | let i: number = 0; 58 | 59 | return new NumberIterator(() => { 60 | if (i < n) { 61 | ++i; 62 | 63 | return this.next(); 64 | } 65 | 66 | return DONE; 67 | }); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/common/nuget/NugetHttpService.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Microsoft Corporation. 3 | * 4 | * Licensed under the MIT license found in the 5 | * LICENSE file in the root of this projects source tree. 6 | */ 7 | 8 | import * as path from "path"; 9 | import * as StreamZip from "node-stream-zip"; 10 | 11 | import { StreamZipAsync } from "node-stream-zip"; 12 | 13 | import { makeOneTmpDir } from "../../utils/osUtils"; 14 | import { NugetLiteHttpService } from "./NugetLiteHttpService"; 15 | import type { PqSdkOutputChannelLight } from "../../features/PqSdkOutputChannel"; 16 | import { removeDirectoryRecursively } from "../../utils/files"; 17 | 18 | export class NugetHttpService extends NugetLiteHttpService { 19 | protected override errorHandler: (error: Error) => void = (error: Error) => { 20 | this.outputChannel?.appendErrorLine(`Failed to request to public nuget endpoints due to ${error}`); 21 | }; 22 | constructor(private readonly outputChannel?: PqSdkOutputChannelLight) { 23 | super(); 24 | } 25 | 26 | public override async downloadAndExtractNugetPackage( 27 | packageName: string, 28 | packageVersion: string, 29 | outputLocation: string, 30 | ): Promise { 31 | const oneTmpDir: string = makeOneTmpDir(); 32 | const targetFilePath: string = path.join(oneTmpDir, `${packageName}.${packageVersion}.zip`); 33 | 34 | this.outputChannel?.appendInfoLine(`Start to download ${packageName} ${packageVersion}`); 35 | 36 | await this.downloadNugetPackage(packageName, packageVersion, targetFilePath).then(() => { 37 | this.outputChannel?.appendInfoLine(`Nuget package download completed.`); 38 | }); 39 | 40 | const zip: StreamZipAsync = new StreamZip.async({ file: targetFilePath }); 41 | await zip.extract(null, outputLocation); 42 | await zip.close(); 43 | 44 | setTimeout(async () => { 45 | try { 46 | await removeDirectoryRecursively(oneTmpDir); 47 | } catch (e: unknown) { 48 | this.outputChannel?.appendErrorLine(`Cannot remove ${oneTmpDir} due to ${e}`); 49 | } 50 | }, 4e3); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/common/promises/cancelable.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Microsoft Corporation. 3 | * 4 | * Licensed under the MIT license found in the 5 | * LICENSE file in the root of this projects source tree. 6 | */ 7 | 8 | /* eslint-disable prefer-rest-params */ 9 | /* eslint-disable @typescript-eslint/no-explicit-any */ 10 | 11 | import { CancelAction, CancellationToken, CancellationTokenSource } from "./CancellationToken"; 12 | import { AnyReturnedFunction } from "./types"; 13 | import { setFunctionNameAndLength } from "./setFunctionNameAndLength"; 14 | 15 | export type InternalPromise = Promise & { cancel: CancelAction }; 16 | 17 | export const cancelable = >>( 18 | target: F, 19 | name: string | undefined = undefined, 20 | ): F extends (arg0: CancellationToken, ...args: unknown[]) => unknown 21 | ? F 22 | : (arg0: CancellationToken, ...args: Parameters) => ReturnType => 23 | setFunctionNameAndLength( 24 | function cancelableWrapper(this: any): ReturnType { 25 | // eslint-disable-next-line @typescript-eslint/no-this-alias,no-invalid-this 26 | const self: unknown = this as any; 27 | const args: unknown[] = [...arguments]; 28 | const length: number = arguments.length; 29 | 30 | if (length !== 0 && CancellationToken.isCancellationToken(arguments[0])) { 31 | // eslint-disable-next-line no-invalid-this 32 | return target.apply(self, args) as ReturnType; 33 | } 34 | 35 | const cancellationTokenSource: CancellationTokenSource = new CancellationTokenSource(); 36 | const newArgs: unknown[] = new Array(length + 1); 37 | newArgs[0] = cancellationTokenSource.token; 38 | 39 | for (let i: number = 0; i < length; ++i) { 40 | newArgs[i + 1] = arguments[i]; 41 | } 42 | 43 | // eslint-disable-next-line no-invalid-this 44 | const promise: InternalPromise = target.apply(this, newArgs) as unknown as InternalPromise; 45 | promise.cancel = cancellationTokenSource.cancel; 46 | 47 | return promise as unknown as ReturnType; 48 | }, 49 | name ?? target.name, 50 | target.length - 1, 51 | ) as unknown as any; 52 | -------------------------------------------------------------------------------- /src/common/promises/defer.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Microsoft Corporation. 3 | * 4 | * Licensed under the MIT license found in the 5 | * LICENSE file in the root of this projects source tree. 6 | */ 7 | 8 | /* eslint-disable @typescript-eslint/no-explicit-any */ 9 | export type DeferredResult = { 10 | promise: Promise; 11 | resolve: (value: T) => void; 12 | reject: (reason: any) => void; 13 | }; 14 | 15 | export function defer(): DeferredResult { 16 | let resolve: (value: T) => void = undefined as any; 17 | let reject: (reason: any) => void = undefined as any; 18 | 19 | const promise: Promise = new Promise((_resolve: (value: T) => void, _reject: (reason: any) => void) => { 20 | resolve = _resolve; 21 | reject = _reject; 22 | }); 23 | 24 | return { 25 | promise, 26 | resolve, 27 | reject, 28 | }; 29 | } 30 | -------------------------------------------------------------------------------- /src/common/promises/doResolve.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Microsoft Corporation. 3 | * 4 | * Licensed under the MIT license found in the 5 | * LICENSE file in the root of this projects source tree. 6 | */ 7 | 8 | import { isPromise } from "./isPromise"; 9 | 10 | export type DidResolvedResult = (value: T) => T extends Promise ? T : Promise; 11 | 12 | export function doResolve(value: T): DidResolvedResult { 13 | return (isPromise(value) ? value : Promise.resolve(value)) as unknown as DidResolvedResult; 14 | } 15 | -------------------------------------------------------------------------------- /src/common/promises/fromEvent.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Microsoft Corporation. 3 | * 4 | * Licensed under the MIT license found in the 5 | * LICENSE file in the root of this projects source tree. 6 | */ 7 | 8 | /* eslint-disable prefer-rest-params */ 9 | /* eslint-disable @typescript-eslint/no-explicit-any */ 10 | 11 | import { AnyFunction } from "./types"; 12 | import { cancelable } from "./cancelable"; 13 | import { CancellationToken } from "./CancellationToken"; 14 | import { noop } from "./noop"; 15 | import { once } from "./once"; 16 | import WebSocket from "ws"; 17 | 18 | export type AnyEventListener = (type: string, callback: AnyFunction) => void; 19 | 20 | export type ExpectedEmitter = 21 | | { 22 | addEventListener?: AnyEventListener; 23 | removeEventListener?: AnyEventListener; 24 | addListener?: AnyEventListener; 25 | removeListener?: AnyEventListener; 26 | on?: AnyEventListener; 27 | off?: AnyEventListener; 28 | } 29 | | WebSocket; 30 | 31 | export function makeEventAdder( 32 | cancellationToken: CancellationToken, 33 | emitter: ExpectedEmitter, 34 | allParametersInArray: boolean = false, 35 | ): AnyEventListener { 36 | const add: AnyFunction | undefined = emitter.addEventListener || emitter.addListener || emitter.on; 37 | 38 | if (add === undefined) { 39 | throw new Error("cannot register event listener"); 40 | } 41 | 42 | const remove: AnyFunction | undefined = emitter.removeEventListener || emitter.removeListener || emitter.off; 43 | 44 | const eventsAndListeners: (AnyEventListener | string)[] = []; 45 | 46 | let clean: AnyFunction = noop; 47 | 48 | if (remove) { 49 | clean = once(() => { 50 | for (let i: number = 0, n: number = eventsAndListeners.length; i < n; i += 2) { 51 | remove.call(emitter, eventsAndListeners[i], eventsAndListeners[i + 1]); 52 | } 53 | }); 54 | 55 | void cancellationToken.promise.then(clean); 56 | } 57 | 58 | return allParametersInArray 59 | ? (eventName: string, cb: AnyFunction): void => { 60 | function listener(): void { 61 | clean(); 62 | const args: unknown[] = Array.prototype.slice.call(arguments); 63 | (args as any).name = eventName; 64 | cb(args); 65 | } 66 | 67 | eventsAndListeners.push(eventName, listener); 68 | add.call(emitter, eventName, listener); 69 | } 70 | : (event: string, cb: AnyFunction): void => { 71 | const listener: AnyEventListener = (arg: unknown) => { 72 | clean(); 73 | cb(arg); 74 | }; 75 | 76 | eventsAndListeners.push(event, listener); 77 | add.call(emitter, event, listener); 78 | }; 79 | } 80 | 81 | export interface FromEventOption { 82 | ignoreErrors?: boolean; 83 | errorEventName?: string; 84 | allParametersInArray?: boolean; 85 | } 86 | 87 | export const fromEvent: (emitter: ExpectedEmitter, event: string, opt?: FromEventOption) => Promise = cancelable( 88 | (cancellationToken: CancellationToken, emitter: ExpectedEmitter, event: string, opt: FromEventOption = {}) => 89 | new Promise((resolve: AnyFunction, reject: AnyFunction) => { 90 | const add: AnyEventListener = makeEventAdder(cancellationToken, emitter, opt.allParametersInArray); 91 | add(event, resolve); 92 | 93 | if (!opt.ignoreErrors) { 94 | const { errorEventName = "error" }: FromEventOption = opt; 95 | 96 | if (errorEventName !== event) { 97 | add(errorEventName, reject); 98 | } 99 | } 100 | }), 101 | ) as unknown as any; 102 | -------------------------------------------------------------------------------- /src/common/promises/fromEvents.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Microsoft Corporation. 3 | * 4 | * Licensed under the MIT license found in the 5 | * LICENSE file in the root of this projects source tree. 6 | */ 7 | 8 | /* eslint-disable prefer-rest-params */ 9 | /* eslint-disable @typescript-eslint/no-explicit-any */ 10 | import { AnyFunction } from "./types"; 11 | import { cancelable } from "./cancelable"; 12 | import { CancellationToken } from "./CancellationToken"; 13 | 14 | import { AnyEventListener, ExpectedEmitter, FromEventOption, makeEventAdder } from "./fromEvent"; 15 | 16 | export const fromEvents: ( 17 | emitter: ExpectedEmitter, 18 | successEvents: string[], 19 | errorEvents?: string[], 20 | opt?: FromEventOption, 21 | ) => Promise = cancelable( 22 | ( 23 | cancellationToken: CancellationToken, 24 | emitter: ExpectedEmitter, 25 | successEvents: string[], 26 | errorEvents: string[] = ["error"], 27 | opt: FromEventOption = { allParametersInArray: true }, 28 | ) => { 29 | if (typeof opt.allParametersInArray !== "boolean") { 30 | opt.allParametersInArray = true; 31 | } 32 | 33 | return new Promise((resolve: AnyFunction, reject: AnyFunction) => { 34 | const add: AnyEventListener = makeEventAdder(cancellationToken, emitter, opt.allParametersInArray); 35 | 36 | for (const oneSuccessEvtName of successEvents) { 37 | add(oneSuccessEvtName, resolve); 38 | } 39 | 40 | if (!opt.ignoreErrors) { 41 | for (const oneErrorEvtName of errorEvents) { 42 | add(oneErrorEvtName, reject); 43 | } 44 | } 45 | }); 46 | }, 47 | ) as unknown as any; 48 | -------------------------------------------------------------------------------- /src/common/promises/isPromise.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Microsoft Corporation. 3 | * 4 | * Licensed under the MIT license found in the 5 | * LICENSE file in the root of this projects source tree. 6 | */ 7 | 8 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 9 | export const isPromise: (value: unknown) => boolean = (value: any) => value != null && typeof value.then === "function"; 10 | -------------------------------------------------------------------------------- /src/common/promises/noop.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Microsoft Corporation. 3 | * 4 | * Licensed under the MIT license found in the 5 | * LICENSE file in the root of this projects source tree. 6 | */ 7 | 8 | export function noop(): void { 9 | // noop 10 | // I am a lovely noop function, do not remove me 11 | } 12 | -------------------------------------------------------------------------------- /src/common/promises/once.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Microsoft Corporation. 3 | * 4 | * Licensed under the MIT license found in the 5 | * LICENSE file in the root of this projects source tree. 6 | */ 7 | 8 | /* eslint-disable prefer-rest-params */ 9 | /* eslint-disable @typescript-eslint/no-explicit-any */ 10 | 11 | import { AnyFunction } from "./types"; 12 | 13 | export const once = (fun: F): F => { 14 | let result: ReturnType | undefined = undefined; 15 | let internalFun: F | undefined = fun; 16 | 17 | return function (this: any) { 18 | // eslint-disable-next-line @typescript-eslint/no-this-alias,no-invalid-this 19 | const self: unknown = this as any; 20 | const args: unknown[] = [...arguments]; 21 | 22 | if (internalFun) { 23 | result = fun.apply(self, args); 24 | internalFun = undefined; 25 | } 26 | 27 | return result; 28 | } as F; 29 | }; 30 | -------------------------------------------------------------------------------- /src/common/promises/promisifyTry.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Microsoft Corporation. 3 | * 4 | * Licensed under the MIT license found in the 5 | * LICENSE file in the root of this projects source tree. 6 | */ 7 | 8 | import { DidResolvedResult, doResolve } from "./doResolve"; 9 | import { ReturnedFunction } from "./types"; 10 | 11 | export type PromisifyTriedResult = DidResolvedResult | Promise; 12 | 13 | export function promisifyTry(fn: ReturnedFunction): PromisifyTriedResult { 14 | try { 15 | return doResolve(fn()); 16 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 17 | } catch (error: any) { 18 | return Promise.reject(error); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/common/promises/setFunctionNameAndLength.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Microsoft Corporation. 3 | * 4 | * Licensed under the MIT license found in the 5 | * LICENSE file in the root of this projects source tree. 6 | */ 7 | 8 | import { AnyFunction } from "./types"; 9 | 10 | export function setFunctionNameAndLength( 11 | fn: T, 12 | name: string, 13 | length: number, 14 | ): T & { length: number; name: string } { 15 | return Object.defineProperties(fn, { 16 | length: { 17 | configurable: true, 18 | value: length > 0 ? length : 0, 19 | }, 20 | name: { 21 | configurable: true, 22 | value: name, 23 | }, 24 | }) as T & { length: number; name: string }; 25 | } 26 | -------------------------------------------------------------------------------- /src/common/promises/symbols.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Microsoft Corporation. 3 | * 4 | * Licensed under the MIT license found in the 5 | * LICENSE file in the root of this projects source tree. 6 | */ 7 | 8 | export const $$toStringTag: symbol = Symbol("toStringTag"); 9 | -------------------------------------------------------------------------------- /src/common/promises/types.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Microsoft Corporation. 3 | * 4 | * Licensed under the MIT license found in the 5 | * LICENSE file in the root of this projects source tree. 6 | */ 7 | 8 | /* eslint-disable @typescript-eslint/no-explicit-any */ 9 | export type AnyFunction = (...args: any[]) => any; 10 | export type AnyReturnedFunction = (...args: any[]) => T; 11 | 12 | export type UnknownFunction = (...args: unknown[]) => unknown; 13 | 14 | export type ReturnedFunction = (...args: unknown[]) => T; 15 | 16 | export interface BasicEvent { 17 | type: string; 18 | } 19 | -------------------------------------------------------------------------------- /src/common/vscode-powerquery.api.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Microsoft Corporation. 3 | * 4 | * Licensed under the MIT license found in the 5 | * LICENSE file in the root of this projects source tree. 6 | */ 7 | 8 | // Must be kept in sync with vscode-powerquery extension API. 9 | // https://github.com/microsoft/vscode-powerquery/blob/master/client/src/vscode-powerquery.api.d.ts 10 | // TODO: Add a way to import API as npm module or sync from original code base. 11 | 12 | // The JSON output of a localized standard library. 13 | export type LibraryJson = ReadonlyArray; 14 | 15 | export interface LibraryExportJson { 16 | readonly name: string; 17 | readonly documentation: LibraryDocumentationJson | null; 18 | readonly functionParameters: ReadonlyArray | null; 19 | readonly completionItemKind: number; 20 | readonly isDataSource: boolean; 21 | readonly type: string; 22 | } 23 | 24 | export interface LibraryDocumentationJson { 25 | readonly description: string | null; 26 | readonly longDescription: string | null; 27 | } 28 | 29 | export interface LibraryFunctionParameterJson { 30 | readonly name: string; 31 | readonly type: string; 32 | readonly isRequired: boolean; 33 | readonly isNullable: boolean; 34 | readonly caption: string | null; 35 | readonly description: string | null; 36 | readonly sampleValues: ReadonlyArray | null; 37 | readonly allowedValues: ReadonlyArray | null; 38 | readonly defaultValue: string | number | null; 39 | readonly fields: ReadonlyArray | null; 40 | readonly enumNames: ReadonlyArray | null; 41 | readonly enumCaptions: ReadonlyArray | ReadonlyArray | null; 42 | } 43 | 44 | export interface LibraryFieldJson { 45 | readonly name: string; 46 | readonly type: string; 47 | readonly isRequired: boolean; 48 | readonly caption: string | null; 49 | readonly description: string | null; 50 | } 51 | 52 | export interface PowerQueryApi { 53 | readonly onModuleLibraryUpdated: (workspaceUriPath: string, library: LibraryJson) => void; 54 | } 55 | -------------------------------------------------------------------------------- /src/debugAdapter.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Microsoft Corporation. 3 | * 4 | * Licensed under the MIT license found in the 5 | * LICENSE file in the root of this projects source tree. 6 | */ 7 | 8 | import * as Net from "net"; 9 | import { Socket } from "net"; 10 | 11 | import { MQueryDebugSession } from "./debugAdaptor/MQueryDebugSession"; 12 | 13 | /* 14 | * debugAdapter.js is the entrypoint of the debug adapter when it runs as a separate process. 15 | */ 16 | 17 | /* 18 | * When the debug adapter is run as an external process, 19 | * normally the helper function DebugSession.run(...) takes care of everything: 20 | * 21 | * MockDebugSession.run(MockDebugSession); 22 | * 23 | * but here the helper is not flexible enough to deal with a debug session constructors with a parameter. 24 | * So for now we copied and modified the helper: 25 | */ 26 | 27 | // first parse command line arguments to see whether the debug adapter should run as a server 28 | let port: number = 0; 29 | const args: string[] = process.argv.slice(2); 30 | 31 | args.forEach(function (val: string, _index: number, _array: string[]) { 32 | const portMatch: RegExpMatchArray | null = /^--server=(\d{4,5})$/.exec(val); 33 | 34 | if (portMatch) { 35 | port = parseInt(portMatch[1], 10); 36 | } 37 | }); 38 | 39 | if (port > 0) { 40 | // start a server that creates a new session for every connection request 41 | console.error(`waiting for debug protocol on port ${port}`); 42 | 43 | Net.createServer((socket: Socket) => { 44 | console.error(">> accepted connection from client"); 45 | 46 | socket.on("end", () => { 47 | console.error(">> client connection closed\n"); 48 | }); 49 | 50 | const session: MQueryDebugSession = new MQueryDebugSession(); 51 | session.setRunAsServer(true); 52 | session.start(socket, socket); 53 | }).listen(port); 54 | } else { 55 | // start a single session that communicates via stdin/stdout 56 | const session: MQueryDebugSession = new MQueryDebugSession(); 57 | 58 | process.on("SIGTERM", () => { 59 | session.shutdown(); 60 | }); 61 | 62 | session.start(process.stdin, process.stdout); 63 | } 64 | -------------------------------------------------------------------------------- /src/features/PqSdkOutputChannel.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Microsoft Corporation. 3 | * 4 | * Licensed under the MIT license found in the 5 | * LICENSE file in the root of this projects source tree. 6 | */ 7 | 8 | import * as vscode from "vscode"; 9 | import { OutputChannel, ViewColumn } from "vscode"; 10 | import { ExtensionConstants } from "../constants/PowerQuerySdkExtension"; 11 | import { extensionI18n } from "../i18n/extension"; 12 | import { IDisposable } from "../common/Disposable"; 13 | 14 | // we can do write to file or log according to a log_level over here 15 | export class PqSdkOutputChannel implements OutputChannel, IDisposable { 16 | _channel: vscode.OutputChannel; 17 | 18 | readonly name: string = ExtensionConstants.OutputChannelName; 19 | 20 | constructor() { 21 | this._channel = vscode.window.createOutputChannel(ExtensionConstants.OutputChannelName); 22 | } 23 | 24 | replace(value: string): void { 25 | this._channel.replace(value); 26 | } 27 | 28 | dispose(): void { 29 | this._channel.dispose(); 30 | } 31 | 32 | append(value: string): void { 33 | this._channel.append(value); 34 | } 35 | 36 | appendLine(value: string): void { 37 | this._channel.appendLine(value); 38 | } 39 | 40 | public appendLineWithTimeStamp(line: string): void { 41 | const now: Date = new Date(); 42 | this.appendLine(`[${now.toLocaleTimeString()}]\t${line}`); 43 | } 44 | 45 | public appendDebugLine(value: string): void { 46 | this.appendLineWithTimeStamp(`[${extensionI18n["PQSdk.common.logLevel.Debug"]}]\t${value}`); 47 | } 48 | 49 | public appendTraceLine(_value: string): void { 50 | // // temporarily turn this off as it is too noisy 51 | // this.appendLineWithTimeStamp(`[${extensionI18n["PQSdk.common.logLevel.Trace"]}]\t${_value}`); 52 | } 53 | 54 | public appendInfoLine(value: string): void { 55 | this.appendLineWithTimeStamp(`[${extensionI18n["PQSdk.common.logLevel.Info"]}]\t${value}`); 56 | } 57 | 58 | public appendErrorLine(value: string): void { 59 | this.appendLineWithTimeStamp(`[${extensionI18n["PQSdk.common.logLevel.Error"]}]\t${value}`); 60 | } 61 | 62 | clear(): void { 63 | this._channel.clear(); 64 | } 65 | 66 | hide(): void { 67 | this._channel.hide(); 68 | } 69 | 70 | show(preserveFocus?: boolean): void; 71 | show(column?: ViewColumn, preserveFocus?: boolean): void; 72 | show(...args: unknown[]): void { 73 | this._channel.show(...(args as Parameters)); 74 | } 75 | } 76 | 77 | export type PqSdkOutputChannelLight = Pick; 78 | -------------------------------------------------------------------------------- /src/i18n/extension.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Microsoft Corporation. 3 | * 4 | * Licensed under the MIT license found in the 5 | * LICENSE file in the root of this projects source tree. 6 | */ 7 | 8 | import * as fs from "fs"; 9 | import * as path from "path"; 10 | 11 | import defaultJson from "../i18n/extension.json"; 12 | import { ExtensionConfigurations } from "../constants/PowerQuerySdkConfiguration"; 13 | import { RecordKeys } from "../utils/types"; 14 | import { replaceAt } from "../utils/strings"; 15 | 16 | export type ExtensionI18nRecord = typeof defaultJson; 17 | export type ExtensionI18nKeys = RecordKeys; 18 | 19 | const currentFolder: string = __dirname; 20 | let currentLocale: string = "en"; 21 | let currentLocaleJson: Partial = defaultJson; 22 | 23 | function createExtensionI18nRecord(): ExtensionI18nRecord { 24 | return new Proxy(defaultJson, { 25 | get(target: ExtensionI18nRecord, property: ExtensionI18nKeys, _receiver: unknown): string { 26 | return currentLocaleJson[property] ?? target[property]; 27 | }, 28 | }); 29 | } 30 | 31 | export function handleLocaleChanged(nextLocale?: string): void { 32 | nextLocale = nextLocale ?? ExtensionConfigurations.pqLocale; 33 | const expectedLocalJsonPath: string = path.join(currentFolder, `extension.${nextLocale}.json`); 34 | 35 | if (currentLocale !== nextLocale && fs.existsSync(expectedLocalJsonPath)) { 36 | const expectedLocaleContent: string = fs.readFileSync(expectedLocalJsonPath, { encoding: "utf-8" }); 37 | 38 | try { 39 | currentLocaleJson = JSON.parse(expectedLocaleContent); 40 | } catch (e) { 41 | // noop 42 | } 43 | } else { 44 | currentLocaleJson = defaultJson; 45 | } 46 | 47 | currentLocale = nextLocale; 48 | } 49 | 50 | handleLocaleChanged(); 51 | 52 | export const extensionI18n: ExtensionI18nRecord = createExtensionI18nRecord(); 53 | 54 | const I18nTemplateItemRegex: RegExp = /{([a-zA-Z0-9_]*)}/gm; 55 | 56 | // we might share doResolveI18nTemplate with another i18n record 57 | function doResolveI18nTemplate>( 58 | i18nRecord: R, 59 | i18nKey: RecordKeys, 60 | argumentsPackage: Record = {}, 61 | ): string { 62 | let result: string = i18nRecord[i18nKey]; 63 | 64 | I18nTemplateItemRegex.lastIndex = 0; 65 | let curMatch: RegExpExecArray | null = I18nTemplateItemRegex.exec(result); 66 | 67 | while (curMatch) { 68 | const theMatchedArgumentName: string = curMatch[1]; 69 | let theReplacedStr: string = ""; 70 | 71 | if (theMatchedArgumentName && argumentsPackage[theMatchedArgumentName]) { 72 | theReplacedStr = argumentsPackage[theMatchedArgumentName] ?? theMatchedArgumentName; 73 | } else if (theMatchedArgumentName) { 74 | theReplacedStr = theMatchedArgumentName; 75 | } 76 | 77 | result = replaceAt(result, curMatch.index, curMatch[0].length, theReplacedStr); 78 | I18nTemplateItemRegex.lastIndex = curMatch.index + theReplacedStr.length; 79 | curMatch = I18nTemplateItemRegex.exec(result); 80 | } 81 | 82 | return result; 83 | } 84 | 85 | export function resolveI18nTemplate( 86 | i18nKey: ExtensionI18nKeys, 87 | argumentsPackage: Record = {}, 88 | ): string { 89 | return doResolveI18nTemplate(extensionI18n, i18nKey, argumentsPackage); 90 | } 91 | -------------------------------------------------------------------------------- /src/test/.eslintrc.js: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line no-undef 2 | module.exports = { 3 | rules: { 4 | "no-await-in-loop": "off", 5 | "@typescript-eslint/typedef": "off", 6 | "@typescript-eslint/no-floating-promises": "off", 7 | "@typescript-eslint/prefer-namespace-keyword": "off", 8 | "@typescript-eslint/no-non-null-assertion": "off", 9 | "@typescript-eslint/no-namespace": "off", 10 | }, 11 | }; 12 | -------------------------------------------------------------------------------- /src/test/TestUtils.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Microsoft Corporation. 3 | * 4 | * Licensed under the MIT license found in the 5 | * LICENSE file in the root of this projects source tree. 6 | */ 7 | 8 | import * as assert from "assert"; 9 | import * as vscode from "vscode"; 10 | 11 | import { extensionId, extensionPublisher } from "./common"; 12 | 13 | const defaultTestPromiseTimeout: number = 5000; 14 | 15 | const sdkExtensionId: string = `${extensionPublisher}.${extensionId}`; 16 | 17 | export function CreateAsyncTestResult(fn: () => void): Promise { 18 | return new Promise((resolve, reject) => { 19 | fn(); 20 | resolve(); 21 | 22 | setTimeout(() => { 23 | reject(new Error(`TestResult timeout exceeded: ${defaultTestPromiseTimeout}ms`)); 24 | }, defaultTestPromiseTimeout); 25 | }); 26 | } 27 | 28 | export async function activateExtension(): Promise { 29 | const extension: vscode.Extension = 30 | vscode.extensions.getExtension(sdkExtensionId) || assert.fail(`Extension not found: ${sdkExtensionId}`); 31 | 32 | if (!extension.isActive) { 33 | await extension.activate(); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/test/common.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Microsoft Corporation. 3 | * 4 | * Licensed under the MIT license found in the 5 | * LICENSE file in the root of this projects source tree. 6 | */ 7 | 8 | import { ExtensionConstants } from "../constants/PowerQuerySdkExtension"; 9 | 10 | import * as os from "os"; 11 | import * as path from "path"; 12 | 13 | import extension18nJson from "../i18n/extension.json"; 14 | import root18nJson from "../../package.nls.json"; 15 | import rootPackageJson from "../../package.json"; 16 | 17 | export const rootI18n = root18nJson; 18 | export const extensionI18n = extension18nJson; 19 | 20 | export const defaultPqCommandCategory = "Power query"; 21 | export const pqSdkOutputChannelName: string = "Power Query SDK"; 22 | 23 | export const MAX_AWAIT_TIME: number = 2 * 60e3; 24 | export const AWAIT_INTERVAL: number = 5e3; 25 | 26 | export const extensionId: string = rootPackageJson.name; 27 | export const extensionVersion: string = rootPackageJson.version; 28 | export const extensionPublisher: string = rootPackageJson.publisher; 29 | export const extensionLanguageServiceId: string = ExtensionConstants.PQLanguageServiceExtensionId; 30 | 31 | export const homeDirectory = os.homedir(); 32 | export const extensionDevelopmentPath = path.resolve(__dirname, "../../../"); 33 | export const extensionInstalledDirectory = path.join( 34 | homeDirectory, 35 | ".vscode", 36 | "extensions", 37 | `${extensionPublisher.toLowerCase()}.${extensionId.toLowerCase()}-${extensionVersion.toLowerCase()}`, 38 | ); 39 | 40 | export const NugetBaseFolderName: string = ExtensionConstants.NugetBaseFolder; 41 | 42 | export const NugetPackagesDirectory: string = path.join(extensionInstalledDirectory, NugetBaseFolderName); 43 | 44 | export const PqTestSubPath: string[] = ExtensionConstants.PqTestSubPath; 45 | export const buildPqSdkSubPath: (version: string) => string[] = (version: string) => 46 | ExtensionConstants.buildNugetPackageSubPath(ExtensionConstants.InternalMsftPqSdkToolsNugetName, version); 47 | 48 | export const PublicMsftPqSdkToolsNugetName: string = ExtensionConstants.PublicMsftPqSdkToolsNugetName; 49 | export const MaximumPqTestNugetVersion: string = ExtensionConstants.MaximumPqTestNugetVersion; 50 | -------------------------------------------------------------------------------- /src/test/commonSuite/PqSdkToolAcquisition.spec.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Microsoft Corporation. 3 | * 4 | * Licensed under the MIT license found in the 5 | * LICENSE file in the root of this projects source tree. 6 | */ 7 | 8 | import { Workbench } from "vscode-extension-tester"; 9 | 10 | import { PqSdkNugetPackages } from "../utils"; 11 | 12 | import { delay } from "../../utils/pids"; 13 | import { MAX_AWAIT_TIME } from "../common"; 14 | 15 | describe("PQSdk Tool Acquisition Test", () => { 16 | it("Seize the latest by default.", async () => { 17 | const workbench = new Workbench(); 18 | 19 | await PqSdkNugetPackages.assertPqSdkToolExisting(); 20 | 21 | await workbench.executeCommand("power query: Update SDK Tool"); 22 | 23 | await delay(3e3); 24 | 25 | await PqSdkNugetPackages.assertPqSdkToolExisting(); 26 | }).timeout(MAX_AWAIT_TIME); 27 | }); 28 | -------------------------------------------------------------------------------- /src/test/connectors/.gitignore: -------------------------------------------------------------------------------- 1 | bin/ -------------------------------------------------------------------------------- /src/test/connectors/AadTestConnector/.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "type": "powerquery", 9 | "request": "launch", 10 | "name": "Evaluate default query file", 11 | "program": "${config:powerquery.sdk.pqtest.queryFile}" 12 | } 13 | ] 14 | } -------------------------------------------------------------------------------- /src/test/connectors/AadTestConnector/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "powerquery.sdk.defaultQueryFile": "${workspaceFolder}\\${workspaceFolderBasename}.query.pq", 3 | "powerquery.sdk.defaultExtension": "${workspaceFolder}\\bin\\AnyCPU\\Debug\\${workspaceFolderBasename}.mez" 4 | } 5 | -------------------------------------------------------------------------------- /src/test/connectors/AadTestConnector/AadTestConnector.pq: -------------------------------------------------------------------------------- 1 | [Version = "1.0.0"] 2 | section AadTestConnector; 3 | 4 | [DataSource.Kind = "AadTestConnector"] 5 | shared AadTestConnector.Contents = () => 6 | let 7 | currentCredential = Extension.CurrentCredential(), 8 | access_token = currentCredential[access_token], 9 | expires = currentCredential[expires], 10 | tokenTtl = GetTokenTtl(access_token) 11 | in 12 | #table( 13 | {"HasRefreshToken", "expires", "tokenTtl"}, 14 | {{currentCredential[refresh_token]? <> null, expires, Duration.ToText(tokenTtl)}} 15 | ); 16 | 17 | DecodeBase64Url = (string as text) as binary => 18 | Binary.FromText( 19 | Text.Replace(Text.Replace(string, "-", "+"), "_", "/") & {"", "", "==", "="}{Number.Mod(Text.Length(string), 4)}, 20 | BinaryEncoding.Base64 21 | ); 22 | 23 | DateTimeFromUnixTimeStamp = (timestamp as number) as datetimezone => 24 | #datetimezone(1970, 1, 1, 0, 0, 0, 0, 0) + #duration(0, 0, 0, timestamp); 25 | 26 | GetTokenTtl = (token as text) as duration => 27 | let 28 | payloadEncoded = Text.Split(token, "."){1}, 29 | payload = Json.Document(Text.FromBinary(DecodeBase64Url(payloadEncoded))), 30 | expires = DateTimeFromUnixTimeStamp(payload[exp]) 31 | in 32 | expires - DateTimeZone.UtcNow(); 33 | 34 | AadTestConnector = [ 35 | Authentication = [ 36 | Aad = [ 37 | AuthorizationUri = "https://login.microsoftonline.com/common/oauth2/authorize", 38 | Resource = "https://powerquery.microsoft.com" 39 | ] 40 | ] 41 | ]; 42 | -------------------------------------------------------------------------------- /src/test/connectors/AadTestConnector/AadTestConnector.query.pq: -------------------------------------------------------------------------------- 1 | AadTestConnector.Contents() 2 | -------------------------------------------------------------------------------- /src/test/connectors/MultipleAuthKindConnector/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "powerquery.sdk.pqtest.queryFile": "${workspaceFolder}\\${workspaceFolderBasename}.query.pq", 3 | "powerquery.sdk.pqtest.extension": "${workspaceFolder}\\bin\\AnyCPU\\Debug\\${workspaceFolderBasename}.mez" 4 | } 5 | -------------------------------------------------------------------------------- /src/test/connectors/MultipleAuthKindConnector/MultipleAuthKindConnector.pq: -------------------------------------------------------------------------------- 1 | [Version = "1.0.0"] 2 | section MultipleAuthKindConnector; 3 | 4 | [DataSource.Kind="MultipleAuthKindConnector"] 5 | shared MultipleAuthKindConnector.Contents = () => Extension.CurrentCredential()[AuthenticationKind]; 6 | 7 | MultipleAuthKindConnector = [ 8 | Authentication = [ 9 | Key = [], 10 | UsernamePassword = [], 11 | Windows = [], 12 | Anonymous = [] 13 | ] 14 | ]; 15 | -------------------------------------------------------------------------------- /src/test/connectors/MultipleAuthKindConnector/MultipleAuthKindConnector.proj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | $(MSBuildProjectDirectory)\bin\ 5 | $(MSBuildProjectDirectory)\obj\ 6 | $(IntermediateOutputPath)MEZ\ 7 | $(OutputPath)$(MsBuildProjectName).mez 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /src/test/connectors/MultipleAuthKindConnector/MultipleAuthKindConnector.query.pq: -------------------------------------------------------------------------------- 1 | let 2 | result = MultipleAuthKindConnector.Contents() 3 | in 4 | result 5 | -------------------------------------------------------------------------------- /src/test/connectors/OAuthConnector/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "powerquery.sdk.pqtest.queryFile": "${workspaceFolder}\\${workspaceFolderBasename}.query.pq", 3 | "powerquery.sdk.pqtest.extension": "${workspaceFolder}\\bin\\AnyCPU\\Debug\\${workspaceFolderBasename}.mez" 4 | } 5 | -------------------------------------------------------------------------------- /src/test/connectors/OAuthConnector/OAuthConnector.query.pq: -------------------------------------------------------------------------------- 1 | // Use this file to write queries to test your data connector 2 | let 3 | result = OAuthConnector.Contents() 4 | in 5 | result 6 | -------------------------------------------------------------------------------- /src/test/runTest.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Microsoft Corporation. 3 | * 4 | * Licensed under the MIT license found in the 5 | * LICENSE file in the root of this projects source tree. 6 | */ 7 | 8 | import * as cp from "child_process"; 9 | import * as path from "path"; 10 | import { downloadAndUnzipVSCode, resolveCliArgsFromVSCodeExecutablePath, runTests } from "@vscode/test-electron"; 11 | 12 | async function main(): Promise { 13 | try { 14 | const extensionDevelopmentPath = path.resolve(__dirname, "../../../"); 15 | const extensionTestsPath = path.resolve(__dirname, "./suite/index"); 16 | const vscodeExecutablePath = await downloadAndUnzipVSCode("stable"); 17 | const [cliPath, ...args] = resolveCliArgsFromVSCodeExecutablePath(vscodeExecutablePath); 18 | 19 | // Install powerquery language service package dependency. 20 | cp.spawnSync(cliPath, [...args, "--install-extension", "powerquery.vscode-powerquery"], { 21 | encoding: "utf-8", 22 | stdio: "inherit", 23 | }); 24 | 25 | // Run the extension test 26 | await runTests({ 27 | vscodeExecutablePath, 28 | extensionDevelopmentPath, 29 | extensionTestsPath, 30 | }); 31 | } catch (err) { 32 | console.error("Failed to run tests"); 33 | process.exit(1); 34 | } 35 | } 36 | 37 | main(); 38 | -------------------------------------------------------------------------------- /src/test/suite/extension.test.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Microsoft Corporation. 3 | * 4 | * Licensed under the MIT license found in the 5 | * LICENSE file in the root of this projects source tree. 6 | */ 7 | 8 | import * as assert from "assert"; 9 | import * as vscode from "vscode"; 10 | 11 | import * as TestUtils from "../TestUtils"; 12 | 13 | import { extensionLanguageServiceId } from "../common"; 14 | 15 | const languageServiceId: string = extensionLanguageServiceId; 16 | 17 | suite("Extension Test Suite", () => { 18 | suiteSetup(TestUtils.activateExtension); 19 | 20 | test("Language service extension", async () => { 21 | const languageServiceExtension = 22 | vscode.extensions.getExtension(languageServiceId) || 23 | assert.fail(`Failed to get language service extension: ${languageServiceId}`); 24 | 25 | if (!languageServiceExtension.isActive) { 26 | await languageServiceExtension.activate(); 27 | } 28 | 29 | await TestUtils.CreateAsyncTestResult(() => { 30 | assert.equal(languageServiceExtension.isActive, true, "Language service extension failed to activate"); 31 | }); 32 | }); 33 | 34 | // TODO: Add onModuleLibraryUpdated test when language service extension returns a result that can be validated. 35 | }); 36 | -------------------------------------------------------------------------------- /src/test/suite/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Microsoft Corporation. 3 | * 4 | * Licensed under the MIT license found in the 5 | * LICENSE file in the root of this projects source tree. 6 | */ 7 | 8 | import * as path from "path"; 9 | 10 | import { glob } from "glob"; 11 | import Mocha from "mocha"; 12 | 13 | export function run(testsRoot: string, cb: (error: unknown, failures?: number) => void): void { 14 | // Create the mocha test 15 | const mocha = new Mocha({ 16 | ui: "tdd", 17 | color: true, 18 | }); 19 | 20 | glob("**/**.test.js", { cwd: testsRoot }) 21 | .then(files => { 22 | // Add files to the test suite 23 | files.forEach(f => mocha.addFile(path.resolve(testsRoot, f))); 24 | 25 | try { 26 | // Run the mocha test 27 | mocha.run(failures => { 28 | cb(null, failures); 29 | }); 30 | } catch (err) { 31 | console.error(err); 32 | cb(err); 33 | } 34 | }) 35 | .catch(err => cb(err)); 36 | } 37 | -------------------------------------------------------------------------------- /src/test/suite/project.test.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Microsoft Corporation. 3 | * 4 | * Licensed under the MIT license found in the 5 | * LICENSE file in the root of this projects source tree. 6 | */ 7 | 8 | import * as assert from "assert"; 9 | import * as vscode from "vscode"; 10 | 11 | import * as TestUtils from "../TestUtils"; 12 | 13 | import { LifecycleCommands } from "../../../src/commands/LifecycleCommands"; 14 | 15 | // import { makeOneTmpDir } from "../../utils/osUtils"; 16 | 17 | suite("New extension project Tests", () => { 18 | suiteSetup(TestUtils.activateExtension); 19 | 20 | // const newExtensionName: string = "FirstConn"; 21 | // const oneTmpDir: string | undefined = makeOneTmpDir(); 22 | 23 | test("New extension project command exists", async () => { 24 | await TestUtils.CreateAsyncTestResult(() => { 25 | assert.ok( 26 | vscode.commands 27 | .getCommands(true) 28 | .then(commands => commands.includes(LifecycleCommands.CreateNewProjectCommand)), 29 | `${LifecycleCommands.CreateNewProjectCommand} command not found`, 30 | ); 31 | }); 32 | }); 33 | }); 34 | -------------------------------------------------------------------------------- /src/test/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "rootDir": "../.." 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /src/test/utils/connectorProjects.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Microsoft Corporation. 3 | * 4 | * Licensed under the MIT license found in the 5 | * LICENSE file in the root of this projects source tree. 6 | */ 7 | 8 | import * as chai from "chai"; 9 | import * as fs from "fs"; 10 | import * as path from "path"; 11 | 12 | import { InputBox, Key, Workbench } from "vscode-extension-tester"; 13 | 14 | import { defaultPqCommandCategory, rootI18n } from "../common"; 15 | import { delay } from "../../utils/pids"; 16 | 17 | const expect = chai.expect; 18 | 19 | export module ConnectorProjects { 20 | export async function createOneNewExtensionProject( 21 | workbench: Workbench, 22 | newExtensionName: string, 23 | targetDirectory: string, 24 | ): Promise { 25 | const createNewProjectCommandTitle = rootI18n["extension.pqtest.CreateNewProjectCommand.title"]; 26 | 27 | await workbench.executeCommand(`${defaultPqCommandCategory}: ${createNewProjectCommandTitle}`); 28 | 29 | // InputBox. 30 | const inputBox = await InputBox.create(); 31 | await inputBox.setText(newExtensionName); 32 | await inputBox.sendKeys(Key.ENTER); 33 | await delay(250); 34 | await inputBox.sendKeys(Key.chord(Key.CONTROL, "A")); 35 | await delay(250); 36 | await inputBox.sendKeys(targetDirectory); 37 | await inputBox.sendKeys(Key.ENTER); 38 | 39 | await delay(7e3); 40 | } 41 | 42 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 43 | export function getVscSettings(newExtensionName: string, targetDirectory: string): any | undefined { 44 | const settingPath = path.join(targetDirectory, newExtensionName, ".vscode", "settings.json"); 45 | 46 | const settingJsonContent = fs.readFileSync(settingPath, { encoding: "utf8" }); 47 | 48 | return JSON.parse(settingJsonContent); 49 | } 50 | 51 | export function assertNewlyCreatedWorkspaceSettingsIntact(newExtensionName: string, targetDirectory: string): void { 52 | const currentSdkSettings = ConnectorProjects.getVscSettings(newExtensionName, targetDirectory); 53 | 54 | expect(currentSdkSettings["powerquery.sdk.defaultQueryFile"]).eq( 55 | "${workspaceFolder}\\${workspaceFolderBasename}.query.pq", 56 | ); 57 | 58 | expect(currentSdkSettings["powerquery.sdk.defaultExtension"]).eq( 59 | "${workspaceFolder}\\bin\\AnyCPU\\Debug\\${workspaceFolderBasename}.mez", 60 | ); 61 | 62 | // assert we got SDK populated correctly when needed 63 | if (currentSdkSettings["powerquery.general.mode"]) { 64 | expect(currentSdkSettings["powerquery.general.mode"]).eq("SDK"); 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/test/utils/editorUtils.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Microsoft Corporation. 3 | * 4 | * Licensed under the MIT license found in the 5 | * LICENSE file in the root of this projects source tree. 6 | */ 7 | 8 | import * as chai from "chai"; 9 | import { EditorView, Workbench } from "vscode-extension-tester"; 10 | import { extensionI18n, rootI18n } from "../common"; 11 | 12 | const expect = chai.expect; 13 | 14 | export module VscEditors { 15 | export async function getCurrentlyOpenedEditorTitles(workbench?: Workbench): Promise { 16 | const editorView = workbench ? workbench.getEditorView() : new EditorView(); 17 | 18 | return await editorView.getOpenEditorTitles(); 19 | } 20 | 21 | export async function assertPqTestResultEditorExisting(workbench?: Workbench): Promise { 22 | const resultViewTitle = extensionI18n["PQTest.result.view.title"]; 23 | 24 | const currentAllEditorTitle = await VscEditors.getCurrentlyOpenedEditorTitles(workbench); 25 | expect(currentAllEditorTitle.indexOf(resultViewTitle)).gt(-1); 26 | } 27 | 28 | export async function evalCurPqOfAnEditor(fileName: string, workbench?: Workbench): Promise { 29 | const runTestBatteryCommandTitle = rootI18n["extension.pqtest.RunTestBatteryCommand.title"]; 30 | 31 | const editorView = workbench ? workbench.getEditorView() : new EditorView(); 32 | const openedEditor = await editorView.openEditor(fileName); 33 | const curCtxMenu = await openedEditor.openContextMenu(); 34 | const evalMenuItem = await curCtxMenu.getItem(runTestBatteryCommandTitle); 35 | 36 | return evalMenuItem?.click(); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/test/utils/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Microsoft Corporation. 3 | * 4 | * Licensed under the MIT license found in the 5 | * LICENSE file in the root of this projects source tree. 6 | */ 7 | 8 | export { ConnectorProjects } from "./connectorProjects"; 9 | export { VscEditors } from "./editorUtils"; 10 | export { VscNotifications } from "./notificationUtils"; 11 | export { VscOutputChannels } from "./outputChannelUtils"; 12 | export { PqSdkNugetPackages } from "./pqSdkNugetPackageUtils"; 13 | export { PqSdkTestOutputChannel } from "./pqSdkTestOutputChannel"; 14 | export { VscSettings } from "./settingUtils"; 15 | export { VscSideBars } from "./sideBarUtils"; 16 | export { VscTitleBar } from "./titleBarUtils"; 17 | -------------------------------------------------------------------------------- /src/test/utils/notificationUtils.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Microsoft Corporation. 3 | * 4 | * Licensed under the MIT license found in the 5 | * LICENSE file in the root of this projects source tree. 6 | */ 7 | 8 | import * as chai from "chai"; 9 | 10 | import { Workbench } from "vscode-extension-tester"; 11 | 12 | const expect = chai.expect; 13 | 14 | export module VscNotifications { 15 | export async function assetNotificationsExisting(workbench?: Workbench): Promise { 16 | const notifications = workbench ? await workbench.getNotifications() : await new Workbench().getNotifications(); 17 | expect(notifications.length).gt(0); 18 | } 19 | 20 | export async function assetNotificationsLength(length: number, workbench?: Workbench): Promise { 21 | const notifications = workbench ? await workbench.getNotifications() : await new Workbench().getNotifications(); 22 | expect(notifications.length).eq(length); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/test/utils/outputChannelUtils.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Microsoft Corporation. 3 | * 4 | * Licensed under the MIT license found in the 5 | * LICENSE file in the root of this projects source tree. 6 | */ 7 | 8 | import * as chai from "chai"; 9 | 10 | import { BottomBarPanel, OutputView } from "vscode-extension-tester"; 11 | 12 | import { delay } from "../../utils/pids"; 13 | import { pqSdkOutputChannelName } from "../common"; 14 | 15 | const expect = chai.expect; 16 | 17 | export module VscOutputChannels { 18 | export async function bringUpPQSdkOutputChannel(): Promise { 19 | const outputView = await new BottomBarPanel().openOutputView(); 20 | 21 | // get names of all available channels 22 | const outputChannelNames = await outputView.getChannelNames(); 23 | expect(outputChannelNames.indexOf(pqSdkOutputChannelName)).gt(-1); 24 | // select a channel from the drop box by name 25 | await outputView.selectChannel(pqSdkOutputChannelName); 26 | 27 | await delay(250); 28 | 29 | return outputView; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/test/utils/pqSdkNugetPackageUtils.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Microsoft Corporation. 3 | * 4 | * Licensed under the MIT license found in the 5 | * LICENSE file in the root of this projects source tree. 6 | */ 7 | 8 | import * as chai from "chai"; 9 | import * as fs from "fs"; 10 | import * as path from "path"; 11 | 12 | import { 13 | AWAIT_INTERVAL, 14 | buildPqSdkSubPath, 15 | MAX_AWAIT_TIME, 16 | MaximumPqTestNugetVersion, 17 | NugetPackagesDirectory, 18 | PqTestSubPath, 19 | PublicMsftPqSdkToolsNugetName, 20 | } from "../common"; 21 | 22 | import { delay } from "../../utils/pids"; 23 | import { NugetLiteHttpService } from "../../common/nuget/NugetLiteHttpService"; 24 | import { NugetVersions } from "../../utils/NugetVersions"; 25 | 26 | const nugetHttpService = new NugetLiteHttpService(); 27 | 28 | const expect = chai.expect; 29 | 30 | const MaximumPqTestNugetVersions: NugetVersions | undefined = MaximumPqTestNugetVersion 31 | ? NugetVersions.createFromFuzzyVersionString(MaximumPqTestNugetVersion) 32 | : undefined; 33 | 34 | export module PqSdkNugetPackages { 35 | let latestPQSdkNugetVersion: NugetVersions | undefined = undefined; 36 | 37 | export function getExpectedPqSdkToolPath(maybeNextVersion?: string): string | undefined { 38 | const pqTestSubPath: string[] = maybeNextVersion ? buildPqSdkSubPath(maybeNextVersion) : PqTestSubPath; 39 | 40 | return path.resolve(NugetPackagesDirectory, ...pqTestSubPath); 41 | } 42 | 43 | export async function getAllPQSdkVersions(): Promise { 44 | const releasedVersions = await nugetHttpService.getSortedPackageReleasedVersions( 45 | PublicMsftPqSdkToolsNugetName, 46 | { 47 | maximumNugetVersion: MaximumPqTestNugetVersions, 48 | }, 49 | ); 50 | 51 | expect(releasedVersions.length).gt(0); 52 | 53 | return releasedVersions; 54 | } 55 | 56 | export async function assertPqSdkToolExisting(): Promise { 57 | let i = 0; 58 | 59 | if (!latestPQSdkNugetVersion) { 60 | const allNugetVersions: NugetVersions[] = await getAllPQSdkVersions(); 61 | 62 | // eslint-disable-next-line require-atomic-updates 63 | latestPQSdkNugetVersion = allNugetVersions[allNugetVersions.length - 1]; 64 | } 65 | 66 | const mayBeExpectedPQSDKToolExePath: string | undefined = getExpectedPqSdkToolPath( 67 | latestPQSdkNugetVersion.toString(), 68 | ); 69 | 70 | expect(typeof mayBeExpectedPQSDKToolExePath).eq("string"); 71 | 72 | const expectedPqTestExePath: string = mayBeExpectedPQSDKToolExePath as string; 73 | 74 | while (i < MAX_AWAIT_TIME / AWAIT_INTERVAL) { 75 | i += 1; 76 | // eslint-disable-next-line no-await-in-loop 77 | await delay(AWAIT_INTERVAL); 78 | 79 | if (fs.existsSync(expectedPqTestExePath)) { 80 | expect(true).true; 81 | 82 | return; 83 | } 84 | } 85 | 86 | expect(false).true; 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/test/utils/pqSdkTestOutputChannel.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Microsoft Corporation. 3 | * 4 | * Licensed under the MIT license found in the 5 | * LICENSE file in the root of this projects source tree. 6 | */ 7 | 8 | import type { PqSdkOutputChannelLight } from "../../features/PqSdkOutputChannel"; 9 | 10 | export class PqSdkTestOutputChannel implements PqSdkOutputChannelLight { 11 | private readonly _lines: string[] = []; 12 | 13 | public appendInfoLine(value: string): void { 14 | this._lines.push(`\t\t[test][info] ${value}`); 15 | } 16 | 17 | public appendErrorLine(value: string): void { 18 | this._lines.push(`\t\t[test][error] ${value}`); 19 | } 20 | 21 | public emit(): void { 22 | for (const line of this._lines) { 23 | console.log(line); 24 | } 25 | 26 | this._lines.length = 0; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/test/utils/settingUtils.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Microsoft Corporation. 3 | * 4 | * Licensed under the MIT license found in the 5 | * LICENSE file in the root of this projects source tree. 6 | */ 7 | 8 | import { Setting, SettingsEditor, Workbench } from "vscode-extension-tester"; 9 | import { delay } from "../../utils/pids"; 10 | 11 | export module VscSettings { 12 | export async function findSettings(title: string, categories: string[], workbench?: Workbench): Promise { 13 | const settingsEditor = workbench ? await workbench.openSettings() : new SettingsEditor(); 14 | 15 | return settingsEditor.findSetting(title, ...categories); 16 | } 17 | 18 | export function findEnableServiceHostSetting(workbench?: Workbench): Promise { 19 | // this string got rephrased out of the contributor path 'powerquery.sdk.features.useServiceHost' 20 | return VscSettings.findSettings("Use Service Host", ["Powerquery", "Sdk", "Features"], workbench); 21 | } 22 | 23 | export async function ensureUseServiceHostDisabled(workbench?: Workbench): Promise { 24 | const enableServiceHostSetting = await VscSettings.findEnableServiceHostSetting(workbench); 25 | const enableServiceHostValue = await enableServiceHostSetting.getValue(); 26 | 27 | if (enableServiceHostValue) { 28 | await enableServiceHostSetting.setValue(false); 29 | // vsc built in commands 30 | workbench?.executeCommand("Developer: Reload Window"); 31 | } 32 | 33 | await delay(750); 34 | } 35 | 36 | export async function ensureUseServiceHostEnabled(workbench?: Workbench): Promise { 37 | const enableServiceHostSetting = await VscSettings.findEnableServiceHostSetting(workbench); 38 | const enableServiceHostValue = await enableServiceHostSetting.getValue(); 39 | 40 | if (!enableServiceHostValue) { 41 | await enableServiceHostSetting.setValue(true); 42 | // vsc built in commands 43 | workbench?.executeCommand("Developer: Reload Window"); 44 | } 45 | 46 | await delay(750); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/test/utils/sideBarUtils.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Microsoft Corporation. 3 | * 4 | * Licensed under the MIT license found in the 5 | * LICENSE file in the root of this projects source tree. 6 | */ 7 | 8 | import { DefaultTreeSection, InputBox, SideBarView, ViewSection, Workbench } from "vscode-extension-tester"; 9 | 10 | import { extensionI18n, rootI18n } from "../common"; 11 | import { delay } from "../../utils/pids"; 12 | 13 | export module VscSideBars { 14 | export async function expandAndShowPqSdkSection(workbench?: Workbench): Promise { 15 | const primaryExplorerName = rootI18n["extension.pqtest.explorer.name"]; 16 | 17 | const sideBarView = workbench ? workbench.getSideBar() : new SideBarView(); 18 | const pqSdkViewSection = await sideBarView.getContent().getSection(primaryExplorerName); 19 | 20 | await pqSdkViewSection.expand(); 21 | 22 | return pqSdkViewSection; 23 | } 24 | 25 | export async function openFileFromDefaultViewSection( 26 | workspaceName: string, 27 | fileNameToOpen: string, 28 | workbench?: Workbench, 29 | ): Promise { 30 | const sideBarView = workbench ? workbench.getSideBar() : new SideBarView(); 31 | 32 | const defaultViewSection = (await sideBarView.getContent().getSection(workspaceName)) as DefaultTreeSection; 33 | 34 | const theFile = await defaultViewSection.findItem(fileNameToOpen); 35 | 36 | return theFile?.click(); 37 | } 38 | 39 | export async function clickClearAllCredentials(pqSdkViewSection: ViewSection): Promise { 40 | const deleteAllCredentialsTitle = extensionI18n["PQSdk.lifecycleTreeView.item.deleteAllCredentials.title"]; 41 | const clearAllCredentialsItem = await pqSdkViewSection.findItem(deleteAllCredentialsTitle); 42 | clearAllCredentialsItem?.click(); 43 | await delay(750); 44 | } 45 | 46 | export async function clickSetCredentialAndPick( 47 | pqSdkViewSection: ViewSection, 48 | candidates: string[], 49 | ): Promise { 50 | const createOneCredentialTitle = extensionI18n["PQSdk.lifecycleTreeView.item.createOneCredential.title"]; 51 | const setCredentialItem = await pqSdkViewSection.findItem(createOneCredentialTitle); 52 | setCredentialItem?.click(); 53 | await delay(750); 54 | 55 | const stepCount = candidates.length; 56 | let step = 0; 57 | 58 | while (step < stepCount) { 59 | // eslint-disable-next-line no-await-in-loop 60 | const currentInputBox = await InputBox.create(); 61 | const picks = await currentInputBox.getQuickPicks(); 62 | let selectedPick = picks[0]; 63 | 64 | for (const onePick of picks) { 65 | const onePickText = await onePick.getText(); 66 | 67 | if (onePickText.indexOf(candidates[step]) > -1) { 68 | selectedPick = onePick; 69 | } 70 | } 71 | 72 | await selectedPick.select(); 73 | 74 | await delay(750); 75 | step++; 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/test/utils/titleBarUtils.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Microsoft Corporation. 3 | * 4 | * Licensed under the MIT license found in the 5 | * LICENSE file in the root of this projects source tree. 6 | */ 7 | 8 | import { TitleBar, Workbench } from "vscode-extension-tester"; 9 | 10 | import { delay } from "../../utils/pids"; 11 | 12 | export module VscTitleBar { 13 | export async function closeFolder(workbench?: Workbench): Promise { 14 | const titleBar = workbench ? workbench.getTitleBar() : new TitleBar(); 15 | 16 | // vsc built in main menu item 17 | const fileItem = await titleBar.getItem("File"); 18 | const fileSubItem = await fileItem?.select(); 19 | // vsc built in file menu item 20 | await fileSubItem?.select("Close Folder"); 21 | 22 | await delay(2e3); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/utils/assertUtils.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Microsoft Corporation. 3 | * 4 | * Licensed under the MIT license found in the 5 | * LICENSE file in the root of this projects source tree. 6 | */ 7 | 8 | import * as assert from "assert"; 9 | 10 | export function assertNotNull(value: T | undefined, errorMessage: string = "Found an unexpected nullable value"): T { 11 | assert.ok(Boolean(value), errorMessage); 12 | 13 | return value as T; 14 | } 15 | -------------------------------------------------------------------------------- /src/utils/debounce.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Microsoft Corporation. 3 | * 4 | * Licensed under the MIT license found in the 5 | * LICENSE file in the root of this projects source tree. 6 | */ 7 | 8 | type TargetFunctionType> = (...args: Args) => void; 9 | 10 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 11 | export function debounce = any[]>( 12 | fn: TargetFunctionType, 13 | ms: number, 14 | ): TargetFunctionType { 15 | let timeout: NodeJS.Timeout | undefined; 16 | 17 | return function (...args: Args) { 18 | const _args: Args = (args?.slice() ?? []) as Args; 19 | timeout && clearTimeout(timeout); 20 | 21 | timeout = setTimeout(function () { 22 | fn(..._args); 23 | }, ms); 24 | }; 25 | } 26 | -------------------------------------------------------------------------------- /src/utils/executables.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Microsoft Corporation. 3 | * 4 | * Licensed under the MIT license found in the 5 | * LICENSE file in the root of this projects source tree. 6 | */ 7 | 8 | import * as fs from "fs"; 9 | import * as path from "path"; 10 | import * as process from "process"; 11 | 12 | export function findExecutable(exeName: string, extArr: string[] = [""]): string | undefined { 13 | const envPath: string = process.env.PATH ?? ""; 14 | const pathDirectories: string[] = envPath.replace(/["]+/g, "").split(path.delimiter).filter(Boolean); 15 | let result: string | undefined = undefined; 16 | 17 | pathDirectories.some((oneDirectory: string) => 18 | extArr.some((oneExt: string) => { 19 | let maybeFsStat: fs.Stats | undefined = undefined; 20 | 21 | try { 22 | const thePath: string = path.join(oneDirectory, exeName + oneExt); 23 | 24 | if (fs.existsSync(thePath)) { 25 | maybeFsStat = fs.statSync(path.join(oneDirectory, exeName + oneExt)); 26 | } 27 | } catch (e) { 28 | // noop 29 | } 30 | 31 | if (maybeFsStat?.isFile()) { 32 | result = path.join(oneDirectory, exeName + oneExt); 33 | 34 | return true; 35 | } 36 | 37 | return false; 38 | }), 39 | ); 40 | 41 | return result; 42 | } 43 | -------------------------------------------------------------------------------- /src/utils/files.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Microsoft Corporation. 3 | * 4 | * Licensed under the MIT license found in the 5 | * LICENSE file in the root of this projects source tree. 6 | */ 7 | 8 | import * as fs from "fs"; 9 | import * as path from "path"; 10 | 11 | const defaultMatcher: (_: string) => boolean = () => true; 12 | 13 | export async function* globFiles( 14 | dir: string, 15 | matcher: (path: string) => boolean = defaultMatcher, 16 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 17 | ): AsyncGenerator { 18 | if (!fs.existsSync(dir)) return; 19 | 20 | const dirents: fs.Dirent[] = await fs.promises.readdir(dir, { withFileTypes: true }); 21 | 22 | for (const dirent of dirents) { 23 | const currentFullPath: string = path.resolve(dir, dirent.name); 24 | 25 | if (dirent.isDirectory()) { 26 | yield* globFiles(currentFullPath, matcher); 27 | } else if (matcher(currentFullPath)) { 28 | yield currentFullPath; 29 | } 30 | } 31 | } 32 | 33 | export function removeDirectoryRecursively(directoryFullName: string): Promise { 34 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 35 | return new Promise((resolve: () => void, reject: (reason?: any) => void) => { 36 | fs.rm(directoryFullName, { recursive: true, force: true }, (err: NodeJS.ErrnoException | null) => { 37 | if (err) { 38 | reject(err); 39 | 40 | return; 41 | } 42 | 43 | resolve(); 44 | }); 45 | }); 46 | } 47 | 48 | export function tryRemoveDirectoryRecursively(directoryFullName: string): Promise { 49 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 50 | return new Promise((resolve: () => void, _reject: (reason?: any) => void) => { 51 | fs.rm(directoryFullName, { recursive: true, force: true }, (_err: NodeJS.ErrnoException | null) => { 52 | resolve(); 53 | }); 54 | }); 55 | } 56 | 57 | // The timestamp indicating the last time the file status was changed. 58 | export function getMtimeOfAFile(fileFullPath: string): Date { 59 | if (fs.existsSync(fileFullPath)) { 60 | const fileStats: fs.Stats = fs.statSync(fileFullPath); 61 | 62 | // ctime: The timestamp indicating the last time the file status was changed. 63 | // mtime: The timestamp indicating the last time this file was modified. 64 | // so we should use mtime over here. 65 | return fileStats.mtime; 66 | } 67 | 68 | return new Date(0); 69 | } 70 | -------------------------------------------------------------------------------- /src/utils/ids.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Microsoft Corporation. 3 | * 4 | * Licensed under the MIT license found in the 5 | * LICENSE file in the root of this projects source tree. 6 | */ 7 | 8 | const characters: string = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; 9 | const charactersLength: number = characters.length; 10 | 11 | export function makeId(length: number): string { 12 | let result: string = ""; 13 | 14 | for (let i: number = 0; i < length; i++) { 15 | result += characters.charAt(Math.floor(Math.random() * charactersLength)); 16 | } 17 | 18 | return result; 19 | } 20 | -------------------------------------------------------------------------------- /src/utils/numbers.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Microsoft Corporation. 3 | * 4 | * Licensed under the MIT license found in the 5 | * LICENSE file in the root of this projects source tree. 6 | */ 7 | 8 | export function convertStringToInteger(str: string): number | undefined { 9 | const oneNum: number = Number.parseInt(str, 10); 10 | 11 | if (Number.isInteger(oneNum)) { 12 | return oneNum; 13 | } 14 | 15 | return undefined; 16 | } 17 | -------------------------------------------------------------------------------- /src/utils/osUtils.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Microsoft Corporation. 3 | * 4 | * Licensed under the MIT license found in the 5 | * LICENSE file in the root of this projects source tree. 6 | */ 7 | 8 | import * as fs from "fs"; 9 | import * as os from "os"; 10 | import * as path from "path"; 11 | 12 | import { makeId } from "./ids"; 13 | 14 | export function makeOneTmpDir(): string { 15 | const tmpDir: string = os.tmpdir(); 16 | const tmpDirBaseName: string = makeId(7); 17 | const targetDir: string = path.join(tmpDir, tmpDirBaseName); 18 | fs.mkdirSync(targetDir); 19 | 20 | return targetDir; 21 | } 22 | -------------------------------------------------------------------------------- /src/utils/pids.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Microsoft Corporation. 3 | * 4 | * Licensed under the MIT license found in the 5 | * LICENSE file in the root of this projects source tree. 6 | */ 7 | 8 | import * as net from "net"; 9 | import * as process from "process"; 10 | 11 | /** 12 | * Will throw an error if target does not exist, and as a special case, a signal 13 | * of 0 can be used to test for the existence of a process 14 | * @param pid: number 15 | */ 16 | export function pidIsRunning(pid: number): boolean { 17 | try { 18 | process.kill(pid, 0); 19 | 20 | return true; 21 | } catch (e) { 22 | return false; 23 | } 24 | } 25 | 26 | export function delay(ms: number): Promise { 27 | return new Promise((resolve: (value: void | PromiseLike) => void) => setTimeout(resolve, ms)); 28 | } 29 | 30 | export function isPortBusy(port: number): Promise { 31 | return new Promise((resolve: (value: boolean | PromiseLike) => void) => { 32 | const theServer: net.Server = net.createServer((socket: net.Socket) => { 33 | // write a space char to activate the socket, do not remove it 34 | socket.write(" "); 35 | socket.pipe(socket); 36 | }); 37 | 38 | theServer.on("error", (_err: Error) => { 39 | resolve(true); 40 | }); 41 | 42 | theServer.on("listening", () => { 43 | theServer.close(); 44 | resolve(false); 45 | }); 46 | 47 | theServer.listen(port, "127.0.0.1"); 48 | }); 49 | } 50 | -------------------------------------------------------------------------------- /src/utils/strings.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Microsoft Corporation. 3 | * 4 | * Licensed under the MIT license found in the 5 | * LICENSE file in the root of this projects source tree. 6 | */ 7 | 8 | export function replaceAt(str: string, index: number, length: number, replacement: string): string { 9 | return str.substring(0, index) + replacement + str.substring(index + length); 10 | } 11 | 12 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 13 | export function stringifyJson(obj: any): string { 14 | return JSON.stringify(obj); 15 | } 16 | 17 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 18 | export function prettifyJson(obj: any): string { 19 | return JSON.stringify(obj, null, 4); 20 | } 21 | 22 | const TemplateSubstitutedValueRegexp: RegExp = /{{([A-Za-z0-9.]*)}}/g; 23 | 24 | function doResolveOneTemplateSubstitutedValue(valueName: string, context: Record): string { 25 | // eslint-disable-next-line security/detect-object-injection 26 | return context[valueName] ?? ""; 27 | } 28 | 29 | // todo, need to think about it....these substituted values could be very annoying...should we support em 30 | // https://code.visualstudio.com/docs/editor/variables-reference 31 | export function resolveTemplateSubstitutedValues(str: string, context: Record): string { 32 | if (str) { 33 | let result: string = str; 34 | let curMatch: RegExpExecArray | null = TemplateSubstitutedValueRegexp.exec(result ?? ""); 35 | 36 | while (curMatch && result) { 37 | result = replaceAt( 38 | result, 39 | curMatch.index, 40 | curMatch[0].length, 41 | doResolveOneTemplateSubstitutedValue(curMatch[1], context), 42 | ); 43 | 44 | curMatch = TemplateSubstitutedValueRegexp.exec(result ?? ""); 45 | } 46 | 47 | return result; 48 | } 49 | 50 | return str; 51 | } 52 | 53 | export function getNonce(): string { 54 | let text: string = ""; 55 | const possible: string = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; 56 | 57 | for (let i: number = 0; i < 32; ++i) { 58 | text += possible.charAt(Math.floor(Math.random() * possible.length)); 59 | } 60 | 61 | return text; 62 | } 63 | -------------------------------------------------------------------------------- /src/utils/types.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Microsoft Corporation. 3 | * 4 | * Licensed under the MIT license found in the 5 | * LICENSE file in the root of this projects source tree. 6 | */ 7 | 8 | export type RecordKeys = R extends Record ? K : unknown; 9 | -------------------------------------------------------------------------------- /templates/PQConn.pq: -------------------------------------------------------------------------------- 1 | // This file contains your Data Connector logic 2 | [Version = "1.0.0"] 3 | section {{ProjectName}}; 4 | 5 | [DataSource.Kind="{{ProjectName}}", Publish="{{ProjectName}}.Publish"] 6 | shared {{ProjectName}}.Contents = (optional message as text) => 7 | let 8 | _message = if (message <> null) then message else "(no message)", 9 | a = "Hello from {{ProjectName}}: " & _message 10 | in 11 | a; 12 | 13 | // Data Source Kind description 14 | {{ProjectName}} = [ 15 | Authentication = [ 16 | // Key = [], 17 | // UsernamePassword = [], 18 | // Windows = [], 19 | Anonymous = [] 20 | ] 21 | ]; 22 | 23 | // Data Source UI publishing description 24 | {{ProjectName}}.Publish = [ 25 | Beta = true, 26 | Category = "Other", 27 | ButtonText = { Extension.LoadString("ButtonTitle"), Extension.LoadString("ButtonHelp") }, 28 | LearnMoreUrl = "https://powerbi.microsoft.com/", 29 | SourceImage = {{ProjectName}}.Icons, 30 | SourceTypeImage = {{ProjectName}}.Icons 31 | ]; 32 | 33 | {{ProjectName}}.Icons = [ 34 | Icon16 = { Extension.Contents("{{ProjectName}}16.png"), Extension.Contents("{{ProjectName}}20.png"), Extension.Contents("{{ProjectName}}24.png"), Extension.Contents("{{ProjectName}}32.png") }, 35 | Icon32 = { Extension.Contents("{{ProjectName}}32.png"), Extension.Contents("{{ProjectName}}40.png"), Extension.Contents("{{ProjectName}}48.png"), Extension.Contents("{{ProjectName}}64.png") } 36 | ]; 37 | -------------------------------------------------------------------------------- /templates/PQConn.proj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | $(MSBuildProjectDirectory)\bin\AnyCPU\Debug\ 5 | $(MSBuildProjectDirectory)\obj\ 6 | $(IntermediateOutputPath)MEZ\ 7 | $(OutputPath)$(MsBuildProjectName).mez 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /templates/PQConn.query.pq: -------------------------------------------------------------------------------- 1 | // Use this file to write queries to test your data connector 2 | let 3 | result = {{ProjectName}}.Contents() 4 | in 5 | result 6 | -------------------------------------------------------------------------------- /templates/PQConn16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/vscode-powerquery-sdk/cc03dafadd188d5442c64efd6f0885d1b2390c8e/templates/PQConn16.png -------------------------------------------------------------------------------- /templates/PQConn20.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/vscode-powerquery-sdk/cc03dafadd188d5442c64efd6f0885d1b2390c8e/templates/PQConn20.png -------------------------------------------------------------------------------- /templates/PQConn24.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/vscode-powerquery-sdk/cc03dafadd188d5442c64efd6f0885d1b2390c8e/templates/PQConn24.png -------------------------------------------------------------------------------- /templates/PQConn32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/vscode-powerquery-sdk/cc03dafadd188d5442c64efd6f0885d1b2390c8e/templates/PQConn32.png -------------------------------------------------------------------------------- /templates/PQConn40.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/vscode-powerquery-sdk/cc03dafadd188d5442c64efd6f0885d1b2390c8e/templates/PQConn40.png -------------------------------------------------------------------------------- /templates/PQConn48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/vscode-powerquery-sdk/cc03dafadd188d5442c64efd6f0885d1b2390c8e/templates/PQConn48.png -------------------------------------------------------------------------------- /templates/PQConn64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/vscode-powerquery-sdk/cc03dafadd188d5442c64efd6f0885d1b2390c8e/templates/PQConn64.png -------------------------------------------------------------------------------- /templates/PQConn80.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/vscode-powerquery-sdk/cc03dafadd188d5442c64efd6f0885d1b2390c8e/templates/PQConn80.png -------------------------------------------------------------------------------- /templates/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "powerquery.sdk.defaultQueryFile": "${workspaceFolder}\\${workspaceFolderBasename}.query.pq", 3 | "powerquery.sdk.defaultExtension": "${workspaceFolder}\\bin\\AnyCPU\\Debug\\${workspaceFolderBasename}.mez" 4 | } 5 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": ".", 4 | "module": "commonjs", // TODO: should this be "Node16"? 5 | "target": "ES2020", 6 | "lib": ["ES2020"], 7 | "sourceMap": true, 8 | "rootDir": ".", 9 | "strict": true, 10 | "allowUnreachableCode": false, 11 | "allowUnusedLabels": false, 12 | "esModuleInterop": true, 13 | "forceConsistentCasingInFileNames": true, 14 | "noEmitOnError": true, 15 | "noFallthroughCasesInSwitch": true, 16 | "noImplicitAny": true, 17 | "noImplicitOverride": true, 18 | "noImplicitReturns": true, 19 | "noUnusedLocals": true, 20 | "resolveJsonModule": true, 21 | "strictPropertyInitialization": true, 22 | "skipLibCheck": true 23 | }, 24 | "exclude": ["node_modules", ".vscode-test", "webviews", "scripts", "test-resources", "unit-tests"] 25 | } 26 | -------------------------------------------------------------------------------- /unit-tests/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": true, 4 | "node": true, 5 | "commonjs": true, 6 | "es6": true 7 | }, 8 | "parser": "@typescript-eslint/parser", 9 | "rules": { 10 | "@typescript-eslint/ban-ts-comment": 0, 11 | "@typescript-eslint/typedef": 0, 12 | "prefer-const": 0 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /unit-tests/common/Utils.spec.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Microsoft Corporation. 3 | * 4 | * Licensed under the MIT license found in the 5 | * LICENSE file in the root of this projects source tree. 6 | */ 7 | 8 | import * as chai from "chai"; 9 | import * as fs from "fs"; 10 | 11 | import { makeOneTmpDir } from "../../src/utils/osUtils"; 12 | import { tryRemoveDirectoryRecursively } from "../../src/utils/files"; 13 | 14 | const expect = chai.expect; 15 | 16 | describe("Utils unit testes", () => { 17 | it("Create a tmp directory", async () => { 18 | const oneTmpDir = makeOneTmpDir(); 19 | 20 | expect(fs.existsSync(oneTmpDir)).true; 21 | 22 | await tryRemoveDirectoryRecursively(oneTmpDir); 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /unit-tests/common/iterables.spec.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Microsoft Corporation. 3 | * 4 | * Licensed under the MIT license found in the 5 | * LICENSE file in the root of this projects source tree. 6 | */ 7 | 8 | import * as chai from "chai"; 9 | 10 | import { NumberGenerator, NumberIterator } from "../../src/common/iterables/NumberIterator"; 11 | import { fibonacciNumbers } from "../../src/common/iterables/FibonacciNumbers"; 12 | 13 | const expect = chai.expect; 14 | 15 | const testIterable: (iterable: NumberGenerator, values: number[]) => void = ( 16 | iterable: () => NumberIterator, 17 | values: number[], 18 | ) => { 19 | let iterator: NumberIterator = iterable(); 20 | 21 | if (!iterable == null) { 22 | throw new TypeError("is not iterable"); 23 | } 24 | 25 | for (const value of values) { 26 | const cursor = iterator.next(); 27 | 28 | if (cursor.done) { 29 | throw new Error("unexpected end of iterable"); 30 | } 31 | 32 | expect(cursor.value).eq(value); 33 | } 34 | }; 35 | 36 | describe("Promises::iterables", () => { 37 | it("fibonacciNumbers generator", () => { 38 | testIterable(fibonacciNumbers, [1, 1, 2, 3, 5, 8, 13, 21, 34, 55]); 39 | }); 40 | }); 41 | -------------------------------------------------------------------------------- /unit-tests/common/nuget/NugetCommandService.spec.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Microsoft Corporation. 3 | * 4 | * Licensed under the MIT license found in the 5 | * LICENSE file in the root of this projects source tree. 6 | */ 7 | 8 | import * as chai from "chai"; 9 | import * as fs from "fs"; 10 | import * as path from "path"; 11 | 12 | import { findExecutable } from "../../../src/utils/executables"; 13 | import { makeOneTmpDir } from "../../../src/utils/osUtils"; 14 | import { NugetCommandService } from "../../../src/common/nuget/NugetCommandService"; 15 | import { TestConstants } from "../testConstants"; 16 | import { tryRemoveDirectoryRecursively } from "../../../src/utils/files"; 17 | 18 | const expect = chai.expect; 19 | 20 | describe(`${TestConstants.ExternalTestFlag} NugetCommandService unit tests`, () => { 21 | const nugetPath = findExecutable("nuget", [".exe", ""]); 22 | 23 | // disable these test cases for the ci due to auth config 24 | if (nugetPath && process.env.CI !== "true") { 25 | let oneTmpDir: string; 26 | let nugetCommandService: NugetCommandService; 27 | 28 | before(() => { 29 | oneTmpDir = makeOneTmpDir(); 30 | nugetCommandService = new NugetCommandService(oneTmpDir); 31 | }); 32 | 33 | it("getPackageReleasedVersions v1", async () => { 34 | const res = await nugetCommandService.getPackageReleasedVersions( 35 | nugetPath, 36 | TestConstants.InternalNugetFeed, 37 | TestConstants.SdkPackageName, 38 | ); 39 | 40 | expect(res.length).gt(1); 41 | }).timeout(3e4); 42 | 43 | it("downloadAndExtractNugetPackage v1", async () => { 44 | const res = await nugetCommandService.getPackageReleasedVersions( 45 | nugetPath, 46 | TestConstants.InternalNugetFeed, 47 | TestConstants.SdkPackageName, 48 | ); 49 | 50 | expect(res.length).gt(1); 51 | const theVersion = res[0]; 52 | const theVersionStr = theVersion.toString(); 53 | 54 | await nugetCommandService.downloadAndExtractNugetPackage( 55 | nugetPath, 56 | TestConstants.InternalNugetFeed, 57 | TestConstants.SdkPackageName, 58 | theVersionStr, 59 | ); 60 | 61 | expect( 62 | fs.existsSync( 63 | path.resolve( 64 | oneTmpDir, 65 | ".nuget", 66 | `${TestConstants.SdkPackageName}.${theVersionStr}`, 67 | `Microsoft.PowerQuery.SdkTools.${theVersionStr}.nupkg`, 68 | ), 69 | ), 70 | ).true; 71 | 72 | expect( 73 | fs.existsSync( 74 | path.resolve( 75 | oneTmpDir, 76 | ".nuget", 77 | `${TestConstants.SdkPackageName}.${theVersionStr}`, 78 | "tools", 79 | "PQTest.exe", 80 | ), 81 | ), 82 | ).true; 83 | 84 | await tryRemoveDirectoryRecursively(oneTmpDir); 85 | }).timeout(9e4); 86 | 87 | after(() => { 88 | setTimeout(() => { 89 | if (oneTmpDir) { 90 | void tryRemoveDirectoryRecursively(oneTmpDir); 91 | oneTmpDir = ""; 92 | } 93 | }, 25); 94 | }); 95 | } 96 | }); 97 | -------------------------------------------------------------------------------- /unit-tests/common/nuget/NugetHttpService.spec.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Microsoft Corporation. 3 | * 4 | * Licensed under the MIT license found in the 5 | * LICENSE file in the root of this projects source tree. 6 | */ 7 | 8 | import * as chai from "chai"; 9 | import * as fs from "fs"; 10 | import * as path from "path"; 11 | 12 | import { makeOneTmpDir } from "../../../src/utils/osUtils"; 13 | import { MAX_AWAIT_TIME } from "../../../src/test/common"; 14 | import { NugetHttpService } from "../../../src/common/nuget/NugetHttpService"; 15 | import { NugetVersions } from "../../../src/utils/NugetVersions"; 16 | import { PqSdkTestOutputChannel } from "../../../src/test/utils/pqSdkTestOutputChannel"; 17 | import { TestConstants } from "../testConstants"; 18 | import { tryRemoveDirectoryRecursively } from "../../../src/utils/files"; 19 | 20 | const expect = chai.expect; 21 | 22 | describe(`${TestConstants.ExternalTestFlag} NugetHttpService unit tests`, () => { 23 | const testOutputChannel = new PqSdkTestOutputChannel(); 24 | const nugetHttpService = new NugetHttpService(testOutputChannel); 25 | 26 | afterEach(() => testOutputChannel.emit()); 27 | 28 | it("getPackageReleasedVersions v1", async () => { 29 | const res = await nugetHttpService.getPackageReleasedVersions(TestConstants.SdkPackageName); 30 | expect(res.versions.length).gt(1); 31 | }).timeout(MAX_AWAIT_TIME); 32 | 33 | it("getSortedPackageReleasedVersions v1", async () => { 34 | const allVersions: NugetVersions[] = await nugetHttpService.getSortedPackageReleasedVersions( 35 | TestConstants.SdkPackageName, 36 | ); 37 | expect(allVersions.length).gt(1); 38 | 39 | const _2_110_Versions: NugetVersions[] = await nugetHttpService.getSortedPackageReleasedVersions( 40 | TestConstants.SdkPackageName, 41 | { 42 | maximumNugetVersion: NugetVersions.createFromFuzzyVersionString("2.110.x"), 43 | }, 44 | ); 45 | 46 | expect(_2_110_Versions.length).gt(1); 47 | expect(_2_110_Versions.length).lt(allVersions.length); 48 | expect(_2_110_Versions[_2_110_Versions.length - 1].minor).eq("110"); 49 | expect(parseInt(allVersions[_2_110_Versions.length].minor, 10)).gt(110); 50 | }).timeout(MAX_AWAIT_TIME); 51 | 52 | it("downloadAndExtractNugetPackage v1", async () => { 53 | const oneTmpDir = makeOneTmpDir(); 54 | const res = await nugetHttpService.getPackageReleasedVersions(TestConstants.SdkPackageName); 55 | expect(res.versions.length).gt(1); 56 | const theVersion = res.versions[res.versions.length - 1]; 57 | await nugetHttpService.downloadAndExtractNugetPackage(TestConstants.SdkPackageName, theVersion, oneTmpDir); 58 | 59 | expect(fs.existsSync(path.resolve(oneTmpDir, "Microsoft.PowerQuery.SdkTools.nuspec"))).true; 60 | expect(fs.existsSync(path.resolve(oneTmpDir, "tools", "PQTest.exe"))).true; 61 | 62 | await tryRemoveDirectoryRecursively(oneTmpDir); 63 | }).timeout(MAX_AWAIT_TIME); 64 | }); 65 | -------------------------------------------------------------------------------- /unit-tests/common/promises/cancelable.spec.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Microsoft Corporation. 3 | * 4 | * Licensed under the MIT license found in the 5 | * LICENSE file in the root of this projects source tree. 6 | */ 7 | 8 | import * as chai from "chai"; 9 | import * as sinon from "sinon"; 10 | 11 | import { cancelable } from "../../../src/common/promises/cancelable"; 12 | import { CancellationToken } from "../../../src/common/promises/CancellationToken"; 13 | import { noop } from "../../../src/common/promises/noop"; 14 | 15 | const expect = chai.expect; 16 | 17 | describe("Promises::cancelable", () => { 18 | it("do not replace the existing cancel token", () => { 19 | const token = new CancellationToken(noop); 20 | const spy = sinon.spy(); 21 | cancelable(spy)(token, "yoo", "ha"); 22 | expect(spy.calledOnceWith(token, "yoo", "ha")).true; 23 | }); 24 | 25 | it("inject an existing cancel token", () => { 26 | const token = new CancellationToken(noop); 27 | const callerStub = sinon.stub().returns(Promise.resolve()); 28 | const spy = sinon.spy(callerStub); 29 | cancelable(spy)(token, "yoo", "ha"); 30 | expect(spy.calledOnce).true; 31 | const spiedFirstCall = spy.getCall(0); 32 | expect(CancellationToken.isCancellationToken(spiedFirstCall.firstArg)).true; 33 | }); 34 | }); 35 | -------------------------------------------------------------------------------- /unit-tests/common/promises/fromEvents.spec.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Microsoft Corporation. 3 | * 4 | * Licensed under the MIT license found in the 5 | * LICENSE file in the root of this projects source tree. 6 | */ 7 | 8 | import * as chai from "chai"; 9 | 10 | import { EventEmitter } from "events"; 11 | 12 | import { fromEvents } from "../../../src/common/promises/fromEvents"; 13 | 14 | const expect = chai.expect; 15 | 16 | const emitter: EventEmitter = new EventEmitter(); 17 | 18 | describe("Promises::fromEvents", () => { 19 | it("Nodejs EventEmitter: success event", () => { 20 | const promise = fromEvents(emitter, ["yoo", "ha"]); 21 | emitter.emit("yoo", "arg1", "arg2"); 22 | 23 | return promise.then(value => { 24 | expect(value.name).eq("yoo"); 25 | expect(value).eql(["arg1", "arg2"]); 26 | }); 27 | }); 28 | 29 | it("Nodejs EventEmitter: error event", () => { 30 | const promise = fromEvents(emitter, ["yoo", "ha"], ["oops"]); 31 | emitter.emit("oops", "errArg1", "errArg2"); 32 | 33 | return promise.catch(value => { 34 | expect(value.name).eq("oops"); 35 | expect(value).eql(["errArg1", "errArg2"]); 36 | }); 37 | }); 38 | }); 39 | -------------------------------------------------------------------------------- /unit-tests/common/testConstants.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Microsoft Corporation. 3 | * 4 | * Licensed under the MIT license found in the 5 | * LICENSE file in the root of this projects source tree. 6 | */ 7 | 8 | export enum TestConstants { 9 | ExternalTestFlag = "[External]", 10 | SdkPackageName = "Microsoft.PowerQuery.SdkTools", 11 | InternalNugetFeed = "https://powerbi.pkgs.visualstudio.com/_packaging/PowerBiComponents/nuget/v3/index.json", 12 | } 13 | -------------------------------------------------------------------------------- /unit-tests/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "baseUrl": "..", 5 | "rootDir": ".." 6 | }, 7 | "exclude": ["node_modules"] 8 | } 9 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | //@ts-check 2 | /** @typedef import('webpack').WebpackConfig **/ 3 | 4 | "use strict"; 5 | 6 | const path = require("path"); 7 | const TsconfigPathsPlugin = require("tsconfig-paths-webpack-plugin"); 8 | 9 | module.exports = function (env, argv) { 10 | /** @type WebpackConfig */ 11 | return { 12 | target: "node", // vscode extensions run in a Node.js-context 📖 -> https://webpack.js.org/configuration/node/ 13 | mode: argv.mode === "production" ? "production" : "development", // this leaves the source code as close as possible to the original (when packaging we set this to 'production') 14 | devtool: argv.mode === "production" ? undefined : "source-map", 15 | 16 | entry: { 17 | debugAdapter: "./src/debugAdapter.ts", 18 | extension: "./src/extension.ts", 19 | }, // the entry point of this extension, 📖 -> https://webpack.js.org/configuration/entry-context/ 20 | output: { 21 | // the bundle is stored in the 'dist' folder (check package.json), 📖 -> https://webpack.js.org/configuration/output/ 22 | path: path.resolve(__dirname, "dist"), 23 | filename: "[name].js", 24 | libraryTarget: "commonjs2", 25 | }, 26 | externals: { 27 | vscode: "commonjs vscode", // the vscode-module is created on-the-fly and must be excluded. Add other modules that cannot be webpack'ed, 📖 -> https://webpack.js.org/configuration/externals/ 28 | // modules added here also need to be added in the .vscodeignore file 29 | }, 30 | resolve: { 31 | // support reading TypeScript and JavaScript files, 📖 -> https://github.com/TypeStrong/ts-loader 32 | extensions: [".ts", ".js"], 33 | plugins: [ 34 | new TsconfigPathsPlugin({ 35 | configFile: path.join(__dirname, "tsconfig.json"), 36 | }), 37 | ], 38 | }, 39 | module: { 40 | rules: [ 41 | { 42 | test: /\.ts$/, 43 | exclude: [/node_modules/, path.resolve(__dirname, "src/test/")], 44 | use: [ 45 | { 46 | loader: "ts-loader", 47 | }, 48 | ], 49 | }, 50 | ], 51 | }, 52 | infrastructureLogging: { 53 | level: "log", // enables logging required for problem matchers 54 | }, 55 | }; 56 | }; 57 | -------------------------------------------------------------------------------- /webviews/pq-test-result-view/.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | config 3 | public 4 | **/*.js 5 | -------------------------------------------------------------------------------- /webviews/pq-test-result-view/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "react-app", 4 | "react-app/jest" 5 | ], 6 | "rules": { 7 | "no-console": "warn" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /webviews/pq-test-result-view/config/paths.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Microsoft Corporation. 3 | * 4 | * Licensed under the MIT license found in the 5 | * LICENSE file in the root of this projects source tree. 6 | */ 7 | 8 | "use strict"; 9 | 10 | const path = require("path"); 11 | const fs = require("fs"); 12 | 13 | // Make sure any symlinks in the project folder are resolved: 14 | // https://github.com/facebook/create-react-app/issues/637 15 | const appDirectory = fs.realpathSync(process.cwd()); 16 | const resolveApp = relativePath => path.resolve(appDirectory, relativePath); 17 | 18 | const moduleFileExtensions = [".js", ".jsx", ".ts", ".tsx", ".css", ".scss"]; 19 | 20 | // Resolve file paths in the same order as webpack 21 | const resolveModule = (resolveFn, filePath) => { 22 | const extension = moduleFileExtensions.find(extension => fs.existsSync(resolveFn(`${filePath}.${extension}`))); 23 | 24 | if (extension) { 25 | return resolveFn(`${filePath}.${extension}`); 26 | } 27 | 28 | return resolveFn(`${filePath}.js`); 29 | }; 30 | 31 | // config after eject: we're in ./config/ 32 | module.exports = { 33 | dotenv: resolveApp(".env"), 34 | appPath: resolveApp("."), 35 | appBuild: resolveApp("../../webviewDist/pq-test-result-view"), 36 | appPublic: resolveApp("public"), 37 | appHtml: resolveApp("public/index.html"), 38 | appIndexJs: resolveModule(resolveApp, "src/index"), 39 | appPackageJson: resolveApp("package.json"), 40 | appSrc: resolveApp("src"), 41 | appTsConfig: resolveApp("tsconfig.json"), 42 | appJsConfig: resolveApp("jsconfig.json"), 43 | yarnLockFile: resolveApp("yarn.lock"), 44 | testsSetup: resolveModule(resolveApp, "src/setupTests"), 45 | proxySetup: resolveApp("src/setupProxy.js"), 46 | appNodeModules: resolveApp("node_modules"), 47 | swSrc: resolveModule(resolveApp, "src/service-worker"), 48 | }; 49 | 50 | module.exports.moduleFileExtensions = moduleFileExtensions; 51 | -------------------------------------------------------------------------------- /webviews/pq-test-result-view/config/webpack.common.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Microsoft Corporation. 3 | * 4 | * Licensed under the MIT license found in the 5 | * LICENSE file in the root of this projects source tree. 6 | */ 7 | 8 | const webpack = require("webpack"); 9 | const HtmlWebpackPlugin = require("html-webpack-plugin"); 10 | const ESLintPlugin = require("eslint-webpack-plugin"); 11 | const ForkTsCheckerWebpackPlugin = require("fork-ts-checker-webpack-plugin"); 12 | const paths = require("./paths"); 13 | const getClientEnvironment = require("./env"); 14 | const env = getClientEnvironment(); 15 | 16 | module.exports = { 17 | entry: "./src/index.ts", 18 | resolve: { 19 | extensions: paths.moduleFileExtensions, 20 | alias: { 21 | vscode: false, 22 | "react/jsx-dev-runtime": "react/jsx-dev-runtime.js", 23 | "react/jsx-runtime": "react/jsx-runtime.js", 24 | }, 25 | }, 26 | performance: { 27 | maxAssetSize: 512000, 28 | }, 29 | module: { 30 | rules: [ 31 | { 32 | test: /\.(png|jpe?g|gif|woff|svg|eot|ttf)$/i, 33 | use: [{ loader: "file-loader" }], 34 | }, 35 | { 36 | test: /\.scss|\.css$/, 37 | use: ["style-loader", "css-loader", "sass-loader"], 38 | }, 39 | { 40 | test: /\.tsx?$/, 41 | exclude: /node_modules/, 42 | use: { 43 | loader: "babel-loader", 44 | options: { 45 | presets: ["@babel/preset-env", "@babel/preset-react", "@babel/preset-typescript"], 46 | plugins: ["@babel/plugin-transform-runtime", "@babel/plugin-proposal-class-properties"], 47 | }, 48 | }, 49 | }, 50 | ], 51 | }, 52 | plugins: [ 53 | new webpack.DefinePlugin(env.stringified), 54 | new HtmlWebpackPlugin({ 55 | template: "./public/index.html", 56 | }), 57 | new ESLintPlugin({ 58 | // Plugin options 59 | extensions: ["js", "mjs", "jsx", "ts", "tsx"], 60 | eslintPath: require.resolve("eslint"), 61 | context: paths.appSrc, 62 | // ESLint class options 63 | cwd: paths.appPath, 64 | resolvePluginsRelativeTo: __dirname, 65 | baseConfig: { 66 | extends: [require.resolve("eslint-config-react-app/base")], 67 | }, 68 | }), 69 | new ForkTsCheckerWebpackPlugin(), 70 | ], 71 | }; 72 | -------------------------------------------------------------------------------- /webviews/pq-test-result-view/config/webpack.dev.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Microsoft Corporation. 3 | * 4 | * Licensed under the MIT license found in the 5 | * LICENSE file in the root of this projects source tree. 6 | */ 7 | 8 | "use strict"; 9 | 10 | // Do this as the first thing so that any code reading it knows the right env. 11 | process.env.BABEL_ENV = "development"; 12 | process.env.NODE_ENV = "development"; 13 | 14 | const { merge } = require("webpack-merge"); 15 | 16 | const commonConfig = require("./webpack.common"); 17 | // const packageJson = require("../package.json"); 18 | 19 | const devConfig = { 20 | mode: "development", 21 | devtool: "eval-source-map", 22 | output: { 23 | publicPath: "http://localhost:3001/", 24 | }, 25 | devServer: { 26 | port: 3001, 27 | // client: { 28 | // webSocketURL: { 29 | // hostname: "127.0.0.1", 30 | // pathname: "/ws", 31 | // // password: 'dev-server', 32 | // port: 3001, 33 | // protocol: "ws", 34 | // // username: 'webpack', 35 | // }, 36 | // }, 37 | client: false, 38 | webSocketServer: false, 39 | historyApiFallback: { 40 | index: "index.html", 41 | }, 42 | }, 43 | plugins: [], 44 | }; 45 | 46 | module.exports = merge(commonConfig, devConfig); 47 | -------------------------------------------------------------------------------- /webviews/pq-test-result-view/config/webpack.prod.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Microsoft Corporation. 3 | * 4 | * Licensed under the MIT license found in the 5 | * LICENSE file in the root of this projects source tree. 6 | */ 7 | 8 | "use strict"; 9 | 10 | const path = require("path"); 11 | // Do this as the first thing so that any code reading it knows the right env. 12 | process.env.BABEL_ENV = "production"; 13 | process.env.NODE_ENV = "production"; 14 | 15 | const { merge } = require("webpack-merge"); 16 | const CopyPlugin = require("copy-webpack-plugin"); 17 | const paths = require("./paths"); 18 | 19 | const commonConfig = require("./webpack.common"); 20 | // const packageJson = require("../package.json"); 21 | 22 | const prodConfig = { 23 | mode: "production", 24 | output: { 25 | path: paths.appBuild, 26 | // filename: "[name].[contenthash:8].js", 27 | // since we host it from the 28 | filename: "[name].js", 29 | }, 30 | plugins: [ 31 | new CopyPlugin({ 32 | patterns: [{ from: path.resolve(paths.appPublic, "i18n"), to: path.resolve(paths.appBuild, "i18n") }], 33 | }), 34 | ], 35 | }; 36 | 37 | module.exports = merge(commonConfig, prodConfig); 38 | -------------------------------------------------------------------------------- /webviews/pq-test-result-view/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "pq-test-result-view", 3 | "private": true, 4 | "description": "The web view for pqtest results", 5 | "scripts": { 6 | "clean": "rimraf ../../webviewDist/pq-test-result-view", 7 | "start": "webpack serve --config config/webpack.dev.js", 8 | "build": "npm run clean && webpack --config config/webpack.prod.js", 9 | "lint": "eslint src --ext ts", 10 | "audit": "npm audit" 11 | }, 12 | "engines": { 13 | "node": ">=20" 14 | }, 15 | "dependencies": { 16 | "@fluentui/react": "^8.115.7", 17 | "classnames": "^2.5.1", 18 | "react": "17.0.2", 19 | "react-dom": "17.0.2" 20 | }, 21 | "devDependencies": { 22 | "@babel/core": "^7.24.0", 23 | "@babel/plugin-proposal-class-properties": "^7.18.6", 24 | "@babel/plugin-transform-runtime": "^7.24.0", 25 | "@babel/preset-env": "^7.24.0", 26 | "@babel/preset-react": "^7.23.3", 27 | "@babel/preset-typescript": "^7.23.3", 28 | "@types/classnames": "^2.3.1", 29 | "@types/react": "^17.0.76", 30 | "@types/react-dom": "^17.0.25", 31 | "@types/vscode-webview": "^1.57.5", 32 | "@typescript-eslint/eslint-plugin": "^5.62.0", 33 | "@typescript-eslint/parser": "^5.62.0", 34 | "babel-eslint": "^10.1.0", 35 | "babel-loader": "^8.3.0", 36 | "clean-webpack-plugin": "^4.0.0", 37 | "copy-webpack-plugin": "^11.0.0", 38 | "css-loader": "^6.10.0", 39 | "dotenv": "^8.6.0", 40 | "dotenv-expand": "^8.0.3", 41 | "eslint": "^8.57.0", 42 | "eslint-config-react-app": "^7.0.1", 43 | "eslint-plugin-flowtype": "^8.0.3", 44 | "eslint-plugin-import": "^2.29.1", 45 | "eslint-plugin-jest": "^26.9.0", 46 | "eslint-plugin-jsx-a11y": "^6.8.0", 47 | "eslint-plugin-license-header": "^0.4.0", 48 | "eslint-plugin-react": "^7.34.0", 49 | "eslint-plugin-react-hooks": "^4.6.0", 50 | "eslint-plugin-testing-library": "^5.11.1", 51 | "eslint-webpack-plugin": "^3.2.0", 52 | "file-loader": "^6.2.0", 53 | "fork-ts-checker-webpack-plugin": "^7.3.0", 54 | "html-webpack-plugin": "^5.6.0", 55 | "node-sass": "^9.0.0", 56 | "rimraf": "^3.0.2", 57 | "sass-loader": "^14.1.1", 58 | "style-loader": "^3.3.4", 59 | "typescript": "^4.9.5", 60 | "webpack": "^5.90.3", 61 | "webpack-cli": "^4.10.0", 62 | "webpack-dev-server": "^4.15.1", 63 | "webpack-merge": "^5.10.0" 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /webviews/pq-test-result-view/public/i18n/pq-test-result-view.json: -------------------------------------------------------------------------------- 1 | { 2 | "common.dismiss.label" : "Close", 3 | "common.error.label" : "Error", 4 | "testBatteryResView.Table.Output.Title" : "Output", 5 | "testBatteryResView.Table.Output.Summary" : "Summary", 6 | "testBatteryResView.Table.Output.DataSource" : "DataSource" 7 | } 8 | -------------------------------------------------------------------------------- /webviews/pq-test-result-view/src/App.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Microsoft Corporation. 3 | * 4 | * Licensed under the MIT license found in the 5 | * LICENSE file in the root of this projects source tree. 6 | */ 7 | 8 | *{ 9 | box-sizing: border-box; 10 | font-family: var(--vscode-editor-font-family); 11 | font-size: var(--vscode-editor-font-size); 12 | font-weight: var(--vscode-editor-font-weight); 13 | color: var(--vscode-editor-foreground); 14 | } 15 | 16 | body{ 17 | width: calc( 100vw - 40px ); 18 | height: 100vh; 19 | } 20 | 21 | 22 | .entry-container{ 23 | overflow: auto; 24 | min-width: calc( 100vw - 40px ); 25 | min-height: 100vh; 26 | } 27 | 28 | 29 | //fluent message bar 30 | .ms-MessageBar.ms-MessageBar--error{ 31 | background-color: var(--vscode-notificationsErrorIcon-foreground); 32 | } -------------------------------------------------------------------------------- /webviews/pq-test-result-view/src/App.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Microsoft Corporation. 3 | * 4 | * Licensed under the MIT license found in the 5 | * LICENSE file in the root of this projects source tree. 6 | */ 7 | 8 | import React from "react"; 9 | import { ThemeProvider } from "@fluentui/react/lib/Theme"; 10 | 11 | import { useVSCodeContextProps, VSCodeContextProvider } from "./contexts/VscodeContexts"; 12 | import { TestBatteryResultView } from "./views/TestBatteryResultView"; 13 | 14 | import "./App.scss"; 15 | 16 | const Entry: React.FC<{}> = React.memo(() => { 17 | const { latestPqTestResult, fluentTheme } = useVSCodeContextProps(); 18 | 19 | return ( 20 |
21 | 22 | {!!latestPqTestResult && Array.isArray(latestPqTestResult) && latestPqTestResult.length ? ( 23 | 24 | ) : null} 25 | 26 |
27 | ); 28 | }); 29 | 30 | const App: React.FC<{}> = React.memo(() => { 31 | return ( 32 | 33 | 34 | 35 | ); 36 | }); 37 | 38 | export default App; 39 | -------------------------------------------------------------------------------- /webviews/pq-test-result-view/src/bootstrap.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Microsoft Corporation. 3 | * 4 | * Licensed under the MIT license found in the 5 | * LICENSE file in the root of this projects source tree. 6 | */ 7 | 8 | import React from "react"; 9 | import ReactDom from "react-dom"; 10 | 11 | import { initializeIcons } from "@fluentui/react/lib/Icons"; 12 | import App from "./App"; 13 | 14 | initializeIcons(); 15 | 16 | window.addEventListener( 17 | "contextmenu", 18 | e => { 19 | e.stopImmediatePropagation(); 20 | }, 21 | true, 22 | ); 23 | 24 | ReactDom.render(, document.querySelector("#root")); 25 | -------------------------------------------------------------------------------- /webviews/pq-test-result-view/src/components/MessageBoxComp.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Microsoft Corporation. 3 | * 4 | * Licensed under the MIT license found in the 5 | * LICENSE file in the root of this projects source tree. 6 | */ 7 | 8 | import React, { useState, useCallback } from "react"; 9 | import { MessageBar, IMessageBarProps } from "@fluentui/react/lib/MessageBar"; 10 | import { useI18n } from "../i18n"; 11 | 12 | export const CloseableMessageBoxComp: React.FC = (props: IMessageBarProps) => { 13 | const { children } = props; 14 | 15 | const OutputLabel = useI18n("common.dismiss.label"); 16 | 17 | const [shown, setShown] = useState(true); 18 | const handleDismiss = useCallback(() => { 19 | setShown(false); 20 | }, []); 21 | 22 | return shown ? ( 23 | 24 | {children} 25 | 26 | ) : null; 27 | }; 28 | -------------------------------------------------------------------------------- /webviews/pq-test-result-view/src/components/TestBatteryGeneralGrid.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Microsoft Corporation. 3 | * 4 | * Licensed under the MIT license found in the 5 | * LICENSE file in the root of this projects source tree. 6 | */ 7 | 8 | import React from "react"; 9 | import { DetailsList, DetailsListLayoutMode, SelectionMode, ConstrainMode } from "@fluentui/react/lib/DetailsList"; 10 | import { mergeStyles } from "@fluentui/react/lib/Styling"; 11 | 12 | interface TestBatteryOutputGridProps { 13 | items: any[]; 14 | } 15 | 16 | const testBatteryGeneralGrid = mergeStyles({ 17 | height: "calc( 100vh - 44px)", 18 | overflow: "auto", 19 | }); 20 | 21 | export const TestBatteryGeneralGrid: React.FC = React.memo(props => { 22 | const { items } = props; 23 | 24 | return ( 25 |
26 | 34 |
35 | ); 36 | }); 37 | -------------------------------------------------------------------------------- /webviews/pq-test-result-view/src/i18n.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Microsoft Corporation. 3 | * 4 | * Licensed under the MIT license found in the 5 | * LICENSE file in the root of this projects source tree. 6 | */ 7 | 8 | import { useMemo } from "react"; 9 | import type I18nRecordType from "../public/i18n/pq-test-result-view.json"; 10 | import { RecordKeys } from "./utils/types"; 11 | import { useVSCodeContextProps } from "./contexts/VscodeContexts"; 12 | 13 | export type I18nRecord = typeof I18nRecordType; 14 | export type I18nKeys = RecordKeys; 15 | 16 | let defaultLocaleJson: I18nRecord = {} as I18nRecord; 17 | let currentLocaleJson: Partial = {}; 18 | 19 | function createExtensionI18nRecord(): I18nRecord { 20 | return new Proxy(defaultLocaleJson, { 21 | get(target: I18nRecord, property: I18nKeys, _receiver: unknown): string { 22 | return currentLocaleJson[property] ?? target[property]; 23 | }, 24 | }); 25 | } 26 | 27 | let i18nRecord: I18nRecord | undefined = undefined; 28 | let activateDefaultLocaleJsonDeferred: Promise> | undefined = undefined; 29 | 30 | const defaultLocaleJsonUrl = "i18n/pq-test-result-view.json"; 31 | 32 | const noCacheHeaders = new Headers(); 33 | noCacheHeaders.append("pragma", "no-cache"); 34 | noCacheHeaders.append("cache-control", "no-cache"); 35 | const noCacheGetInit = { 36 | method: "GET", 37 | headers: noCacheHeaders, 38 | }; 39 | 40 | export function handleLocaleChange(nextLocal = "en"): Promise { 41 | let targetUrl = defaultLocaleJsonUrl; 42 | if (!activateDefaultLocaleJsonDeferred) { 43 | activateDefaultLocaleJsonDeferred = (async () => { 44 | const res = await fetch(defaultLocaleJsonUrl, noCacheGetInit); 45 | if (res.ok) { 46 | defaultLocaleJson = (await res.json()) as I18nRecord; 47 | i18nRecord = createExtensionI18nRecord(); 48 | } 49 | return defaultLocaleJson; 50 | })(); 51 | } 52 | if (nextLocal.toLowerCase() === "en") { 53 | return activateDefaultLocaleJsonDeferred; 54 | } else { 55 | targetUrl = `i18n/pq-test-result-view.${nextLocal.toLowerCase()}.json`; 56 | } 57 | 58 | const activateCurrentLocaleJsonDeferred = (async () => { 59 | const res = await fetch(targetUrl, noCacheGetInit); 60 | if (res.ok) { 61 | currentLocaleJson = (await res.json()) as Partial; 62 | } 63 | return currentLocaleJson; 64 | })(); 65 | 66 | return Promise.all([activateDefaultLocaleJsonDeferred, activateCurrentLocaleJsonDeferred]); 67 | } 68 | 69 | export function useI18n(key: I18nKeys) { 70 | const { locale } = useVSCodeContextProps(); 71 | // eslint-disable-next-line react-hooks/exhaustive-deps 72 | return useMemo(() => i18nRecord?.[key] ?? "", [locale, key, i18nRecord]); 73 | } 74 | -------------------------------------------------------------------------------- /webviews/pq-test-result-view/src/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Microsoft Corporation. 3 | * 4 | * Licensed under the MIT license found in the 5 | * LICENSE file in the root of this projects source tree. 6 | */ 7 | 8 | import("./bootstrap"); 9 | -------------------------------------------------------------------------------- /webviews/pq-test-result-view/src/themes.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Microsoft Corporation. 3 | * 4 | * Licensed under the MIT license found in the 5 | * LICENSE file in the root of this projects source tree. 6 | */ 7 | 8 | export function getCssVariable(variableName: string, defaultValue: string): string { 9 | // do not use nullish coalescing here, as getPropertyValue would return empty string 10 | // "" ?? "yoo" -> "" 11 | // undefined ?? "yoo" -> "yoo" 12 | // "" || "yoo" -> "yoo" 13 | // undefined || "yoo" -> "yoo" 14 | return getComputedStyle(document.documentElement).getPropertyValue(variableName) || defaultValue; 15 | } 16 | 17 | export function buildTheme() { 18 | return { 19 | palette: { 20 | themePrimary: getCssVariable("--vscode-button-background", "#348dd1"), 21 | themeLighterAlt: getCssVariable("--vscode-scrollbar-shadow", "#020608"), 22 | themeLighter: getCssVariable("--vscode-editor-background", "#081721"), 23 | themeLight: getCssVariable("--vscode-editorWidget-background", "#102a3f"), 24 | themeTertiary: getCssVariable("--vscode-progressBar-background", "#1f557d"), 25 | themeSecondary: getCssVariable("--vscode-progressBar-background", "#2e7cb8"), 26 | themeDarkAlt: getCssVariable("---vscode-sash-hoverBorder", "#4597d6"), 27 | themeDark: getCssVariable("--vscode-editorInfo-foreground", "#5fa6dc"), 28 | themeDarker: getCssVariable("--vscode-editorLink-activeForeground", "#85bce5"), 29 | neutralLighterAlt: getCssVariable("--vscode-editorGroup-border", "#323232"), 30 | neutralLighter: getCssVariable("--vscode-editorGroup-dropBackground", "#313131"), 31 | neutralLight: getCssVariable("-vscode-editorGroupHeader-tabsBackground", "#2f2f2f"), 32 | neutralQuaternaryAlt: getCssVariable("--vscode-editorPane-background", "#2c2c2c"), 33 | neutralQuaternary: getCssVariable("--vscode-tab-activeBackground", "#2a2a2a"), 34 | neutralTertiaryAlt: getCssVariable("--vscode-statusBarItem-prominentBackground", "#282828"), 35 | neutralTertiary: getCssVariable("--vscode-editorGroup-dropIntoPromptForeground", "#c8c8c8"), 36 | neutralSecondary: getCssVariable("--vscode-panelTitle-activeForeground", "#d0d0d0"), 37 | neutralPrimaryAlt: getCssVariable("--vscode-panelTitle-activeBorder", "#dadada"), 38 | neutralPrimary: getCssVariable("--vscode-banner-foreground", "#ffffff"), 39 | neutralDark: getCssVariable("--vscode-panelTitle-inactiveForeground", "#f4f4f4"), 40 | black: getCssVariable("--vscode-editor-foreground", "#f8f8f8"), 41 | white: getCssVariable("--vscode-editor-background", "#333333"), 42 | }, 43 | }; 44 | } 45 | -------------------------------------------------------------------------------- /webviews/pq-test-result-view/src/utils/jsons.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Microsoft Corporation. 3 | * 4 | * Licensed under the MIT license found in the 5 | * LICENSE file in the root of this projects source tree. 6 | */ 7 | 8 | export const flattenJSON = (obj: any = {}, res: any = {}, extraKey = "") => { 9 | for (const key in obj) { 10 | if (typeof obj[key] !== "object") { 11 | res[extraKey + key] = obj[key]; 12 | } else { 13 | flattenJSON(obj[key], res, `${extraKey}${key}.`); 14 | } 15 | } 16 | return res; 17 | }; 18 | -------------------------------------------------------------------------------- /webviews/pq-test-result-view/src/utils/types.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Microsoft Corporation. 3 | * 4 | * Licensed under the MIT license found in the 5 | * LICENSE file in the root of this projects source tree. 6 | */ 7 | 8 | export type RecordKeys = R extends Record ? K : unknown; 9 | -------------------------------------------------------------------------------- /webviews/pq-test-result-view/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": "src", 4 | "target": "es6", 5 | "lib": ["dom", "dom.iterable", "esnext"], 6 | "allowJs": true, 7 | "skipLibCheck": true, 8 | "esModuleInterop": true, 9 | "allowSyntheticDefaultImports": true, 10 | "experimentalDecorators": true, 11 | "strict": true, 12 | "forceConsistentCasingInFileNames": true, 13 | "downlevelIteration": true, 14 | "module": "esnext", 15 | "moduleResolution": "node", 16 | "resolveJsonModule": true, 17 | "isolatedModules": false, 18 | "noEmit": true, 19 | "jsx": "react", 20 | "sourceMap": true 21 | }, 22 | "include": ["src"], 23 | "exclude": ["node_modules"] 24 | } 25 | --------------------------------------------------------------------------------