@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>
136 lines (121 loc) • 4.6 kB
text/typescript
import { describe, it, expect, vi, beforeEach, Mock } from 'vitest';
import { URI } from 'vscode-uri';
import {
Offense,
SourceCodeType,
Severity,
Position,
Suggestion,
path,
} from '@shopify/theme-check-common';
import { DiagnosticsManager } from '../../diagnostics';
import { DocumentManager } from '../../documents';
import { ApplySuggestionProvider } from './ApplySuggestionProvider';
import { TextDocument } from 'vscode-languageserver-textdocument';
import { offenseToDiagnostic } from '../../diagnostics/offenseToDiagnostic';
describe('Unit: ApplySuggestionProvider', () => {
const uri = path.normalize(URI.file('/path/to/file.liquid'));
const contents = `
{% assign x = 1 %}
<script src="2.js"></script>
<script src="3.js"></script>
`;
const version = 0;
const document = TextDocument.create(uri, 'liquid', version, contents);
let connection: { sendDiagnostics: Mock; sendRequest: Mock };
let documentManager: DocumentManager;
let diagnosticsManager: DiagnosticsManager;
let applySuggestionProvider: ApplySuggestionProvider;
function makeOffense(
checkName: string,
needle: string,
unfixable?: boolean,
): Offense<SourceCodeType.LiquidHtml> {
const start = contents.indexOf(needle);
const end = start + needle.length;
const suggestion = (replaceWith: string): Suggestion<SourceCodeType.LiquidHtml> => ({
message: `Replace with ${replaceWith}`,
fix: (corrector) => corrector.replace(start, end, replaceWith),
});
return {
type: SourceCodeType.LiquidHtml,
check: checkName,
message: 'Parser blocking script detected',
uri: 'file:///path/to/file.liquid',
severity: Severity.ERROR,
start: { ...document.positionAt(start), index: start },
end: { ...document.positionAt(end), index: end },
suggest: unfixable ? undefined : [suggestion('defer'), suggestion('async')],
};
}
beforeEach(() => {
connection = { sendRequest: vi.fn(), sendDiagnostics: vi.fn() };
documentManager = new DocumentManager();
diagnosticsManager = new DiagnosticsManager(connection as any);
documentManager.open(uri, contents, version);
const capabilities = { hasApplyEditSupport: vi.fn().mockReturnValue(true) };
applySuggestionProvider = new ApplySuggestionProvider(
documentManager,
diagnosticsManager,
capabilities as any,
connection as any,
);
});
describe('execute', () => {
it('applies the suggestion for the given offense id and suggestion index', async () => {
const offenses = [
makeOffense('Offense1', '{% assign x = 1 %}'),
makeOffense('Offense2', '<script src="2.js"></script>'),
makeOffense('Offense3', '<script src="3.js"></script>'),
];
diagnosticsManager.set(uri, version, offenses);
const offenseId = 1; // 2.js
const suggestionIndex = 1; // async
await applySuggestionProvider.execute(uri, version, offenseId, suggestionIndex);
expect(connection.sendRequest).toHaveBeenCalledWith(expect.anything(), {
label: 'Apply suggestion: Replace with async',
edit: {
documentChanges: [
{
textDocument: expect.anything(),
edits: [
{
newText: 'async',
range: {
start: asLspPosition(offenses[1].start),
end: asLspPosition(offenses[1].end),
},
},
],
},
],
},
});
// We clean up stale diagnostics when we're done
expect(connection.sendDiagnostics).toHaveBeenCalledWith({
uri: expect.anything(),
version: expect.any(Number),
diagnostics: offenses.filter((_, index) => index !== offenseId).map(offenseToDiagnostic),
});
});
it('does not apply fixes if the document or diagnostics are not available', async () => {
const offenses = [
makeOffense('Offense1', '{% assign x = 1 %}'),
makeOffense('Offense2', '<script src="2.js"></script>'),
makeOffense('Offense3', '<script src="3.js"></script>'),
];
diagnosticsManager.set(uri, 0, offenses);
documentManager.close(uri);
await applySuggestionProvider.execute(uri, 1, 0, 0);
expect(connection.sendRequest).not.toHaveBeenCalled();
});
});
});
function omit(obj: any, key: string) {
const clone = { ...obj };
delete clone[key];
return clone;
}
function asLspPosition(position: Position): any {
return omit(position, 'index');
}