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

fix(typecheck): use projectInfo to get root file to trigger project diagnostics #143

Merged
merged 4 commits into from
Jan 12, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/typecheck/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
"chalk": "^4.1.0",
"fast-glob": "^3.2.4",
"minimist": "^1.2.5",
"resolve-from": "^5.0.0",
"typescript": "^4.0.3"
}
}
17 changes: 15 additions & 2 deletions packages/typecheck/src/TypeScriptServerHost.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,15 @@ import Path from 'path'
import { createInterface, Interface } from 'readline'
import { Readable, Writable } from 'stream'
import Proto from 'typescript/lib/protocol'
import resolveFrom from 'resolve-from'

function resolve(moduleId: string, directory: string): string {
try {
return resolveFrom(directory, moduleId)
} catch {
return require.resolve(moduleId)
}
}

const isDebugMode = process.env.DEBUG != null
function debug(...args: any[]): void {
Expand All @@ -18,7 +27,7 @@ export class TypeScriptServerHost {
Proto.CommandTypes.GeterrForProject,
]

public readonly serverPath = require.resolve('typescript/lib/tsserver')
public readonly serverPath = resolve('typescript/lib/tsserver', process.cwd())
public readonly pluginPath = Path.dirname(
require.resolve('@vuedx/typescript-plugin-vue/package.json'),
)
Expand All @@ -44,9 +53,13 @@ export class TypeScriptServerHost {
}

constructor() {
// prettier-ignore
const debugArgs =
process.env.DEBUG_TS_SERVER != null
? ['--logVerbosity', 'verbose', '--logFile', 'tsserver.log']
? [
'--logVerbosity', 'verbose',
'--logFile', process.env.TS_SERVER_LOG_FILE ?? 'tsserver.log',
]
: []
// prettier-ignore
this.server = fork(this.serverPath, [
Expand Down
2 changes: 1 addition & 1 deletion packages/typecheck/src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ async function createTextDocument(file: string): Promise<TextDocument> {
}

function toNormalizedPath(fileName: string): string {
return fileName.replace(/\\/g, '/')
return TS.server.toNormalizedPath(fileName)
}

function formatLocation(
Expand Down
116 changes: 73 additions & 43 deletions packages/typecheck/src/diagnostics.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
import glob from 'fast-glob'
import { getContainingFile } from '@vuedx/vue-virtual-textdocument'
import ts from 'typescript/lib/tsserverlibrary' // TODO: Load from current directory.
import Path from 'path'
import FS from 'fs'
import type ts from 'typescript/lib/tsserverlibrary'
import TS from 'typescript/lib/tsserverlibrary'
import { TypeScriptServerHost } from './TypeScriptServerHost'

function toNormalizedPath(fileName: string): string {
return TS.server.toNormalizedPath(fileName)
}
export type Diagnostics = Array<{
fileName: string
diagnostics: ts.server.protocol.Diagnostic[]
Expand Down Expand Up @@ -44,7 +49,7 @@ export async function* getDiagnostics(
cancellationToken.onabort = async () => {
await host.close()
}

const projectRootPath = toNormalizedPath(directory)
const diagnosticsPerFile = new Map<
string,
{
Expand All @@ -55,13 +60,12 @@ export async function* getDiagnostics(
>()

function setDiagnostics(
file: string,
fileName: string,
kind: 'semantic' | 'syntax' | 'suggestion',
diagnostics: ts.server.protocol.Diagnostic[],
): void {
if (file.includes('/node_modules/')) return
if (fileName.includes('/node_modules/')) return
if (diagnostics.length > 0) {
const fileName = getContainingFile(file)
const current = diagnosticsPerFile.get(fileName) ?? {}
diagnosticsPerFile.set(fileName, {
...current,
Expand All @@ -81,14 +85,14 @@ export async function* getDiagnostics(
}))
.filter((item) => item.diagnostics.length > 0)

let useProject: boolean = true
const refresh = async (files: string[]): Promise<Diagnostics> => {
diagnosticsPerFile.clear()
const start = Date.now()
if (logging) console.log(`Checking...`)
const id = await host.sendCommand('geterrForProject', {
file: files[0],
delay: 0,
})
const id = useProject
? await host.sendCommand('geterrForProject', { file: files[0], delay: 1 })
: await host.sendCommand('geterr', { files, delay: 1 })

return await new Promise((resolve) => {
const off = host.on('requestCompleted', async (event) => {
Expand All @@ -104,47 +108,73 @@ export async function* getDiagnostics(
})
})
}

const files = await glob(
['**/*.vue', '**/*.ts', '**/*.js', '**/*.jsx', '**/*.tsx'],
{
cwd: directory,
absolute: true,
ignore: ['node_modules', 'node_modules/**/*', '**/node_modules'],
},
)
await host.sendCommand('configure', {
hostInfo: '@vuedx/typecheck',
preferences: { disableSuggestions: false },
})
await host.sendCommand('compilerOptionsForInferredProjects', {
options: {
allowJs: true,
checkJs: true,
strict: true,
alwaysStrict: true,
allowNonTsExtensions: true,
jsx: 'preserve' as any,
},
})
if (files.length === 0) {
throw new Error('No ts/js/vue files found in current directory.')
}

const checkFile = files.find((file) => /\.(ts|js)x?/.test(file)) ?? files[0]
await host.sendCommand('updateOpen', {
openFiles: [{ file: checkFile, projectRootPath: directory }],
})
let files: string[]
const jsConfig = Path.resolve(directory, 'jsconfig.json')
const tsConfig = Path.resolve(directory, 'tsconfig.json')
if (FS.existsSync(tsConfig) || FS.existsSync(jsConfig)) {
useProject = true
const configFile = FS.existsSync(tsConfig) ? tsConfig : jsConfig
await host.sendCommand('updateOpen', {
openFiles: [
{
file: toNormalizedPath(configFile),
projectRootPath,
},
],
})

const { body: project } = await host.sendCommand('projectInfo', {
file: checkFile,
needFileNameList: false,
})
const { body } = await host.sendCommand('projectInfo', {
file: toNormalizedPath(configFile),
projectFileName: toNormalizedPath(configFile),
needFileNameList: true,
})

files =
body?.fileNames?.filter(
(fileName) =>
!fileName.includes('/node_modules/') && !fileName.endsWith('.json'),
) ?? []

if (files.length > 0) {
await host.sendCommand('updateOpen', {
closedFiles: [toNormalizedPath(configFile)],
})
await host.sendCommand('updateOpen', {
openFiles: [
{
file: toNormalizedPath(files[0]),
projectFileName: toNormalizedPath(configFile),
},
],
})
}
} else {
await host.sendCommand('compilerOptionsForInferredProjects', {
options: {
allowJs: true,
checkJs: true,
strict: true,
alwaysStrict: true,
allowNonTsExtensions: true,
jsx: 'preserve' as any,
},
})

files = (
await glob(['**/*.vue', '**/*.ts', '**/*.js', '**/*.jsx', '**/*.tsx'], {
cwd: directory,
absolute: true,
ignore: ['node_modules', 'dist'],
})
).map((fileName) => toNormalizedPath(fileName))

if (project?.configFileName?.endsWith('inferredProject1*') === true) {
// Inferred project open all files.
await host.sendCommand('updateOpen', {
openFiles: files.map((file) => ({ file, projectRootPath: directory })),
openFiles: files.map((file) => ({ file, projectRootPath })),
})
}

Expand Down
8 changes: 7 additions & 1 deletion packages/typecheck/test/typecheck.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,13 @@ describe('typecheck', () => {
const p = fork(
bin,
[...options, Path.resolve(__dirname, '../../../samples', directory)],
{ stdio: 'pipe' },
{
stdio: 'pipe',
env: {
DEBUG_TS_SERVER: 'yes',
TS_SERVER_LOG_FILE: Path.resolve(__dirname, '../../../test/output/tsserver.log')
},
},
)

let output = ''
Expand Down
Loading