diff --git a/client/src/commands/lspCommands.ts b/client/src/commands/lspCommands.ts index cfcf3ccb..ee3dd943 100644 --- a/client/src/commands/lspCommands.ts +++ b/client/src/commands/lspCommands.ts @@ -22,6 +22,17 @@ export function showAst(client: LanguageClient) { } } +/** + * Adds the file with the given `uri` and `src` content to the compiler. + * + * Unlike the file watchers, this does not require the file to be part of the workspace, and the + * content is taken from `src` rather than from disk. Tests use this to load a workspace without + * copying any files into place, and to empty a file again by adding it with no content. + */ +export function addUri(client: LanguageClient) { + return (uri: string, src: string) => client.sendNotification(jobs.Request.apiAddUri, { uri, src }) +} + export function allJobsFinished(client: LanguageClient, eventEmitter: EventEmitter) { return () => new Promise(resolve => { diff --git a/client/src/extension.ts b/client/src/extension.ts index 747573cf..b578c727 100644 --- a/client/src/extension.ts +++ b/client/src/extension.ts @@ -17,7 +17,7 @@ import { setupProjectWatchers, setupSingleFileTracking, disposeWatchers } from ' import { startSession } from './lsp/session' import { getUserConfiguration, getCheckCount } from './lsp/notifications' -import { showAst, allJobsFinished } from './commands/lspCommands' +import { showAst, allJobsFinished, addUri } from './commands/lspCommands' import { runMain, cmdInit, @@ -139,6 +139,10 @@ export async function activate(context: vscode.ExtensionContext, launchOptions: // filesystem change and wait for it to advance, to deterministically detect the resulting check. registerCommand('flix.checkCount', () => getCheckCount()) + // Add a file directly to the compiler, bypassing the file system. Tests use this to set up a + // workspace without copying files into place. + registerCommand('flix.addUri', addUri(client)) + if (isProjectMode()) { // In project mode, watch the file system for .flix/.fpkg/.jar/flix.toml changes. setupProjectWatchers(client, makeHandleRestartClient(context, launchOptions)) diff --git a/package.json b/package.json index 68be500d..e3ef0dd1 100644 --- a/package.json +++ b/package.json @@ -85,6 +85,11 @@ "command": "flix.checkCount", "title": "Flix (debugging): Get the number of lsp/check responses observed", "enablement": "false" + }, + { + "command": "flix.addUri", + "title": "Flix (debugging): Add a file with the given content to the compiler", + "enablement": "false" } ], "menus": { diff --git a/server/src/engine/workspace.ts b/server/src/engine/workspace.ts index aa900188..f1624957 100644 --- a/server/src/engine/workspace.ts +++ b/server/src/engine/workspace.ts @@ -25,13 +25,17 @@ export function initWorkspaceFiles(files: string[]) { /** * Add the given `uri` to the workspace. + * + * If `src` is given, it is used as the content of the file, which therefore does not have to exist + * on disk. Otherwise the content is read from disk when the job is processed. */ -export function addUri(uri: string) { +export function addUri(uri: string, src?: string) { currentWorkspaceFiles.add(uri) const job: jobs.Job = { request: jobs.Request.apiAddUri, uri, + src, } queue.enqueue(job) } diff --git a/server/src/handlers/resources.ts b/server/src/handlers/resources.ts index 78cd567b..f38b1996 100644 --- a/server/src/handlers/resources.ts +++ b/server/src/handlers/resources.ts @@ -27,8 +27,13 @@ interface UriInput { uri: string } -export function handleAddUri({ uri }: UriInput) { - engine.addUri(uri) +interface AddUriInput extends UriInput { + /** The content of the file. If omitted, it is read from disk when the job is processed. */ + src?: string +} + +export function handleAddUri({ uri, src }: AddUriInput) { + engine.addUri(uri, src) } export function handleRemUri({ uri }: UriInput) { diff --git a/test/src/codeActions.test.ts b/test/src/codeActions.test.ts index 3c59aef0..c6fd9056 100644 --- a/test/src/codeActions.test.ts +++ b/test/src/codeActions.test.ts @@ -16,19 +16,21 @@ import * as assert from 'assert' import * as vscode from 'vscode' -import { getTestDocUri, init, open, stringify } from './util' +import { getFixtureDocUri, init, stringify, teardown } from './util' suite('CodeActionProvider', () => { - const mainDocUri = getTestDocUri('src/Main.flix') - const dateDocUri = getTestDocUri('src/Date.flix') + const mainDocUri = getFixtureDocUri('codeActions', 'Main.flix') + const dateDocUri = getFixtureDocUri('codeActions', 'Date.flix') suiteSetup(async () => { await init('codeActions') }) - test('Empty line should not suggest code actions', async () => { - await open(mainDocUri) + suiteTeardown(async () => { + await teardown('codeActions') + }) + test('Empty line should not suggest code actions', async () => { const r = await vscode.commands.executeCommand( 'vscode.executeCodeActionProvider', mainDocUri, @@ -47,8 +49,6 @@ suite('CodeActionProvider', () => { }) async function testCodeAction(docUri: vscode.Uri, position: vscode.Position, expectedKeywords: string[]) { - await open(docUri) - const r = await vscode.commands.executeCommand( 'vscode.executeCodeActionProvider', docUri, diff --git a/test/src/codeLenses.test.ts b/test/src/codeLenses.test.ts index 341df9ef..39348de5 100644 --- a/test/src/codeLenses.test.ts +++ b/test/src/codeLenses.test.ts @@ -16,18 +16,21 @@ import * as assert from 'assert' import * as vscode from 'vscode' -import { getTestDocUri, init, open } from './util' +import { getFixtureDocUri, init, teardown } from './util' suite('CodeLensProvider', () => { - const mainDocUri = getTestDocUri('src/Main.flix') - const areaDocUri = getTestDocUri('src/Area.flix') + const mainDocUri = getFixtureDocUri('codeLenses', 'Main.flix') + const areaDocUri = getFixtureDocUri('codeLenses', 'Area.flix') suiteSetup(async () => { await init('codeLenses') }) + suiteTeardown(async () => { + await teardown('codeLenses') + }) + test('Should propose running main function', async () => { - await open(mainDocUri) const r = await vscode.commands.executeCommand('vscode.executeCodeLensProvider', mainDocUri) assert.strictEqual( r.some(l => l.command?.command === 'flix.runMain'), @@ -36,7 +39,6 @@ suite('CodeLensProvider', () => { }) test('Should propose running test function', async () => { - await open(areaDocUri) const r = await vscode.commands.executeCommand('vscode.executeCodeLensProvider', areaDocUri) assert.strictEqual( r.some(l => l.command?.command === 'flix.runMain'), diff --git a/test/src/completions.test.ts b/test/src/completions.test.ts index b3bb81fe..44f2a370 100644 --- a/test/src/completions.test.ts +++ b/test/src/completions.test.ts @@ -16,18 +16,25 @@ import * as assert from 'assert' import * as vscode from 'vscode' -import { getTestDocUri, init, open, typeText, addFile } from './util' +import { getFixtureDocUri, init, teardown, typeText } from './util' suite('CompletionProvider', () => { - const docUri = getTestDocUri('src/Temp.flix') + const docUri = getFixtureDocUri('completions', 'Empty.flix') suiteSetup(async () => { await init('completions') }) + suiteTeardown(async () => { + // Discard the typed text without saving it, so that the fixture is left empty on disk, and no + // dirty editor is left for the next suite to trip over when it closes all editors. + await vscode.commands.executeCommand('workbench.action.revertAndCloseActiveEditor') + await teardown('completions') + }) + test('Should propose completing mod', async () => { - await addFile(docUri, '') - await open(docUri) + // Typing goes to the active editor, so the document has to be shown + await vscode.window.showTextDocument(await vscode.workspace.openTextDocument(docUri)) await typeText('mo') const position = new vscode.Position(0, 2) diff --git a/test/src/diagnostics.test.ts b/test/src/diagnostics.test.ts index 7e3d1cfd..971a2765 100644 --- a/test/src/diagnostics.test.ts +++ b/test/src/diagnostics.test.ts @@ -16,80 +16,65 @@ import * as assert from 'assert' import * as vscode from 'vscode' -import { getTestDocUri, init, copyFile, deleteFile, replaceDocumentContent } from './util' +import { blankFile, getFixtureDocUri, init, loadFile, teardown } from './util' suite('Diagnostics', () => { - /** The optional URI of the document which should be deleted after each test. */ - let tempDocUri: vscode.Uri | null = null - suiteSetup(async () => { await init('diagnostics') }) - teardown(async () => { - if (tempDocUri !== null) { - await deleteFile(tempDocUri) - } - }) - - test('Should show WeederError', async () => { - await testDiagnostics('WeederError.flix', ['duplicate', 'parameter']) + suiteTeardown(async () => { + await teardown('diagnostics') }) - test('Should show NameError', async () => { - await testDiagnostics('NameError.flix', ['duplicate', 'definition']) + test('Should show NameError', () => { + testDiagnostics('NameError.flix', ['duplicate', 'definition']) }) - test('Should show ResolutionError', async () => { - await testDiagnostics('ResolutionError.flix', ['cyclic', 'type']) + test('Should show ResolutionError', () => { + testDiagnostics('ResolutionError.flix', ['undefined', 'name']) }) - test('Should show TypeError', async () => { - await testDiagnostics('TypeError.flix', ['expected', 'type', 'found']) + test('Should show TypeError', () => { + testDiagnostics('TypeError.flix', ['expected', 'type', 'found']) }) - test('Should show RedundancyError', async () => { - await testDiagnostics('RedundancyError.flix', ['shadowed']) + test('Should show RedundancyError', () => { + testDiagnostics('RedundancyError.flix', ['shadowed']) }) - test('Should show SafetyError', async () => { - await testDiagnostics('SafetyError.flix', ['throw']) + test('Should show SafetyError', () => { + testDiagnostics('SafetyError.flix', ['throw']) }) test('Should clear diagnostics when file content is cleared', async () => { - const srcUri = getTestDocUri('src/ClearTest.flix') - const latentUri = getTestDocUri('latent/WeederError.flix') - - // Delete the file after the test - tempDocUri = srcUri - - // Copy a file with errors into src/ - await copyFile(latentUri, srcUri) + const docUri = getFixtureDocUri('diagnostics', 'NameError.flix') // Verify the error is present - const before = vscode.languages.getDiagnostics(srcUri) + const before = vscode.languages.getDiagnostics(docUri) assert.strictEqual(before.length > 0, true, 'Expected diagnostics before clearing') // Clear the file content (simulates select all + delete) - await replaceDocumentContent(srcUri, '') + await blankFile(docUri) // Verify the error is gone - const after = vscode.languages.getDiagnostics(srcUri) + const after = vscode.languages.getDiagnostics(docUri) + + // Restore the file, so that this test does not depend on being the last one + await loadFile(docUri) + assert.strictEqual(after.length, 0, `Expected no diagnostics after clearing, got: ${JSON.stringify(after)}`) }) /** - * Assert that copying the file `fileName` from the `latent` directory to the `src` directory results in a diagnostic message containing all of the `expectedKeywords` (case-insensitive). + * Assert that the file `fileName` of the test workspace has a diagnostic message containing all of the `expectedKeywords` (case-insensitive). */ - async function testDiagnostics(fileName: string, expectedKeywords: string[]) { - const latentUri = getTestDocUri(`latent/${fileName}`) - const srcUri = getTestDocUri(`src/${fileName}`) - - // Delete the file after the test - tempDocUri = srcUri - await copyFile(latentUri, srcUri) + function testDiagnostics(fileName: string, expectedKeywords: string[]) { + assertDiagnostics(getFixtureDocUri('diagnostics', fileName), expectedKeywords) + } - const diagnostics = vscode.languages.getDiagnostics(srcUri) + function assertDiagnostics(docUri: vscode.Uri, expectedKeywords: string[]) { + const diagnostics = vscode.languages.getDiagnostics(docUri) assert.strictEqual( diagnostics.some(d => { const msgLower = d.message.toLowerCase() diff --git a/test/src/files.test.ts b/test/src/files.test.ts index f833998b..7a2a7165 100644 --- a/test/src/files.test.ts +++ b/test/src/files.test.ts @@ -15,46 +15,118 @@ */ import * as assert from 'assert' +import * as path from 'path' import * as vscode from 'vscode' -import { init, addFile, deleteFile, getTestDocUri } from './util' +import { awaitCheck, getFileUri, getFixtureDocUri, init, teardown } from './util' suite('File manipulation', () => { - const mainDocUri = getTestDocUri('src/Main.flix') - const areaDocUri = getTestDocUri('src/Area.flix') - const fpkgUri = getTestDocUri('lib/circleArea.fpkg') + // `Main.flix` and `Assert.flix` are compiled where they lie, as in every other suite. + const mainDocUri = getFixtureDocUri('files', 'Main.flix') - setup(async () => { - // Restore the original content of the files before each test + // These two are real files of the active workspace: this suite is about the extension noticing + // that they appear and disappear, which only a file-system watcher can report. + const areaDocUri = getWorkspaceDocUri('src/Area.flix') + const fpkgUri = getWorkspaceDocUri('lib/circleArea.fpkg') + + suiteSetup(async () => { await init('files') }) - test('Should remove deleted source-file', async () => { - await deleteFile(areaDocUri) - assert.strictEqual(await workspaceValid(), false) + suiteTeardown(async () => { + await tryDeleteFile(areaDocUri) + await tryDeleteFile(fpkgUri) + await teardown('files') + }) + + setup(async () => { + // Create the files from scratch before each test, so that the compiler is given them by the file + // watcher no matter what the previous test did to them. + await recreateFile(areaDocUri, 'src/Area.flix') + await recreateFile(fpkgUri, 'lib/circleArea.fpkg') }) test('Should add created source-file', async () => { - const content = await vscode.workspace.fs.readFile(areaDocUri) await deleteFile(areaDocUri) - await addFile(areaDocUri, content) + await addFile(areaDocUri, await fixtureContent('src/Area.flix')) assert.strictEqual(await workspaceValid(), true) }) - test('Should remove deleted fpkg-file', async () => { - await deleteFile(fpkgUri) + test('Should remove deleted source-file', async () => { + await deleteFile(areaDocUri) assert.strictEqual(await workspaceValid(), false) }) test('Should add created fpkg-file', async () => { - const content = await vscode.workspace.fs.readFile(fpkgUri) await deleteFile(fpkgUri) - await addFile(fpkgUri, content) + await addFile(fpkgUri, await fixtureContent('lib/circleArea.fpkg')) assert.strictEqual(await workspaceValid(), true) }) + test('Should remove deleted fpkg-file', async () => { + await deleteFile(fpkgUri) + assert.strictEqual(await workspaceValid(), false) + }) + async function workspaceValid() { // If all files are not present in the compiler, then Main.flix will contain a resolution error const r = [...vscode.languages.getDiagnostics(mainDocUri), ...vscode.languages.getDiagnostics(areaDocUri)] return r.length === 0 } + + /** + * Get the URI of the file at `p` in the active workspace, e.g. `src/Area.flix`. + * + * Unlike {@linkcode getFixtureDocUri}, this points at a file which only exists while this suite is + * running: no other suite puts anything in the active workspace. + */ + function getWorkspaceDocUri(p: string) { + return getFileUri(path.resolve(__dirname, '../activeWorkspace', p)) + } + + /** + * Returns the content of the file at `p` in the `workspace` directory of the test workspace, which + * holds the files this suite copies into the active workspace. + */ + async function fixtureContent(p: string): Promise { + return vscode.workspace.fs.readFile(getFixtureDocUri('files', `workspace/${p}`)) + } + + /** + * Deletes the file at `uri` if it exists, and creates it again with the content of `p`. + */ + async function recreateFile(uri: vscode.Uri, p: string) { + await tryDeleteFile(uri) + await addFile(uri, await fixtureContent(p)) + } + + /** + * Add a file with the given `uri` and `content`, and wait for the compiler to process this. + */ + async function addFile(uri: vscode.Uri, content: Uint8Array) { + await awaitCheck(async () => { + await vscode.workspace.fs.writeFile(uri, content) + }) + } + + /** + * Delete the file at `uri`, and wait for the compiler to process this. + * + * Throws if the file does not exist. + */ + async function deleteFile(uri: vscode.Uri) { + await awaitCheck(async () => { + await vscode.workspace.fs.delete(uri) + }) + } + + /** + * Tries to delete the file at `uri`, but does nothing if the file does not exist. + */ + async function tryDeleteFile(uri: vscode.Uri) { + try { + await deleteFile(uri) + } catch { + // File does not exist - no need to delete + } + } }) diff --git a/test/src/findReferences.test.ts b/test/src/findReferences.test.ts index ddef12c0..d0416340 100644 --- a/test/src/findReferences.test.ts +++ b/test/src/findReferences.test.ts @@ -17,15 +17,19 @@ import * as assert from 'assert' import * as vscode from 'vscode' -import { findMarkerPosition, getTestDocUri, init } from './util' +import { findMarkerPosition, getFixtureDocUri, init, teardown } from './util' suite('FindReferencesProvider', () => { - const mainDocUri = getTestDocUri('src/Main.flix') + const mainDocUri = getFixtureDocUri('findReferences', 'Main.flix') suiteSetup(async () => { await init('findReferences') }) + suiteTeardown(async () => { + await teardown('findReferences') + }) + test('Should find references to function parameter', async () => { const position = await findMarkerPosition(mainDocUri, 's1') const locations = await testFindReferences(mainDocUri, position) diff --git a/test/src/foldingRanges.test.ts b/test/src/foldingRanges.test.ts index a952ee75..61b5819d 100644 --- a/test/src/foldingRanges.test.ts +++ b/test/src/foldingRanges.test.ts @@ -16,23 +16,26 @@ import * as assert from 'assert' import * as vscode from 'vscode' -import { getTestDocUri, init, open } from './util' +import { getFixtureDocUri, init, teardown } from './util' suite('FoldingRangeProvider', () => { - const mainDocUri = getTestDocUri('src/Main.flix') + const mainDocUri = getFixtureDocUri('foldingRanges', 'Main.flix') suiteSetup(async () => { await init('foldingRanges') }) + suiteTeardown(async () => { + await teardown('foldingRanges') + }) + test('Should fold multi-line doc, line, and block comments', async () => { - await open(mainDocUri) const ranges = await vscode.commands.executeCommand( 'vscode.executeFoldingRangeProvider', mainDocUri, ) - // Lines are zero-indexed. See `test/testWorkspaces/foldingRanges/src/Main.flix`. + // Lines are zero-indexed. See `test/testWorkspaces/foldingRanges/Main.flix`. const actual = ranges.map(r => ({ start: r.start, end: r.end, kind: r.kind })).sort((a, b) => a.start - b.start) const expected = [ diff --git a/test/src/goto.test.ts b/test/src/goto.test.ts index 786c8649..4350d6b8 100644 --- a/test/src/goto.test.ts +++ b/test/src/goto.test.ts @@ -16,15 +16,19 @@ import * as assert from 'assert' import * as vscode from 'vscode' -import { findMarkerPosition, getTestDocUri, init } from './util' +import { findMarkerPosition, getFixtureDocUri, init, teardown } from './util' suite('GotoDefinitionProvider', () => { - const mainDocUri = getTestDocUri('src/Main.flix') + const mainDocUri = getFixtureDocUri('goto', 'Main.flix') suiteSetup(async () => { await init('goto') }) + suiteTeardown(async () => { + await teardown('goto') + }) + test('Should go to definition of formal parameter', async () => { const position = await findMarkerPosition(mainDocUri, 's') const location = await testGotoDefinition(mainDocUri, position) diff --git a/test/src/highlight.test.ts b/test/src/highlight.test.ts index da0291a9..daf6121a 100644 --- a/test/src/highlight.test.ts +++ b/test/src/highlight.test.ts @@ -16,15 +16,19 @@ import * as assert from 'assert' import * as vscode from 'vscode' -import { findMarkerPosition, getTestDocUri, init } from './util' +import { findMarkerPosition, getFixtureDocUri, init, teardown } from './util' suite('DocumentHighlightProvider', () => { - const mainDocUri = getTestDocUri('src/Main.flix') + const mainDocUri = getFixtureDocUri('highlight', 'Main.flix') suiteSetup(async () => { await init('highlight') }) + suiteTeardown(async () => { + await teardown('highlight') + }) + test('Should find highlights of function parameter', async () => { const position = await findMarkerPosition(mainDocUri, 's1') const highlights = await testHighlight(mainDocUri, position) diff --git a/test/src/hover.test.ts b/test/src/hover.test.ts index 4cfd69a1..3d5c24ab 100644 --- a/test/src/hover.test.ts +++ b/test/src/hover.test.ts @@ -16,14 +16,17 @@ import * as assert from 'assert' import * as vscode from 'vscode' -import { findMarkerPosition, getTestDocUri, init, open } from './util' +import { findMarkerPosition, getFixtureDocUri, init, teardown } from './util' suite('HoverProvider', () => { - const docUri = getTestDocUri('src/Main.flix') + const docUri = getFixtureDocUri('hover', 'Main.flix') suiteSetup(async () => { await init('hover') - await open(docUri) + }) + + suiteTeardown(async () => { + await teardown('hover') }) test('Should show Type when hovering on Unit', async () => { diff --git a/test/src/implementation.test.ts b/test/src/implementation.test.ts index 73d20cb2..4a7fa38e 100644 --- a/test/src/implementation.test.ts +++ b/test/src/implementation.test.ts @@ -16,15 +16,19 @@ import * as assert from 'assert' import * as vscode from 'vscode' -import { getTestDocUri, init } from './util' +import { getFixtureDocUri, init, teardown } from './util' suite('ImplementationProvider', () => { - const dividableDocUri = getTestDocUri('src/Dividable.flix') + const dividableDocUri = getFixtureDocUri('implementation', 'Dividable.flix') suiteSetup(async () => { await init('implementation') }) + suiteTeardown(async () => { + await teardown('implementation') + }) + test('Should not show anything on empty line', async () => { const position = new vscode.Position(0, 0) const r = await vscode.commands.executeCommand<(vscode.Location | vscode.LocationLink)[]>( diff --git a/test/src/rename.test.ts b/test/src/rename.test.ts index 8b68b463..1753dcb9 100644 --- a/test/src/rename.test.ts +++ b/test/src/rename.test.ts @@ -16,15 +16,19 @@ import * as assert from 'assert' import * as vscode from 'vscode' -import { findMarkerPosition, getTestDocUri, init, open } from './util' +import { findMarkerPosition, getFixtureDocUri, init, teardown } from './util' suite('RenameProvider', () => { - const mainDocUri = getTestDocUri('src/Main.flix') + const mainDocUri = getFixtureDocUri('rename', 'Main.flix') suiteSetup(async () => { await init('rename') }) + suiteTeardown(async () => { + await teardown('rename') + }) + test('Should rename variable', async () => { const position = await findMarkerPosition(mainDocUri, 's1') const ranges = await testRename(mainDocUri, position) @@ -38,7 +42,6 @@ suite('RenameProvider', () => { }) async function testRename(uri: vscode.Uri, position: vscode.Position): Promise { - await open(uri) const newName = 'NewName' const r = await vscode.commands.executeCommand( 'vscode.executeDocumentRenameProvider', diff --git a/test/src/util.ts b/test/src/util.ts index 75939cd9..c7669de3 100644 --- a/test/src/util.ts +++ b/test/src/util.ts @@ -26,18 +26,21 @@ import * as vscode from 'vscode' const CHECK_TIMEOUT_MS = 20000 /** - * How long to wait for the compiler to stay quiescent before considering a batch of workspace file - * changes fully settled. Must exceed the reconciliation debounce in the client's file watchers - * (`scheduleReconciliation`, currently 300ms), which can enqueue a follow-up check *after* the - * change's initial check has already gone idle. See {@linkcode settleAfterChange}. - */ -const RECONCILE_SETTLE_MS = 500 - -/** - * Activates the extension and swaps the active workspace to the contents of the given test workspace - * directory, waiting deterministically for the compiler to finish compiling the result. + * Activates the extension and loads the contents of the given test workspace directory into the + * compiler directly, without touching the file system. + * + * Every `.flix` file directly in the directory is handed to the compiler by URI and content via the + * `flix.addUri` test command, so the files are compiled where they live in `testWorkspaces` — use + * {@linkcode getFixtureDocUri} to refer to them. Nothing is copied into the active workspace, and + * since nothing changes on disk, no file-system watcher is involved. + * + * The directory therefore holds plain files rather than a workspace layout: no `flix.toml`, and no + * `src` directory. Files in a subdirectory are not loaded — see {@linkcode loadFile}. + * + * The suite must call {@linkcode teardown} with the same name when it is done, since no file-system + * watcher will ever report these files as gone. * - * @param testWorkspaceName The name of the workspace directory to copy, e.g. `codeActions`. + * @param testWorkspaceName The name of the workspace directory to load, e.g. `codeActions`. */ export async function init(testWorkspaceName: string) { // Show errors in the console @@ -52,119 +55,168 @@ export async function init(testWorkspaceName: string) { throw new Error('Failed to activate extension') } - vscode.commands.executeCommand('workbench.action.closeAllEditors') - const activeWorkspaceUri = vscode.workspace.workspaceFolders![0].uri + await vscode.commands.executeCommand('workbench.action.closeAllEditors') - // The `flix.checkCount` synchronization used below only works once the extension is running. - // - // On the very first suite the extension has not started yet: there is no check baseline to capture - // and no file-system watcher to report the changes below, so the copied files are instead picked - // up by the initial workspace scan performed when `ext.activate()` runs. On every later suite the - // extension is already active and we synchronize on the checks its watchers trigger. - const wasActive = ext.isActive + const fixtureUris = await findFixtureFiles(testWorkspaceName) - // Remove the previous suite's files. When the extension is already running, wait for the compiler - // to observe the removals *before* copying the new files. Otherwise VS Code can coalesce a - // delete-then-create of the same path into a single change event, which the file-system watcher - // does not handle — leaving the compiler with stale file contents. - const clearBaseline = wasActive ? await getCheckCount() : 0 - const removedFlixFiles = await clearDir(activeWorkspaceUri) - if (wasActive && removedFlixFiles > 0) { - await settleAfterChange(clearBaseline) + if (!ext.isActive) { + // Every suite which puts files in the active workspace deletes them again, so it is only left + // dirty by an interrupted run. Clean it before the extension starts, so that the compiler never + // hears about those files: making it forget them afterwards would race with the workspace scan, + // which is only enqueued once the compiler connects — that is, after `activate()` has returned. + await deleteWorkspaceFiles() } - // Copy in the new workspace. - const copyBaseline = wasActive ? await getCheckCount() : 0 - const testWorkspacePath = path.resolve(__dirname, '../testWorkspaces', testWorkspaceName) - await copyDirContents(vscode.Uri.file(testWorkspacePath), activeWorkspaceUri) - // Ensure the extension is active. On the first suite this starts (and, on a cold CI run, - // downloads) the compiler and triggers the initial scan+compile of the files copied above. + // downloads) the compiler. `ext.activate()` only resolves once the server has been told to start, + // so the notifications sent below are ordered after it. await ext.activate() - // Wait for the compiler to finish compiling the new workspace and go idle. - await settleAfterChange(copyBaseline) + for (const uri of fixtureUris) { + await addFileToCompiler(uri, await readFileContent(uri)) + } + + // Open the documents here, so the `didOpen` each of them triggers is handled as part of the setup + // rather than in the middle of a test. + await Promise.all(fixtureUris.map(uri => vscode.workspace.openTextDocument(uri))) + + await awaitIdle() } /** - * Recursively deletes all test-owned files (matched by extension) from `uri`, always keeping - * `.gitkeep` and `flix.jar`. + * Blanks every file loaded by {@linkcode init} from the given test workspace directory, so that + * they no longer contribute anything to the program, and waits for the compiler to finish + * recompiling. + * + * Since the files are compiled where they live in `testWorkspaces`, no file-system watcher will ever + * report them as gone, so a suite has to clean up after itself — otherwise its definitions would + * still be part of the program compiled by the next suite. Only the content held by the compiler is + * emptied; the files on disk are left untouched. * - * @returns the number of `.flix` files that were removed, so the caller can tell whether the - * deletion will trigger a recompile to wait for. + * @param testWorkspaceName The name of the workspace directory which was loaded, e.g. `codeActions`. */ -async function clearDir(uri: vscode.Uri): Promise { - const contents = await vscode.workspace.fs.readDirectory(uri) - - // Recurse into subdirectories - const dirs = contents.filter(([_, type]) => type === vscode.FileType.Directory) - const dirUris = dirs.map(([name, _]) => vscode.Uri.joinPath(uri, name)) - const removedInSubdirs = await Promise.all(dirUris.map(clearDir)) +export async function teardown(testWorkspaceName: string) { + for (const uri of await findFixtureFiles(testWorkspaceName)) { + await addFileToCompiler(uri, '') + } + await awaitIdle() +} - const files = contents.filter(([_, type]) => type !== vscode.FileType.Directory) - const fileNames = files.map(([name, _]) => name) +/** + * Loads the file at `uri` into the compiler with its content as it is on disk, without copying it + * anywhere, and waits for the compiler to process it. + * + * As with {@linkcode init}, no file-system watcher will ever report this file as gone, so the + * caller has to {@linkcode blankFile} it again once it should no longer be part of the program. + */ +export async function loadFile(uri: vscode.Uri) { + await addFileToCompiler(uri, await readFileContent(uri)) + await awaitIdle() +} - // Be careful, and only delete files with known extensions - const extensionsToDelete = ['flix', 'toml', 'jar', 'fpkg', 'txt'] +/** + * Empties the content the compiler holds for the file at `uri`, so that it no longer contributes + * anything to the program, and waits for the compiler to process it. + * + * The file itself is left untouched on disk. + */ +export async function blankFile(uri: vscode.Uri) { + await addFileToCompiler(uri, '') + await awaitIdle() +} - // Always keep .gitkeep and flix.jar - const namesToKeep = ['.gitkeep', 'flix.jar'] +/** + * Hands the file at `uri` to the compiler with `src` as its content, without waiting for the + * compiler to process it. + * + * The notification this sends and the one {@linkcode awaitIdle} sends travel the same ordered + * connection, and the server enqueues the resulting job as it handles the notification. Waiting for + * the compiler to go idle afterwards therefore covers this file — no check has to be counted, as it + * does for a change the extension only hears about through a file-system watcher. + */ +async function addFileToCompiler(uri: vscode.Uri, src: string) { + await vscode.commands.executeCommand('flix.addUri', uri.toString(), src) +} - const namesToDelete = fileNames.filter( - name => !namesToKeep.includes(name) && extensionsToDelete.includes(name.split('.').at(-1)), +/** + * Deletes the files of the active workspace which the extension would hand to the compiler when it + * scans the workspace. + * + * Must only be called while the extension is not running: there is no file-system watcher to report + * the deletions then, which is the point — the compiler is never told about these files at all. + */ +async function deleteWorkspaceFiles() { + const activeWorkspaceFolder = vscode.workspace.workspaceFolders![0] + // NB: Must match `getFlixGlobPattern` and `getFpkgGlobPattern` in `client/src/util/workspace.ts`. + const pattern = new vscode.RelativePattern( + activeWorkspaceFolder, + '{*.flix,src/**/*.flix,test/**/*.flix,lib/**/*.fpkg}', ) - const urisToDelete = namesToDelete.map(name => vscode.Uri.joinPath(uri, name)) - await Promise.allSettled(urisToDelete.map(uri => vscode.workspace.fs.delete(uri))) - const removedHere = namesToDelete.filter(name => name.endsWith('.flix')).length - return removedHere + removedInSubdirs.reduce((sum, n) => sum + n, 0) + const uris = await vscode.workspace.findFiles(pattern) + await Promise.all(uris.map(uri => vscode.workspace.fs.delete(uri))) } /** - * Opens the document at `docUri` in the main editor. + * Returns the content of the file at `uri` as a string. */ -export async function open(docUri: vscode.Uri) { - const doc = await vscode.workspace.openTextDocument(docUri) - await vscode.window.showTextDocument(doc) +async function readFileContent(uri: vscode.Uri): Promise { + return Buffer.from(await vscode.workspace.fs.readFile(uri)).toString('utf8') } /** - * Types the given `text` in the editor at the current position. + * Finds the `.flix` files directly in the given test workspace directory, which are the ones + * {@linkcode init} loads. + * + * Subdirectories are left alone, so that a fixture which must not be part of the program from the + * start can be put in one, and loaded by the test itself with {@linkcode loadFile}. + */ +async function findFixtureFiles(testWorkspaceName: string): Promise { + const dirUri = getFileUri(path.resolve(__dirname, '../testWorkspaces', testWorkspaceName)) + const contents = await vscode.workspace.fs.readDirectory(dirUri) + + return contents + .filter(([name, type]) => type !== vscode.FileType.Directory && name.endsWith('.flix')) + .map(([name, _]) => vscode.Uri.joinPath(dirUri, name)) +} + +/** + * Types the given `text` in the editor at the current position, and waits for the compiler to + * process it. + * + * The document is deliberately not saved: the extension sends the compiler the content of the + * editor, so the change reaches it either way, and leaving the file on disk alone means the caller + * can type into a fixture without modifying it. */ export async function typeText(text: string) { await awaitCheck(async () => { await vscode.commands.executeCommand('type', { text }) - await vscode.window.activeTextEditor.document.save() }) } /** - * Replaces the entire content of the given document with `newContent`, saves, and waits for the compiler to process. + * Get the URI of the file at `p` in the test workspace directory `testWorkspaceName`, e.g. + * `Main.flix` in `codeActions`. + * + * This points at the file where it lives in `testWorkspaces`, which is where {@linkcode init} + * leaves it. */ -export async function replaceDocumentContent(docUri: vscode.Uri, newContent: string) { - const doc = await vscode.workspace.openTextDocument(docUri) - await vscode.window.showTextDocument(doc) - await awaitCheck(async () => { - const fullRange = new vscode.Range(doc.positionAt(0), doc.positionAt(doc.getText().length)) - const edit = new vscode.WorkspaceEdit() - edit.replace(docUri, fullRange, newContent) - await vscode.workspace.applyEdit(edit) - await doc.save() - }) +export function getFixtureDocUri(testWorkspaceName: string, p: string) { + return getFileUri(path.resolve(__dirname, '../testWorkspaces', testWorkspaceName, p)) } /** - * Get the URI of the test document at `p` relative to the active workspace, e.g. `src/Main.flix`. + * Get the URI of the file at the absolute path `p`. */ -export function getTestDocUri(p: string) { +export function getFileUri(p: string) { // The only way to produce a URI with the same path as the ones generated by vscode (lowercase drive letter). - return vscode.Uri.file(vscode.Uri.file(path.resolve(__dirname, '../activeWorkspace', p)).fsPath) + return vscode.Uri.file(vscode.Uri.file(p).fsPath) } /** * Sleeps for `ms` milliseconds. */ -export async function sleep(ms: number) { +async function sleep(ms: number) { return new Promise(resolve => setTimeout(resolve, ms)) } @@ -203,39 +255,13 @@ async function awaitIdle() { } /** - * Waits for the compiler to finish reacting to a batch of workspace file changes (the setup in - * {@linkcode init}) and reach a stable idle state, given the {@linkcode getCheckCount} value - * observed *before* the changes were made. - * - * Unlike a fixed sleep, this is anchored to observable compiler progress: - * - * 1. {@linkcode waitForCheckSince} blocks until the file-system watcher has fired and a check has - * completed, so we never sample idle against stale, pre-change state. - * 2. We then repeatedly drain the queue ({@linkcode awaitIdle}) until the observed check count stops - * advancing across a full {@linkcode RECONCILE_SETTLE_MS} window. A create/delete schedules a - * debounced reconciliation that can enqueue a *follow-up* check (e.g. when VS Code delivers a - * single folder-level event instead of per-file events), so returning on the first idle would be - * premature. - */ -async function settleAfterChange(baseline: number) { - await waitForCheckSince(baseline) - for (;;) { - await awaitIdle() - const count = await getCheckCount() - await sleep(RECONCILE_SETTLE_MS) - if ((await getCheckCount()) === count) { - return - } - } -} - -/** - * Runs the filesystem `mutation`, then waits until the `lsp/check` it triggers has finished and the - * compiler is idle. + * Runs the `mutation`, then waits until the `lsp/check` it triggers has finished and the compiler is + * idle. * - * This is the synchronization primitive for in-test file mutations (those that run while the - * extension is already active and idle), and it is race-free because it baselines the check count - * *before* the mutation: + * This is the synchronization primitive for changes the extension only hears about through a + * file-system watcher or an editor, which fire at a time of their own choosing — unlike a file + * handed to the compiler directly, for which waiting for idle is enough. It is race-free because it + * baselines the check count *before* the mutation: * * - The leading {@linkcode waitForCheckSince} proves the file-system watcher fired and a check * completed, so we never sample idle against stale, pre-change state (the old `sleep(1000)` was a @@ -245,7 +271,7 @@ async function settleAfterChange(baseline: number) { * collection, because the server sends them before the idle signal on the same ordered channel * (so the old trailing `sleep(1000)` is unnecessary). */ -async function awaitCheck(mutation: () => Promise): Promise { +export async function awaitCheck(mutation: () => Promise): Promise { const before = await getCheckCount() const result = await mutation() await waitForCheckSince(before) @@ -253,62 +279,6 @@ async function awaitCheck(mutation: () => Promise): Promise { return result } -/** - * Add a file with the given `uri` and `content`, and wait for the compiler to process this. - */ -export async function addFile(uri: vscode.Uri, content: string | Uint8Array) { - await awaitCheck(async () => { - await vscode.workspace.fs.writeFile(uri, Buffer.from(content)) - }) -} - -/** - * Copies the contents of the given folder `from` to the folder `to`, leaving non-overlapping files - * intact. - * - * Does not wait for the compiler to react — callers synchronize via {@linkcode settleAfterChange} - * (workspace setup) or {@linkcode awaitCheck} (in-test mutations). - */ -export async function copyDirContents(from: vscode.Uri, to: vscode.Uri) { - const contents = await vscode.workspace.fs.readDirectory(from) - const names = contents.map(([name, _]) => name) - - const uris = names.map(name => ({ from: vscode.Uri.joinPath(from, name), to: vscode.Uri.joinPath(to, name) })) - - await Promise.allSettled(uris.map(({ from, to }) => vscode.workspace.fs.copy(from, to, { overwrite: true }))) -} - -/** - * Copy the file from `from` to `to`, and wait for the compiler to process this. - */ -export async function copyFile(from: vscode.Uri, to: vscode.Uri) { - await awaitCheck(async () => { - await vscode.workspace.fs.copy(from, to, { overwrite: true }) - }) -} - -/** - * Delete the file at `uri`, and wait for the compiler to process this. - * - * Throws if the file does not exist. - */ -export async function deleteFile(uri: vscode.Uri) { - await awaitCheck(async () => { - await vscode.workspace.fs.delete(uri) - }) -} - -/** - * Tries to delete the file at `uri`, but does nothing if the file does not exist. - */ -export async function tryDeleteFile(uri: vscode.Uri) { - try { - await deleteFile(uri) - } catch { - // File does not exist - no need to delete - } -} - /** * Pretty print the given `val` as a JSON string. */ @@ -316,26 +286,6 @@ export function stringify(val: unknown): string { return JSON.stringify(val, null, 2) } -/** - * Normalize the given `uri` to a canonical form. - */ -function normalizeUri(uri: vscode.Uri) { - // Strip out unnecessary information such as _formatted - return vscode.Uri.parse(uri.toString()) -} - -/** - * Returns the given `location` (which can be either a {@linkcode vscode.Location} or {@linkcode vscode.LocationLink}) - * as a {@linkcode vscode.Location} in a canonical form. - */ -export function normalizeLocation(location: vscode.Location | vscode.LocationLink) { - if (location instanceof vscode.Location) { - return new vscode.Location(normalizeUri(location.uri), location.range) - } else { - return new vscode.Location(normalizeUri(location.targetUri), location.targetRange) - } -} - /** * Finds a marker in the document and returns the position 2 characters before it. * The marker should be placed one space after the position of interest. diff --git a/test/testWorkspaces/codeActions/src/Date.flix b/test/testWorkspaces/codeActions/Date.flix similarity index 100% rename from test/testWorkspaces/codeActions/src/Date.flix rename to test/testWorkspaces/codeActions/Date.flix diff --git a/test/testWorkspaces/codeActions/src/Main.flix b/test/testWorkspaces/codeActions/Main.flix similarity index 100% rename from test/testWorkspaces/codeActions/src/Main.flix rename to test/testWorkspaces/codeActions/Main.flix diff --git a/test/testWorkspaces/codeActions/flix.toml b/test/testWorkspaces/codeActions/flix.toml deleted file mode 100644 index 16cfc76b..00000000 --- a/test/testWorkspaces/codeActions/flix.toml +++ /dev/null @@ -1,6 +0,0 @@ -[package] -name = "vscode-flix-test" -description = "test" -version = "0.1.0" -flix = "0.44.0" -authors = ["John Doe "] diff --git a/test/testWorkspaces/codeLenses/src/Area.flix b/test/testWorkspaces/codeLenses/Area.flix similarity index 100% rename from test/testWorkspaces/codeLenses/src/Area.flix rename to test/testWorkspaces/codeLenses/Area.flix diff --git a/test/testWorkspaces/codeLenses/src/Main.flix b/test/testWorkspaces/codeLenses/Main.flix similarity index 100% rename from test/testWorkspaces/codeLenses/src/Main.flix rename to test/testWorkspaces/codeLenses/Main.flix diff --git a/test/testWorkspaces/codeLenses/flix.toml b/test/testWorkspaces/codeLenses/flix.toml deleted file mode 100644 index 16cfc76b..00000000 --- a/test/testWorkspaces/codeLenses/flix.toml +++ /dev/null @@ -1,6 +0,0 @@ -[package] -name = "vscode-flix-test" -description = "test" -version = "0.1.0" -flix = "0.44.0" -authors = ["John Doe "] diff --git a/test/testWorkspaces/completions/Empty.flix b/test/testWorkspaces/completions/Empty.flix new file mode 100644 index 00000000..e69de29b diff --git a/test/testWorkspaces/completions/flix.toml b/test/testWorkspaces/completions/flix.toml deleted file mode 100644 index 16cfc76b..00000000 --- a/test/testWorkspaces/completions/flix.toml +++ /dev/null @@ -1,6 +0,0 @@ -[package] -name = "vscode-flix-test" -description = "test" -version = "0.1.0" -flix = "0.44.0" -authors = ["John Doe "] diff --git a/test/testWorkspaces/diagnostics/latent/NameError.flix b/test/testWorkspaces/diagnostics/NameError.flix similarity index 100% rename from test/testWorkspaces/diagnostics/latent/NameError.flix rename to test/testWorkspaces/diagnostics/NameError.flix diff --git a/test/testWorkspaces/diagnostics/RedundancyError.flix b/test/testWorkspaces/diagnostics/RedundancyError.flix new file mode 100644 index 00000000..26b6829e --- /dev/null +++ b/test/testWorkspaces/diagnostics/RedundancyError.flix @@ -0,0 +1,8 @@ +// Contains a function which is never used, and in which the name 'x' is shadowed. + +def unusedFunction(): Int32 = + let x = 1; + match Some(2) { + case Some(x) => x + case None => x + } diff --git a/test/testWorkspaces/diagnostics/ResolutionError.flix b/test/testWorkspaces/diagnostics/ResolutionError.flix new file mode 100644 index 00000000..a849bb7f --- /dev/null +++ b/test/testWorkspaces/diagnostics/ResolutionError.flix @@ -0,0 +1,3 @@ +// Contains a call to a function which is not defined. + +def callUndefined(): Int32 = undefinedFunction() diff --git a/test/testWorkspaces/diagnostics/latent/SafetyError.flix b/test/testWorkspaces/diagnostics/SafetyError.flix similarity index 100% rename from test/testWorkspaces/diagnostics/latent/SafetyError.flix rename to test/testWorkspaces/diagnostics/SafetyError.flix diff --git a/test/testWorkspaces/diagnostics/latent/TypeError.flix b/test/testWorkspaces/diagnostics/TypeError.flix similarity index 100% rename from test/testWorkspaces/diagnostics/latent/TypeError.flix rename to test/testWorkspaces/diagnostics/TypeError.flix diff --git a/test/testWorkspaces/diagnostics/flix.toml b/test/testWorkspaces/diagnostics/flix.toml deleted file mode 100644 index 16cfc76b..00000000 --- a/test/testWorkspaces/diagnostics/flix.toml +++ /dev/null @@ -1,6 +0,0 @@ -[package] -name = "vscode-flix-test" -description = "test" -version = "0.1.0" -flix = "0.44.0" -authors = ["John Doe "] diff --git a/test/testWorkspaces/diagnostics/latent/RedundancyError.flix b/test/testWorkspaces/diagnostics/latent/RedundancyError.flix deleted file mode 100644 index 9920c0e9..00000000 --- a/test/testWorkspaces/diagnostics/latent/RedundancyError.flix +++ /dev/null @@ -1,8 +0,0 @@ -// Contains a function in which the name 'file' is shadowed. - -def printFile(file: String): Unit \ IO = { - match Files.readLines(file) { - case Ok(file) => println(file) - case Err(msg) => println("An error occurred with message: ${msg}") - } -} diff --git a/test/testWorkspaces/diagnostics/latent/ResolutionError.flix b/test/testWorkspaces/diagnostics/latent/ResolutionError.flix deleted file mode 100644 index ccf8bbce..00000000 --- a/test/testWorkspaces/diagnostics/latent/ResolutionError.flix +++ /dev/null @@ -1,4 +0,0 @@ -// Contains two cyclical type aliases. - -type alias Even = not Odd -type alias Odd = not Even diff --git a/test/testWorkspaces/diagnostics/latent/UnusedFunction.flix b/test/testWorkspaces/diagnostics/latent/UnusedFunction.flix deleted file mode 100644 index 6be7ebe4..00000000 --- a/test/testWorkspaces/diagnostics/latent/UnusedFunction.flix +++ /dev/null @@ -1,39 +0,0 @@ -mod Reachable.Logic { - def reachable(origin: n, edges: f[(n, n)]): Set[n] with Foldable[f], Order[n] = - let edgeFacts = inject edges into Edge; - // origin is reachable and this set is expanded by the edge facts - let reachable = #{ - Reachable(origin). - Reachable(end) :- Reachable(start), Edge(start, end). - }; - query edgeFacts, reachable select x from Reachable(x) |> Vector.toSet -} - -mod Graphs { - pub def graph1(): Set[(Int32, Int32)] = Set#{} - - pub def graph2(): Set[(Int32, Int32)] = Set#{ - (1, 2), (2, 3), (3, 4), (4, 1) - } - - pub def graph3(): Set[(Int32, Int32)] = Set#{ - (1, 2), (1, 3), (4, 5) - } - - pub def graph4(): Set[(Int32, Int32)] = Set#{ - (2, 3), (3, 4) - } - - pub def graph5(): Set[(Int32, Int32)] = Set#{ - (4, 5), (5, 6), (4, 3), (4, 2), (12, 13), (29, 4) - } - - pub def validate(reachable: Int32 -> Set[(Int32, Int32)] -> Set[Int32]): Bool = { - let test1 = reachable(42, Graphs.graph1()) == Set#{42}; - let test2 = reachable(1, Graphs.graph2()) == Set#{1, 2, 3, 4}; - let test3 = reachable(1, Graphs.graph3()) == Set#{1, 2, 3}; - let test4 = reachable(1, Graphs.graph4()) == Set#{1}; - let test5 = reachable(4, Graphs.graph5()) == Set#{2, 3, 4, 5, 6}; - test1 and test2 and test3 and test4 and test5 - } -} diff --git a/test/testWorkspaces/diagnostics/latent/WeederError.flix b/test/testWorkspaces/diagnostics/latent/WeederError.flix deleted file mode 100644 index 0c3557a2..00000000 --- a/test/testWorkspaces/diagnostics/latent/WeederError.flix +++ /dev/null @@ -1,3 +0,0 @@ -// Contains a function where the same formal parameter name is used twice. - -def add(a: Int32, a: Int32): Int32 = a + a diff --git a/test/testWorkspaces/files/src/Assert.flix b/test/testWorkspaces/files/Assert.flix similarity index 100% rename from test/testWorkspaces/files/src/Assert.flix rename to test/testWorkspaces/files/Assert.flix diff --git a/test/testWorkspaces/files/src/Main.flix b/test/testWorkspaces/files/Main.flix similarity index 100% rename from test/testWorkspaces/files/src/Main.flix rename to test/testWorkspaces/files/Main.flix diff --git a/test/testWorkspaces/files/flix.toml b/test/testWorkspaces/files/flix.toml deleted file mode 100644 index 16cfc76b..00000000 --- a/test/testWorkspaces/files/flix.toml +++ /dev/null @@ -1,6 +0,0 @@ -[package] -name = "vscode-flix-test" -description = "test" -version = "0.1.0" -flix = "0.44.0" -authors = ["John Doe "] diff --git a/test/testWorkspaces/files/lib/circleArea.fpkg b/test/testWorkspaces/files/workspace/lib/circleArea.fpkg similarity index 100% rename from test/testWorkspaces/files/lib/circleArea.fpkg rename to test/testWorkspaces/files/workspace/lib/circleArea.fpkg diff --git a/test/testWorkspaces/files/lib/circleArea.fpkg.txt b/test/testWorkspaces/files/workspace/lib/circleArea.fpkg.txt similarity index 100% rename from test/testWorkspaces/files/lib/circleArea.fpkg.txt rename to test/testWorkspaces/files/workspace/lib/circleArea.fpkg.txt diff --git a/test/testWorkspaces/files/src/Area.flix b/test/testWorkspaces/files/workspace/src/Area.flix similarity index 100% rename from test/testWorkspaces/files/src/Area.flix rename to test/testWorkspaces/files/workspace/src/Area.flix diff --git a/test/testWorkspaces/findReferences/src/Main.flix b/test/testWorkspaces/findReferences/Main.flix similarity index 100% rename from test/testWorkspaces/findReferences/src/Main.flix rename to test/testWorkspaces/findReferences/Main.flix diff --git a/test/testWorkspaces/findReferences/flix.toml b/test/testWorkspaces/findReferences/flix.toml deleted file mode 100644 index 16cfc76b..00000000 --- a/test/testWorkspaces/findReferences/flix.toml +++ /dev/null @@ -1,6 +0,0 @@ -[package] -name = "vscode-flix-test" -description = "test" -version = "0.1.0" -flix = "0.44.0" -authors = ["John Doe "] diff --git a/test/testWorkspaces/foldingRanges/src/Main.flix b/test/testWorkspaces/foldingRanges/Main.flix similarity index 100% rename from test/testWorkspaces/foldingRanges/src/Main.flix rename to test/testWorkspaces/foldingRanges/Main.flix diff --git a/test/testWorkspaces/foldingRanges/flix.toml b/test/testWorkspaces/foldingRanges/flix.toml deleted file mode 100644 index 16cfc76b..00000000 --- a/test/testWorkspaces/foldingRanges/flix.toml +++ /dev/null @@ -1,6 +0,0 @@ -[package] -name = "vscode-flix-test" -description = "test" -version = "0.1.0" -flix = "0.44.0" -authors = ["John Doe "] diff --git a/test/testWorkspaces/goto/src/Main.flix b/test/testWorkspaces/goto/Main.flix similarity index 100% rename from test/testWorkspaces/goto/src/Main.flix rename to test/testWorkspaces/goto/Main.flix diff --git a/test/testWorkspaces/goto/flix.toml b/test/testWorkspaces/goto/flix.toml deleted file mode 100644 index 16cfc76b..00000000 --- a/test/testWorkspaces/goto/flix.toml +++ /dev/null @@ -1,6 +0,0 @@ -[package] -name = "vscode-flix-test" -description = "test" -version = "0.1.0" -flix = "0.44.0" -authors = ["John Doe "] diff --git a/test/testWorkspaces/highlight/src/Main.flix b/test/testWorkspaces/highlight/Main.flix similarity index 100% rename from test/testWorkspaces/highlight/src/Main.flix rename to test/testWorkspaces/highlight/Main.flix diff --git a/test/testWorkspaces/highlight/flix.toml b/test/testWorkspaces/highlight/flix.toml deleted file mode 100644 index 16cfc76b..00000000 --- a/test/testWorkspaces/highlight/flix.toml +++ /dev/null @@ -1,6 +0,0 @@ -[package] -name = "vscode-flix-test" -description = "test" -version = "0.1.0" -flix = "0.44.0" -authors = ["John Doe "] diff --git a/test/testWorkspaces/hover/src/Main.flix b/test/testWorkspaces/hover/Main.flix similarity index 100% rename from test/testWorkspaces/hover/src/Main.flix rename to test/testWorkspaces/hover/Main.flix diff --git a/test/testWorkspaces/hover/flix.toml b/test/testWorkspaces/hover/flix.toml deleted file mode 100644 index 16cfc76b..00000000 --- a/test/testWorkspaces/hover/flix.toml +++ /dev/null @@ -1,6 +0,0 @@ -[package] -name = "vscode-flix-test" -description = "test" -version = "0.1.0" -flix = "0.44.0" -authors = ["John Doe "] diff --git a/test/testWorkspaces/implementation/src/Dividable.flix b/test/testWorkspaces/implementation/Dividable.flix similarity index 100% rename from test/testWorkspaces/implementation/src/Dividable.flix rename to test/testWorkspaces/implementation/Dividable.flix diff --git a/test/testWorkspaces/implementation/flix.toml b/test/testWorkspaces/implementation/flix.toml deleted file mode 100644 index 16cfc76b..00000000 --- a/test/testWorkspaces/implementation/flix.toml +++ /dev/null @@ -1,6 +0,0 @@ -[package] -name = "vscode-flix-test" -description = "test" -version = "0.1.0" -flix = "0.44.0" -authors = ["John Doe "] diff --git a/test/testWorkspaces/implementation/src/Dividable.flix.old b/test/testWorkspaces/implementation/src/Dividable.flix.old deleted file mode 100644 index 704727f3..00000000 --- a/test/testWorkspaces/implementation/src/Dividable.flix.old +++ /dev/null @@ -1,17 +0,0 @@ -// TODO: Use this file instead as soon as https://github.com/flix/flix/issues/8326 is fixed. - -// Blank line -eff DivByZero { - def raise(): Void -} - -trait Dividable[t] { - type Aef: Eff - pub def div(x: t, y: t): t \ Dividable.Aef[t] -} - -instance Dividable[Int32] { - type Aef = DivByZero - pub def div(x: Int32, y: Int32): Int32 \ DivByZero = - if (y == 0) DivByZero.raise() else x / y -} diff --git a/test/testWorkspaces/rename/src/Main.flix b/test/testWorkspaces/rename/Main.flix similarity index 100% rename from test/testWorkspaces/rename/src/Main.flix rename to test/testWorkspaces/rename/Main.flix diff --git a/test/testWorkspaces/rename/flix.toml b/test/testWorkspaces/rename/flix.toml deleted file mode 100644 index 16cfc76b..00000000 --- a/test/testWorkspaces/rename/flix.toml +++ /dev/null @@ -1,6 +0,0 @@ -[package] -name = "vscode-flix-test" -description = "test" -version = "0.1.0" -flix = "0.44.0" -authors = ["John Doe "]