diff --git a/.eslintrc.js b/.eslintrc.js index 03cd5ce38..419dbbb00 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -1,30 +1,30 @@ module.exports = { - root: true, - extends: '@sveltejs', - plugins: ['import'], - env: { - node: true - }, - rules: { - // enabling these rules makes the linting extremely slow. - // (it's conceivable some subset of them could be enabled without impacting speed) - // see https://github.com/typescript-eslint/typescript-eslint/blob/master/docs/getting-started/linting/FAQ.md#eslint-plugin-import - 'import/named': 'off', - 'import/namespace': 'off', - 'import/default': 'off', - 'import/no-named-as-default-member': 'off', - 'import/no-named-as-default': 'off', - 'import/no-cycle': 'off', - 'import/no-unused-modules': 'off', - 'import/no-deprecated': 'off', - // project-specific settings - 'max-len': ['error', { code: 100, ignoreComments: true, ignoreStrings: true }], - 'no-trailing-spaces': 'error', - 'one-var': ['error', 'never'], - '@typescript-eslint/no-unused-vars': ['error', { args: 'none' }], - '@typescript-eslint/no-namespace': 'warn', - '@typescript-eslint/no-non-null-assertion': 'warn', - // exclude workspace dependencies - 'import/no-unresolved': [2, { ignore: ['svelte-language-server'] }] - } + root: true, + extends: '@sveltejs', + plugins: ['import'], + env: { + node: true + }, + rules: { + // enabling these rules makes the linting extremely slow. + // (it's conceivable some subset of them could be enabled without impacting speed) + // see https://github.com/typescript-eslint/typescript-eslint/blob/master/docs/getting-started/linting/FAQ.md#eslint-plugin-import + 'import/named': 'off', + 'import/namespace': 'off', + 'import/default': 'off', + 'import/no-named-as-default-member': 'off', + 'import/no-named-as-default': 'off', + 'import/no-cycle': 'off', + 'import/no-unused-modules': 'off', + 'import/no-deprecated': 'off', + // project-specific settings + 'max-len': ['error', { code: 100, ignoreComments: true, ignoreStrings: true }], + 'no-trailing-spaces': 'error', + 'one-var': ['error', 'never'], + '@typescript-eslint/no-unused-vars': ['error', { args: 'none' }], + '@typescript-eslint/no-namespace': 'warn', + '@typescript-eslint/no-non-null-assertion': 'warn', + // exclude workspace dependencies + 'import/no-unresolved': [2, { ignore: ['svelte-language-server'] }] + } }; diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index d933d4de7..45f2c3aaa 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -29,13 +29,14 @@ A clear and concise description of what you expected to happen. If applicable, add screenshots to help explain your problem. **System (please complete the following information):** - - OS: [e.g. Windows] - - IDE: [e.g. VSCode, Atom] - - Plugin/Package: [e.g. "Svelte for VSCode", or `svelte-language-server`, `svelte-check`, or `svelte2tsx` if you use one of the npm packages directly] + +- OS: [e.g. Windows] +- IDE: [e.g. VSCode, Atom] +- Plugin/Package: [e.g. "Svelte for VSCode", or `svelte-language-server`, `svelte-check`, or `svelte2tsx` if you use one of the npm packages directly] **Additional context** Add any other context about the problem here. \ No newline at end of file +--> diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md index e80bc46c4..6d1b1fd96 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -21,4 +21,4 @@ Add any other context or screenshots about the feature request here. \ No newline at end of file +--> diff --git a/.github/ISSUE_TEMPLATE/something-else.md b/.github/ISSUE_TEMPLATE/something-else.md index 65f6e38a8..196e33d4a 100644 --- a/.github/ISSUE_TEMPLATE/something-else.md +++ b/.github/ISSUE_TEMPLATE/something-else.md @@ -6,5 +6,3 @@ labels: '' assignees: '' --- - - diff --git a/.prettierrc b/.prettierrc index 5a219d934..b8797bfb7 100644 --- a/.prettierrc +++ b/.prettierrc @@ -1,8 +1,23 @@ { - "useTabs": false, - "printWidth": 100, - "tabWidth": 4, - "semi": true, - "trailingComma": "none", - "singleQuote": true + "arrowParens": "always", + "useTabs": true, + "printWidth": 100, + "tabWidth": 4, + "semi": true, + "trailingComma": "none", + "singleQuote": true, + "overrides": [ + { + "files": ["package.json"], + "options": { + "tabWidth": 2 + } + }, + { + "files": ["*.md"], + "options": { + "useTabs": false + } + } + ] } diff --git a/.vscode/launch.json b/.vscode/launch.json index 6dc886f5e..fb96cba06 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -1,57 +1,57 @@ { - "version": "0.2.0", - "configurations": [ - { - "name": "Run VS Code Extension", - "type": "extensionHost", - "request": "launch", - "runtimeExecutable": "${execPath}", - "args": ["--extensionDevelopmentPath=${workspaceRoot}/packages/svelte-vscode"], - "stopOnEntry": false, - "sourceMaps": true, - "outFiles": ["${workspaceRoot}/packages/svelte-vscode/dist/**/*.js"], - "preLaunchTask": "npm: watch" - }, - { - "type": "node", - "request": "launch", - "name": "Run 'svelte2tsx/repl/debug.ts' with debugger", - "runtimeArgs": ["-r", "ts-node/register"], - "args": ["${workspaceFolder}/packages/svelte2tsx/repl/debug.ts"], - "env": { - "TS_NODE_COMPILER_OPTIONS": "{\"esModuleInterop\":true, \"target\": \"es2018\"}", - "TS_NODE_TRANSPILE_ONLY": "true" - }, - "console": "integratedTerminal", - "internalConsoleOptions": "neverOpen" - }, - { - "type": "node", - "request": "launch", - "name": "Run tests with debugger", - "program": "${workspaceFolder}/node_modules/mocha/bin/_mocha", - "args": [ - "-r", - "ts-node/register", - "--colors", - "${workspaceFolder}/packages/*/test/**/*.ts" - ], - "env": { - "TS_NODE_COMPILER_OPTIONS": "{\"esModuleInterop\":true}" - }, - "console": "integratedTerminal", - "internalConsoleOptions": "neverOpen" - }, - { - "type": "node", - "request": "attach", - "name": "Attach debugger to language server", - "port": 6009, - "outFiles": [ - "${workspaceRoot}/packages/language-server/dist/**/*.js", - "${workspaceRoot}/packages/svelte2tsx/index.js" - ], - "skipFiles": ["/**"] - } - ] + "version": "0.2.0", + "configurations": [ + { + "name": "Run VS Code Extension", + "type": "extensionHost", + "request": "launch", + "runtimeExecutable": "${execPath}", + "args": ["--extensionDevelopmentPath=${workspaceRoot}/packages/svelte-vscode"], + "stopOnEntry": false, + "sourceMaps": true, + "outFiles": ["${workspaceRoot}/packages/svelte-vscode/dist/**/*.js"], + "preLaunchTask": "npm: watch" + }, + { + "type": "node", + "request": "launch", + "name": "Run 'svelte2tsx/repl/debug.ts' with debugger", + "runtimeArgs": ["-r", "ts-node/register"], + "args": ["${workspaceFolder}/packages/svelte2tsx/repl/debug.ts"], + "env": { + "TS_NODE_COMPILER_OPTIONS": "{\"esModuleInterop\":true, \"target\": \"es2018\"}", + "TS_NODE_TRANSPILE_ONLY": "true" + }, + "console": "integratedTerminal", + "internalConsoleOptions": "neverOpen" + }, + { + "type": "node", + "request": "launch", + "name": "Run tests with debugger", + "program": "${workspaceFolder}/node_modules/mocha/bin/_mocha", + "args": [ + "-r", + "ts-node/register", + "--colors", + "${workspaceFolder}/packages/*/test/**/*.ts" + ], + "env": { + "TS_NODE_COMPILER_OPTIONS": "{\"esModuleInterop\":true}" + }, + "console": "integratedTerminal", + "internalConsoleOptions": "neverOpen" + }, + { + "type": "node", + "request": "attach", + "name": "Attach debugger to language server", + "port": 6009, + "outFiles": [ + "${workspaceRoot}/packages/language-server/dist/**/*.js", + "${workspaceRoot}/packages/svelte2tsx/index.js" + ], + "skipFiles": ["/**"] + } + ] } diff --git a/.vscode/settings.json b/.vscode/settings.json index f865d2f2c..dc0756a56 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,3 +1,3 @@ { - "typescript.preferences.quoteStyle": "single" + "typescript.preferences.quoteStyle": "single" } diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 05af6c90b..8a491bd31 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -1,18 +1,18 @@ { - "version": "2.0.0", - "tasks": [ - { - "type": "npm", - "script": "watch", - "problemMatcher": "$tsc-watch", - "isBackground": true, - "presentation": { - "reveal": "never" - }, - "group": { - "kind": "build", - "isDefault": true - } - } - ] + "version": "2.0.0", + "tasks": [ + { + "type": "npm", + "script": "watch", + "problemMatcher": "$tsc-watch", + "isBackground": true, + "presentation": { + "reveal": "never" + }, + "group": { + "kind": "build", + "isDefault": true + } + } + ] } diff --git a/README.md b/README.md index b3b4732e5..39afdd248 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@

- Cybernetically enhanced web apps: Svelte + Cybernetically enhanced web apps: Svelte @@ -20,7 +20,7 @@ Svelte Language Tools contains a library implementing the Language Server Protoc A `.svelte` file would look something like this: -```html +```svelte - +

{count} * 2 = {doubled}

{doubled} * 2 = {quadrupled}

diff --git a/docs/README.md b/docs/README.md index 4f42d85ac..bd4210bba 100644 --- a/docs/README.md +++ b/docs/README.md @@ -47,19 +47,19 @@ LSP-compatible editors, you can use an HTML comment with the `@component` tag: The VS Code extension comes with its own syntax highlighting grammar which defines special scopes. If your syntax highlighting seems to be not working for Svelte components or you feel that some colors are wrong, you can add something like the following to your `settings.json`: -``` +```json { "editor.tokenColorCustomizations": { "[]": { "textMateRules": [ { "settings": { - "foreground": "#569CD6", // any color you like + "foreground": "#569CD6" // any color you like }, "scope": "support.class.component.svelte" // scope name you want to adjust highlighting for } - ], - }, + ] + } } } ``` @@ -80,7 +80,7 @@ You need to save the file to see the changes. If the problem persists after savi ```json "files.watcherExclude": { - "**/*": true, + "**/*": true, } ``` @@ -94,13 +94,13 @@ If you have the `Babel Javascript` plugin installed, this may be the cause. Disa Your default formatter for Svelte files may be wrong. -- Mabye it's set to the old Svelte extension, if so, remove the setting +- Maybe it's set to the old Svelte extension, if so, remove the setting - Maybe you set all files to be formatted by the prettier extension. Then you have two options: Either install `prettier-plugin-svelte` from npm, or tell VSCode to format the code with the `Svelte for VSCode extension`: ```json - "[svelte]": { +"[svelte]": { "editor.defaultFormatter": "svelte.svelte-vscode" - }, +}, ``` ## Internals diff --git a/docs/internal/overview.md b/docs/internal/overview.md index b9ff47fe2..96e1c1bb0 100644 --- a/docs/internal/overview.md +++ b/docs/internal/overview.md @@ -30,10 +30,11 @@ Our `language-server` can roughly be split into [four areas](/packages/language- The last area, TS/JS, is where most of the work is done. Svelte is a mix of JavaScript/TypeScript inside `script` and more of it within Svelte's template syntax. To get a holistic view of the file, we need to account for both. This is also the reason why preprocessors such as `svelte-preprocess` cannot do type checking because it produces wrong diagnostics. To preprocess the script content, it only gets the content from the script. Think of this situation: -```html +```svelte + {a} ``` @@ -64,7 +65,7 @@ This example shows how our `language-server` uses `svelte2tsx`: 1. Svelte file comes in -```html +```svelte diff --git a/docs/preprocessors/in-general.md b/docs/preprocessors/in-general.md index 496908688..299e15183 100644 --- a/docs/preprocessors/in-general.md +++ b/docs/preprocessors/in-general.md @@ -17,7 +17,7 @@ module.exports = { It's also necessary to add a `type="text/language-name"` or `lang="language-name"` to your `style` and `script` tags, which defines how that code should be interpreted by the extension. -```html +```svelte

Hello, world!

diff --git a/docs/preprocessors/scss-less.md b/docs/preprocessors/scss-less.md index c185032d7..f292f00fc 100644 --- a/docs/preprocessors/scss-less.md +++ b/docs/preprocessors/scss-less.md @@ -35,7 +35,7 @@ module.exports = { To gain syntax highlighing for your SCSS code and to make us understand the language you are using, add a `type` or `lang` attribute to your style tags like so: -```html +```svelte '); - assert.deepEqual(document.scriptInfo, { - content: 'a', - attributes: { - lang: 'javascript' - }, - start: 8, - end: 9, - startPos: Position.create(0, 8), - endPos: Position.create(0, 9), - container: { start: 0, end: 18 } - }); - assert.deepEqual(document.styleInfo, { - content: 'b', - attributes: { - lang: 'css' - }, - start: 25, - end: 26, - startPos: Position.create(0, 25), - endPos: Position.create(0, 26), - container: { start: 18, end: 34 } - }); - - document.setText(''); - assert.deepEqual(document.scriptInfo, { - content: 'b', - attributes: { - lang: 'javascript' - }, - start: 8, - end: 9, - startPos: Position.create(0, 8), - endPos: Position.create(0, 9), - container: { start: 0, end: 18 } - }); - assert.strictEqual(document.styleInfo, null); - }); - - it('returns the correct file path', () => { - const document = new Document('file:///hello.svelte', 'hello'); - - assert.strictEqual(document.getFilePath(), '/hello.svelte'); - }); - - it('returns null for non file urls', () => { - const document = new Document('ftp:///hello.svelte', 'hello'); - - assert.strictEqual(document.getFilePath(), null); - }); - - it('gets the text length', () => { - const document = new Document('file:///hello.svelte', 'Hello, world!'); - assert.strictEqual(document.getTextLength(), 13); - }); - - it('updates the text range', () => { - const document = new Document('file:///hello.svelte', 'Hello, world!'); - document.update('svelte', 7, 12); - assert.strictEqual(document.getText(), 'Hello, svelte!'); - }); - - it('gets the correct position from offset', () => { - const document = new Document('file:///hello.svelte', 'Hello\nworld\n'); - assert.deepStrictEqual(document.positionAt(1), { line: 0, character: 1 }); - assert.deepStrictEqual(document.positionAt(9), { line: 1, character: 3 }); - assert.deepStrictEqual(document.positionAt(12), { line: 2, character: 0 }); - }); - - it('gets the correct offset from position', () => { - const document = new Document('file:///hello.svelte', 'Hello\nworld\n'); - assert.strictEqual(document.offsetAt({ line: 0, character: 1 }), 1); - assert.strictEqual(document.offsetAt({ line: 1, character: 3 }), 9); - assert.strictEqual(document.offsetAt({ line: 2, character: 0 }), 12); - }); - - it('gets the correct position from offset with CRLF', () => { - const document = new Document('file:///hello.svelte', 'Hello\r\nworld\r\n'); - assert.deepStrictEqual(document.positionAt(1), { line: 0, character: 1 }); - assert.deepStrictEqual(document.positionAt(10), { line: 1, character: 3 }); - assert.deepStrictEqual(document.positionAt(14), { line: 2, character: 0 }); - }); - - it('gets the correct offset from position with CRLF', () => { - const document = new Document('file:///hello.svelte', 'Hello\r\nworld\r\n'); - assert.strictEqual(document.offsetAt({ line: 0, character: 1 }), 1); - assert.strictEqual(document.offsetAt({ line: 1, character: 3 }), 10); - assert.strictEqual(document.offsetAt({ line: 2, character: 0 }), 14); - }); - - it('limits the position when offset is out of bounds', () => { - const document = new Document('file:///hello.svelte', 'Hello\nworld\n'); - assert.deepStrictEqual(document.positionAt(20), { line: 2, character: 0 }); - assert.deepStrictEqual(document.positionAt(-1), { line: 0, character: 0 }); - }); - - it('limits the offset when position is out of bounds', () => { - const document = new Document('file:///hello.svelte', 'Hello\nworld\n'); - assert.strictEqual(document.offsetAt({ line: 5, character: 0 }), 12); - assert.strictEqual(document.offsetAt({ line: 1, character: 20 }), 12); - assert.strictEqual(document.offsetAt({ line: -1, character: 0 }), 0); - }); - - it('supports empty contents', () => { - const document = new Document('file:///hello.svelte', ''); - assert.strictEqual(document.offsetAt({ line: 0, character: 0 }), 0); - assert.deepStrictEqual(document.positionAt(0), { line: 0, character: 0 }); - }); + it('gets the correct text', () => { + const document = new Document('file:///hello.svelte', '

Hello, world!

'); + assert.strictEqual(document.getText(), '

Hello, world!

'); + }); + + it('sets the text', () => { + const document = new Document('file:///hello.svelte', '

Hello, world!

'); + document.setText('

Hello, svelte!

'); + assert.strictEqual(document.getText(), '

Hello, svelte!

'); + }); + + it('increments the version on edits', () => { + const document = new Document('file:///hello.svelte', 'hello'); + assert.strictEqual(document.version, 0); + + document.setText('Hello, world!'); + assert.strictEqual(document.version, 1); + document.update('svelte', 7, 12); + assert.strictEqual(document.version, 2); + }); + + it('recalculates the tag infos on edits', () => { + const document = new Document('file:///hello.svelte', ''); + assert.deepEqual(document.scriptInfo, { + content: 'a', + attributes: { + lang: 'javascript' + }, + start: 8, + end: 9, + startPos: Position.create(0, 8), + endPos: Position.create(0, 9), + container: { start: 0, end: 18 } + }); + assert.deepEqual(document.styleInfo, { + content: 'b', + attributes: { + lang: 'css' + }, + start: 25, + end: 26, + startPos: Position.create(0, 25), + endPos: Position.create(0, 26), + container: { start: 18, end: 34 } + }); + + document.setText(''); + assert.deepEqual(document.scriptInfo, { + content: 'b', + attributes: { + lang: 'javascript' + }, + start: 8, + end: 9, + startPos: Position.create(0, 8), + endPos: Position.create(0, 9), + container: { start: 0, end: 18 } + }); + assert.strictEqual(document.styleInfo, null); + }); + + it('returns the correct file path', () => { + const document = new Document('file:///hello.svelte', 'hello'); + + assert.strictEqual(document.getFilePath(), '/hello.svelte'); + }); + + it('returns null for non file urls', () => { + const document = new Document('ftp:///hello.svelte', 'hello'); + + assert.strictEqual(document.getFilePath(), null); + }); + + it('gets the text length', () => { + const document = new Document('file:///hello.svelte', 'Hello, world!'); + assert.strictEqual(document.getTextLength(), 13); + }); + + it('updates the text range', () => { + const document = new Document('file:///hello.svelte', 'Hello, world!'); + document.update('svelte', 7, 12); + assert.strictEqual(document.getText(), 'Hello, svelte!'); + }); + + it('gets the correct position from offset', () => { + const document = new Document('file:///hello.svelte', 'Hello\nworld\n'); + assert.deepStrictEqual(document.positionAt(1), { line: 0, character: 1 }); + assert.deepStrictEqual(document.positionAt(9), { line: 1, character: 3 }); + assert.deepStrictEqual(document.positionAt(12), { line: 2, character: 0 }); + }); + + it('gets the correct offset from position', () => { + const document = new Document('file:///hello.svelte', 'Hello\nworld\n'); + assert.strictEqual(document.offsetAt({ line: 0, character: 1 }), 1); + assert.strictEqual(document.offsetAt({ line: 1, character: 3 }), 9); + assert.strictEqual(document.offsetAt({ line: 2, character: 0 }), 12); + }); + + it('gets the correct position from offset with CRLF', () => { + const document = new Document('file:///hello.svelte', 'Hello\r\nworld\r\n'); + assert.deepStrictEqual(document.positionAt(1), { line: 0, character: 1 }); + assert.deepStrictEqual(document.positionAt(10), { line: 1, character: 3 }); + assert.deepStrictEqual(document.positionAt(14), { line: 2, character: 0 }); + }); + + it('gets the correct offset from position with CRLF', () => { + const document = new Document('file:///hello.svelte', 'Hello\r\nworld\r\n'); + assert.strictEqual(document.offsetAt({ line: 0, character: 1 }), 1); + assert.strictEqual(document.offsetAt({ line: 1, character: 3 }), 10); + assert.strictEqual(document.offsetAt({ line: 2, character: 0 }), 14); + }); + + it('limits the position when offset is out of bounds', () => { + const document = new Document('file:///hello.svelte', 'Hello\nworld\n'); + assert.deepStrictEqual(document.positionAt(20), { line: 2, character: 0 }); + assert.deepStrictEqual(document.positionAt(-1), { line: 0, character: 0 }); + }); + + it('limits the offset when position is out of bounds', () => { + const document = new Document('file:///hello.svelte', 'Hello\nworld\n'); + assert.strictEqual(document.offsetAt({ line: 5, character: 0 }), 12); + assert.strictEqual(document.offsetAt({ line: 1, character: 20 }), 12); + assert.strictEqual(document.offsetAt({ line: -1, character: 0 }), 0); + }); + + it('supports empty contents', () => { + const document = new Document('file:///hello.svelte', ''); + assert.strictEqual(document.offsetAt({ line: 0, character: 0 }), 0); + assert.deepStrictEqual(document.positionAt(0), { line: 0, character: 0 }); + }); }); diff --git a/packages/language-server/test/lib/documents/DocumentManager.test.ts b/packages/language-server/test/lib/documents/DocumentManager.test.ts index 05c737b9c..7ff332a58 100644 --- a/packages/language-server/test/lib/documents/DocumentManager.test.ts +++ b/packages/language-server/test/lib/documents/DocumentManager.test.ts @@ -4,80 +4,80 @@ import { TextDocumentItem, Range } from 'vscode-languageserver-types'; import { DocumentManager, Document } from '../../../src/lib/documents'; describe('Document Manager', () => { - const textDocument: TextDocumentItem = { - uri: 'file:///hello.svelte', - version: 0, - languageId: 'svelte', - text: 'Hello, world!' - }; - - const createTextDocument = (textDocument: Pick) => - new Document(textDocument.uri, textDocument.text); - - it('opens documents', () => { - const createDocument = sinon.spy(); - const manager = new DocumentManager(createDocument); - - manager.openDocument(textDocument); - - sinon.assert.calledOnce(createDocument); - sinon.assert.calledWith(createDocument.firstCall, textDocument); - }); - - it('updates the whole document', () => { - const document = createTextDocument(textDocument); - const update = sinon.spy(document, 'update'); - const createDocument = sinon.stub().returns(document); - const manager = new DocumentManager(createDocument); - - manager.openDocument(textDocument); - manager.updateDocument(textDocument, [{ text: 'New content' }]); - - sinon.assert.calledOnce(update); - sinon.assert.calledWith(update.firstCall, 'New content', 0, textDocument.text.length); - }); - - it('updates the parts of the document', () => { - const document = createTextDocument(textDocument); - const update = sinon.spy(document, 'update'); - const createDocument = sinon.stub().returns(document); - const manager = new DocumentManager(createDocument); - - manager.openDocument(textDocument); - manager.updateDocument(textDocument, [ - { - text: 'svelte', - range: Range.create(0, 7, 0, 12), - rangeLength: 5 - }, - { - text: 'Greetings', - range: Range.create(0, 0, 0, 5), - rangeLength: 5 - } - ]); - - sinon.assert.calledTwice(update); - sinon.assert.calledWith(update.firstCall, 'svelte', 7, 12); - sinon.assert.calledWith(update.secondCall, 'Greetings', 0, 5); - }); - - it("fails to update if document isn't open", () => { - const manager = new DocumentManager(createTextDocument); - - assert.throws(() => manager.updateDocument(textDocument, [])); - }); - - it('emits a document change event on open and update', () => { - const manager = new DocumentManager(createTextDocument); - const cb = sinon.spy(); - - manager.on('documentChange', cb); - - manager.openDocument(textDocument); - sinon.assert.calledOnce(cb); - - manager.updateDocument(textDocument, []); - sinon.assert.calledTwice(cb); - }); + const textDocument: TextDocumentItem = { + uri: 'file:///hello.svelte', + version: 0, + languageId: 'svelte', + text: 'Hello, world!' + }; + + const createTextDocument = (textDocument: Pick) => + new Document(textDocument.uri, textDocument.text); + + it('opens documents', () => { + const createDocument = sinon.spy(); + const manager = new DocumentManager(createDocument); + + manager.openDocument(textDocument); + + sinon.assert.calledOnce(createDocument); + sinon.assert.calledWith(createDocument.firstCall, textDocument); + }); + + it('updates the whole document', () => { + const document = createTextDocument(textDocument); + const update = sinon.spy(document, 'update'); + const createDocument = sinon.stub().returns(document); + const manager = new DocumentManager(createDocument); + + manager.openDocument(textDocument); + manager.updateDocument(textDocument, [{ text: 'New content' }]); + + sinon.assert.calledOnce(update); + sinon.assert.calledWith(update.firstCall, 'New content', 0, textDocument.text.length); + }); + + it('updates the parts of the document', () => { + const document = createTextDocument(textDocument); + const update = sinon.spy(document, 'update'); + const createDocument = sinon.stub().returns(document); + const manager = new DocumentManager(createDocument); + + manager.openDocument(textDocument); + manager.updateDocument(textDocument, [ + { + text: 'svelte', + range: Range.create(0, 7, 0, 12), + rangeLength: 5 + }, + { + text: 'Greetings', + range: Range.create(0, 0, 0, 5), + rangeLength: 5 + } + ]); + + sinon.assert.calledTwice(update); + sinon.assert.calledWith(update.firstCall, 'svelte', 7, 12); + sinon.assert.calledWith(update.secondCall, 'Greetings', 0, 5); + }); + + it("fails to update if document isn't open", () => { + const manager = new DocumentManager(createTextDocument); + + assert.throws(() => manager.updateDocument(textDocument, [])); + }); + + it('emits a document change event on open and update', () => { + const manager = new DocumentManager(createTextDocument); + const cb = sinon.spy(); + + manager.on('documentChange', cb); + + manager.openDocument(textDocument); + sinon.assert.calledOnce(cb); + + manager.updateDocument(textDocument, []); + sinon.assert.calledTwice(cb); + }); }); diff --git a/packages/language-server/test/lib/documents/DocumentMapper.test.ts b/packages/language-server/test/lib/documents/DocumentMapper.test.ts index 3c21edb38..a84d5d6df 100644 --- a/packages/language-server/test/lib/documents/DocumentMapper.test.ts +++ b/packages/language-server/test/lib/documents/DocumentMapper.test.ts @@ -2,45 +2,45 @@ import * as assert from 'assert'; import { FragmentMapper, positionAt } from '../../../src/lib/documents'; describe('DocumentMapper', () => { - describe('FragmentMapper', () => { - function setup(content: string, start: number, end: number) { - return new FragmentMapper( - content, - { - start, - end, - endPos: positionAt(end, content), - content: content.substring(start, end) - }, - 'file:///hello.svelte' - ); - } + describe('FragmentMapper', () => { + function setup(content: string, start: number, end: number) { + return new FragmentMapper( + content, + { + start, + end, + endPos: positionAt(end, content), + content: content.substring(start, end) + }, + 'file:///hello.svelte' + ); + } - it('isInGenerated works', () => { - const fragment = setup('Hello, \nworld!', 8, 13); + it('isInGenerated works', () => { + const fragment = setup('Hello, \nworld!', 8, 13); - assert.strictEqual(fragment.isInGenerated({ line: 0, character: 0 }), false); - assert.strictEqual(fragment.isInGenerated({ line: 1, character: 0 }), true); - assert.strictEqual(fragment.isInGenerated({ line: 1, character: 5 }), true); - assert.strictEqual(fragment.isInGenerated({ line: 1, character: 6 }), false); - }); + assert.strictEqual(fragment.isInGenerated({ line: 0, character: 0 }), false); + assert.strictEqual(fragment.isInGenerated({ line: 1, character: 0 }), true); + assert.strictEqual(fragment.isInGenerated({ line: 1, character: 5 }), true); + assert.strictEqual(fragment.isInGenerated({ line: 1, character: 6 }), false); + }); - it('calculates the position in parent', () => { - const fragment = setup('Hello, \nworld!', 8, 13); + it('calculates the position in parent', () => { + const fragment = setup('Hello, \nworld!', 8, 13); - assert.deepStrictEqual(fragment.getOriginalPosition({ line: 0, character: 2 }), { - line: 1, - character: 2 - }); - }); + assert.deepStrictEqual(fragment.getOriginalPosition({ line: 0, character: 2 }), { + line: 1, + character: 2 + }); + }); - it('calculates the position in fragment', () => { - const fragment = setup('Hello, \nworld!', 8, 13); + it('calculates the position in fragment', () => { + const fragment = setup('Hello, \nworld!', 8, 13); - assert.deepStrictEqual(fragment.getGeneratedPosition({ line: 1, character: 2 }), { - line: 0, - character: 2 - }); - }); - }); + assert.deepStrictEqual(fragment.getGeneratedPosition({ line: 1, character: 2 }), { + line: 0, + character: 2 + }); + }); + }); }); diff --git a/packages/language-server/test/lib/documents/configLoader.test.ts b/packages/language-server/test/lib/documents/configLoader.test.ts index 676b999ec..288cee74f 100644 --- a/packages/language-server/test/lib/documents/configLoader.test.ts +++ b/packages/language-server/test/lib/documents/configLoader.test.ts @@ -4,146 +4,146 @@ import { pathToFileURL, URL } from 'url'; import assert from 'assert'; describe('ConfigLoader', () => { - function configFrom(path: string) { - return { - compilerOptions: { - dev: true, - generate: false - }, - preprocess: pathToFileURL(path).toString() - }; - } + function configFrom(path: string) { + return { + compilerOptions: { + dev: true, + generate: false + }, + preprocess: pathToFileURL(path).toString() + }; + } - async function assertFindsConfig( - configLoader: ConfigLoader, - filePath: string, - configPath: string - ) { - filePath = path.join(...filePath.split('/')); - configPath = path.join(...configPath.split('/')); - assert.deepStrictEqual(configLoader.getConfig(filePath), configFrom(configPath)); - assert.deepStrictEqual(await configLoader.awaitConfig(filePath), configFrom(configPath)); - } + async function assertFindsConfig( + configLoader: ConfigLoader, + filePath: string, + configPath: string + ) { + filePath = path.join(...filePath.split('/')); + configPath = path.join(...configPath.split('/')); + assert.deepStrictEqual(configLoader.getConfig(filePath), configFrom(configPath)); + assert.deepStrictEqual(await configLoader.awaitConfig(filePath), configFrom(configPath)); + } - it('should load all config files below and the one inside/above given directory', async () => { - const configLoader = new ConfigLoader( - () => ['svelte.config.js', 'below/svelte.config.js'], - { existsSync: () => true }, - path, - (module: URL) => Promise.resolve({ default: { preprocess: module.toString() } }) - ); - await configLoader.loadConfigs('/some/path'); + it('should load all config files below and the one inside/above given directory', async () => { + const configLoader = new ConfigLoader( + () => ['svelte.config.js', 'below/svelte.config.js'], + { existsSync: () => true }, + path, + (module: URL) => Promise.resolve({ default: { preprocess: module.toString() } }) + ); + await configLoader.loadConfigs('/some/path'); - assertFindsConfig(configLoader, '/some/path/comp.svelte', '/some/path/svelte.config.js'); - assertFindsConfig( - configLoader, - '/some/path/aside/comp.svelte', - '/some/path/svelte.config.js' - ); - assertFindsConfig( - configLoader, - '/some/path/below/comp.svelte', - '/some/path/below/svelte.config.js' - ); - assertFindsConfig( - configLoader, - '/some/path/below/further/comp.svelte', - '/some/path/below/svelte.config.js' - ); - }); + assertFindsConfig(configLoader, '/some/path/comp.svelte', '/some/path/svelte.config.js'); + assertFindsConfig( + configLoader, + '/some/path/aside/comp.svelte', + '/some/path/svelte.config.js' + ); + assertFindsConfig( + configLoader, + '/some/path/below/comp.svelte', + '/some/path/below/svelte.config.js' + ); + assertFindsConfig( + configLoader, + '/some/path/below/further/comp.svelte', + '/some/path/below/svelte.config.js' + ); + }); - it('finds first above if none found inside/below directory', async () => { - const configLoader = new ConfigLoader( - () => [], - { - existsSync: (p) => - typeof p === 'string' && p.endsWith(path.join('some', 'svelte.config.js')) - }, - path, - (module: URL) => Promise.resolve({ default: { preprocess: module.toString() } }) - ); - await configLoader.loadConfigs('/some/path'); + it('finds first above if none found inside/below directory', async () => { + const configLoader = new ConfigLoader( + () => [], + { + existsSync: (p) => + typeof p === 'string' && p.endsWith(path.join('some', 'svelte.config.js')) + }, + path, + (module: URL) => Promise.resolve({ default: { preprocess: module.toString() } }) + ); + await configLoader.loadConfigs('/some/path'); - assertFindsConfig(configLoader, '/some/path/comp.svelte', '/some/svelte.config.js'); - }); + assertFindsConfig(configLoader, '/some/path/comp.svelte', '/some/svelte.config.js'); + }); - it('adds fallback if no config found', async () => { - const configLoader = new ConfigLoader( - () => [], - { existsSync: () => false }, - path, - (module: URL) => Promise.resolve({ default: { preprocess: module.toString() } }) - ); - await configLoader.loadConfigs('/some/path'); + it('adds fallback if no config found', async () => { + const configLoader = new ConfigLoader( + () => [], + { existsSync: () => false }, + path, + (module: URL) => Promise.resolve({ default: { preprocess: module.toString() } }) + ); + await configLoader.loadConfigs('/some/path'); - assert.deepStrictEqual( - // Can't do the equal-check directly, instead check if it's the expected object props - // of svelte-preprocess - Object.keys(configLoader.getConfig('/some/path/comp.svelte')?.preprocess || {}).sort(), - ['defaultLanguages', 'markup', 'script', 'style'].sort() - ); - }); + assert.deepStrictEqual( + // Can't do the equal-check directly, instead check if it's the expected object props + // of svelte-preprocess + Object.keys(configLoader.getConfig('/some/path/comp.svelte')?.preprocess || {}).sort(), + ['defaultLanguages', 'markup', 'script', 'style'].sort() + ); + }); - it('will not load config multiple times if config loading started in parallel', async () => { - let firstGlobCall = true; - let nrImportCalls = 0; - const configLoader = new ConfigLoader( - () => { - if (firstGlobCall) { - firstGlobCall = false; - return ['svelte.config.js']; - } else { - return []; - } - }, - { - existsSync: (p) => - typeof p === 'string' && - p.endsWith(path.join('some', 'path', 'svelte.config.js')) - }, - path, - (module: URL) => { - nrImportCalls++; - return new Promise((resolve) => { - setTimeout(() => resolve({ default: { preprocess: module.toString() } }), 500); - }); - } - ); - await Promise.all([ - configLoader.loadConfigs('/some/path'), - configLoader.loadConfigs('/some/path/sub'), - configLoader.awaitConfig('/some/path/file.svelte') - ]); + it('will not load config multiple times if config loading started in parallel', async () => { + let firstGlobCall = true; + let nrImportCalls = 0; + const configLoader = new ConfigLoader( + () => { + if (firstGlobCall) { + firstGlobCall = false; + return ['svelte.config.js']; + } else { + return []; + } + }, + { + existsSync: (p) => + typeof p === 'string' && + p.endsWith(path.join('some', 'path', 'svelte.config.js')) + }, + path, + (module: URL) => { + nrImportCalls++; + return new Promise((resolve) => { + setTimeout(() => resolve({ default: { preprocess: module.toString() } }), 500); + }); + } + ); + await Promise.all([ + configLoader.loadConfigs('/some/path'), + configLoader.loadConfigs('/some/path/sub'), + configLoader.awaitConfig('/some/path/file.svelte') + ]); - assertFindsConfig(configLoader, '/some/path/comp.svelte', '/some/path/svelte.config.js'); - assertFindsConfig( - configLoader, - '/some/path/sub/comp.svelte', - '/some/path/svelte.config.js' - ); - assert.deepStrictEqual(nrImportCalls, 1); - }); + assertFindsConfig(configLoader, '/some/path/comp.svelte', '/some/path/svelte.config.js'); + assertFindsConfig( + configLoader, + '/some/path/sub/comp.svelte', + '/some/path/svelte.config.js' + ); + assert.deepStrictEqual(nrImportCalls, 1); + }); - it('can deal with missing config', () => { - const configLoader = new ConfigLoader( - () => [], - { existsSync: () => false }, - path, - () => Promise.resolve('unimportant') - ); - assert.deepStrictEqual(configLoader.getConfig('/some/file.svelte'), undefined); - }); + it('can deal with missing config', () => { + const configLoader = new ConfigLoader( + () => [], + { existsSync: () => false }, + path, + () => Promise.resolve('unimportant') + ); + assert.deepStrictEqual(configLoader.getConfig('/some/file.svelte'), undefined); + }); - it('should await config', async () => { - const configLoader = new ConfigLoader( - () => [], - { existsSync: () => true }, - path, - (module: URL) => Promise.resolve({ default: { preprocess: module.toString() } }) - ); - assert.deepStrictEqual( - await configLoader.awaitConfig(path.join('some', 'file.svelte')), - configFrom(path.join('some', 'svelte.config.js')) - ); - }); + it('should await config', async () => { + const configLoader = new ConfigLoader( + () => [], + { existsSync: () => true }, + path, + (module: URL) => Promise.resolve({ default: { preprocess: module.toString() } }) + ); + assert.deepStrictEqual( + await configLoader.awaitConfig(path.join('some', 'file.svelte')), + configFrom(path.join('some', 'svelte.config.js')) + ); + }); }); diff --git a/packages/language-server/test/lib/documents/parseHtml.test.ts b/packages/language-server/test/lib/documents/parseHtml.test.ts index 0d6d814a5..32d192801 100644 --- a/packages/language-server/test/lib/documents/parseHtml.test.ts +++ b/packages/language-server/test/lib/documents/parseHtml.test.ts @@ -3,74 +3,74 @@ import { HTMLDocument } from 'vscode-html-languageservice'; import { parseHtml } from '../../../src/lib/documents/parseHtml'; describe('parseHtml', () => { - const testRootElements = (document: HTMLDocument) => { - assert.deepStrictEqual( - document.roots.map((r) => r.tag), - ['Foo', 'style'] - ); - }; + const testRootElements = (document: HTMLDocument) => { + assert.deepStrictEqual( + document.roots.map((r) => r.tag), + ['Foo', 'style'] + ); + }; - it('ignore arrow inside moustache', () => { - testRootElements( - parseHtml( - ` console.log('ya!!!')} /> + it('ignore arrow inside moustache', () => { + testRootElements( + parseHtml( + ` console.log('ya!!!')} /> ` - ) - ); - }); + ) + ); + }); - it('ignore greater than operator inside moustache', () => { - testRootElements( - parseHtml( - ` 1} /> + it('ignore greater than operator inside moustache', () => { + testRootElements( + parseHtml( + ` 1} /> ` - ) - ); - }); + ) + ); + }); - it('ignore less than operator inside moustache', () => { - testRootElements( - parseHtml( - ` + it('ignore less than operator inside moustache', () => { + testRootElements( + parseHtml( + ` ` - ) - ); - }); + ) + ); + }); - it('ignore less than operator inside moustache with tag not self closed', () => { - testRootElements( - parseHtml( - ` + it('ignore less than operator inside moustache with tag not self closed', () => { + testRootElements( + parseHtml( + ` ` - ) - ); - }); + ) + ); + }); - it('parse baseline html', () => { - testRootElements( - parseHtml( - ` + it('parse baseline html', () => { + testRootElements( + parseHtml( + ` ` - ) - ); - }); + ) + ); + }); - it('parse baseline html with moustache', () => { - testRootElements( - parseHtml( - ` + it('parse baseline html with moustache', () => { + testRootElements( + parseHtml( + ` ` - ) - ); - }); + ) + ); + }); - it('parse baseline html with possibly un-closed start tag', () => { - testRootElements( - parseHtml( - ` { + testRootElements( + parseHtml( + `` - ) - ); - }); + ) + ); + }); }); diff --git a/packages/language-server/test/lib/documents/utils.test.ts b/packages/language-server/test/lib/documents/utils.test.ts index a76fd97eb..d90eb161c 100644 --- a/packages/language-server/test/lib/documents/utils.test.ts +++ b/packages/language-server/test/lib/documents/utils.test.ts @@ -1,167 +1,167 @@ import * as assert from 'assert'; import { - getLineAtPosition, - extractStyleTag, - extractScriptTags, - updateRelativeImport, - getWordAt + getLineAtPosition, + extractStyleTag, + extractScriptTags, + updateRelativeImport, + getWordAt } from '../../../src/lib/documents/utils'; import { Position } from 'vscode-languageserver'; describe('document/utils', () => { - describe('extractTag', () => { - it('supports boolean attributes', () => { - const extracted = extractStyleTag(''); - assert.deepStrictEqual(extracted?.attributes, { test: 'test' }); - }); + describe('extractTag', () => { + it('supports boolean attributes', () => { + const extracted = extractStyleTag(''); + assert.deepStrictEqual(extracted?.attributes, { test: 'test' }); + }); - it('supports unquoted attributes', () => { - const extracted = extractStyleTag(''); - assert.deepStrictEqual(extracted?.attributes, { - type: 'text/css' - }); - }); + it('supports unquoted attributes', () => { + const extracted = extractStyleTag(''); + assert.deepStrictEqual(extracted?.attributes, { + type: 'text/css' + }); + }); - it('does not extract style tag inside comment', () => { - const text = ` + it('does not extract style tag inside comment', () => { + const text = `

bla

`; - assert.deepStrictEqual(extractStyleTag(text), { - content: 'p{ color: blue; }', - attributes: {}, - start: 108, - end: 125, - startPos: Position.create(3, 23), - endPos: Position.create(3, 40), - container: { start: 101, end: 133 } - }); - }); + assert.deepStrictEqual(extractStyleTag(text), { + content: 'p{ color: blue; }', + attributes: {}, + start: 108, + end: 125, + startPos: Position.create(3, 23), + endPos: Position.create(3, 40), + container: { start: 101, end: 133 } + }); + }); - it('does not extract tags starting with style/script', () => { - // https://github.com/sveltejs/language-tools/issues/43 - // this would previously match .... due to misconfigured attribute matching regex - const text = ` + it('does not extract tags starting with style/script', () => { + // https://github.com/sveltejs/language-tools/issues/43 + // this would previously match .... due to misconfigured attribute matching regex + const text = ` p{ color: blue; }

bla

> `; - assert.deepStrictEqual(extractStyleTag(text), null); - }); + assert.deepStrictEqual(extractStyleTag(text), null); + }); - it('is canse sensitive to style/script', () => { - const text = ` + it('is canse sensitive to style/script', () => { + const text = ` `; - assert.deepStrictEqual(extractStyleTag(text), null); - assert.deepStrictEqual(extractScriptTags(text), null); - }); + assert.deepStrictEqual(extractStyleTag(text), null); + assert.deepStrictEqual(extractScriptTags(text), null); + }); - it('only extract attribute until tag ends', () => { - const text = ` + it('only extract attribute until tag ends', () => { + const text = ` `; - const extracted = extractScriptTags(text); - const attributes = extracted?.script?.attributes; - assert.deepStrictEqual(attributes, { type: 'typescript' }); - }); + const extracted = extractScriptTags(text); + const attributes = extracted?.script?.attributes; + assert.deepStrictEqual(attributes, { type: 'typescript' }); + }); - it('can extract with self-closing component before it', () => { - const extracted = extractStyleTag(''); - assert.deepStrictEqual(extracted, { - start: 22, - end: 22, - startPos: { - character: 22, - line: 0 - }, - endPos: { - character: 22, - line: 0 - }, - attributes: {}, - content: '', - container: { - end: 30, - start: 15 - } - }); - }); + it('can extract with self-closing component before it', () => { + const extracted = extractStyleTag(''); + assert.deepStrictEqual(extracted, { + start: 22, + end: 22, + startPos: { + character: 22, + line: 0 + }, + endPos: { + character: 22, + line: 0 + }, + attributes: {}, + content: '', + container: { + end: 30, + start: 15 + } + }); + }); - it('can extract with unclosed component after it', () => { - const extracted = extractStyleTag('asd

{/if}'); - assert.deepStrictEqual(extracted, { - start: 7, - end: 7, - startPos: { - character: 7, - line: 0 - }, - endPos: { - character: 7, - line: 0 - }, - attributes: {}, - content: '', - container: { - start: 0, - end: 15 - } - }); - }); + it('can extract with unclosed component after it', () => { + const extracted = extractStyleTag('asd

{/if}'); + assert.deepStrictEqual(extracted, { + start: 7, + end: 7, + startPos: { + character: 7, + line: 0 + }, + endPos: { + character: 7, + line: 0 + }, + attributes: {}, + content: '', + container: { + start: 0, + end: 15 + } + }); + }); - it('extracts style tag', () => { - const text = ` + it('extracts style tag', () => { + const text = `

bla

`; - assert.deepStrictEqual(extractStyleTag(text), { - content: 'p{ color: blue; }', - attributes: {}, - start: 51, - end: 68, - startPos: Position.create(2, 23), - endPos: Position.create(2, 40), - container: { start: 44, end: 76 } - }); - }); + assert.deepStrictEqual(extractStyleTag(text), { + content: 'p{ color: blue; }', + attributes: {}, + start: 51, + end: 68, + startPos: Position.create(2, 23), + endPos: Position.create(2, 40), + container: { start: 44, end: 76 } + }); + }); - it('extracts style tag with attributes', () => { - const text = ` + it('extracts style tag with attributes', () => { + const text = ` `; - assert.deepStrictEqual(extractStyleTag(text), { - content: 'p{ color: blue; }', - attributes: { lang: 'scss' }, - start: 36, - end: 53, - startPos: Position.create(1, 35), - endPos: Position.create(1, 52), - container: { start: 17, end: 61 } - }); - }); + assert.deepStrictEqual(extractStyleTag(text), { + content: 'p{ color: blue; }', + attributes: { lang: 'scss' }, + start: 36, + end: 53, + startPos: Position.create(1, 35), + endPos: Position.create(1, 52), + container: { start: 17, end: 61 } + }); + }); - it('extracts style tag with attributes and extra whitespace', () => { - const text = ` + it('extracts style tag with attributes and extra whitespace', () => { + const text = ` `; - assert.deepStrictEqual(extractStyleTag(text), { - content: ' p{ color: blue; } ', - attributes: { lang: 'scss' }, - start: 44, - end: 65, - startPos: Position.create(1, 43), - endPos: Position.create(1, 64), - container: { start: 17, end: 73 } - }); - }); + assert.deepStrictEqual(extractStyleTag(text), { + content: ' p{ color: blue; } ', + attributes: { lang: 'scss' }, + start: 44, + end: 65, + startPos: Position.create(1, 43), + endPos: Position.create(1, 64), + container: { start: 17, end: 73 } + }); + }); - it('extracts top level script tag only', () => { - const text = ` + it('extracts top level script tag only', () => { + const text = ` {#if name} `; - assert.deepStrictEqual(extractScriptTags(text)?.script, { - content: 'top level script', - attributes: {}, - start: 1243, - end: 1259, - startPos: Position.create(34, 24), - endPos: Position.create(34, 40), - container: { start: 1235, end: 1268 } - }); - }); + assert.deepStrictEqual(extractScriptTags(text)?.script, { + content: 'top level script', + attributes: {}, + start: 1243, + end: 1259, + startPos: Position.create(34, 24), + endPos: Position.create(34, 40), + container: { start: 1235, end: 1268 } + }); + }); - it('ignores script tag in svelte:head', () => { - // https://github.com/sveltejs/language-tools/issues/143#issuecomment-636422045 - const text = ` + it('ignores script tag in svelte:head', () => { + // https://github.com/sveltejs/language-tools/issues/143#issuecomment-636422045 + const text = ` `; - assert.deepStrictEqual(extractScriptTags(text), { - moduleScript: { - attributes: { - context: 'module' - }, - container: { - end: 48, - start: 13 - }, - content: 'a', - start: 38, - end: 39, - startPos: { - character: 37, - line: 1 - }, - endPos: { - character: 38, - line: 1 - } - }, - script: { - attributes: {}, - container: { - end: 79, - start: 61 - }, - content: 'b', - start: 69, - end: 70, - startPos: { - character: 20, - line: 2 - }, - endPos: { - character: 21, - line: 2 - } - } - }); - }); + assert.deepStrictEqual(extractScriptTags(text), { + moduleScript: { + attributes: { + context: 'module' + }, + container: { + end: 48, + start: 13 + }, + content: 'a', + start: 38, + end: 39, + startPos: { + character: 37, + line: 1 + }, + endPos: { + character: 38, + line: 1 + } + }, + script: { + attributes: {}, + container: { + end: 79, + start: 61 + }, + content: 'b', + start: 69, + end: 70, + startPos: { + character: 20, + line: 2 + }, + endPos: { + character: 21, + line: 2 + } + } + }); + }); - it('extract tag correctly with #if and < operator', () => { - const text = ` + it('extract tag correctly with #if and < operator', () => { + const text = ` {#if value < 3}
bla @@ -298,98 +298,98 @@ describe('document/utils', () => { {:else if value < 4} {/if}
`; - assert.deepStrictEqual(extractScriptTags(text)?.script, { - content: 'let value = 2', - attributes: {}, - start: 159, - end: 172, - startPos: Position.create(7, 18), - endPos: Position.create(7, 31), - container: { start: 151, end: 181 } - }); - }); - }); + assert.deepStrictEqual(extractScriptTags(text)?.script, { + content: 'let value = 2', + attributes: {}, + start: 159, + end: 172, + startPos: Position.create(7, 18), + endPos: Position.create(7, 31), + container: { start: 151, end: 181 } + }); + }); + }); - describe('#getLineAtPosition', () => { - it('should return line at position (only one line)', () => { - assert.deepStrictEqual(getLineAtPosition(Position.create(0, 1), 'ABC'), 'ABC'); - }); + describe('#getLineAtPosition', () => { + it('should return line at position (only one line)', () => { + assert.deepStrictEqual(getLineAtPosition(Position.create(0, 1), 'ABC'), 'ABC'); + }); - it('should return line at position (multiple lines)', () => { - assert.deepStrictEqual( - getLineAtPosition(Position.create(1, 1), 'ABC\nDEF\nGHI'), - 'DEF\n' - ); - }); - }); + it('should return line at position (multiple lines)', () => { + assert.deepStrictEqual( + getLineAtPosition(Position.create(1, 1), 'ABC\nDEF\nGHI'), + 'DEF\n' + ); + }); + }); - describe('#updateRelativeImport', () => { - it('should update path of component with ending', () => { - const newPath = updateRelativeImport( - 'C:/absolute/path/oldPath', - 'C:/absolute/newPath', - './Component.svelte' - ); - assert.deepStrictEqual(newPath, '../path/oldPath/Component.svelte'); - }); + describe('#updateRelativeImport', () => { + it('should update path of component with ending', () => { + const newPath = updateRelativeImport( + 'C:/absolute/path/oldPath', + 'C:/absolute/newPath', + './Component.svelte' + ); + assert.deepStrictEqual(newPath, '../path/oldPath/Component.svelte'); + }); - it('should update path of file without ending', () => { - const newPath = updateRelativeImport( - 'C:/absolute/path/oldPath', - 'C:/absolute/newPath', - './someTsFile' - ); - assert.deepStrictEqual(newPath, '../path/oldPath/someTsFile'); - }); + it('should update path of file without ending', () => { + const newPath = updateRelativeImport( + 'C:/absolute/path/oldPath', + 'C:/absolute/newPath', + './someTsFile' + ); + assert.deepStrictEqual(newPath, '../path/oldPath/someTsFile'); + }); - it('should update path of file going one up', () => { - const newPath = updateRelativeImport( - 'C:/absolute/path/oldPath', - 'C:/absolute/path', - './someTsFile' - ); - assert.deepStrictEqual(newPath, './oldPath/someTsFile'); - }); - }); + it('should update path of file going one up', () => { + const newPath = updateRelativeImport( + 'C:/absolute/path/oldPath', + 'C:/absolute/path', + './someTsFile' + ); + assert.deepStrictEqual(newPath, './oldPath/someTsFile'); + }); + }); - describe('#getWordAt', () => { - it('returns word between whitespaces', () => { - assert.equal(getWordAt('qwd asd qwd', 5), 'asd'); - }); + describe('#getWordAt', () => { + it('returns word between whitespaces', () => { + assert.equal(getWordAt('qwd asd qwd', 5), 'asd'); + }); - it('returns word between whitespace and end of string', () => { - assert.equal(getWordAt('qwd asd', 5), 'asd'); - }); + it('returns word between whitespace and end of string', () => { + assert.equal(getWordAt('qwd asd', 5), 'asd'); + }); - it('returns word between start of string and whitespace', () => { - assert.equal(getWordAt('asd qwd', 2), 'asd'); - }); + it('returns word between start of string and whitespace', () => { + assert.equal(getWordAt('asd qwd', 2), 'asd'); + }); - it('returns word between start of string and end of string', () => { - assert.equal(getWordAt('asd', 2), 'asd'); - }); + it('returns word between start of string and end of string', () => { + assert.equal(getWordAt('asd', 2), 'asd'); + }); - it('returns word with custom delimiters', () => { - assert.equal( - getWordAt('asd on:asd-qwd="asd" ', 10, { left: /\S+$/, right: /[\s=]/ }), - 'on:asd-qwd' - ); - }); + it('returns word with custom delimiters', () => { + assert.equal( + getWordAt('asd on:asd-qwd="asd" ', 10, { left: /\S+$/, right: /[\s=]/ }), + 'on:asd-qwd' + ); + }); - function testEvent(str: string, pos: number, expected: string) { - assert.equal(getWordAt(str, pos, { left: /\S+$/, right: /[^\w$:]/ }), expected); - } + function testEvent(str: string, pos: number, expected: string) { + assert.equal(getWordAt(str, pos, { left: /\S+$/, right: /[^\w$:]/ }), expected); + } - it('returns event #1', () => { - testEvent('
', 8, 'on:'); - }); + it('returns event #1', () => { + testEvent('
', 8, 'on:'); + }); - it('returns event #2', () => { - testEvent('
', 8, 'on:'); - }); + it('returns event #2', () => { + testEvent('
', 8, 'on:'); + }); - it('returns empty string when only whitespace', () => { - assert.equal(getWordAt('a a', 2), ''); - }); - }); + it('returns empty string when only whitespace', () => { + assert.equal(getWordAt('a a', 2), ''); + }); + }); }); diff --git a/packages/language-server/test/plugins/PluginHost.test.ts b/packages/language-server/test/plugins/PluginHost.test.ts index bfe733a35..8c0fd5c3e 100644 --- a/packages/language-server/test/plugins/PluginHost.test.ts +++ b/packages/language-server/test/plugins/PluginHost.test.ts @@ -1,11 +1,11 @@ import sinon from 'sinon'; import { - CompletionItem, - Location, - LocationLink, - Position, - Range, - TextDocumentItem + CompletionItem, + Location, + LocationLink, + Position, + Range, + TextDocumentItem } from 'vscode-languageserver-types'; import { DocumentManager, Document } from '../../src/lib/documents'; import { LSPProviderConfig, PluginHost } from '../../src/plugins'; @@ -13,171 +13,171 @@ import { CompletionTriggerKind } from 'vscode-languageserver'; import assert from 'assert'; describe('PluginHost', () => { - const textDocument: TextDocumentItem = { - uri: 'file:///hello.svelte', - version: 0, - languageId: 'svelte', - text: 'Hello, world!' - }; - - function setup( - pluginProviderStubs: T, - config: LSPProviderConfig = { - definitionLinkSupport: true, - filterIncompleteCompletions: false - } - ) { - const docManager = new DocumentManager( - (textDocument) => new Document(textDocument.uri, textDocument.text) - ); - - const pluginHost = new PluginHost(docManager); - const plugin = { - ...pluginProviderStubs - }; - - pluginHost.initialize(config); - pluginHost.register(plugin); - - return { docManager, pluginHost, plugin }; - } - - it('executes getDiagnostics on plugins', async () => { - const { docManager, pluginHost, plugin } = setup({ - getDiagnostics: sinon.stub().returns([]) - }); - const document = docManager.openDocument(textDocument); - - await pluginHost.getDiagnostics(textDocument); - - sinon.assert.calledOnce(plugin.getDiagnostics); - sinon.assert.calledWithExactly(plugin.getDiagnostics, document); - }); - - it('executes doHover on plugins', async () => { - const { docManager, pluginHost, plugin } = setup({ - doHover: sinon.stub().returns(null) - }); - const document = docManager.openDocument(textDocument); - const pos = Position.create(0, 0); - - await pluginHost.doHover(textDocument, pos); - - sinon.assert.calledOnce(plugin.doHover); - sinon.assert.calledWithExactly(plugin.doHover, document, pos); - }); - - it('executes getCompletions on plugins', async () => { - const { docManager, pluginHost, plugin } = setup({ - getCompletions: sinon.stub().returns({ items: [] }) - }); - const document = docManager.openDocument(textDocument); - const pos = Position.create(0, 0); - - await pluginHost.getCompletions(textDocument, pos, { - triggerKind: CompletionTriggerKind.TriggerCharacter, - triggerCharacter: '.' - }); - - sinon.assert.calledOnce(plugin.getCompletions); - sinon.assert.calledWithExactly(plugin.getCompletions, document, pos, { - triggerKind: CompletionTriggerKind.TriggerCharacter, - triggerCharacter: '.' - }); - }); - - describe('getCompletions (incomplete)', () => { - function setupGetIncompleteCompletions(filterServerSide: boolean) { - const { docManager, pluginHost } = setup( - { - getCompletions: sinon.stub().returns({ - isIncomplete: true, - items: [{ label: 'Hello' }, { label: 'foo' }] - }) - }, - { definitionLinkSupport: true, filterIncompleteCompletions: filterServerSide } - ); - docManager.openDocument(textDocument); - return pluginHost; - } - - it('filters client side', async () => { - const pluginHost = setupGetIncompleteCompletions(false); - const completions = await pluginHost.getCompletions( - textDocument, - Position.create(0, 2) - ); - - assert.deepStrictEqual(completions.items, [ - { label: 'Hello' }, - { label: 'foo' } - ]); - }); - - it('filters server side', async () => { - const pluginHost = setupGetIncompleteCompletions(true); - const completions = await pluginHost.getCompletions( - textDocument, - Position.create(0, 2) - ); - - assert.deepStrictEqual(completions.items, [{ label: 'Hello' }]); - }); - }); - - describe('getDefinitions', () => { - function setupGetDefinitions(linkSupport: boolean) { - const { pluginHost, docManager } = setup( - { - getDefinitions: sinon.stub().returns([ - { - targetRange: Range.create(Position.create(0, 0), Position.create(0, 2)), - targetSelectionRange: Range.create( - Position.create(0, 0), - Position.create(0, 1) - ), - targetUri: 'uri' - } - ]) - }, - { definitionLinkSupport: linkSupport, filterIncompleteCompletions: false } - ); - docManager.openDocument(textDocument); - return pluginHost; - } - - it('uses LocationLink', async () => { - const pluginHost = setupGetDefinitions(true); - const definitions = await pluginHost.getDefinitions( - textDocument, - Position.create(0, 0) - ); - - assert.deepStrictEqual(definitions, [ - { - targetRange: Range.create(Position.create(0, 0), Position.create(0, 2)), - targetSelectionRange: Range.create( - Position.create(0, 0), - Position.create(0, 1) - ), - targetUri: 'uri' - } - ]); - }); - - it('uses Location', async () => { - const pluginHost = setupGetDefinitions(false); - const definitions = await pluginHost.getDefinitions( - textDocument, - Position.create(0, 0) - ); - - assert.deepStrictEqual(definitions, [ - { - range: Range.create(Position.create(0, 0), Position.create(0, 1)), - uri: 'uri' - } - ]); - }); - }); + const textDocument: TextDocumentItem = { + uri: 'file:///hello.svelte', + version: 0, + languageId: 'svelte', + text: 'Hello, world!' + }; + + function setup( + pluginProviderStubs: T, + config: LSPProviderConfig = { + definitionLinkSupport: true, + filterIncompleteCompletions: false + } + ) { + const docManager = new DocumentManager( + (textDocument) => new Document(textDocument.uri, textDocument.text) + ); + + const pluginHost = new PluginHost(docManager); + const plugin = { + ...pluginProviderStubs + }; + + pluginHost.initialize(config); + pluginHost.register(plugin); + + return { docManager, pluginHost, plugin }; + } + + it('executes getDiagnostics on plugins', async () => { + const { docManager, pluginHost, plugin } = setup({ + getDiagnostics: sinon.stub().returns([]) + }); + const document = docManager.openDocument(textDocument); + + await pluginHost.getDiagnostics(textDocument); + + sinon.assert.calledOnce(plugin.getDiagnostics); + sinon.assert.calledWithExactly(plugin.getDiagnostics, document); + }); + + it('executes doHover on plugins', async () => { + const { docManager, pluginHost, plugin } = setup({ + doHover: sinon.stub().returns(null) + }); + const document = docManager.openDocument(textDocument); + const pos = Position.create(0, 0); + + await pluginHost.doHover(textDocument, pos); + + sinon.assert.calledOnce(plugin.doHover); + sinon.assert.calledWithExactly(plugin.doHover, document, pos); + }); + + it('executes getCompletions on plugins', async () => { + const { docManager, pluginHost, plugin } = setup({ + getCompletions: sinon.stub().returns({ items: [] }) + }); + const document = docManager.openDocument(textDocument); + const pos = Position.create(0, 0); + + await pluginHost.getCompletions(textDocument, pos, { + triggerKind: CompletionTriggerKind.TriggerCharacter, + triggerCharacter: '.' + }); + + sinon.assert.calledOnce(plugin.getCompletions); + sinon.assert.calledWithExactly(plugin.getCompletions, document, pos, { + triggerKind: CompletionTriggerKind.TriggerCharacter, + triggerCharacter: '.' + }); + }); + + describe('getCompletions (incomplete)', () => { + function setupGetIncompleteCompletions(filterServerSide: boolean) { + const { docManager, pluginHost } = setup( + { + getCompletions: sinon.stub().returns({ + isIncomplete: true, + items: [{ label: 'Hello' }, { label: 'foo' }] + }) + }, + { definitionLinkSupport: true, filterIncompleteCompletions: filterServerSide } + ); + docManager.openDocument(textDocument); + return pluginHost; + } + + it('filters client side', async () => { + const pluginHost = setupGetIncompleteCompletions(false); + const completions = await pluginHost.getCompletions( + textDocument, + Position.create(0, 2) + ); + + assert.deepStrictEqual(completions.items, [ + { label: 'Hello' }, + { label: 'foo' } + ]); + }); + + it('filters server side', async () => { + const pluginHost = setupGetIncompleteCompletions(true); + const completions = await pluginHost.getCompletions( + textDocument, + Position.create(0, 2) + ); + + assert.deepStrictEqual(completions.items, [{ label: 'Hello' }]); + }); + }); + + describe('getDefinitions', () => { + function setupGetDefinitions(linkSupport: boolean) { + const { pluginHost, docManager } = setup( + { + getDefinitions: sinon.stub().returns([ + { + targetRange: Range.create(Position.create(0, 0), Position.create(0, 2)), + targetSelectionRange: Range.create( + Position.create(0, 0), + Position.create(0, 1) + ), + targetUri: 'uri' + } + ]) + }, + { definitionLinkSupport: linkSupport, filterIncompleteCompletions: false } + ); + docManager.openDocument(textDocument); + return pluginHost; + } + + it('uses LocationLink', async () => { + const pluginHost = setupGetDefinitions(true); + const definitions = await pluginHost.getDefinitions( + textDocument, + Position.create(0, 0) + ); + + assert.deepStrictEqual(definitions, [ + { + targetRange: Range.create(Position.create(0, 0), Position.create(0, 2)), + targetSelectionRange: Range.create( + Position.create(0, 0), + Position.create(0, 1) + ), + targetUri: 'uri' + } + ]); + }); + + it('uses Location', async () => { + const pluginHost = setupGetDefinitions(false); + const definitions = await pluginHost.getDefinitions( + textDocument, + Position.create(0, 0) + ); + + assert.deepStrictEqual(definitions, [ + { + range: Range.create(Position.create(0, 0), Position.create(0, 1)), + uri: 'uri' + } + ]); + }); + }); }); diff --git a/packages/language-server/test/plugins/css/CSSPlugin.test.ts b/packages/language-server/test/plugins/css/CSSPlugin.test.ts index c29cc1695..759ea2a0f 100644 --- a/packages/language-server/test/plugins/css/CSSPlugin.test.ts +++ b/packages/language-server/test/plugins/css/CSSPlugin.test.ts @@ -1,411 +1,411 @@ import * as assert from 'assert'; import { - Range, - Position, - Hover, - CompletionItem, - CompletionItemKind, - TextEdit, - CompletionContext, - SelectionRange, - CompletionTriggerKind + Range, + Position, + Hover, + CompletionItem, + CompletionItemKind, + TextEdit, + CompletionContext, + SelectionRange, + CompletionTriggerKind } from 'vscode-languageserver'; import { DocumentManager, Document } from '../../../src/lib/documents'; import { CSSPlugin } from '../../../src/plugins'; import { LSConfigManager } from '../../../src/ls-config'; describe('CSS Plugin', () => { - function setup(content: string) { - const document = new Document('file:///hello.svelte', content); - const docManager = new DocumentManager(() => document); - const pluginManager = new LSConfigManager(); - const plugin = new CSSPlugin(docManager, pluginManager); - docManager.openDocument('some doc'); - return { plugin, document }; - } - - describe('provides hover info', () => { - it('for normal css', () => { - const { plugin, document } = setup(''); - - assert.deepStrictEqual(plugin.doHover(document, Position.create(0, 8)), { - contents: [ - { language: 'html', value: '

' }, - '[Selector Specificity](https://developer.mozilla.org/en-US/docs/Web/CSS/Specificity): (0, 0, 1)' - ], - range: Range.create(0, 7, 0, 9) - }); - - assert.strictEqual(plugin.doHover(document, Position.create(0, 10)), null); - }); - - it('not for SASS', () => { - const { plugin, document } = setup(''); - assert.deepStrictEqual(plugin.doHover(document, Position.create(0, 20)), null); - }); - - it('not for stylus', () => { - const { plugin, document } = setup(''); - assert.deepStrictEqual(plugin.doHover(document, Position.create(0, 22)), null); - }); - - it('for style attribute', () => { - const { plugin, document } = setup('
'); - assert.deepStrictEqual(plugin.doHover(document, Position.create(0, 13)), { - contents: { - kind: 'markdown', - value: - 'Specifies the height of the content area,' + - " padding area or border area \\(depending on 'box\\-sizing'\\)" + - ' of certain boxes\\.\n' + - '\nSyntax: <viewport\\-length>\\{1,2\\}\n\n' + - '[MDN Reference](https://developer.mozilla.org/docs/Web/CSS/height)' - }, - range: Range.create(0, 12, 0, 24) - }); - }); - - it('not for style attribute with interpolation', () => { - const { plugin, document } = setup('
'); - assert.deepStrictEqual(plugin.doHover(document, Position.create(0, 13)), null); - }); - }); - - describe('provides completions', () => { - it('for normal css', () => { - const { plugin, document } = setup(''); - - const completions = plugin.getCompletions(document, Position.create(0, 7), { - triggerCharacter: '.' - } as CompletionContext); - assert.ok( - Array.isArray(completions && completions.items), - 'Expected completion items to be an array' - ); - assert.ok(completions!.items.length > 0, 'Expected completions to have length'); - - assert.deepStrictEqual(completions!.items[0], { - label: '@charset', - kind: CompletionItemKind.Keyword, - documentation: { - kind: 'markdown', - value: - 'Defines character set of the document\\.\n\n[MDN Reference](https://developer.mozilla.org/docs/Web/CSS/@charset)' - }, - textEdit: TextEdit.insert(Position.create(0, 7), '@charset'), - tags: [] - }); - }); - - it('for :global modifier', () => { - const { plugin, document } = setup(''); - - const completions = plugin.getCompletions(document, Position.create(0, 9), { - triggerCharacter: ':' - } as CompletionContext); - const globalCompletion = completions?.items.find((item) => item.label === ':global()'); - assert.ok(globalCompletion); - }); - - it('not for stylus', () => { - const { plugin, document } = setup(''); - const completions = plugin.getCompletions(document, Position.create(0, 21), { - triggerCharacter: '.' - } as CompletionContext); - assert.deepStrictEqual(completions, null); - }); - - it('for style attribute', () => { - const { plugin, document } = setup('
'); - const completions = plugin.getCompletions(document, Position.create(0, 22), { - triggerKind: CompletionTriggerKind.Invoked - } as CompletionContext); - assert.deepStrictEqual( - completions?.items.find((item) => item.label === 'none'), - { - insertTextFormat: undefined, - kind: 12, - label: 'none', - documentation: { - kind: 'markdown', - value: 'The element and its descendants generates no boxes\\.' - }, - sortText: ' ', - tags: [], - textEdit: { - newText: 'none', - range: { - start: { - line: 0, - character: 21 - }, - end: { - line: 0, - character: 22 - } - } - } - } - ); - }); - - it('not for style attribute with interpolation', () => { - const { plugin, document } = setup('
'); - assert.deepStrictEqual(plugin.getCompletions(document, Position.create(0, 21)), null); - }); - }); - - describe('provides diagnostics', () => { - it('- everything ok', () => { - const { plugin, document } = setup(''); - - const diagnostics = plugin.getDiagnostics(document); - - assert.deepStrictEqual(diagnostics, []); - }); - - it('- has error', () => { - const { plugin, document } = setup(''); - - const diagnostics = plugin.getDiagnostics(document); - - assert.deepStrictEqual(diagnostics, [ - { - code: 'unknownProperties', - message: "Unknown property: 'iDunnoDisProperty'", - range: { - end: { - character: 28, - line: 0 - }, - start: { - character: 11, - line: 0 - } - }, - severity: 2, - source: 'css' - } - ]); - }); - - it('- no diagnostics for sass', () => { - const { plugin, document } = setup( - `'); + + assert.deepStrictEqual(plugin.doHover(document, Position.create(0, 8)), { + contents: [ + { language: 'html', value: '

' }, + '[Selector Specificity](https://developer.mozilla.org/en-US/docs/Web/CSS/Specificity): (0, 0, 1)' + ], + range: Range.create(0, 7, 0, 9) + }); + + assert.strictEqual(plugin.doHover(document, Position.create(0, 10)), null); + }); + + it('not for SASS', () => { + const { plugin, document } = setup(''); + assert.deepStrictEqual(plugin.doHover(document, Position.create(0, 20)), null); + }); + + it('not for stylus', () => { + const { plugin, document } = setup(''); + assert.deepStrictEqual(plugin.doHover(document, Position.create(0, 22)), null); + }); + + it('for style attribute', () => { + const { plugin, document } = setup('
'); + assert.deepStrictEqual(plugin.doHover(document, Position.create(0, 13)), { + contents: { + kind: 'markdown', + value: + 'Specifies the height of the content area,' + + " padding area or border area \\(depending on 'box\\-sizing'\\)" + + ' of certain boxes\\.\n' + + '\nSyntax: <viewport\\-length>\\{1,2\\}\n\n' + + '[MDN Reference](https://developer.mozilla.org/docs/Web/CSS/height)' + }, + range: Range.create(0, 12, 0, 24) + }); + }); + + it('not for style attribute with interpolation', () => { + const { plugin, document } = setup('
'); + assert.deepStrictEqual(plugin.doHover(document, Position.create(0, 13)), null); + }); + }); + + describe('provides completions', () => { + it('for normal css', () => { + const { plugin, document } = setup(''); + + const completions = plugin.getCompletions(document, Position.create(0, 7), { + triggerCharacter: '.' + } as CompletionContext); + assert.ok( + Array.isArray(completions && completions.items), + 'Expected completion items to be an array' + ); + assert.ok(completions!.items.length > 0, 'Expected completions to have length'); + + assert.deepStrictEqual(completions!.items[0], { + label: '@charset', + kind: CompletionItemKind.Keyword, + documentation: { + kind: 'markdown', + value: + 'Defines character set of the document\\.\n\n[MDN Reference](https://developer.mozilla.org/docs/Web/CSS/@charset)' + }, + textEdit: TextEdit.insert(Position.create(0, 7), '@charset'), + tags: [] + }); + }); + + it('for :global modifier', () => { + const { plugin, document } = setup(''); + + const completions = plugin.getCompletions(document, Position.create(0, 9), { + triggerCharacter: ':' + } as CompletionContext); + const globalCompletion = completions?.items.find((item) => item.label === ':global()'); + assert.ok(globalCompletion); + }); + + it('not for stylus', () => { + const { plugin, document } = setup(''); + const completions = plugin.getCompletions(document, Position.create(0, 21), { + triggerCharacter: '.' + } as CompletionContext); + assert.deepStrictEqual(completions, null); + }); + + it('for style attribute', () => { + const { plugin, document } = setup('
'); + const completions = plugin.getCompletions(document, Position.create(0, 22), { + triggerKind: CompletionTriggerKind.Invoked + } as CompletionContext); + assert.deepStrictEqual( + completions?.items.find((item) => item.label === 'none'), + { + insertTextFormat: undefined, + kind: 12, + label: 'none', + documentation: { + kind: 'markdown', + value: 'The element and its descendants generates no boxes\\.' + }, + sortText: ' ', + tags: [], + textEdit: { + newText: 'none', + range: { + start: { + line: 0, + character: 21 + }, + end: { + line: 0, + character: 22 + } + } + } + } + ); + }); + + it('not for style attribute with interpolation', () => { + const { plugin, document } = setup('
'); + assert.deepStrictEqual(plugin.getCompletions(document, Position.create(0, 21)), null); + }); + }); + + describe('provides diagnostics', () => { + it('- everything ok', () => { + const { plugin, document } = setup(''); + + const diagnostics = plugin.getDiagnostics(document); + + assert.deepStrictEqual(diagnostics, []); + }); + + it('- has error', () => { + const { plugin, document } = setup(''); + + const diagnostics = plugin.getDiagnostics(document); + + assert.deepStrictEqual(diagnostics, [ + { + code: 'unknownProperties', + message: "Unknown property: 'iDunnoDisProperty'", + range: { + end: { + character: 28, + line: 0 + }, + start: { + character: 11, + line: 0 + } + }, + severity: 2, + source: 'css' + } + ]); + }); + + it('- no diagnostics for sass', () => { + const { plugin, document } = setup( + `` - ); - const diagnostics = plugin.getDiagnostics(document); - assert.deepStrictEqual(diagnostics, []); - }); - - it('- no diagnostics for stylus', () => { - const { plugin, document } = setup( - `` - ); - const diagnostics = plugin.getDiagnostics(document); - assert.deepStrictEqual(diagnostics, []); - }); - }); - - describe('provides document colors', () => { - it('for normal css', () => { - const { plugin, document } = setup(''); - - const colors = plugin.getColorPresentations( - document, - { - start: { line: 0, character: 17 }, - end: { line: 0, character: 21 } - }, - { alpha: 1, blue: 255, green: 0, red: 0 } - ); - - assert.deepStrictEqual(colors, [ - { - label: 'rgb(0, 0, 65025)', - textEdit: { - range: { - end: { - character: 21, - line: 0 - }, - start: { - character: 17, - line: 0 - } - }, - newText: 'rgb(0, 0, 65025)' - } - }, - { - label: '#00000fe01', - textEdit: { - range: { - end: { - character: 21, - line: 0 - }, - start: { - character: 17, - line: 0 - } - }, - newText: '#00000fe01' - } - }, - { - label: 'hsl(240, -101%, 12750%)', - textEdit: { - range: { - end: { - character: 21, - line: 0 - }, - start: { - character: 17, - line: 0 - } - }, - newText: 'hsl(240, -101%, 12750%)' - } - } - ]); - }); - - it('not for SASS', () => { - const { plugin, document } = setup(`'); + + const colors = plugin.getColorPresentations( + document, + { + start: { line: 0, character: 17 }, + end: { line: 0, character: 21 } + }, + { alpha: 1, blue: 255, green: 0, red: 0 } + ); + + assert.deepStrictEqual(colors, [ + { + label: 'rgb(0, 0, 65025)', + textEdit: { + range: { + end: { + character: 21, + line: 0 + }, + start: { + character: 17, + line: 0 + } + }, + newText: 'rgb(0, 0, 65025)' + } + }, + { + label: '#00000fe01', + textEdit: { + range: { + end: { + character: 21, + line: 0 + }, + start: { + character: 17, + line: 0 + } + }, + newText: '#00000fe01' + } + }, + { + label: 'hsl(240, -101%, 12750%)', + textEdit: { + range: { + end: { + character: 21, + line: 0 + }, + start: { + character: 17, + line: 0 + } + }, + newText: 'hsl(240, -101%, 12750%)' + } + } + ]); + }); + + it('not for SASS', () => { + const { plugin, document } = setup(``); - assert.deepStrictEqual( - plugin.getColorPresentations( - document, - { - start: { line: 2, character: 22 }, - end: { line: 2, character: 26 } - }, - { alpha: 1, blue: 255, green: 0, red: 0 } - ), - [] - ); - assert.deepStrictEqual(plugin.getDocumentColors(document), []); - }); - - it('not for stylus', () => { - const { plugin, document } = setup(``); - assert.deepStrictEqual( - plugin.getColorPresentations( - document, - { - start: { line: 2, character: 22 }, - end: { line: 2, character: 26 } - }, - { alpha: 1, blue: 255, green: 0, red: 0 } - ), - [] - ); - assert.deepStrictEqual(plugin.getDocumentColors(document), []); - }); - }); - - describe('provides document symbols', () => { - it('for normal css', () => { - const { plugin, document } = setup(''); - - const symbols = plugin.getDocumentSymbols(document); - - assert.deepStrictEqual(symbols, [ - { - containerName: 'style', - kind: 5, - location: { - range: { - end: { - character: 23, - line: 0 - }, - start: { - character: 7, - line: 0 - } - }, - uri: 'file:///hello.svelte' - }, - name: 'h1' - } - ]); - }); - - it('not for SASS', () => { - const { plugin, document } = setup(''); - assert.deepStrictEqual(plugin.getDocumentSymbols(document), []); - }); - - it('not for stylus', () => { - const { plugin, document } = setup(''); - assert.deepStrictEqual(plugin.getDocumentSymbols(document), []); - }); - }); - - it('provides selection range', () => { - const { plugin, document } = setup(''); - - const selectionRange = plugin.getSelectionRange(document, Position.create(0, 11)); - - assert.deepStrictEqual(selectionRange, { - parent: { - parent: { - parent: undefined, - range: { - end: { - character: 12, - line: 0 - }, - start: { - character: 7, - line: 0 - } - } - }, - range: { - end: { - character: 12, - line: 0 - }, - start: { - character: 10, - line: 0 - } - } - }, - range: { - end: { - character: 11, - line: 0 - }, - start: { - character: 11, - line: 0 - } - } - }); - }); - - it('return null for selection range when not in style', () => { - const { plugin, document } = setup(''); - - const selectionRange = plugin.getSelectionRange(document, Position.create(0, 10)); - - assert.equal(selectionRange, null); - }); + assert.deepStrictEqual( + plugin.getColorPresentations( + document, + { + start: { line: 2, character: 22 }, + end: { line: 2, character: 26 } + }, + { alpha: 1, blue: 255, green: 0, red: 0 } + ), + [] + ); + assert.deepStrictEqual(plugin.getDocumentColors(document), []); + }); + }); + + describe('provides document symbols', () => { + it('for normal css', () => { + const { plugin, document } = setup(''); + + const symbols = plugin.getDocumentSymbols(document); + + assert.deepStrictEqual(symbols, [ + { + containerName: 'style', + kind: 5, + location: { + range: { + end: { + character: 23, + line: 0 + }, + start: { + character: 7, + line: 0 + } + }, + uri: 'file:///hello.svelte' + }, + name: 'h1' + } + ]); + }); + + it('not for SASS', () => { + const { plugin, document } = setup(''); + assert.deepStrictEqual(plugin.getDocumentSymbols(document), []); + }); + + it('not for stylus', () => { + const { plugin, document } = setup(''); + assert.deepStrictEqual(plugin.getDocumentSymbols(document), []); + }); + }); + + it('provides selection range', () => { + const { plugin, document } = setup(''); + + const selectionRange = plugin.getSelectionRange(document, Position.create(0, 11)); + + assert.deepStrictEqual(selectionRange, { + parent: { + parent: { + parent: undefined, + range: { + end: { + character: 12, + line: 0 + }, + start: { + character: 7, + line: 0 + } + } + }, + range: { + end: { + character: 12, + line: 0 + }, + start: { + character: 10, + line: 0 + } + } + }, + range: { + end: { + character: 11, + line: 0 + }, + start: { + character: 11, + line: 0 + } + } + }); + }); + + it('return null for selection range when not in style', () => { + const { plugin, document } = setup(''); + + const selectionRange = plugin.getSelectionRange(document, Position.create(0, 10)); + + assert.equal(selectionRange, null); + }); }); diff --git a/packages/language-server/test/plugins/css/features/getIdClassCompletion.test.ts b/packages/language-server/test/plugins/css/features/getIdClassCompletion.test.ts index 6d3321246..1630c10eb 100644 --- a/packages/language-server/test/plugins/css/features/getIdClassCompletion.test.ts +++ b/packages/language-server/test/plugins/css/features/getIdClassCompletion.test.ts @@ -5,74 +5,74 @@ import { LSConfigManager } from '../../../../src/ls-config'; import { CSSPlugin } from '../../../../src/plugins'; import { CSSDocument } from '../../../../src/plugins/css/CSSDocument'; import { - collectSelectors, - NodeType, - CSSNode + collectSelectors, + NodeType, + CSSNode } from '../../../../src/plugins/css/features/getIdClassCompletion'; describe('getIdClassCompletion', () => { - function createDocument(content: string) { - return new Document('file:///hello.svelte', content); - } + function createDocument(content: string) { + return new Document('file:///hello.svelte', content); + } - function createCSSDocument(content: string) { - return new CSSDocument(createDocument(content)); - } + function createCSSDocument(content: string) { + return new CSSDocument(createDocument(content)); + } - function testSelectors(items: CompletionItem[], expectedSelectors: string[]) { - assert.deepStrictEqual( - items.map((item) => item.label), - expectedSelectors, - 'vscode-language-services might have changed the NodeType enum. Check if we need to update it' - ); - } + function testSelectors(items: CompletionItem[], expectedSelectors: string[]) { + assert.deepStrictEqual( + items.map((item) => item.label), + expectedSelectors, + 'vscode-language-services might have changed the NodeType enum. Check if we need to update it' + ); + } - it('collect css classes', () => { - const actual = collectSelectors( - createCSSDocument('').stylesheet as CSSNode, - NodeType.ClassSelector - ); - testSelectors(actual, ['abc']); - }); + it('collect css classes', () => { + const actual = collectSelectors( + createCSSDocument('').stylesheet as CSSNode, + NodeType.ClassSelector + ); + testSelectors(actual, ['abc']); + }); - it('collect css ids', () => { - const actual = collectSelectors( - createCSSDocument('').stylesheet as CSSNode, - NodeType.IdentifierSelector - ); - testSelectors(actual, ['abc']); - }); + it('collect css ids', () => { + const actual = collectSelectors( + createCSSDocument('').stylesheet as CSSNode, + NodeType.IdentifierSelector + ); + testSelectors(actual, ['abc']); + }); - function setup(content: string) { - const document = createDocument(content); - const docManager = new DocumentManager(() => document); - const pluginManager = new LSConfigManager(); - const plugin = new CSSPlugin(docManager, pluginManager); - docManager.openDocument('some doc'); - return { plugin, document }; - } + function setup(content: string) { + const document = createDocument(content); + const docManager = new DocumentManager(() => document); + const pluginManager = new LSConfigManager(); + const plugin = new CSSPlugin(docManager, pluginManager); + docManager.openDocument('some doc'); + return { plugin, document }; + } - it('provides css classes completion for class attribute', () => { - const { plugin, document } = setup('
'); - assert.deepStrictEqual(plugin.getCompletions(document, { line: 0, character: 11 }), { - isIncomplete: false, - items: [{ label: 'abc', kind: CompletionItemKind.Keyword }] - } as CompletionList); - }); + it('provides css classes completion for class attribute', () => { + const { plugin, document } = setup('
'); + assert.deepStrictEqual(plugin.getCompletions(document, { line: 0, character: 11 }), { + isIncomplete: false, + items: [{ label: 'abc', kind: CompletionItemKind.Keyword }] + } as CompletionList); + }); - it('provides css classes completion for class directive', () => { - const { plugin, document } = setup('
'); - assert.deepStrictEqual(plugin.getCompletions(document, { line: 0, character: 11 }), { - isIncomplete: false, - items: [{ label: 'abc', kind: CompletionItemKind.Keyword }] - } as CompletionList); - }); + it('provides css classes completion for class directive', () => { + const { plugin, document } = setup('
'); + assert.deepStrictEqual(plugin.getCompletions(document, { line: 0, character: 11 }), { + isIncomplete: false, + items: [{ label: 'abc', kind: CompletionItemKind.Keyword }] + } as CompletionList); + }); - it('provides css id completion for id attribute', () => { - const { plugin, document } = setup('
'); - assert.deepStrictEqual(plugin.getCompletions(document, { line: 0, character: 8 }), { - isIncomplete: false, - items: [{ label: 'abc', kind: CompletionItemKind.Keyword }] - } as CompletionList); - }); + it('provides css id completion for id attribute', () => { + const { plugin, document } = setup('
'); + assert.deepStrictEqual(plugin.getCompletions(document, { line: 0, character: 8 }), { + isIncomplete: false, + items: [{ label: 'abc', kind: CompletionItemKind.Keyword }] + } as CompletionList); + }); }); diff --git a/packages/language-server/test/plugins/html/HTMLPlugin.test.ts b/packages/language-server/test/plugins/html/HTMLPlugin.test.ts index f1301c421..439efe96e 100644 --- a/packages/language-server/test/plugins/html/HTMLPlugin.test.ts +++ b/packages/language-server/test/plugins/html/HTMLPlugin.test.ts @@ -1,192 +1,192 @@ import * as assert from 'assert'; import { - Range, - Position, - Hover, - CompletionItem, - TextEdit, - CompletionItemKind, - InsertTextFormat + Range, + Position, + Hover, + CompletionItem, + TextEdit, + CompletionItemKind, + InsertTextFormat } from 'vscode-languageserver'; import { HTMLPlugin } from '../../../src/plugins'; import { DocumentManager, Document } from '../../../src/lib/documents'; import { LSConfigManager } from '../../../src/ls-config'; describe('HTML Plugin', () => { - function setup(content: string) { - const document = new Document('file:///hello.svelte', content); - const docManager = new DocumentManager(() => document); - const pluginManager = new LSConfigManager(); - const plugin = new HTMLPlugin(docManager, pluginManager); - docManager.openDocument('some doc'); - return { plugin, document }; - } - - it('provides hover info', async () => { - const { plugin, document } = setup('

Hello, world!

'); - - assert.deepStrictEqual(plugin.doHover(document, Position.create(0, 2)), { - contents: { - kind: 'markdown', - value: - 'The h1 element represents a section heading.\n\n[MDN Reference](https://developer.mozilla.org/docs/Web/HTML/Element/Heading_Elements)' - }, - - range: Range.create(0, 1, 0, 3) - }); - - assert.strictEqual(plugin.doHover(document, Position.create(0, 10)), null); - }); - - it('does not provide hover info for component having the same name as a html element but being uppercase', async () => { - const { plugin, document } = setup('
'); - - assert.deepStrictEqual(plugin.doHover(document, Position.create(0, 2)), null); - }); - - it('provides completions', async () => { - const { plugin, document } = setup('<'); - - const completions = plugin.getCompletions(document, Position.create(0, 1)); - assert.ok(Array.isArray(completions && completions.items)); - assert.ok(completions!.items.length > 0); - - assert.deepStrictEqual(completions!.items[0], { - label: '!DOCTYPE', - kind: CompletionItemKind.Property, - documentation: 'A preamble for an HTML document.', - textEdit: TextEdit.insert(Position.create(0, 1), '!DOCTYPE html>'), - insertTextFormat: InsertTextFormat.PlainText - }); - }); - - it('does not provide completions inside of moustache tag', async () => { - const { plugin, document } = setup('
'); - - const completions = plugin.getCompletions(document, Position.create(0, 20)); - assert.strictEqual(completions, null); - - const tagCompletion = plugin.doTagComplete(document, Position.create(0, 20)); - assert.strictEqual(tagCompletion, null); - }); - - it('does provide completions outside of moustache tag', async () => { - const { plugin, document } = setup('
'); - - const completions = plugin.getCompletions(document, Position.create(0, 21)); - assert.deepEqual(completions?.items[0], { - filterText: '
', - insertTextFormat: 2, - kind: 10, - label: '
', - textEdit: { - newText: '$0

', - range: { - end: { - character: 21, - line: 0 - }, - start: { - character: 21, - line: 0 - } - } - } - }); - - const tagCompletion = plugin.doTagComplete(document, Position.create(0, 21)); - assert.strictEqual(tagCompletion, '$0
'); - }); - - it('does provide lang in completions', async () => { - const { plugin, document } = setup(' item.label === 'style (lang="less")')); - }); - - it('does not provide lang in completions for attributes', async () => { - const { plugin, document } = setup('
item.label === 'style (lang="less")'), - undefined - ); - }); - - it('does not provide rename for element being uppercase', async () => { - const { plugin, document } = setup('
'); - - assert.deepStrictEqual(plugin.prepareRename(document, Position.create(0, 2)), null); - assert.deepStrictEqual(plugin.rename(document, Position.create(0, 2), 'p'), null); - }); - - it('does not provide rename for valid element but incorrect position', () => { - const { plugin, document } = setup('
ab}>asd
'); - const newName = 'p'; - - assert.deepStrictEqual(plugin.prepareRename(document, Position.create(0, 16)), null); - assert.deepStrictEqual(plugin.prepareRename(document, Position.create(0, 5)), null); - assert.deepStrictEqual(plugin.prepareRename(document, Position.create(0, 26)), null); - - assert.deepStrictEqual(plugin.rename(document, Position.create(0, 16), newName), null); - assert.deepStrictEqual(plugin.rename(document, Position.create(0, 5), newName), null); - assert.deepStrictEqual(plugin.rename(document, Position.create(0, 26), newName), null); - }); - - it('provides rename for element', () => { - const { plugin, document } = setup('
{}}>
'); - const newName = 'p'; - - const pepareRenameInfo = Range.create(Position.create(0, 1), Position.create(0, 4)); - assert.deepStrictEqual( - plugin.prepareRename(document, Position.create(0, 2)), - pepareRenameInfo - ); - assert.deepStrictEqual( - plugin.prepareRename(document, Position.create(0, 28)), - pepareRenameInfo - ); - - const renameInfo = { - changes: { - [document.uri]: [ - { - newText: 'p', - range: { - start: { line: 0, character: 1 }, - end: { line: 0, character: 4 } - } - }, - { - newText: 'p', - range: { - start: { line: 0, character: 27 }, - end: { line: 0, character: 30 } - } - } - ] - } - }; - assert.deepStrictEqual(plugin.rename(document, Position.create(0, 2), newName), renameInfo); - assert.deepStrictEqual( - plugin.rename(document, Position.create(0, 28), newName), - renameInfo - ); - }); - - it('provides linked editing ranges', async () => { - const { plugin, document } = setup('
'); - - const ranges = plugin.getLinkedEditingRanges(document, Position.create(0, 3)); - assert.deepStrictEqual(ranges, { - ranges: [ - { start: { line: 0, character: 1 }, end: { line: 0, character: 4 } }, - { start: { line: 0, character: 7 }, end: { line: 0, character: 10 } } - ] - }); - }); + function setup(content: string) { + const document = new Document('file:///hello.svelte', content); + const docManager = new DocumentManager(() => document); + const pluginManager = new LSConfigManager(); + const plugin = new HTMLPlugin(docManager, pluginManager); + docManager.openDocument('some doc'); + return { plugin, document }; + } + + it('provides hover info', async () => { + const { plugin, document } = setup('

Hello, world!

'); + + assert.deepStrictEqual(plugin.doHover(document, Position.create(0, 2)), { + contents: { + kind: 'markdown', + value: + 'The h1 element represents a section heading.\n\n[MDN Reference](https://developer.mozilla.org/docs/Web/HTML/Element/Heading_Elements)' + }, + + range: Range.create(0, 1, 0, 3) + }); + + assert.strictEqual(plugin.doHover(document, Position.create(0, 10)), null); + }); + + it('does not provide hover info for component having the same name as a html element but being uppercase', async () => { + const { plugin, document } = setup('
'); + + assert.deepStrictEqual(plugin.doHover(document, Position.create(0, 2)), null); + }); + + it('provides completions', async () => { + const { plugin, document } = setup('<'); + + const completions = plugin.getCompletions(document, Position.create(0, 1)); + assert.ok(Array.isArray(completions && completions.items)); + assert.ok(completions!.items.length > 0); + + assert.deepStrictEqual(completions!.items[0], { + label: '!DOCTYPE', + kind: CompletionItemKind.Property, + documentation: 'A preamble for an HTML document.', + textEdit: TextEdit.insert(Position.create(0, 1), '!DOCTYPE html>'), + insertTextFormat: InsertTextFormat.PlainText + }); + }); + + it('does not provide completions inside of moustache tag', async () => { + const { plugin, document } = setup('
'); + + const completions = plugin.getCompletions(document, Position.create(0, 20)); + assert.strictEqual(completions, null); + + const tagCompletion = plugin.doTagComplete(document, Position.create(0, 20)); + assert.strictEqual(tagCompletion, null); + }); + + it('does provide completions outside of moustache tag', async () => { + const { plugin, document } = setup('
'); + + const completions = plugin.getCompletions(document, Position.create(0, 21)); + assert.deepEqual(completions?.items[0], { + filterText: '
', + insertTextFormat: 2, + kind: 10, + label: '
', + textEdit: { + newText: '$0
', + range: { + end: { + character: 21, + line: 0 + }, + start: { + character: 21, + line: 0 + } + } + } + }); + + const tagCompletion = plugin.doTagComplete(document, Position.create(0, 21)); + assert.strictEqual(tagCompletion, '$0
'); + }); + + it('does provide lang in completions', async () => { + const { plugin, document } = setup(' item.label === 'style (lang="less")')); + }); + + it('does not provide lang in completions for attributes', async () => { + const { plugin, document } = setup('
item.label === 'style (lang="less")'), + undefined + ); + }); + + it('does not provide rename for element being uppercase', async () => { + const { plugin, document } = setup('
'); + + assert.deepStrictEqual(plugin.prepareRename(document, Position.create(0, 2)), null); + assert.deepStrictEqual(plugin.rename(document, Position.create(0, 2), 'p'), null); + }); + + it('does not provide rename for valid element but incorrect position', () => { + const { plugin, document } = setup('
ab}>asd
'); + const newName = 'p'; + + assert.deepStrictEqual(plugin.prepareRename(document, Position.create(0, 16)), null); + assert.deepStrictEqual(plugin.prepareRename(document, Position.create(0, 5)), null); + assert.deepStrictEqual(plugin.prepareRename(document, Position.create(0, 26)), null); + + assert.deepStrictEqual(plugin.rename(document, Position.create(0, 16), newName), null); + assert.deepStrictEqual(plugin.rename(document, Position.create(0, 5), newName), null); + assert.deepStrictEqual(plugin.rename(document, Position.create(0, 26), newName), null); + }); + + it('provides rename for element', () => { + const { plugin, document } = setup('
{}}>
'); + const newName = 'p'; + + const pepareRenameInfo = Range.create(Position.create(0, 1), Position.create(0, 4)); + assert.deepStrictEqual( + plugin.prepareRename(document, Position.create(0, 2)), + pepareRenameInfo + ); + assert.deepStrictEqual( + plugin.prepareRename(document, Position.create(0, 28)), + pepareRenameInfo + ); + + const renameInfo = { + changes: { + [document.uri]: [ + { + newText: 'p', + range: { + start: { line: 0, character: 1 }, + end: { line: 0, character: 4 } + } + }, + { + newText: 'p', + range: { + start: { line: 0, character: 27 }, + end: { line: 0, character: 30 } + } + } + ] + } + }; + assert.deepStrictEqual(plugin.rename(document, Position.create(0, 2), newName), renameInfo); + assert.deepStrictEqual( + plugin.rename(document, Position.create(0, 28), newName), + renameInfo + ); + }); + + it('provides linked editing ranges', async () => { + const { plugin, document } = setup('
'); + + const ranges = plugin.getLinkedEditingRanges(document, Position.create(0, 3)); + assert.deepStrictEqual(ranges, { + ranges: [ + { start: { line: 0, character: 1 }, end: { line: 0, character: 4 } }, + { start: { line: 0, character: 7 }, end: { line: 0, character: 10 } } + ] + }); + }); }); diff --git a/packages/language-server/test/plugins/svelte/SvelteDocument.test.ts b/packages/language-server/test/plugins/svelte/SvelteDocument.test.ts index 02d24cc7d..c44dcdb57 100644 --- a/packages/language-server/test/plugins/svelte/SvelteDocument.test.ts +++ b/packages/language-server/test/plugins/svelte/SvelteDocument.test.ts @@ -4,126 +4,126 @@ import { Position } from 'vscode-languageserver'; import { Document } from '../../../src/lib/documents'; import * as importPackage from '../../../src/importPackage'; import { - SvelteDocument, - TranspiledSvelteDocument + SvelteDocument, + TranspiledSvelteDocument } from '../../../src/plugins/svelte/SvelteDocument'; import { configLoader, SvelteConfig } from '../../../src/lib/documents/configLoader'; describe('Svelte Document', () => { - function getSourceCode(transpiled: boolean): string { - return ` + function getSourceCode(transpiled: boolean): string { + return `

jo

Hello, world!

`; - } - - function setup(config: SvelteConfig = {}) { - sinon.stub(configLoader, 'getConfig').returns(config); - const parent = new Document('file:///hello.svelte', getSourceCode(false)); - sinon.restore(); - const svelteDoc = new SvelteDocument(parent); - return { parent, svelteDoc }; - } - - it('gets the parents text', () => { - const { parent, svelteDoc } = setup(); - assert.strictEqual(svelteDoc.getText(), parent.getText()); - }); - - describe('#transpiled', () => { - async function setupTranspiled() { - const { parent, svelteDoc } = setup({ - preprocess: { - script: () => ({ - code: '', - map: JSON.stringify({ - version: 3, - file: '', - names: [], - sources: [], - sourceRoot: '', - mappings: '' - }) - }) - } - }); - - // stub svelte preprocess and getOriginalPosition - // to fake a source mapping process - sinon.stub(importPackage, 'importSvelte').returns({ - preprocess: (text, preprocessor) => { - preprocessor = Array.isArray(preprocessor) ? preprocessor : [preprocessor]; - preprocessor.forEach((p) => p.script?.({})); - return Promise.resolve({ - code: getSourceCode(true), - dependencies: [], - toString: () => getSourceCode(true), - map: null - }); - }, - walk: null, - VERSION: '', - compile: null, - parse: null - }); - const transpiled = await svelteDoc.getTranspiled(); - const scriptSourceMapper = (transpiled.scriptMapper).sourceMapper; - // hacky reset of method because mocking the SourceMap constructor is an impossible task - scriptSourceMapper.getOriginalPosition = ({ line, character }: Position) => ({ - line: line - 1, - character - }); - scriptSourceMapper.getGeneratedPosition = ({ line, character }: Position) => ({ - line: line + 1, - character - }); - sinon.restore(); - - return { parent, svelteDoc, transpiled }; - } - - function assertCanMapBackAndForth( - transpiled: TranspiledSvelteDocument, - generatedPosition: Position, - originalPosition: Position - ) { - assert.deepStrictEqual( - transpiled.getOriginalPosition(generatedPosition), - originalPosition, - 'error mapping to original position' - ); - - assert.deepStrictEqual( - transpiled.getGeneratedPosition(originalPosition), - generatedPosition, - 'error mapping to generated position' - ); - } - - it('should map correctly within sourcemapped script', async () => { - const { transpiled } = await setupTranspiled(); - - assertCanMapBackAndForth(transpiled, Position.create(3, 2), Position.create(2, 18)); - }); - - it('should map correctly in template before script', async () => { - const { transpiled } = await setupTranspiled(); - - assertCanMapBackAndForth(transpiled, Position.create(1, 1), Position.create(1, 1)); - }); - - it('should map correctly in template after script', async () => { - const { transpiled } = await setupTranspiled(); - - assertCanMapBackAndForth(transpiled, Position.create(4, 1), Position.create(3, 1)); - }); - - it('should map correctly in style', async () => { - const { transpiled } = await setupTranspiled(); - - assertCanMapBackAndForth(transpiled, Position.create(5, 18), Position.create(4, 18)); - }); - }); + } + + function setup(config: SvelteConfig = {}) { + sinon.stub(configLoader, 'getConfig').returns(config); + const parent = new Document('file:///hello.svelte', getSourceCode(false)); + sinon.restore(); + const svelteDoc = new SvelteDocument(parent); + return { parent, svelteDoc }; + } + + it('gets the parents text', () => { + const { parent, svelteDoc } = setup(); + assert.strictEqual(svelteDoc.getText(), parent.getText()); + }); + + describe('#transpiled', () => { + async function setupTranspiled() { + const { parent, svelteDoc } = setup({ + preprocess: { + script: () => ({ + code: '', + map: JSON.stringify({ + version: 3, + file: '', + names: [], + sources: [], + sourceRoot: '', + mappings: '' + }) + }) + } + }); + + // stub svelte preprocess and getOriginalPosition + // to fake a source mapping process + sinon.stub(importPackage, 'importSvelte').returns({ + preprocess: (text, preprocessor) => { + preprocessor = Array.isArray(preprocessor) ? preprocessor : [preprocessor]; + preprocessor.forEach((p) => p.script?.({})); + return Promise.resolve({ + code: getSourceCode(true), + dependencies: [], + toString: () => getSourceCode(true), + map: null + }); + }, + walk: null, + VERSION: '', + compile: null, + parse: null + }); + const transpiled = await svelteDoc.getTranspiled(); + const scriptSourceMapper = (transpiled.scriptMapper).sourceMapper; + // hacky reset of method because mocking the SourceMap constructor is an impossible task + scriptSourceMapper.getOriginalPosition = ({ line, character }: Position) => ({ + line: line - 1, + character + }); + scriptSourceMapper.getGeneratedPosition = ({ line, character }: Position) => ({ + line: line + 1, + character + }); + sinon.restore(); + + return { parent, svelteDoc, transpiled }; + } + + function assertCanMapBackAndForth( + transpiled: TranspiledSvelteDocument, + generatedPosition: Position, + originalPosition: Position + ) { + assert.deepStrictEqual( + transpiled.getOriginalPosition(generatedPosition), + originalPosition, + 'error mapping to original position' + ); + + assert.deepStrictEqual( + transpiled.getGeneratedPosition(originalPosition), + generatedPosition, + 'error mapping to generated position' + ); + } + + it('should map correctly within sourcemapped script', async () => { + const { transpiled } = await setupTranspiled(); + + assertCanMapBackAndForth(transpiled, Position.create(3, 2), Position.create(2, 18)); + }); + + it('should map correctly in template before script', async () => { + const { transpiled } = await setupTranspiled(); + + assertCanMapBackAndForth(transpiled, Position.create(1, 1), Position.create(1, 1)); + }); + + it('should map correctly in template after script', async () => { + const { transpiled } = await setupTranspiled(); + + assertCanMapBackAndForth(transpiled, Position.create(4, 1), Position.create(3, 1)); + }); + + it('should map correctly in style', async () => { + const { transpiled } = await setupTranspiled(); + + assertCanMapBackAndForth(transpiled, Position.create(5, 18), Position.create(4, 18)); + }); + }); }); diff --git a/packages/language-server/test/plugins/svelte/SveltePlugin.test.ts b/packages/language-server/test/plugins/svelte/SveltePlugin.test.ts index 78646827c..a82df4252 100644 --- a/packages/language-server/test/plugins/svelte/SveltePlugin.test.ts +++ b/packages/language-server/test/plugins/svelte/SveltePlugin.test.ts @@ -7,140 +7,140 @@ import * as importPackage from '../../../src/importPackage'; import sinon from 'sinon'; describe('Svelte Plugin', () => { - function setup(content: string, prettierConfig?: any) { - const document = new Document('file:///hello.svelte', content); - const docManager = new DocumentManager(() => document); - const pluginManager = new LSConfigManager(); - pluginManager.updatePrettierConfig(prettierConfig); - const plugin = new SveltePlugin(pluginManager); - docManager.openDocument('some doc'); - return { plugin, document }; - } - - it('provides diagnostic warnings', async () => { - const { plugin, document } = setup('

Hello, world!

\n'); - - const diagnostics = await plugin.getDiagnostics(document); - const diagnostic = Diagnostic.create( - Range.create(1, 0, 1, 21), - 'A11y: element should have an alt attribute', - DiagnosticSeverity.Warning, - 'a11y-missing-attribute', - 'svelte' - ); - - assert.deepStrictEqual(diagnostics, [diagnostic]); - }); - - it('provides diagnostic errors', async () => { - const { plugin, document } = setup('
'); - - const diagnostics = await plugin.getDiagnostics(document); - const diagnostic = Diagnostic.create( - Range.create(0, 10, 0, 18), - 'whatever is not declared', - DiagnosticSeverity.Error, - 'binding-undeclared', - 'svelte' - ); - - assert.deepStrictEqual(diagnostics, [diagnostic]); - }); - - describe('#formatDocument', () => { - function stubPrettier(config: any) { - const formatStub = sinon.stub().returns('formatted'); - - sinon.stub(importPackage, 'importPrettier').returns({ - resolveConfig: () => Promise.resolve(config), - getFileInfo: () => ({ ignored: false }), - format: formatStub, - getSupportInfo: () => ({ languages: [{ name: 'svelte' }] }) - }); - - return formatStub; - } - - async function testFormat(config: any, fallbackPrettierConfig: any) { - const { plugin, document } = setup('unformatted', fallbackPrettierConfig); - const formatStub = stubPrettier(config); - - const formatted = await plugin.formatDocument(document, { - insertSpaces: true, - tabSize: 4 - }); - assert.deepStrictEqual(formatted, [ - { - newText: 'formatted', - range: { - end: { - character: 11, - line: 0 - }, - start: { - character: 0, - line: 0 - } - } - } - ]); - - return formatStub; - } - - afterEach(() => { - sinon.restore(); - }); - - it('should use config for formatting', async () => { - const formatStub = await testFormat({ fromConfig: true }, { fallbackConfig: true }); - sinon.assert.calledOnceWithExactly(formatStub, 'unformatted', { - fromConfig: true, - plugins: [], - parser: 'svelte' - }); - }); - - const defaultSettings = { - svelteSortOrder: 'options-scripts-markup-styles', - svelteStrictMode: false, - svelteAllowShorthand: true, - svelteBracketNewLine: true, - svelteIndentScriptAndStyle: true, - printWidth: 80, - singleQuote: false - }; - - it('should use prettier fallback config for formatting', async () => { - const formatStub = await testFormat(undefined, { fallbackConfig: true }); - sinon.assert.calledOnceWithExactly(formatStub, 'unformatted', { - fallbackConfig: true, - plugins: [], - parser: 'svelte', - ...defaultSettings - }); - }); - - it('should use FormattingOptions for formatting', async () => { - const formatStub = await testFormat(undefined, undefined); - sinon.assert.calledOnceWithExactly(formatStub, 'unformatted', { - tabWidth: 4, - useTabs: false, - plugins: [], - parser: 'svelte', - ...defaultSettings - }); - }); - - it('should use FormattingOptions for formatting when configs are empty objects', async () => { - const formatStub = await testFormat({}, {}); - sinon.assert.calledOnceWithExactly(formatStub, 'unformatted', { - tabWidth: 4, - useTabs: false, - plugins: [], - parser: 'svelte', - ...defaultSettings - }); - }); - }); + function setup(content: string, prettierConfig?: any) { + const document = new Document('file:///hello.svelte', content); + const docManager = new DocumentManager(() => document); + const pluginManager = new LSConfigManager(); + pluginManager.updatePrettierConfig(prettierConfig); + const plugin = new SveltePlugin(pluginManager); + docManager.openDocument('some doc'); + return { plugin, document }; + } + + it('provides diagnostic warnings', async () => { + const { plugin, document } = setup('

Hello, world!

\n'); + + const diagnostics = await plugin.getDiagnostics(document); + const diagnostic = Diagnostic.create( + Range.create(1, 0, 1, 21), + 'A11y: element should have an alt attribute', + DiagnosticSeverity.Warning, + 'a11y-missing-attribute', + 'svelte' + ); + + assert.deepStrictEqual(diagnostics, [diagnostic]); + }); + + it('provides diagnostic errors', async () => { + const { plugin, document } = setup('
'); + + const diagnostics = await plugin.getDiagnostics(document); + const diagnostic = Diagnostic.create( + Range.create(0, 10, 0, 18), + 'whatever is not declared', + DiagnosticSeverity.Error, + 'binding-undeclared', + 'svelte' + ); + + assert.deepStrictEqual(diagnostics, [diagnostic]); + }); + + describe('#formatDocument', () => { + function stubPrettier(config: any) { + const formatStub = sinon.stub().returns('formatted'); + + sinon.stub(importPackage, 'importPrettier').returns({ + resolveConfig: () => Promise.resolve(config), + getFileInfo: () => ({ ignored: false }), + format: formatStub, + getSupportInfo: () => ({ languages: [{ name: 'svelte' }] }) + }); + + return formatStub; + } + + async function testFormat(config: any, fallbackPrettierConfig: any) { + const { plugin, document } = setup('unformatted', fallbackPrettierConfig); + const formatStub = stubPrettier(config); + + const formatted = await plugin.formatDocument(document, { + insertSpaces: true, + tabSize: 4 + }); + assert.deepStrictEqual(formatted, [ + { + newText: 'formatted', + range: { + end: { + character: 11, + line: 0 + }, + start: { + character: 0, + line: 0 + } + } + } + ]); + + return formatStub; + } + + afterEach(() => { + sinon.restore(); + }); + + it('should use config for formatting', async () => { + const formatStub = await testFormat({ fromConfig: true }, { fallbackConfig: true }); + sinon.assert.calledOnceWithExactly(formatStub, 'unformatted', { + fromConfig: true, + plugins: [], + parser: 'svelte' + }); + }); + + const defaultSettings = { + svelteSortOrder: 'options-scripts-markup-styles', + svelteStrictMode: false, + svelteAllowShorthand: true, + svelteBracketNewLine: true, + svelteIndentScriptAndStyle: true, + printWidth: 80, + singleQuote: false + }; + + it('should use prettier fallback config for formatting', async () => { + const formatStub = await testFormat(undefined, { fallbackConfig: true }); + sinon.assert.calledOnceWithExactly(formatStub, 'unformatted', { + fallbackConfig: true, + plugins: [], + parser: 'svelte', + ...defaultSettings + }); + }); + + it('should use FormattingOptions for formatting', async () => { + const formatStub = await testFormat(undefined, undefined); + sinon.assert.calledOnceWithExactly(formatStub, 'unformatted', { + tabWidth: 4, + useTabs: false, + plugins: [], + parser: 'svelte', + ...defaultSettings + }); + }); + + it('should use FormattingOptions for formatting when configs are empty objects', async () => { + const formatStub = await testFormat({}, {}); + sinon.assert.calledOnceWithExactly(formatStub, 'unformatted', { + tabWidth: 4, + useTabs: false, + plugins: [], + parser: 'svelte', + ...defaultSettings + }); + }); + }); }); diff --git a/packages/language-server/test/plugins/svelte/features/getCodeAction.test.ts b/packages/language-server/test/plugins/svelte/features/getCodeAction.test.ts index 6f014a87d..432baa959 100644 --- a/packages/language-server/test/plugins/svelte/features/getCodeAction.test.ts +++ b/packages/language-server/test/plugins/svelte/features/getCodeAction.test.ts @@ -3,343 +3,343 @@ import * as fs from 'fs'; import { EOL } from 'os'; import * as path from 'path'; import { - CodeAction, - CodeActionContext, - CreateFile, - DiagnosticSeverity, - Position, - Range, - TextDocumentEdit, - TextEdit, - OptionalVersionedTextDocumentIdentifier, - WorkspaceEdit + CodeAction, + CodeActionContext, + CreateFile, + DiagnosticSeverity, + Position, + Range, + TextDocumentEdit, + TextEdit, + OptionalVersionedTextDocumentIdentifier, + WorkspaceEdit } from 'vscode-languageserver'; import { Document } from '../../../../src/lib/documents'; import { getCodeActions } from '../../../../src/plugins/svelte/features/getCodeActions'; import { - executeRefactoringCommand, - ExtractComponentArgs, - extractComponentCommand + executeRefactoringCommand, + ExtractComponentArgs, + extractComponentCommand } from '../../../../src/plugins/svelte/features/getCodeActions/getRefactorings'; import { SvelteDocument } from '../../../../src/plugins/svelte/SvelteDocument'; import { pathToUrl } from '../../../../src/utils'; describe('SveltePlugin#getCodeAction', () => { - const testDir = path.join(__dirname, '..', 'testfiles'); + const testDir = path.join(__dirname, '..', 'testfiles'); - function getFullPath(filename: string) { - return path.join(testDir, filename); - } + function getFullPath(filename: string) { + return path.join(testDir, filename); + } - function getUri(filename: string) { - return pathToUrl(getFullPath(filename)); - } + function getUri(filename: string) { + return pathToUrl(getFullPath(filename)); + } - async function expectCodeActionFor(filename: string, context: CodeActionContext) { - const filePath = path.join(testDir, filename); - const document = new Document( - pathToUrl(filePath), - filename ? fs.readFileSync(filePath)?.toString() : '' - ); - const svelteDoc = new SvelteDocument(document); - const codeAction = await getCodeActions( - svelteDoc, - Range.create(Position.create(0, 0), Position.create(0, 0)), - context - ); - return { - toEqual: (expected: CodeAction[]) => assert.deepStrictEqual(codeAction, expected) - }; - } + async function expectCodeActionFor(filename: string, context: CodeActionContext) { + const filePath = path.join(testDir, filename); + const document = new Document( + pathToUrl(filePath), + filename ? fs.readFileSync(filePath)?.toString() : '' + ); + const svelteDoc = new SvelteDocument(document); + const codeAction = await getCodeActions( + svelteDoc, + Range.create(Position.create(0, 0), Position.create(0, 0)), + context + ); + return { + toEqual: (expected: CodeAction[]) => assert.deepStrictEqual(codeAction, expected) + }; + } - describe('It should not provide svelte ignore code actions', () => { - const startRange: Range = Range.create( - { line: 0, character: 0 }, - { line: 0, character: 1 } - ); - it('if no svelte diagnostic', async () => { - ( - await expectCodeActionFor('', { - diagnostics: [ - { - code: 'whatever', - source: 'eslint', - range: startRange, - message: '' - } - ] - }) - ).toEqual([]); - }); + describe('It should not provide svelte ignore code actions', () => { + const startRange: Range = Range.create( + { line: 0, character: 0 }, + { line: 0, character: 1 } + ); + it('if no svelte diagnostic', async () => { + ( + await expectCodeActionFor('', { + diagnostics: [ + { + code: 'whatever', + source: 'eslint', + range: startRange, + message: '' + } + ] + }) + ).toEqual([]); + }); - it('if no diagnostic code', async () => { - ( - await expectCodeActionFor('', { - diagnostics: [ - { - source: 'svelte', - range: startRange, - message: '' - } - ] - }) - ).toEqual([]); - }); + it('if no diagnostic code', async () => { + ( + await expectCodeActionFor('', { + diagnostics: [ + { + source: 'svelte', + range: startRange, + message: '' + } + ] + }) + ).toEqual([]); + }); - it('if diagnostic is error', async () => { - ( - await expectCodeActionFor('', { - diagnostics: [ - { - source: 'svelte', - range: startRange, - message: '', - severity: DiagnosticSeverity.Error - } - ] - }) - ).toEqual([]); - }); - }); + it('if diagnostic is error', async () => { + ( + await expectCodeActionFor('', { + diagnostics: [ + { + source: 'svelte', + range: startRange, + message: '', + severity: DiagnosticSeverity.Error + } + ] + }) + ).toEqual([]); + }); + }); - describe('It should provide svelte ignore code actions ', () => { - const svelteIgnoreCodeAction = 'svelte-ignore-code-action.svelte'; + describe('It should provide svelte ignore code actions ', () => { + const svelteIgnoreCodeAction = 'svelte-ignore-code-action.svelte'; - it('should provide ignore comment', async () => { - ( - await expectCodeActionFor(svelteIgnoreCodeAction, { - diagnostics: [ - { - severity: DiagnosticSeverity.Warning, - code: 'a11y-missing-attribute', - range: Range.create( - { line: 0, character: 0 }, - { line: 0, character: 6 } - ), - message: '', - source: 'svelte' - } - ] - }) - ).toEqual([ - { - edit: { - documentChanges: [ - { - edits: [ - { - // eslint-disable-next-line max-len - newText: `${EOL}`, - range: { - end: { - character: 0, - line: 0 - }, - start: { - character: 0, - line: 0 - } - } - } - ], - textDocument: { - uri: getUri(svelteIgnoreCodeAction), - version: null - } - } - ] - }, - title: '(svelte) Disable a11y-missing-attribute for this line', - kind: 'quickfix' - } - ]); - }); + it('should provide ignore comment', async () => { + ( + await expectCodeActionFor(svelteIgnoreCodeAction, { + diagnostics: [ + { + severity: DiagnosticSeverity.Warning, + code: 'a11y-missing-attribute', + range: Range.create( + { line: 0, character: 0 }, + { line: 0, character: 6 } + ), + message: '', + source: 'svelte' + } + ] + }) + ).toEqual([ + { + edit: { + documentChanges: [ + { + edits: [ + { + // eslint-disable-next-line max-len + newText: `${EOL}`, + range: { + end: { + character: 0, + line: 0 + }, + start: { + character: 0, + line: 0 + } + } + } + ], + textDocument: { + uri: getUri(svelteIgnoreCodeAction), + version: null + } + } + ] + }, + title: '(svelte) Disable a11y-missing-attribute for this line', + kind: 'quickfix' + } + ]); + }); - it('should provide ignore comment with indent', async () => { - ( - await expectCodeActionFor(svelteIgnoreCodeAction, { - diagnostics: [ - { - severity: DiagnosticSeverity.Warning, - code: 'a11y-missing-attribute', - range: Range.create( - { line: 3, character: 4 }, - { line: 3, character: 11 } - ), - message: '', - source: 'svelte' - } - ] - }) - ).toEqual([ - { - edit: { - documentChanges: [ - { - edits: [ - { - newText: `${' '.repeat( - 4 - )}${EOL}`, - range: { - end: { - character: 0, - line: 3 - }, - start: { - character: 0, - line: 3 - } - } - } - ], - textDocument: { - uri: getUri(svelteIgnoreCodeAction), - version: null - } - } - ] - }, - title: '(svelte) Disable a11y-missing-attribute for this line', - kind: 'quickfix' - } - ]); - }); + it('should provide ignore comment with indent', async () => { + ( + await expectCodeActionFor(svelteIgnoreCodeAction, { + diagnostics: [ + { + severity: DiagnosticSeverity.Warning, + code: 'a11y-missing-attribute', + range: Range.create( + { line: 3, character: 4 }, + { line: 3, character: 11 } + ), + message: '', + source: 'svelte' + } + ] + }) + ).toEqual([ + { + edit: { + documentChanges: [ + { + edits: [ + { + newText: `${' '.repeat( + 4 + )}${EOL}`, + range: { + end: { + character: 0, + line: 3 + }, + start: { + character: 0, + line: 3 + } + } + } + ], + textDocument: { + uri: getUri(svelteIgnoreCodeAction), + version: null + } + } + ] + }, + title: '(svelte) Disable a11y-missing-attribute for this line', + kind: 'quickfix' + } + ]); + }); - it('should provide ignore comment with indent of parent tag', async () => { - ( - await expectCodeActionFor(svelteIgnoreCodeAction, { - diagnostics: [ - { - severity: DiagnosticSeverity.Warning, - code: 'a11y-invalid-attribute', - range: Range.create( - { line: 6, character: 8 }, - { line: 6, character: 15 } - ), - message: '', - source: 'svelte' - } - ] - }) - ).toEqual([ - { - edit: { - documentChanges: [ - { - edits: [ - { - newText: `${' '.repeat( - 4 - )}${EOL}`, - range: { - end: { - character: 0, - line: 5 - }, - start: { - character: 0, - line: 5 - } - } - } - ], - textDocument: { - uri: getUri(svelteIgnoreCodeAction), - version: null - } - } - ] - }, - title: '(svelte) Disable a11y-invalid-attribute for this line', - kind: 'quickfix' - } - ]); - }); - }); + it('should provide ignore comment with indent of parent tag', async () => { + ( + await expectCodeActionFor(svelteIgnoreCodeAction, { + diagnostics: [ + { + severity: DiagnosticSeverity.Warning, + code: 'a11y-invalid-attribute', + range: Range.create( + { line: 6, character: 8 }, + { line: 6, character: 15 } + ), + message: '', + source: 'svelte' + } + ] + }) + ).toEqual([ + { + edit: { + documentChanges: [ + { + edits: [ + { + newText: `${' '.repeat( + 4 + )}${EOL}`, + range: { + end: { + character: 0, + line: 5 + }, + start: { + character: 0, + line: 5 + } + } + } + ], + textDocument: { + uri: getUri(svelteIgnoreCodeAction), + version: null + } + } + ] + }, + title: '(svelte) Disable a11y-invalid-attribute for this line', + kind: 'quickfix' + } + ]); + }); + }); - describe('#extractComponent', async () => { - const scriptContent = ``; - const styleContent = ''; - const content = ` + const styleContent = ''; + const content = ` ${scriptContent}

something else

extract me

${styleContent}`; - const doc = new SvelteDocument(new Document('someUrl', content)); + const doc = new SvelteDocument(new Document('someUrl', content)); - async function extractComponent(filePath: string, range: Range) { - return executeRefactoringCommand(doc, extractComponentCommand, [ - '', - { - filePath, - range, - uri: '' - } - ]); - } + async function extractComponent(filePath: string, range: Range) { + return executeRefactoringCommand(doc, extractComponentCommand, [ + '', + { + filePath, + range, + uri: '' + } + ]); + } - async function shouldExtractComponent( - path: 'NewComp' | 'NewComp.svelte' | './NewComp' | './NewComp.svelte' - ) { - const range = Range.create(Position.create(5, 8), Position.create(5, 25)); - const result = await extractComponent(path, range); - assert.deepStrictEqual(result, { - documentChanges: [ - TextDocumentEdit.create( - OptionalVersionedTextDocumentIdentifier.create('someUrl', null), - [ - TextEdit.replace(range, ''), - TextEdit.insert( - doc.script?.startPos || Position.create(0, 0), - "\n import NewComp from './NewComp.svelte';\n" - ) - ] - ), - CreateFile.create('file:///NewComp.svelte', { overwrite: true }), - TextDocumentEdit.create( - OptionalVersionedTextDocumentIdentifier.create( - 'file:///NewComp.svelte', - null - ), - [ - TextEdit.insert( - Position.create(0, 0), - `${scriptContent}\n\n

extract me

\n\n${styleContent}\n\n` - ) - ] - ) - ] - }); - } + async function shouldExtractComponent( + path: 'NewComp' | 'NewComp.svelte' | './NewComp' | './NewComp.svelte' + ) { + const range = Range.create(Position.create(5, 8), Position.create(5, 25)); + const result = await extractComponent(path, range); + assert.deepStrictEqual(result, { + documentChanges: [ + TextDocumentEdit.create( + OptionalVersionedTextDocumentIdentifier.create('someUrl', null), + [ + TextEdit.replace(range, ''), + TextEdit.insert( + doc.script?.startPos || Position.create(0, 0), + "\n import NewComp from './NewComp.svelte';\n" + ) + ] + ), + CreateFile.create('file:///NewComp.svelte', { overwrite: true }), + TextDocumentEdit.create( + OptionalVersionedTextDocumentIdentifier.create( + 'file:///NewComp.svelte', + null + ), + [ + TextEdit.insert( + Position.create(0, 0), + `${scriptContent}\n\n

extract me

\n\n${styleContent}\n\n` + ) + ] + ) + ] + }); + } - it('should extract component (no .svelte at the end)', async () => { - await shouldExtractComponent('./NewComp'); - }); + it('should extract component (no .svelte at the end)', async () => { + await shouldExtractComponent('./NewComp'); + }); - it('should extract component (no .svelte at the end, no relative path)', async () => { - await shouldExtractComponent('NewComp'); - }); + it('should extract component (no .svelte at the end, no relative path)', async () => { + await shouldExtractComponent('NewComp'); + }); - it('should extract component (.svelte at the end, no relative path', async () => { - await shouldExtractComponent('NewComp.svelte'); - }); + it('should extract component (.svelte at the end, no relative path', async () => { + await shouldExtractComponent('NewComp.svelte'); + }); - it('should extract component (.svelte at the end, relative path)', async () => { - await shouldExtractComponent('./NewComp.svelte'); - }); + it('should extract component (.svelte at the end, relative path)', async () => { + await shouldExtractComponent('./NewComp.svelte'); + }); - it('should return "Invalid selection range"', async () => { - const range = Range.create(Position.create(6, 8), Position.create(6, 25)); - const result = await extractComponent('Bla', range); - assert.deepStrictEqual(result, 'Invalid selection range'); - }); + it('should return "Invalid selection range"', async () => { + const range = Range.create(Position.create(6, 8), Position.create(6, 25)); + const result = await extractComponent('Bla', range); + assert.deepStrictEqual(result, 'Invalid selection range'); + }); - it('should update relative imports', async () => { - const content = ` @@ -347,48 +347,48 @@ describe('SveltePlugin#getCodeAction', () => { `; - const existingFileUri = pathToUrl('C:/path/File.svelte'); - const doc = new SvelteDocument(new Document(existingFileUri, content)); - const range = Range.create(Position.create(4, 12), Position.create(4, 21)); - const result = await executeRefactoringCommand(doc, extractComponentCommand, [ - '', - { - filePath: '../NewComp', - range, - uri: '' - } - ]); + const existingFileUri = pathToUrl('C:/path/File.svelte'); + const doc = new SvelteDocument(new Document(existingFileUri, content)); + const range = Range.create(Position.create(4, 12), Position.create(4, 21)); + const result = await executeRefactoringCommand(doc, extractComponentCommand, [ + '', + { + filePath: '../NewComp', + range, + uri: '' + } + ]); - const newFileUri = pathToUrl('C:/NewComp.svelte'); - assert.deepStrictEqual(result, { - documentChanges: [ - TextDocumentEdit.create( - OptionalVersionedTextDocumentIdentifier.create(existingFileUri, null), - [ - TextEdit.replace(range, ''), - TextEdit.insert( - doc.script?.startPos || Position.create(0, 0), - "\n import NewComp from '../NewComp.svelte';\n" - ) - ] - ), - CreateFile.create(newFileUri, { overwrite: true }), - TextDocumentEdit.create( - OptionalVersionedTextDocumentIdentifier.create(newFileUri, null), - [ - TextEdit.insert( - Position.create(0, 0), - `\n\ntoExtract\n\n\n\n` - ) - ] - ) - ] - }); - }); - }); + ) + ] + ) + ] + }); + }); + }); }); diff --git a/packages/language-server/test/plugins/svelte/features/getCompletions.test.ts b/packages/language-server/test/plugins/svelte/features/getCompletions.test.ts index f7a26230f..9f282256d 100644 --- a/packages/language-server/test/plugins/svelte/features/getCompletions.test.ts +++ b/packages/language-server/test/plugins/svelte/features/getCompletions.test.ts @@ -6,125 +6,125 @@ import { SvelteDocument } from '../../../../src/plugins/svelte/SvelteDocument'; import { Document } from '../../../../src/lib/documents'; describe('SveltePlugin#getCompletions', () => { - function expectCompletionsFor( - content: string, - position: Position = Position.create(0, content.length) - ) { - const svelteDoc = new SvelteDocument(new Document('url', content)); - const completions = getCompletions(svelteDoc, position); - return { - toEqual: (expectedLabels: string[] | null) => - assert.deepStrictEqual( - completions?.items.map((item) => item.label) ?? null, - expectedLabels - ) - }; - } - - describe('should return null', () => { - it('if position inside style', () => { - expectCompletionsFor( - '

test

', - Position.create(0, 10) - ).toEqual(null); - }); - - it('if position inside script', () => { - expectCompletionsFor( - '

test

', - Position.create(0, 10) - ).toEqual(null); - }); - - it('if not preceeded by valid content #1', () => { - expectCompletionsFor('{nope').toEqual(null); - }); - - it('if not preceeded by valid content #2', () => { - expectCompletionsFor('not really').toEqual(null); - }); - - it('if not preceeded by valid content #3', () => { - expectCompletionsFor('{#awa.').toEqual(null); - }); - }); - - it('should return completions for #', () => { - expectCompletionsFor('{#').toEqual(['if', 'each', 'await :then', 'await then', 'key']); - }); - - it('should return completions for @', () => { - expectCompletionsFor('{@').toEqual(['html', 'debug']); - }); - - describe('should return no completions for :', () => { - it(' when no open tag before that', () => { - expectCompletionsFor('{:').toEqual(null); - }); - - it(' when only completed tag before that', () => { - expectCompletionsFor('{#if}{/if}{:').toEqual(null); - }); - }); - - describe('should return no completions for /', () => { - it('when no open tag before that', () => { - expectCompletionsFor('{/').toEqual(null); - }); - - it('when only completed tag before that', () => { - expectCompletionsFor('{#if}{/if}{/').toEqual(null); - }); - - it('when the only completed tag before it has white space before close symbol', () => { - expectCompletionsFor('{#if}{ /if}{/').toEqual(null); - }); - }); - - describe('should return completion for :', () => { - it('for if', () => { - expectCompletionsFor('{#if}{:').toEqual(['else', 'else if']); - }); - - it('for each', () => { - expectCompletionsFor('{#each}{:').toEqual(['else']); - }); - - it('for await', () => { - expectCompletionsFor('{#await}{:').toEqual(['then', 'catch']); - }); - - it('for last open tag', () => { - expectCompletionsFor('{#if}{/if}{#if}{#await}{:').toEqual(['then', 'catch']); - }); - }); - - describe('should return completion for /', () => { - it('for if', () => { - expectCompletionsFor('{#if}{/').toEqual(['if']); - }); - - it('for each', () => { - expectCompletionsFor('{#each}{/').toEqual(['each']); - }); - - it('for await', () => { - expectCompletionsFor('{#await}{/').toEqual(['await']); - }); - - it('for key', () => { - expectCompletionsFor('{#key}{/').toEqual(['key']); - }); - - it('for last open tag', () => { - expectCompletionsFor('{#if}{/if}{#if}{#await}{/').toEqual(['await']); - }); - }); - - it('should return completion for component documentation comment', () => { - const content = '"] - }, - "brackets": [ - [""], - ["<", ">"], - ["{", "}"], - ["(", ")"], - ["[", "]"] - ], - "autoClosingPairs": [ - { "open": "{", "close": "}" }, - { "open": "[", "close": "]" }, - { "open": "(", "close": ")" }, - { "open": "'", "close": "'" }, - { "open": "\"", "close": "\"" }, - { "open": "", "notIn": ["comment", "string"] }, - { "open": "/**", "close": "*/", "notIn": ["string"] } - ], - "surroundingPairs": [ - { "open": "'", "close": "'" }, - { "open": "\"", "close": "\"" }, - { "open": "`", "close": "`" }, - { "open": "{", "close": "}" }, - { "open": "[", "close": "]" }, - { "open": "(", "close": ")" }, - { "open": "<", "close": ">" } - ], - "folding": { - "markers": { - "start": "^\\s*//\\s*#?region\\b|^<(template|style|script)[^>]*>", - "end": "^\\s*//\\s*#?endregion\\b|^" - } - } + "comments": { + "blockComment": [""] + }, + "brackets": [ + [""], + ["<", ">"], + ["{", "}"], + ["(", ")"], + ["[", "]"] + ], + "autoClosingPairs": [ + { "open": "{", "close": "}" }, + { "open": "[", "close": "]" }, + { "open": "(", "close": ")" }, + { "open": "'", "close": "'" }, + { "open": "\"", "close": "\"" }, + { "open": "", "notIn": ["comment", "string"] }, + { "open": "/**", "close": "*/", "notIn": ["string"] } + ], + "surroundingPairs": [ + { "open": "'", "close": "'" }, + { "open": "\"", "close": "\"" }, + { "open": "`", "close": "`" }, + { "open": "{", "close": "}" }, + { "open": "[", "close": "]" }, + { "open": "(", "close": ")" }, + { "open": "<", "close": ">" } + ], + "folding": { + "markers": { + "start": "^\\s*//\\s*#?region\\b|^<(template|style|script)[^>]*>", + "end": "^\\s*//\\s*#?endregion\\b|^" + } + } } diff --git a/packages/svelte-vscode/package-json-schema.json b/packages/svelte-vscode/package-json-schema.json index 713c292bb..837d89d33 100644 --- a/packages/svelte-vscode/package-json-schema.json +++ b/packages/svelte-vscode/package-json-schema.json @@ -1,9 +1,9 @@ { - "$schema": "http://json-schema.org/draft-07/schema", - "properties": { - "prettier": { - "description": "Prettier-Plugin-Svelte configuration", - "$ref": "./prettier-options-schema.json" - } - } + "$schema": "http://json-schema.org/draft-07/schema", + "properties": { + "prettier": { + "description": "Prettier-Plugin-Svelte configuration", + "$ref": "./prettier-options-schema.json" + } + } } diff --git a/packages/svelte-vscode/package.json b/packages/svelte-vscode/package.json index f4b002ad2..fef8180e3 100644 --- a/packages/svelte-vscode/package.json +++ b/packages/svelte-vscode/package.json @@ -1,518 +1,518 @@ { - "name": "svelte-vscode", - "version": "0.5.0", - "description": "Svelte language support for VS Code", - "main": "dist/src/extension.js", - "scripts": { - "build:grammar": "npx js-yaml syntaxes/svelte.tmLanguage.src.yaml > syntaxes/svelte.tmLanguage.json", - "build:ts": "tsc -p ./", - "build": "npm run build:ts && npm run build:grammar", - "vscode:prepublish": "npm run build && npm prune --production", - "watch": "npm run build:grammar && tsc -w -p ./", - "test": "echo 'NOOP'" - }, - "repository": { - "type": "git", - "url": "https://github.com/sveltejs/language-tools.git" - }, - "keywords": [ - "svelte", - "vscode" - ], - "author": "James Birtles & the Svelte Core Team", - "license": "MIT", - "bugs": { - "url": "https://github.com/sveltejs/language-tools/issues" - }, - "homepage": "https://github.com/sveltejs/language-tools#readme", - "displayName": "Svelte for VS Code", - "publisher": "svelte", - "icon": "icons/logo.png", - "galleryBanner": { - "color": "#FF3E00", - "theme": "dark" - }, - "categories": [ - "Programming Languages", - "Formatters" - ], - "engines": { - "vscode": "^1.52.0" - }, - "activationEvents": [ - "onLanguage:svelte", - "onCommand:svelte.restartLanguageServer" - ], - "contributes": { - "configuration": { - "type": "object", - "title": "Svelte", - "properties": { - "svelte.language-server.runtime": { - "scope": "application", - "type": "string", - "title": "Language Server Runtime", - "description": "- You normally don't need this - Path to the node executable to use to spawn the language server. This is useful when you depend on native modules such as node-sass as without this they will run in the context of vscode, meaning node version mismatch is likely. Minimum required node version is 12.17. This setting can only be changed in user settings for security reasons." - }, - "svelte.language-server.ls-path": { - "scope": "application", - "type": "string", - "title": "Language Server Path", - "description": "- You normally don't set this - Path to the language server executable. If you installed the \"svelte-language-server\" npm package, it's within there at \"bin/server.js\". Path can be either relative to your workspace root or absolute. Set this only if you want to use a custom version of the language server. This setting can only be changed in user settings for security reasons." - }, - "svelte.language-server.port": { - "type": "number", - "title": "Language Server Port", - "description": "- You normally don't set this - At which port to spawn the language server. Can be used for attaching to the process for debugging / profiling. If you experience crashes due to \"port already in use\", try setting the port. -1 = default port is used.", - "default": -1 - }, - "svelte.trace.server": { - "type": "string", - "enum": [ - "off", - "messages", - "verbose" - ], - "default": "off", - "description": "Traces the communication between VS Code and the Svelte Language Server." - }, - "svelte.plugin.typescript.enable": { - "type": "boolean", - "default": true, - "title": "TypeScript", - "description": "Enable the TypeScript plugin" - }, - "svelte.plugin.typescript.diagnostics.enable": { - "type": "boolean", - "default": true, - "title": "TypeScript: Diagnostics", - "description": "Enable diagnostic messages for TypeScript" - }, - "svelte.plugin.typescript.hover.enable": { - "type": "boolean", - "default": true, - "title": "TypeScript: Hover Info", - "description": "Enable hover info for TypeScript" - }, - "svelte.plugin.typescript.documentSymbols.enable": { - "type": "boolean", - "default": true, - "title": "TypeScript: Symbols in Outline", - "description": "Enable document symbols for TypeScript" - }, - "svelte.plugin.typescript.completions.enable": { - "type": "boolean", - "default": true, - "title": "TypeScript: Completions", - "description": "Enable completions for TypeScript" - }, - "svelte.plugin.typescript.findReferences.enable": { - "type": "boolean", - "default": true, - "title": "TypeScript: Find References", - "description": "Enable find-references for TypeScript" - }, - "svelte.plugin.typescript.definitions.enable": { - "type": "boolean", - "default": true, - "title": "TypeScript: Go to Definition", - "description": "Enable go to definition for TypeScript" - }, - "svelte.plugin.typescript.codeActions.enable": { - "type": "boolean", - "default": true, - "title": "TypeScript: Code Actions", - "description": "Enable code actions for TypeScript" - }, - "svelte.plugin.typescript.selectionRange.enable": { - "type": "boolean", - "default": true, - "title": "TypeScript: Selection Range", - "description": "Enable selection range for TypeScript" - }, - "svelte.plugin.typescript.signatureHelp.enable": { - "type": "boolean", - "default": true, - "title": "TypeScript: Signature Help", - "description": "Enable signature help (parameter hints) for TypeScript" - }, - "svelte.plugin.typescript.rename.enable": { - "type": "boolean", - "default": true, - "title": "TypeScript: Rename", - "description": "Enable rename functionality for JS/TS variables inside Svelte files" - }, - "svelte.plugin.typescript.semanticTokens.enable": { - "type": "boolean", - "default": true, - "title": "TypeScript: Semantic Tokens", - "description": "Enable semantic tokens (semantic highlight) for TypeScript. Doesn't apply to JavaScript" - }, - "svelte.plugin.css.enable": { - "type": "boolean", - "default": true, - "title": "CSS", - "description": "Enable the CSS plugin" - }, - "svelte.plugin.css.globals": { - "type": "string", - "default": "", - "title": "CSS: Global Files", - "description": "Which css files should be checked for global variables (`--global-var: value;`). These variables are added to the css completions. String of comma-separated file paths or globs relative to workspace root." - }, - "svelte.plugin.css.diagnostics.enable": { - "type": "boolean", - "default": true, - "title": "CSS: Diagnostics", - "description": "Enable diagnostic messages for CSS" - }, - "svelte.plugin.css.hover.enable": { - "type": "boolean", - "default": true, - "title": "CSS: Hover Info", - "description": "Enable hover info for CSS" - }, - "svelte.plugin.css.completions.enable": { - "type": "boolean", - "default": true, - "title": "CSS: Auto Complete", - "description": "Enable auto completions for CSS" - }, - "svelte.plugin.css.completions.emmet": { - "type": "boolean", - "default": true, - "title": "CSS: Include Emmet Completions", - "description": "Enable emmet auto completions for CSS" - }, - "svelte.plugin.css.documentColors.enable": { - "type": "boolean", - "default": true, - "title": "CSS: Document Colors", - "description": "Enable document colors for CSS" - }, - "svelte.plugin.css.colorPresentations.enable": { - "type": "boolean", - "default": true, - "title": "CSS: Color Picker", - "description": "Enable color picker for CSS" - }, - "svelte.plugin.css.documentSymbols.enable": { - "type": "boolean", - "default": true, - "title": "CSS: Symbols in Outline", - "description": "Enable document symbols for CSS" - }, - "svelte.plugin.css.selectionRange.enable": { - "type": "boolean", - "default": true, - "title": "CSS: SelectionRange", - "description": "Enable selection range for CSS" - }, - "svelte.plugin.html.enable": { - "type": "boolean", - "default": true, - "title": "HTML", - "description": "Enable the HTML plugin" - }, - "svelte.plugin.html.hover.enable": { - "type": "boolean", - "default": true, - "title": "HTML: Hover Info", - "description": "Enable hover info for HTML" - }, - "svelte.plugin.html.completions.enable": { - "type": "boolean", - "default": true, - "title": "HTML: Auto Complete", - "description": "Enable auto completions for HTML" - }, - "svelte.plugin.html.completions.emmet": { - "type": "boolean", - "default": true, - "title": "HTML: Include Emmet Completions", - "description": "Enable emmet auto completions for HTML" - }, - "svelte.plugin.html.tagComplete.enable": { - "type": "boolean", - "default": true, - "title": "HTML: Tag Auto Closing", - "description": "Enable HTML tag auto closing" - }, - "svelte.plugin.html.documentSymbols.enable": { - "type": "boolean", - "default": true, - "title": "HTML: Symbols in Outline", - "description": "Enable document symbols for HTML" - }, - "svelte.plugin.html.linkedEditing.enable": { - "type": "boolean", - "default": true, - "title": "HTML: Linked Editing", - "description": "Enable Linked Editing for HTML" - }, - "svelte.plugin.html.renameTags.enable": { - "type": "boolean", - "default": true, - "title": "HTML: Rename tags", - "description": "Enable rename for the opening/closing tag pairs in HTML" - }, - "svelte.plugin.svelte.enable": { - "type": "boolean", - "default": true, - "title": "Svelte", - "description": "Enable the Svelte plugin" - }, - "svelte.plugin.svelte.diagnostics.enable": { - "type": "boolean", - "default": true, - "title": "Svelte: Diagnostics", - "description": "Enable diagnostic messages for Svelte" - }, - "svelte.plugin.svelte.compilerWarnings": { - "type": "object", - "additionalProperties": { - "type": "string", - "enum": [ - "ignore", - "error" - ] - }, - "default": {}, - "title": "Svelte: Compiler Warnings Settings", - "description": "Svelte compiler warning codes to ignore or to treat as errors. Example: { 'css-unused-selector': 'ignore', 'unused-export-let': 'error'}" - }, - "svelte.plugin.svelte.format.enable": { - "type": "boolean", - "default": true, - "title": "Svelte: Format", - "description": "Enable formatting for Svelte (includes css & js). You can set some formatting options through this extension. They will be ignored if there's any kind of configuration file, for example a `.prettierrc` file." - }, - "svelte.plugin.svelte.format.config.svelteSortOrder": { - "type": "string", - "default": "options-scripts-markup-styles", - "title": "Svelte Format: Sort Order", - "description": "Format: join the keys `options`, `scripts`, `markup`, `styles` with a - in the order you want. This option is ignored if there's any kind of configuration file, for example a `.prettierrc` file." - }, - "svelte.plugin.svelte.format.config.svelteStrictMode": { - "type": "boolean", - "default": false, - "title": "Svelte Format: Strict Mode", - "description": "More strict HTML syntax. This option is ignored if there's any kind of configuration file, for example a `.prettierrc` file." - }, - "svelte.plugin.svelte.format.config.svelteAllowShorthand": { - "type": "boolean", - "default": true, - "title": "Svelte Format: Allow Shorthand", - "description": "Option to enable/disable component attribute shorthand if attribute name and expression are the same. This option is ignored if there's any kind of configuration file, for example a `.prettierrc` file." - }, - "svelte.plugin.svelte.format.config.svelteBracketNewLine": { - "type": "boolean", - "default": true, - "title": "Svelte Format: Bracket New Line", - "description": "Put the `>` of a multiline element on a new line. This option is ignored if there's any kind of configuration file, for example a `.prettierrc` file." - }, - "svelte.plugin.svelte.format.config.svelteIndentScriptAndStyle": { - "type": "boolean", - "default": true, - "title": "Svelte Format: Indent Script And Style", - "description": "Whether or not to indent code inside `