Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Reinstate expect-error directives for .gts #794

Merged
merged 12 commits into from
Jan 31, 2025
2 changes: 2 additions & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ dist/
!/packages/environment*/-private/dsl/**/*.d.ts
!/packages/environment*/-private/intrinsics/**/*.d.ts

packages/core/__tests__/support/character-position-viewer.html

/packages/vscode/src/generated-meta.ts

# Markdown files: the formatting Prettier uses by default *does. not. match.*
Expand Down
39 changes: 20 additions & 19 deletions packages/core/__tests__/language-server/diagnostics.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { Project } from 'glint-monorepo-test-utils';
import { describe, beforeEach, afterEach, test, expect } from 'vitest';
import { stripIndent } from 'common-tags';
import { TextEdit } from 'vscode-languageserver-textdocument';

describe('Language Server: Diagnostics', () => {
let project!: Project;
Expand Down Expand Up @@ -356,7 +355,7 @@ describe('Language Server: Diagnostics', () => {
expect(server.getDiagnostics(project.fileURI('templates/foo.hbs'))).toEqual([]);
});

test.skip('honors @glint-ignore and @glint-expect-error', async () => {
test('honors @glint-ignore and @glint-expect-error', async () => {
let componentA = stripIndent`
import Component from '@glimmer/component';

Expand Down Expand Up @@ -389,22 +388,22 @@ describe('Language Server: Diagnostics', () => {
const docA = await server.openTextDocument(project.filePath('component-a.gts'), 'glimmer-ts');
let diagnostics = await server.sendDocumentDiagnosticRequest(docA.uri);

expect(diagnostics).toEqual([]);
expect(diagnostics.items).toEqual([]);

const docB = await server.openTextDocument(project.filePath('component-b.gts'), 'glimmer-ts');
diagnostics = await server.sendDocumentDiagnosticRequest(docB.uri);
expect(diagnostics).toEqual([]);
expect(diagnostics.items).toEqual([]);

await server.openTextDocument(project.filePath('component-a.gts'), 'glimmer-ts');
await server.replaceTextDocument(
project.fileURI('component-a.gts'),
componentA.replace('{{! @glint-expect-error }}', ''),
);

expect(await server.sendDocumentDiagnosticRequest(project.fileURI('component-b.gts'))).toEqual(
[],
);
expect(await server.sendDocumentDiagnosticRequest(project.fileURI('component-a.gts')))
expect(
(await server.sendDocumentDiagnosticRequest(project.fileURI('component-b.gts'))).items,
).toEqual([]);
expect((await server.sendDocumentDiagnosticRequest(project.fileURI('component-a.gts'))).items)
.toMatchInlineSnapshot(`
[
{
Expand Down Expand Up @@ -436,23 +435,25 @@ describe('Language Server: Diagnostics', () => {

await server.replaceTextDocument(project.fileURI('component-a.gts'), componentA);

expect(await server.sendDocumentDiagnosticRequest(project.fileURI('component-a.gts'))).toEqual(
[],
);
expect(await server.sendDocumentDiagnosticRequest(project.fileURI('component-b.gts'))).toEqual(
[],
);
expect(
(await server.sendDocumentDiagnosticRequest(project.fileURI('component-a.gts'))).items,
).toEqual([]);
expect(
(await server.sendDocumentDiagnosticRequest(project.fileURI('component-b.gts'))).items,
).toEqual([]);

await server.replaceTextDocument(
project.fileURI('component-a.gts'),
componentA.replace('{{@version}}', ''),
);

expect(await server.sendDocumentDiagnosticRequest(project.fileURI('component-b.gts'))).toEqual(
[],
);
expect(
await server.sendDocumentDiagnosticRequest(project.fileURI('component-a.gts')),
).toMatchInlineSnapshot(`[TODO should display unused glint-expect-error directive]`);
(await server.sendDocumentDiagnosticRequest(project.fileURI('component-b.gts'))).items,
).toEqual([]);

// TODO: uncomment and fix
// expect(
// await server.sendDocumentDiagnosticRequest(project.fileURI('component-a.gts')),
// ).toMatchInlineSnapshot(`[TODO should display unused glint-expect-error directive]`);
});
});
135 changes: 135 additions & 0 deletions packages/core/__tests__/support/character-position-viewer.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
<!DOCTYPE html>
<html lang="en">
<head>
<!--
This is a tool that makes it easier to see the line number, column number, and offset (from start of file)
in a blob of text. This is useful for debugging tests and double checking logic that involves precise
character positions (common in Language Tooling).

Usage: copy and paste the file contents into the textarea, optionally click de-indent (if pasting
from an indented test file). This will generate an equivalent grid of text where each cell has
the positional data.

As you move the cursor around the textarea or select text, the UI
will update, showing the current cursor position and the selected text's position as well
as highlighting the text in the output grid.
-->
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<textarea id="input" style="width: 140ch; height: 400px;"></textarea>
<div>
<button id="de-indent">de-indent</button>
</div>
<div id="info"></div>
<div id="output"></div>

<script>
const input = document.getElementById('input');
const output = document.getElementById('output');
const info = document.getElementById('info');

function getPositionInfo(text, offset) {
const lines = text.split('\n');
let currentOffset = 0;
for (let lineNum = 0; lineNum < lines.length; lineNum++) {
const line = lines[lineNum];
if (offset >= currentOffset && offset <= currentOffset + line.length) {
const charNum = offset - currentOffset;
return { lineNum, charNum, offset };
}
currentOffset += line.length + 1; // +1 for newline
}
return null;
}

function updateInfo() {
const text = input.value;
const selectionStart = input.selectionStart;
const selectionEnd = input.selectionEnd;
const startPos = getPositionInfo(text, selectionStart);

let infoText = `Cursor - Line: ${startPos.lineNum}, Character: ${startPos.charNum}, Offset: ${startPos.offset}`;

if (selectionStart !== selectionEnd) {
const endPos = getPositionInfo(text, selectionEnd);
infoText += `<br>Selection - From (${startPos.lineNum}:${startPos.charNum}, offset ${startPos.offset}) to (${endPos.lineNum}:${endPos.charNum}, offset ${endPos.offset})`;
infoText += `<br>Selected length: ${selectionEnd - selectionStart}`;
}

info.innerHTML = infoText;
updateDisplay();
}

function updateDisplay() {
let offset = 0;
const lines = input.value.split('\n');
lines[lines.length - 1] = lines[lines.length - 1] + '\u0000';

output.innerHTML = lines.map((line, lineNum) => {
const result = (lineNum === lines.length - 1 ? line : line + '\n').split('').map((char, charNum) => {
const currentOffset = offset + charNum;
const displayChar = char === ' ' ? '␣' :
char === '\n' ? '↵' :
char === '\u0000' ? '␀' :
char;
const isAtCursor = currentOffset === input.selectionStart;
const isSelected = currentOffset >= input.selectionStart && currentOffset < input.selectionEnd;
const result = `<div style="display: inline-block; font-family: monospace; border: 1px solid #ccc; margin: 1px; padding: 2px; ${isAtCursor ? 'background-color: #b0b0ff;' : ''} ${isSelected ? 'background-color: #b0b0ff;' : ''}">
<div style="font-size: 16px;">${displayChar}</div>
<div style="font-size: 10px; color: #666;">
${lineNum}<br>
${charNum}<br>
${currentOffset}
</div>
</div>`;
return result;
}).join('') + '<br>';
offset += line.length + 1; // +1 for newline
return result;
}).join('');
}

input.addEventListener('input', updateInfo);
input.addEventListener('click', updateInfo);
input.addEventListener('keyup', updateInfo);
input.addEventListener('select', updateInfo);

document.getElementById('de-indent').addEventListener('click', () => {
const lines = input.value.split('\n');
if (lines.length === 0) return;

// Find number of leading spaces in first line
const firstLine = lines[0];
let leadingSpaces = 0;
for (let i = 0; i < firstLine.length; i++) {
if (firstLine[i] === ' ') {
leadingSpaces++;
} else {
break;
}
}

if (leadingSpaces === 0) return;

// De-indent all lines by that amount, but don't remove non-spaces
const deindented = lines.map(line => {
let spacesToRemove = leadingSpaces;
let i = 0;
// Only remove up to leadingSpaces number of spaces from start
while (spacesToRemove > 0 && i < line.length && line[i] === ' ') {
i++;
spacesToRemove--;
}
return line.slice(i);
});

input.value = deindented.join('\n');
// Trigger input event to update display
input.dispatchEvent(new Event('input'));
});
</script>
</body>
</html>
10 changes: 7 additions & 3 deletions packages/core/src/transform/template/transformed-module.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import GlimmerASTMappingTree from './glimmer-ast-mapping-tree.js';
import { assert } from '../util.js';
import { CodeMapping } from '@volar/language-core';
import { CodeInformation, CodeMapping } from '@volar/language-core';

export type Range = { start: number; end: number };
export type RangeWithMapping = Range & { mapping?: GlimmerASTMappingTree };
Expand Down Expand Up @@ -329,6 +329,11 @@ export default class TransformedModule {
}
});

// TODO: in order to fix/address Issue https://github.com/typed-ember/glint/issues/769,
// we will need to split up this array into multiple CodeMappings, each with a different
// CodeInformation object. Specifically, everything but `verification` should be false or
// omitted for any mappings that represent regions of generated code that don't exist in the source.
// Otherwise there is risk of code completions and other things happening in the wrong place.
return [
{
sourceOffsets,
Expand All @@ -342,8 +347,7 @@ export default class TransformedModule {
semantic: true,
structure: true,
verification: true,
transformedContents: this.transformedContents, // TODO REMOVE
} as any,
} satisfies CodeInformation,
},
];
}
Expand Down
Loading
Loading