UNPKG

@shopify/theme-language-server-common

Version:

<h1 align="center" style="position: relative;" > <br> <img src="https://github.com/Shopify/theme-check-vscode/blob/main/images/shopify_glyph.png?raw=true" alt="logo" width="141" height="160"> <br> Theme Language Server </h1>

512 lines (462 loc) 15.9 kB
import { assert, beforeEach, describe, expect, it } from 'vitest'; import { Position, TextDocumentEdit } from 'vscode-languageserver-protocol'; import { TextDocument } from 'vscode-languageserver-textdocument'; import { DocumentManager } from '../../documents'; import { RenameProvider } from '../RenameProvider'; import { mockConnection, MockConnection } from '../../test/MockConnection'; import { ClientCapabilities } from '../../ClientCapabilities'; const mockRoot = 'file:'; describe('LiquidVariableRenameProvider', () => { const textDocumentUri = `${mockRoot}///snippets/example-snippet.liquid`; const findThemeRootURI = async () => mockRoot; let capabilities: ClientCapabilities; let connection: MockConnection; beforeEach(() => { capabilities = new ClientCapabilities(); connection = mockConnection(mockRoot); connection.spies.sendRequest.mockReturnValue(Promise.resolve(true)); }); describe('unscoped variable', async () => { let documentManager: DocumentManager; let provider: RenameProvider; let textDocument: TextDocument; const documentSource = `{% doc %}@param {string} food - favorite food{% enddoc %} {% assign animal = 'dog' %} {% assign plant = 'cactus' %} {% liquid echo plant echo food %} {% assign painting = 'mona lisa' %} {% assign paintings = 'starry night, sunday afternoon, the scream' %} <p>I have a cool animal, a great plant, and my favorite food</p>`; beforeEach(() => { documentManager = new DocumentManager(); provider = new RenameProvider(connection, capabilities, documentManager, findThemeRootURI); textDocument = TextDocument.create(textDocumentUri, 'liquid', 1, documentSource); documentManager.open(textDocument.uri, documentSource, 1); }); it('returns null when the cursor is not over a liquid variable', async () => { const params = { textDocument, position: Position.create(1, 3), newName: 'pet', }; const result = await provider.rename(params); expect(result).to.be.null; }); it('returns new name after liquid variable is renamed from assign tag', async () => { const params = { textDocument, position: Position.create(1, 11), newName: 'pet', }; const result = await provider.rename(params); assert(result); assert(result.documentChanges); expect((result.documentChanges[0] as TextDocumentEdit).edits).to.applyEdits( textDocument, `{% doc %}@param {string} food - favorite food{% enddoc %} {% assign pet = 'dog' %} {% assign plant = 'cactus' %} {% liquid echo plant echo food %} {% assign painting = 'mona lisa' %} {% assign paintings = 'starry night, sunday afternoon, the scream' %} <p>I have a cool animal, a great plant, and my favorite food</p>`, ); }); it('returns new name after liquid variable is renamed on variable usage', async () => { const params = { textDocument, position: Position.create(4, 11), newName: 'fauna', }; const result = await provider.rename(params); assert(result); assert(result.documentChanges); expect((result.documentChanges[0] as TextDocumentEdit).edits).to.applyEdits( textDocument, `{% doc %}@param {string} food - favorite food{% enddoc %} {% assign animal = 'dog' %} {% assign fauna = 'cactus' %} {% liquid echo fauna echo food %} {% assign painting = 'mona lisa' %} {% assign paintings = 'starry night, sunday afternoon, the scream' %} <p>I have a cool animal, a great plant, and my favorite food</p>`, ); }); it("doesn't rename variables where the name of the one being changed contains within it", async () => { const params = { textDocument, position: Position.create(7, 11), newName: 'famous_painting', }; const result = await provider.rename(params); assert(result); assert(result.documentChanges); expect((result.documentChanges[0] as TextDocumentEdit).edits).to.applyEdits( textDocument, `{% doc %}@param {string} food - favorite food{% enddoc %} {% assign animal = 'dog' %} {% assign plant = 'cactus' %} {% liquid echo plant echo food %} {% assign famous_painting = 'mona lisa' %} {% assign paintings = 'starry night, sunday afternoon, the scream' %} <p>I have a cool animal, a great plant, and my favorite food</p>`, ); }); it('returns new name after liquid doc param is renamed in doc', async () => { const params = { textDocument, position: Position.create(0, 26), newName: 'meal', }; const result = await provider.rename(params); assert(result); assert(result.documentChanges); expect((result.documentChanges[0] as TextDocumentEdit).edits).to.applyEdits( textDocument, `{% doc %}@param {string} meal - favorite food{% enddoc %} {% assign animal = 'dog' %} {% assign plant = 'cactus' %} {% liquid echo plant echo meal %} {% assign painting = 'mona lisa' %} {% assign paintings = 'starry night, sunday afternoon, the scream' %} <p>I have a cool animal, a great plant, and my favorite food</p>`, ); }); it('returns new name after liquid doc param is renamed on variable usage', async () => { const params = { textDocument, position: Position.create(5, 9), newName: 'meal', }; const result = await provider.rename(params); assert(result); assert(result.documentChanges); expect((result.documentChanges[0] as TextDocumentEdit).edits).to.applyEdits( textDocument, `{% doc %}@param {string} meal - favorite food{% enddoc %} {% assign animal = 'dog' %} {% assign plant = 'cactus' %} {% liquid echo plant echo meal %} {% assign painting = 'mona lisa' %} {% assign paintings = 'starry night, sunday afternoon, the scream' %} <p>I have a cool animal, a great plant, and my favorite food</p>`, ); }); }); describe('scoped variable', async () => { let documentManager: DocumentManager; let provider: RenameProvider; let textDocument: TextDocument; const documentSource = `{% assign counter = 0 %} {% assign prod = 'some product' %} {% liquid LOOP prod in products echo prod.title increment counter endLOOP %}`; beforeEach(() => { documentManager = new DocumentManager(); provider = new RenameProvider(connection, capabilities, documentManager, findThemeRootURI); }); ['for', 'tablerow'].forEach((tag) => { [ { placement: 'at loop definition', position: Position.create(3, tag.length + 4) }, { placement: 'at variable usage', position: Position.create(4, 10) }, ].forEach(async ({ placement, position }) => { it(`returns new name after variable is renamed within "${tag}" block (${placement})`, async () => { const source = documentSource.replace(/LOOP/g, tag); textDocument = TextDocument.create(textDocumentUri, 'liquid', 1, source); documentManager.open(textDocument.uri, source, 1); const params = { textDocument, position, newName: 'item', }; const result = await provider.rename(params); assert(result); assert(result.documentChanges); expect((result.documentChanges[0] as TextDocumentEdit).edits).to.applyEdits( textDocument, `{% assign counter = 0 %} {% assign prod = 'some product' %} {% liquid ${tag} item in products echo item.title increment counter end${tag} %}`, ); }); }); }); describe('assign tag', () => { it('returns new name after variable is renamed outside loop', async () => { const source = documentSource.replace(/LOOP/g, 'for'); textDocument = TextDocument.create(textDocumentUri, 'liquid', 1, source); documentManager.open(textDocument.uri, source, 1); const params = { textDocument, position: Position.create(1, 11), newName: 'item', }; const result = await provider.rename(params); assert(result); assert(result.documentChanges); expect((result.documentChanges[0] as TextDocumentEdit).edits).to.applyEdits( textDocument, `{% assign counter = 0 %} {% assign item = 'some product' %} {% liquid for prod in products echo prod.title increment counter endfor %}`, ); }); it('returns new name after variable is renamed inside loop, but is scoped outside it', async () => { const source = documentSource.replace(/LOOP/g, 'for'); textDocument = TextDocument.create(textDocumentUri, 'liquid', 1, source); documentManager.open(textDocument.uri, source, 1); const params = { textDocument, position: Position.create(0, 11), newName: 'x', }; const result = await provider.rename(params); assert(result); assert(result.documentChanges); expect((result.documentChanges[0] as TextDocumentEdit).edits).to.applyEdits( textDocument, `{% assign x = 0 %} {% assign prod = 'some product' %} {% liquid for prod in products echo prod.title increment x endfor %}`, ); }); }); describe('liquid doc param', () => { const documentSource = `{% doc %}@param prod - product{% enddoc %} {% liquid for prod in products echo prod.title endfor echo prod %}`; beforeEach(() => { textDocument = TextDocument.create(textDocumentUri, 'liquid', 1, documentSource); documentManager.open(textDocument.uri, documentSource, 1); }); it('returns new name after variable is renamed outside loop', async () => { const params = { textDocument, position: Position.create(0, 17), newName: 'ppp', }; const result = await provider.rename(params); assert(result); assert(result.documentChanges); expect((result.documentChanges[0] as TextDocumentEdit).edits).to.applyEdits( textDocument, `{% doc %}@param ppp - product{% enddoc %} {% liquid for prod in products echo prod.title endfor echo ppp %}`, ); }); it('returns new name after variable is renamed inside loop, but is scoped outside it', async () => { const params = { textDocument, position: Position.create(3, 10), newName: 'ppp', }; const result = await provider.rename(params); assert(result); assert(result.documentChanges); expect((result.documentChanges[0] as TextDocumentEdit).edits).to.applyEdits( textDocument, `{% doc %}@param prod - product{% enddoc %} {% liquid for ppp in products echo ppp.title endfor echo prod %}`, ); }); }); }); describe('updates across files', async () => { let documentManager: DocumentManager; let provider: RenameProvider; let textDocument: TextDocument; beforeEach(() => { capabilities.setup({ workspace: { applyEdit: true, }, }); textDocument = TextDocument.create( textDocumentUri, 'liquid', 1, `{% doc %} @param [name] - the name @param [age] - the age {% enddoc %}`, ); documentManager = new DocumentManager(); provider = new RenameProvider(connection, capabilities, documentManager, findThemeRootURI); documentManager.open(textDocumentUri, textDocument.getText(), 1); }); it("updates render tag's named parameter when exists", async () => { createSectionWithSource( documentManager, 'section1', `<div>{% render 'example-snippet', name: 'Bob' %}</div>`, ); createSectionWithSource( documentManager, 'section2', `<div>{% render 'example-snippet', age: 60 %}</div>`, ); const params = { textDocument, position: Position.create(1, 11), newName: 'first_name', }; const result = await provider.rename(params); assert(result); assert(result.documentChanges); expect(connection.spies.sendRequest).toHaveBeenCalledOnce(); expect(connection.spies.sendRequest).toHaveBeenCalledWith('workspace/applyEdit', { label: `Rename snippet parameter 'name' to 'first_name'`, edit: { changeAnnotations: { renameSnippetParameter: { label: `Rename snippet parameter 'name' to 'first_name'`, needsConfirmation: false, }, }, documentChanges: [ { textDocument: { uri: getSectionUri('section1'), version: 1, }, edits: [ { newText: 'first_name: ', range: { end: { character: 40, line: 0, }, start: { character: 34, line: 0, }, }, }, ], annotationId: 'renameSnippetParameter', }, ], }, }); }); ['with', 'for'].forEach((aliasTag) => { it(`updates render tag's '${aliasTag} as' alias when exists`, async () => { const renderTagSource = `<div>{% render 'example-snippet' ${aliasTag} 'Bob' as name %}</div>`; createSectionWithSource(documentManager, 'section1', renderTagSource); createSectionWithSource( documentManager, 'section2', `<div>{% render 'example-snippet' ${aliasTag} 'Bob' as friend %}</div>`, ); const params = { textDocument, position: Position.create(1, 11), newName: 'first_name', }; const result = await provider.rename(params); assert(result); assert(result.documentChanges); expect(connection.spies.sendRequest).toHaveBeenCalledOnce(); expect(connection.spies.sendRequest).toHaveBeenCalledWith('workspace/applyEdit', { label: `Rename snippet parameter 'name' to 'first_name'`, edit: { changeAnnotations: { renameSnippetParameter: { label: `Rename snippet parameter 'name' to 'first_name'`, needsConfirmation: false, }, }, documentChanges: [ { textDocument: { uri: getSectionUri('section1'), version: 1, }, edits: [ { newText: 'as first_name', range: { end: { character: renderTagSource.indexOf('as name') + 'as name'.length, line: 0, }, start: { character: renderTagSource.indexOf('as name'), line: 0, }, }, }, ], annotationId: 'renameSnippetParameter', }, ], }, }); }); }); }); }); function createSectionWithSource( documentManager: DocumentManager, sectionName: string, source: string, ) { const sectionUri = getSectionUri(sectionName); const sectionTextDocument = TextDocument.create(sectionUri, 'liquid', 1, source); documentManager.open(sectionUri, sectionTextDocument.getText(), 1); } function getSectionUri(sectionName: string) { return `${mockRoot}///sections/${sectionName}.liquid`; }