Skip to content

Commit

Permalink
Resolve definitions referencing unopened files (#294)
Browse files Browse the repository at this point in the history
Closes #262
  • Loading branch information
remcohaszing authored Feb 9, 2023
1 parent 608f868 commit 1f885bc
Show file tree
Hide file tree
Showing 6 changed files with 106 additions and 51 deletions.
5 changes: 5 additions & 0 deletions .changeset/resolve-unreferenced-definitions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
vscode-mdx: patch
---

Resolve definitions referencing unopened files.
11 changes: 4 additions & 7 deletions packages/language-server/lib/convert.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,6 @@
* @typedef {import('vscode-languageserver-textdocument').TextDocument} TextDocument
*/

import {pathToFileURL} from 'node:url'

import {
CompletionItemKind,
DiagnosticSeverity,
Expand All @@ -31,7 +29,7 @@ import {
SymbolKind
} from 'vscode-languageserver'

import {documents} from './documents.js'
import {getOrReadDocByFileName} from './documents.js'

/**
* Convert a TypeScript script element kind to a Monaco completion kind.
Expand Down Expand Up @@ -285,7 +283,7 @@ function convertRelatedInformation(relatedInformation) {
continue
}

const related = documents.get(info.file.fileName)
const related = getOrReadDocByFileName(info.file.fileName)

if (!related) {
continue
Expand Down Expand Up @@ -368,12 +366,11 @@ export function definitionInfoToLocationLinks(info) {
/** @type {LocationLink[]} */
const locationLinks = []
for (const entry of info) {
const url = String(pathToFileURL(entry.fileName))
const entryDoc = documents.get(url)
const entryDoc = getOrReadDocByFileName(entry.fileName)
if (entryDoc) {
locationLinks.push(
LocationLink.create(
url,
entryDoc.uri,
textSpanToRange(entryDoc, entry.textSpan),
textSpanToRange(entryDoc, entry.textSpan)
)
Expand Down
38 changes: 37 additions & 1 deletion packages/language-server/lib/documents.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import fs from 'node:fs'
import {pathToFileURL} from 'node:url'

import {isMdx} from '@mdx-js/language-service'
Expand All @@ -12,7 +13,8 @@ export const documents = new TextDocuments(TextDocument)
/**
* Return a document based on its file name.
*
* Documents are stored using a file URL. This function allows to do a lookup by file name instead.
* Documents are stored using a file URL. This function allows to do a lookup by
* file name instead. The document will only be returned if it’s open.
*
* @param {string} fileName
* The file name to lookup.
Expand All @@ -23,6 +25,40 @@ export function getDocByFileName(fileName) {
return documents.get(String(pathToFileURL(fileName)))
}

/**
* Return a document based on its file name.
*
* Documents are stored using a file URL. This function allows to do a lookup by
* file name instead. If the file hasn’t been opened, it will be read from the
* file system.
*
* @param {string} fileName
* The file name to lookup.
* @returns {TextDocument | undefined}
* The text document that matches the filename.
*/
export function getOrReadDocByFileName(fileName) {
const doc = getDocByFileName(fileName)
if (doc) {
return doc
}

let content
try {
content = fs.readFileSync(fileName, 'utf8')
} catch {
return
}

return TextDocument.create(
String(pathToFileURL(fileName)),
// The language ID doesn’t really matter for our use case.
'plaintext',
0,
content
)
}

/**
* Get a document, but only if it’s an MDX document.
*
Expand Down
14 changes: 6 additions & 8 deletions packages/language-server/lib/language-service-manager.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,11 @@ import {fileURLToPath, pathToFileURL} from 'node:url'
import {createMdxLanguageService} from '@mdx-js/language-service'

import {loadPlugins} from './configuration.js'
import {documents, getDocByFileName} from './documents.js'
import {
documents,
getDocByFileName,
getOrReadDocByFileName
} from './documents.js'

/**
* Create a function for getting a script snapshot based on a TypeScript module.
Expand All @@ -24,17 +28,11 @@ import {documents, getDocByFileName} from './documents.js'
*/
function createGetScriptSnapshot(ts) {
return (fileName) => {
const doc = getDocByFileName(fileName)
const doc = getOrReadDocByFileName(fileName)

if (doc) {
return ts.ScriptSnapshot.fromString(doc.getText())
}

const text = ts.sys.readFile(fileName)

if (text) {
return ts.ScriptSnapshot.fromString(text)
}
}
}

Expand Down
58 changes: 27 additions & 31 deletions packages/language-server/tests/definitions.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -76,37 +76,33 @@ test('resolve cross-file definitions in ESM if the other file was previously ope
])
})

test(
'resolve cross-file definitions in ESM if the other file is unopened',
{skip: true},
async () => {
await connection.sendRequest(InitializeRequest.type, {
processId: null,
rootUri: null,
capabilities: {}
})

const {uri} = await openTextDocument(connection, 'node16/b.mdx')
const result = await connection.sendRequest(DefinitionRequest.type, {
position: {line: 0, character: 10},
textDocument: {uri}
})

assert.deepEqual(result, [
{
targetRange: {
start: {line: 1, character: 16},
end: {line: 1, character: 17}
},
targetSelectionRange: {
start: {line: 1, character: 16},
end: {line: 1, character: 17}
},
targetUri: fixtureUri('node16/a.mdx')
}
])
}
)
test('resolve cross-file definitions in ESM if the other file is unopened', async () => {
await connection.sendRequest(InitializeRequest.type, {
processId: null,
rootUri: null,
capabilities: {}
})

const {uri} = await openTextDocument(connection, 'node16/b.mdx')
const result = await connection.sendRequest(DefinitionRequest.type, {
position: {line: 0, character: 10},
textDocument: {uri}
})

assert.deepEqual(result, [
{
targetRange: {
start: {line: 1, character: 16},
end: {line: 1, character: 17}
},
targetSelectionRange: {
start: {line: 1, character: 16},
end: {line: 1, character: 17}
},
targetUri: fixtureUri('node16/a.mdx')
}
])
})

test('resolve markdown link references', async () => {
await connection.sendRequest(InitializeRequest.type, {
Expand Down
31 changes: 27 additions & 4 deletions packages/language-server/tests/diagnostics.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {InitializeRequest} from 'vscode-languageserver'

import {
createConnection,
fixtureUri,
openTextDocument,
waitForDiagnostics
} from './utils.js'
Expand Down Expand Up @@ -48,7 +49,18 @@ test('type errors', async () => {
start: {line: 6, character: 15},
end: {line: 6, character: 21}
},
relatedInformation: [],
relatedInformation: [
{
location: {
range: {
end: {line: 12, character: 2},
start: {line: 11, character: 4}
},
uri: fixtureUri('node16/type-errors.mdx')
},
message: "'count' is declared here."
}
],
severity: 4,
tags: []
},
Expand All @@ -57,10 +69,21 @@ test('type errors', async () => {
message:
"Property 'counter' may not exist on type 'Props'. Did you mean 'count'?",
range: {
end: {line: 14, character: 58},
start: {line: 14, character: 51}
start: {line: 14, character: 51},
end: {line: 14, character: 58}
},
relatedInformation: [],
relatedInformation: [
{
location: {
range: {
start: {line: 11, character: 4},
end: {line: 12, character: 2}
},
uri: fixtureUri('node16/type-errors.mdx')
},
message: "'count' is declared here."
}
],
severity: 4,
tags: []
}
Expand Down

0 comments on commit 1f885bc

Please sign in to comment.