From e30b23ddfab3be074c74f1978569e666f19ebb15 Mon Sep 17 00:00:00 2001 From: yoyo930021 Date: Tue, 24 Nov 2020 12:49:08 +0800 Subject: [PATCH 01/33] Add basic project service --- server/src/config.ts | 77 ++++ server/src/embeddedSupport/languageModes.ts | 15 +- server/src/modes/script/javascript.ts | 6 - server/src/modes/template/htmlMode.ts | 5 +- server/src/modes/template/index.ts | 8 +- .../src/modes/template/tagProviders/index.ts | 18 +- server/src/services/dependencyService.ts | 58 +-- server/src/services/projectService.ts | 361 +++++++++++++++ .../services/typescriptService/serviceHost.ts | 26 +- .../services/typescriptService/vueVersion.ts | 4 +- server/src/services/vls.ts | 417 ++++++------------ server/src/utils/paths.ts | 9 + server/src/utils/prettier/index.ts | 8 - server/src/vueServerMain.ts | 4 +- 14 files changed, 665 insertions(+), 351 deletions(-) create mode 100644 server/src/services/projectService.ts diff --git a/server/src/config.ts b/server/src/config.ts index 355a3792c4..9d6da20b73 100644 --- a/server/src/config.ts +++ b/server/src/config.ts @@ -1,3 +1,8 @@ +import path from 'path'; +import { getPathDepth, normalizeFileNameToFsPath, normalizeResolve } from './utils/paths'; +import fg from 'fast-glob'; +import { findConfigFile } from './utils/workspace'; + export interface VLSFormatConfig { defaultFormatter: { [lang: string]: string; @@ -141,3 +146,75 @@ export function getDefaultVLSConfig(): VLSFullConfig { stylusSupremacy: {} }; } + +export interface Component { + name: string; + path: string; +} + +export type Glob = string; + +export interface VeturProject { + root: string; + package?: string; + tsconfig?: string; + globalComponents: C[]; +} + +export interface VeturFullConfig { + settings: Record; + projects: VeturProject[]; +} + +export type VeturConfig = Partial> & { + projects?: Array & Partial)> +}; + +export async function getVeturFullConfig ( + rootPathForConfig: string, + workspacePath: string, + veturConfig: VeturConfig +): Promise { + const oldProjects = veturConfig.projects ?? [workspacePath]; + const projects = oldProjects.map((project) => { + const getFallbackPackagePath = (projectRoot: string) => { + const fallbackPackage = findConfigFile(projectRoot, 'package.json'); + return (fallbackPackage ? normalizeFileNameToFsPath(fallbackPackage) : undefined); + }; + + if (typeof project === 'string') { + const projectRoot = normalizeResolve(rootPathForConfig, project); + const tsconfigPath = findConfigFile(projectRoot, 'tsconfig.json') ?? findConfigFile(projectRoot, 'jsconfig.json'); + + return { + root: projectRoot, + package: getFallbackPackagePath(projectRoot), + tsconfig: tsconfigPath ? normalizeFileNameToFsPath(tsconfigPath) : undefined, + globalComponents: [] + } as VeturProject; + } + + const projectRoot = normalizeResolve(rootPathForConfig, project.root); + return { + root: projectRoot, + package: project.package ?? getFallbackPackagePath(projectRoot), + tsconfig: project.tsconfig ?? undefined, + globalComponents: project.globalComponents + ?.map(comp => { + if (typeof comp === 'string') { + return fg.sync(comp, { cwd: normalizeResolve(rootPathForConfig, projectRoot) }) + .map(fileName => ({ + name: path.basename(fileName, path.extname(fileName)), + path: normalizeFileNameToFsPath(fileName) + })); + } + return comp; + }) ?? [] + } as VeturProject; + }).sort((a, b) => getPathDepth(b.root, '/') - getPathDepth(a.root, '/')); + + return { + settings: veturConfig.settings ?? {}, + projects + } as VeturFullConfig; +} diff --git a/server/src/embeddedSupport/languageModes.ts b/server/src/embeddedSupport/languageModes.ts index 1822e794c7..cd1523e99e 100644 --- a/server/src/embeddedSupport/languageModes.ts +++ b/server/src/embeddedSupport/languageModes.ts @@ -111,7 +111,14 @@ export class LanguageModes { this.modelCaches.push(this.documentRegions); } - async init(workspacePath: string, services: VLSServices, globalSnippetDir?: string) { + async init( + workspacePath: string, + projectPath: string, + tsconfigPath: string | undefined, + packagePath: string | undefined, + services: VLSServices, + globalSnippetDir?: string + ) { const tsModule = services.dependencyService.get('typescript').module; /** @@ -121,7 +128,7 @@ export class LanguageModes { const vueDocument = this.documentRegions.refreshAndGet(document); return vueDocument.getSingleTypeDocument('script'); }); - this.serviceHost = getServiceHost(tsModule, workspacePath, scriptRegionDocuments); + this.serviceHost = getServiceHost(tsModule, projectPath, tsconfigPath, packagePath, scriptRegionDocuments); const autoImportVueService = createAutoImportVueService(tsModule, services.infoService); autoImportVueService.setGetTSScriptTarget(() => this.serviceHost.getComplierOptions().target); autoImportVueService.setGetFilesFn(() => @@ -132,7 +139,8 @@ export class LanguageModes { tsModule, this.serviceHost, this.documentRegions, - workspacePath, + projectPath, + packagePath, autoImportVueService, services.dependencyService, services.infoService @@ -141,7 +149,6 @@ export class LanguageModes { const jsMode = await getJavascriptMode( this.serviceHost, this.documentRegions, - workspacePath, services.dependencyService, services.infoService ); diff --git a/server/src/modes/script/javascript.ts b/server/src/modes/script/javascript.ts index a18d377c30..5ec24caa14 100644 --- a/server/src/modes/script/javascript.ts +++ b/server/src/modes/script/javascript.ts @@ -57,15 +57,9 @@ export const APPLY_REFACTOR_COMMAND = 'vetur.applyRefactorCommand'; export async function getJavascriptMode( serviceHost: IServiceHost, documentRegions: LanguageModelCache, - workspacePath: string | undefined, dependencyService: DependencyService, vueInfoService?: VueInfoService ): Promise { - if (!workspacePath) { - return { - ...nullMode - }; - } const jsDocuments = getLanguageModelCache(10, 60, document => { const vueDocument = documentRegions.refreshAndGet(document); return vueDocument.getSingleTypeDocument('script'); diff --git a/server/src/modes/template/htmlMode.ts b/server/src/modes/template/htmlMode.ts index 1f88959d60..5a7f217080 100644 --- a/server/src/modes/template/htmlMode.ts +++ b/server/src/modes/template/htmlMode.ts @@ -42,14 +42,15 @@ export class HTMLMode implements LanguageMode { constructor( documentRegions: LanguageModelCache, - workspacePath: string | undefined, + projectPath: string | undefined, + packagePath: string | undefined, vueVersion: VueVersion, private dependencyService: DependencyService, private vueDocuments: LanguageModelCache, private autoImportVueService: AutoImportVueService, private vueInfoService?: VueInfoService ) { - this.tagProviderSettings = getTagProviderSettings(workspacePath); + this.tagProviderSettings = getTagProviderSettings(projectPath, packagePath); this.enabledTagProviders = getEnabledTagProviders(this.tagProviderSettings); this.embeddedDocuments = getLanguageModelCache(10, 60, document => documentRegions.refreshAndGet(document).getSingleLanguageDocument('vue-html') diff --git a/server/src/modes/template/index.ts b/server/src/modes/template/index.ts index 4f789af395..3deb06f4d4 100644 --- a/server/src/modes/template/index.ts +++ b/server/src/modes/template/index.ts @@ -25,16 +25,18 @@ export class VueHTMLMode implements LanguageMode { tsModule: RuntimeLibrary['typescript'], serviceHost: IServiceHost, documentRegions: DocumentRegionCache, - workspacePath: string, + projectPath: string, + packagePath: string | undefined, autoImportVueService: AutoImportVueService, dependencyService: DependencyService, vueInfoService?: VueInfoService ) { const vueDocuments = getLanguageModelCache(10, 60, document => parseHTMLDocument(document)); - const vueVersion = inferVueVersion(workspacePath); + const vueVersion = inferVueVersion(projectPath, packagePath); this.htmlMode = new HTMLMode( documentRegions, - workspacePath, + projectPath, + packagePath, vueVersion, dependencyService, vueDocuments, diff --git a/server/src/modes/template/tagProviders/index.ts b/server/src/modes/template/tagProviders/index.ts index b3480ab99c..9c5e4d3b63 100644 --- a/server/src/modes/template/tagProviders/index.ts +++ b/server/src/modes/template/tagProviders/index.ts @@ -31,7 +31,7 @@ export interface CompletionConfiguration { [provider: string]: boolean; } -export function getTagProviderSettings(workspacePath: string | null | undefined) { +export function getTagProviderSettings(projectPath: string | null | undefined, packagePath: string | undefined) { const settings: CompletionConfiguration = { '__vetur-workspace': true, html5: true, @@ -47,16 +47,16 @@ export function getTagProviderSettings(workspacePath: string | null | undefined) nuxt: false, gridsome: false }; - if (!workspacePath) { + if (!projectPath) { return settings; } try { - const packagePath = findConfigFile(workspacePath, 'package.json'); - if (!packagePath) { + const packageJSONPath = packagePath ?? findConfigFile(projectPath, 'package.json'); + if (!packageJSONPath) { return settings; } - const rootPkgJson = JSON.parse(fs.readFileSync(packagePath, 'utf-8')); + const rootPkgJson = JSON.parse(fs.readFileSync(packageJSONPath, 'utf-8')); const dependencies = rootPkgJson.dependencies || {}; const devDependencies = rootPkgJson.devDependencies || {}; @@ -99,7 +99,7 @@ export function getTagProviderSettings(workspacePath: string | null | undefined) dependencies['quasar-framework'] = '^0.0.17'; } if (dependencies['nuxt'] || dependencies['nuxt-edge'] || devDependencies['nuxt'] || devDependencies['nuxt-edge']) { - const nuxtTagProvider = getNuxtTagProvider(workspacePath); + const nuxtTagProvider = getNuxtTagProvider(projectPath); if (nuxtTagProvider) { settings['nuxt'] = true; allTagProviders.push(nuxtTagProvider); @@ -109,7 +109,7 @@ export function getTagProviderSettings(workspacePath: string | null | undefined) settings['gridsome'] = true; } - const workspaceTagProvider = getWorkspaceTagProvider(workspacePath, rootPkgJson); + const workspaceTagProvider = getWorkspaceTagProvider(projectPath, rootPkgJson); if (workspaceTagProvider) { allTagProviders.push(workspaceTagProvider); } @@ -117,7 +117,7 @@ export function getTagProviderSettings(workspacePath: string | null | undefined) for (const dep of [...Object.keys(dependencies), ...Object.keys(devDependencies)]) { let runtimePkgJsonPath; try { - runtimePkgJsonPath = require.resolve(join(dep, 'package.json'), { paths: [workspacePath] }); + runtimePkgJsonPath = require.resolve(join(dep, 'package.json'), { paths: [projectPath] }); } catch { continue; } @@ -127,7 +127,7 @@ export function getTagProviderSettings(workspacePath: string | null | undefined) continue; } - const depTagProvider = getDependencyTagProvider(workspacePath, runtimePkgJson); + const depTagProvider = getDependencyTagProvider(projectPath, runtimePkgJson); if (!depTagProvider) { continue; } diff --git a/server/src/services/dependencyService.ts b/server/src/services/dependencyService.ts index 3faa8b94f7..27928c9e83 100644 --- a/server/src/services/dependencyService.ts +++ b/server/src/services/dependencyService.ts @@ -12,14 +12,15 @@ import stylusSupremacy from 'stylus-supremacy'; import * as prettierPluginPug from '@prettier/plugin-pug'; import { performance } from 'perf_hooks'; import { logger } from '../log'; +import { getPathDepth } from '../utils/paths'; const readFileAsync = util.promisify(fs.readFile); const accessFileAsync = util.promisify(fs.access); -async function createNodeModulesPaths(workspacePath: string) { +async function createNodeModulesPaths(rootPath: string) { const startTime = performance.now(); const nodeModules = await fg('**/node_modules', { - cwd: workspacePath.replace(/\\/g, '/'), + cwd: rootPath.replace(/\\/g, '/'), absolute: true, unique: true, onlyFiles: false, @@ -29,7 +30,7 @@ async function createNodeModulesPaths(workspacePath: string) { ignore: ['**/node_modules/**/node_modules'] }); - logger.logInfo(`Find node_modules paths in ${workspacePath} - ${Math.round(performance.now() - startTime)}ms`); + logger.logInfo(`Find node_modules paths in ${rootPath} - ${Math.round(performance.now() - startTime)}ms`); return nodeModules; } @@ -62,13 +63,9 @@ async function findAllPackages(nodeModulesPaths: string[], moduleName: string) { return packages; } -function getPathDepth(filePath: string) { - return filePath.split(path.sep).length; -} - function compareDependency(a: Dependency, b: Dependency) { - const aDepth = getPathDepth(a.dir); - const bDepth = getPathDepth(b.dir); + const aDepth = getPathDepth(a.dir, path.sep); + const bDepth = getPathDepth(b.dir, path.sep); return bDepth - aDepth; } @@ -92,15 +89,20 @@ export interface RuntimeLibrary { export interface DependencyService { useWorkspaceDependencies: boolean; - workspacePath: string; - init(workspacePath: string, useWorkspaceDependencies: boolean, tsSDKPath?: string): Promise; + init( + rootPathForConfig: string, + workspacePath: string, + useWorkspaceDependencies: boolean, + tsSDKPath?: string + ): Promise; get(lib: L, filePath?: string): Dependency; getBundled(lib: L): Dependency; } -export const createDependencyService = () => { - let useWorkspaceDeps: boolean; - let rootPath: string; +export const createDependencyService = (): DependencyService => { + let $useWorkspaceDependencies: boolean; + let $rootPathForConfig: string; + let $workspacePath: string; let loaded: { [K in keyof RuntimeLibrary]: Dependency[] }; const bundledModules = { @@ -113,8 +115,13 @@ export const createDependencyService = () => { '@prettier/plugin-pug': prettierPluginPug }; - async function init(workspacePath: string, useWorkspaceDependencies: boolean, tsSDKPath?: string) { - const nodeModulesPaths = useWorkspaceDependencies ? await createNodeModulesPaths(workspacePath) : []; + async function init( + rootPathForConfig: string, + workspacePath: string, + useWorkspaceDependencies: boolean, + tsSDKPath?: string + ) { + const nodeModulesPaths = useWorkspaceDependencies ? await createNodeModulesPaths(rootPathForConfig) : []; const loadTypeScript = async (): Promise[]> => { try { @@ -138,7 +145,7 @@ export const createDependencyService = () => { if (useWorkspaceDependencies) { const packages = await findAllPackages(nodeModulesPaths, 'typescript'); if (packages.length === 0) { - throw new Error(`No find any packages in ${workspacePath}.`); + throw new Error(`No find any packages in ${rootPathForConfig}.`); } return packages @@ -175,7 +182,7 @@ export const createDependencyService = () => { if (useWorkspaceDependencies) { const packages = await findAllPackages(nodeModulesPaths, name); if (packages.length === 0) { - throw new Error(`No find ${name} packages in ${workspacePath}.`); + throw new Error(`No find ${name} packages in ${rootPathForConfig}.`); } return packages @@ -207,8 +214,9 @@ export const createDependencyService = () => { } }; - useWorkspaceDeps = useWorkspaceDependencies; - rootPath = workspacePath; + $useWorkspaceDependencies = useWorkspaceDependencies; + $workspacePath = workspacePath; + $rootPathForConfig = rootPathForConfig; loaded = { typescript: await loadTypeScript(), prettier: await loadCommonDep('prettier', bundledModules['prettier']), @@ -237,7 +245,10 @@ export const createDependencyService = () => { const possiblePaths: string[] = []; let tempPath = path.dirname(filePath); - while (rootPath === tempPath || getPathDepth(rootPath) > getPathDepth(tempPath)) { + while ( + $rootPathForConfig === tempPath || + getPathDepth($rootPathForConfig, path.sep) > getPathDepth(tempPath, path.sep) + ) { possiblePaths.push(path.resolve(tempPath, `node_modules/${lib}`)); tempPath = path.resolve(tempPath, '../'); } @@ -257,10 +268,7 @@ export const createDependencyService = () => { return { get useWorkspaceDependencies() { - return useWorkspaceDeps; - }, - get workspacePath() { - return rootPath; + return $useWorkspaceDependencies; }, init, get, diff --git a/server/src/services/projectService.ts b/server/src/services/projectService.ts new file mode 100644 index 0000000000..5483849825 --- /dev/null +++ b/server/src/services/projectService.ts @@ -0,0 +1,361 @@ +import path from 'path'; +import { + CodeAction, + CodeActionParams, + ColorInformation, + ColorPresentation, + ColorPresentationParams, + CompletionItem, + CompletionList, + CompletionParams, + CompletionTriggerKind, + Definition, + Diagnostic, + DocumentColorParams, + DocumentFormattingParams, + DocumentHighlight, + DocumentLink, + DocumentLinkParams, + DocumentSymbolParams, + FoldingRange, + FoldingRangeParams, + Hover, + Location, + SignatureHelp, + SymbolInformation, + TextDocumentPositionParams, + TextEdit, + WorkspaceEdit +} from 'vscode-languageserver'; +import { TextDocument } from 'vscode-languageserver-textdocument'; +import { URI } from 'vscode-uri'; +import { VLSConfig, VLSFullConfig } from '../config'; +import { LanguageId } from '../embeddedSupport/embeddedSupport'; +import { LanguageMode, LanguageModes } from '../embeddedSupport/languageModes'; +import { logger } from '../log'; +import { NULL_COMPLETION, NULL_HOVER, NULL_SIGNATURE } from '../modes/nullMode'; +import { DocumentContext, RefactorAction } from '../types'; +import { VCancellationToken, VCancellationTokenSource } from '../utils/cancellationToken'; +import { getFileFsPath } from '../utils/paths'; +import { DependencyService } from './dependencyService'; +import { DocumentService } from './documentService'; +import { VueInfoService } from './vueInfoService'; + +export interface ProjectService { + configure(config: VLSFullConfig): void; + onDocumentFormatting(params: DocumentFormattingParams): Promise; + onCompletion(params: CompletionParams): Promise; + onCompletionResolve(item: CompletionItem): Promise; + onHover(params: TextDocumentPositionParams): Promise; + onDocumentHighlight(params: TextDocumentPositionParams): Promise; + onDefinition(params: TextDocumentPositionParams): Promise; + onReferences(params: TextDocumentPositionParams): Promise; + onDocumentLinks(params: DocumentLinkParams): Promise; + onDocumentSymbol(params: DocumentSymbolParams): Promise; + onDocumentColors(params: DocumentColorParams): Promise; + onColorPresentations(params: ColorPresentationParams): Promise; + onSignatureHelp(params: TextDocumentPositionParams): Promise; + onFoldingRanges(params: FoldingRangeParams): Promise; + onCodeAction(params: CodeActionParams): Promise; + doValidate(doc: TextDocument, cancellationToken?: VCancellationToken): Promise; + getRefactorEdits(refactorAction: RefactorAction): Promise; + dispose(): Promise; +} + +export async function createProjectService( + rootPathForConfig: string, + workspacePath: string, + projectPath: string, + tsconfigPath: string | undefined, + packagePath: string | undefined, + documentService: DocumentService, + initialConfig: VLSConfig, + globalSnippetDir: string | undefined, + dependencyService: DependencyService +): Promise { + let $config = initialConfig; + + const vueInfoService = new VueInfoService(); + const languageModes = new LanguageModes(); + + function getValidationFlags(): Record { + return { + 'vue-html': $config.vetur.validation.template, + css: $config.vetur.validation.style, + postcss: $config.vetur.validation.style, + scss: $config.vetur.validation.style, + less: $config.vetur.validation.style, + javascript: $config.vetur.validation.script + }; + } + + const validationFlags = getValidationFlags(); + + vueInfoService.init(languageModes); + await languageModes.init( + workspacePath, + projectPath, + tsconfigPath, + packagePath, + { + infoService: vueInfoService, + dependencyService + }, + globalSnippetDir + ); + + function configure(config: VLSFullConfig) { + $config = config; + languageModes.getAllModes().forEach(m => { + if (m.configure) { + m.configure(config); + } + }); + } + configure(initialConfig); + + return { + configure, + async onDocumentFormatting({ textDocument, options }) { + const doc = documentService.getDocument(textDocument.uri)!; + + const modeRanges = languageModes.getAllLanguageModeRangesInDocument(doc); + const allEdits: TextEdit[] = []; + + const errMessages: string[] = []; + + modeRanges.forEach(modeRange => { + if (modeRange.mode && modeRange.mode.format) { + try { + const edits = modeRange.mode.format(doc, { start: modeRange.start, end: modeRange.end }, options); + for (const edit of edits) { + allEdits.push(edit); + } + } catch (err) { + errMessages.push(err.toString()); + } + } + }); + + if (errMessages.length !== 0) { + console.error('Formatting failed: "' + errMessages.join('\n') + '"'); + return []; + } + + return allEdits; + }, + async onCompletion({ textDocument, position, context }) { + const doc = documentService.getDocument(textDocument.uri)!; + const mode = languageModes.getModeAtPosition(doc, position); + if (mode && mode.doComplete) { + /** + * Only use space as trigger character in `vue-html` mode + */ + if ( + mode.getId() !== 'vue-html' && + context && + context?.triggerKind === CompletionTriggerKind.TriggerCharacter && + context.triggerCharacter === ' ' + ) { + return NULL_COMPLETION; + } + + return mode.doComplete(doc, position); + } + + return NULL_COMPLETION; + }, + async onCompletionResolve(item) { + if (item.data) { + const uri: string = item.data.uri; + const languageId: LanguageId = item.data.languageId; + + /** + * Template files need to go through HTML-template service + */ + if (uri.endsWith('.template')) { + const doc = documentService.getDocument(uri.slice(0, -'.template'.length)); + const mode = languageModes.getMode(languageId); + if (doc && mode && mode.doResolve) { + return mode.doResolve(doc, item); + } + } + + if (uri && languageId) { + const doc = documentService.getDocument(uri); + const mode = languageModes.getMode(languageId); + if (doc && mode && mode.doResolve) { + return mode.doResolve(doc, item); + } + } + } + + return item; + }, + async onHover({ textDocument, position }) { + const doc = documentService.getDocument(textDocument.uri)!; + const mode = languageModes.getModeAtPosition(doc, position); + if (mode && mode.doHover) { + return mode.doHover(doc, position); + } + return NULL_HOVER; + }, + async onDocumentHighlight({ textDocument, position }) { + const doc = documentService.getDocument(textDocument.uri)!; + const mode = languageModes.getModeAtPosition(doc, position); + if (mode && mode.findDocumentHighlight) { + return mode.findDocumentHighlight(doc, position); + } + return []; + }, + async onDefinition({ textDocument, position }) { + const doc = documentService.getDocument(textDocument.uri)!; + const mode = languageModes.getModeAtPosition(doc, position); + if (mode && mode.findDefinition) { + return mode.findDefinition(doc, position); + } + return []; + }, + async onReferences({ textDocument, position }) { + const doc = documentService.getDocument(textDocument.uri)!; + const mode = languageModes.getModeAtPosition(doc, position); + if (mode && mode.findReferences) { + return mode.findReferences(doc, position); + } + return []; + }, + async onDocumentLinks({ textDocument }) { + const doc = documentService.getDocument(textDocument.uri)!; + const documentContext: DocumentContext = { + resolveReference: ref => { + if (projectPath && ref[0] === '/') { + return URI.file(path.resolve(projectPath, ref)).toString(); + } + const fsPath = getFileFsPath(doc.uri); + return URI.file(path.resolve(fsPath, '..', ref)).toString(); + } + }; + + const links: DocumentLink[] = []; + languageModes.getAllLanguageModeRangesInDocument(doc).forEach(m => { + if (m.mode.findDocumentLinks) { + links.push.apply(links, m.mode.findDocumentLinks(doc, documentContext)); + } + }); + return links; + }, + async onDocumentSymbol({ textDocument }) { + const doc = documentService.getDocument(textDocument.uri)!; + const symbols: SymbolInformation[] = []; + + languageModes.getAllLanguageModeRangesInDocument(doc).forEach(m => { + if (m.mode.findDocumentSymbols) { + symbols.push.apply(symbols, m.mode.findDocumentSymbols(doc)); + } + }); + return symbols; + }, + async onDocumentColors({ textDocument }) { + const doc = documentService.getDocument(textDocument.uri)!; + const colors: ColorInformation[] = []; + + const distinctModes: Set = new Set(); + languageModes.getAllLanguageModeRangesInDocument(doc).forEach(m => { + distinctModes.add(m.mode); + }); + + for (const mode of distinctModes) { + if (mode.findDocumentColors) { + colors.push.apply(colors, mode.findDocumentColors(doc)); + } + } + + return colors; + }, + async onColorPresentations({ textDocument, color, range }) { + const doc = documentService.getDocument(textDocument.uri)!; + const mode = languageModes.getModeAtPosition(doc, range.start); + if (mode && mode.getColorPresentations) { + return mode.getColorPresentations(doc, color, range); + } + return []; + }, + async onSignatureHelp({ textDocument, position }) { + const doc = documentService.getDocument(textDocument.uri)!; + const mode = languageModes.getModeAtPosition(doc, position); + if (mode && mode.doSignatureHelp) { + return mode.doSignatureHelp(doc, position); + } + return NULL_SIGNATURE; + }, + async onFoldingRanges({ textDocument }) { + const doc = documentService.getDocument(textDocument.uri)!; + const lmrs = languageModes.getAllLanguageModeRangesInDocument(doc); + + const result: FoldingRange[] = []; + + lmrs.forEach(lmr => { + if (lmr.mode.getFoldingRanges) { + lmr.mode.getFoldingRanges(doc).forEach(r => result.push(r)); + } + + result.push({ + startLine: lmr.start.line, + startCharacter: lmr.start.character, + endLine: lmr.end.line, + endCharacter: lmr.end.character + }); + }); + + return result; + }, + async onCodeAction({ textDocument, range, context }: CodeActionParams) { + if (!$config.vetur.languageFeatures.codeActions) { + return []; + } + + const doc = documentService.getDocument(textDocument.uri)!; + const mode = languageModes.getModeAtPosition(doc, range.start); + if (languageModes.getModeAtPosition(doc, range.end) !== mode) { + return []; + } + if (mode && mode.getCodeActions) { + return mode.getCodeActions(doc, range, /*formatParams*/ {} as any, context); + } + return []; + }, + async doValidate(doc: TextDocument, cancellationToken?: VCancellationToken) { + const diagnostics: Diagnostic[] = []; + if (doc.languageId === 'vue') { + for (const lmr of languageModes.getAllLanguageModeRangesInDocument(doc)) { + if (lmr.mode.doValidation) { + if (validationFlags[lmr.mode.getId()]) { + diagnostics.push.apply(diagnostics, await lmr.mode.doValidation(doc, cancellationToken)); + } + // Special case for template type checking + else if (lmr.mode.getId() === 'vue-html' && $config.vetur.experimental.templateInterpolationService) { + diagnostics.push.apply(diagnostics, await lmr.mode.doValidation(doc, cancellationToken)); + } + } + } + } + if (cancellationToken?.isCancellationRequested) { + return null; + } + return diagnostics; + }, + async getRefactorEdits(refactorAction: RefactorAction) { + const uri = URI.file(refactorAction.fileName).toString(); + const doc = documentService.getDocument(uri)!; + const startPos = doc.positionAt(refactorAction.textRange.pos); + const mode = languageModes.getModeAtPosition(doc, startPos); + if (mode && mode.getRefactorEdits) { + return mode.getRefactorEdits(doc, refactorAction); + } + return undefined; + }, + async dispose() { + languageModes.dispose(); + } + }; +} diff --git a/server/src/services/typescriptService/serviceHost.ts b/server/src/services/typescriptService/serviceHost.ts index dde788b460..1488c812c5 100644 --- a/server/src/services/typescriptService/serviceHost.ts +++ b/server/src/services/typescriptService/serviceHost.ts @@ -85,7 +85,9 @@ export interface IServiceHost { */ export function getServiceHost( tsModule: RuntimeLibrary['typescript'], - workspacePath: string, + projectPath: string, + tsconfigPath: string | undefined, + packagePath: string | undefined, updatedScriptRegionDocuments: LanguageModelCache ): IServiceHost { patchTS(tsModule); @@ -99,7 +101,7 @@ export function getServiceHost( const projectFileSnapshots = new Map(); const moduleResolutionCache = new ModuleResolutionCache(); - const parsedConfig = getParsedConfig(tsModule, workspacePath); + const parsedConfig = getParsedConfig(tsModule, projectPath, tsconfigPath); /** * Only js/ts files in local project */ @@ -111,7 +113,7 @@ export function getServiceHost( const vueSys = getVueSys(tsModule, scriptFileNameSet); - const vueVersion = inferVueVersion(workspacePath); + const vueVersion = inferVueVersion(projectPath, packagePath); const compilerOptions = { ...getDefaultCompilerOptions(tsModule), ...parsedConfig.options @@ -207,7 +209,7 @@ export function getServiceHost( if ( isExcludedFile && configFileSpecs && - isExcludedFile(fileFsPath, configFileSpecs, workspacePath, true, workspacePath) + isExcludedFile(fileFsPath, configFileSpecs, projectPath, true, projectPath) ) { return; } @@ -430,7 +432,7 @@ export function getServiceHost( getChangeRange: () => void 0 }; }, - getCurrentDirectory: () => workspacePath, + getCurrentDirectory: () => projectPath, getDefaultLibFileName: tsModule.getDefaultLibFilePath, getNewLine: () => NEWLINE, useCaseSensitiveFileNames: () => true @@ -509,18 +511,20 @@ function getScriptKind(tsModule: RuntimeLibrary['typescript'], langId: string): : tsModule.ScriptKind.JS; } -function getParsedConfig(tsModule: RuntimeLibrary['typescript'], workspacePath: string) { - const configFilename = - tsModule.findConfigFile(workspacePath, tsModule.sys.fileExists, 'tsconfig.json') || - tsModule.findConfigFile(workspacePath, tsModule.sys.fileExists, 'jsconfig.json'); +function getParsedConfig( + tsModule: RuntimeLibrary['typescript'], + projectPath: string, + tsconfigPath: string | undefined +) { + const configFilename = tsconfigPath; const configJson = (configFilename && tsModule.readConfigFile(configFilename, tsModule.sys.readFile).config) || { - exclude: defaultIgnorePatterns(tsModule, workspacePath) + exclude: defaultIgnorePatterns(tsModule, projectPath) }; // existingOptions should be empty since it always takes priority return tsModule.parseJsonConfigFileContent( configJson, tsModule.sys, - workspacePath, + projectPath, /*existingOptions*/ {}, configFilename, /*resolutionStack*/ undefined, diff --git a/server/src/services/typescriptService/vueVersion.ts b/server/src/services/typescriptService/vueVersion.ts index d3868bdf30..0f15e73b23 100644 --- a/server/src/services/typescriptService/vueVersion.ts +++ b/server/src/services/typescriptService/vueVersion.ts @@ -17,8 +17,8 @@ function floatVersionToEnum(v: number) { } } -export function inferVueVersion(workspacePath: string): VueVersion { - const packageJSONPath = findConfigFile(workspacePath, 'package.json'); +export function inferVueVersion(workspacePath: string, packagePath: string | undefined): VueVersion { + const packageJSONPath = packagePath ?? findConfigFile(workspacePath, 'package.json'); try { const packageJSON = packageJSONPath && JSON.parse(readFileSync(packageJSONPath, { encoding: 'utf-8' })); const vueDependencyVersion = packageJSON.dependencies.vue || packageJSON.devDependencies.vue; diff --git a/server/src/services/vls.ts b/server/src/services/vls.ts index e3a48fba63..ef0af191ff 100644 --- a/server/src/services/vls.ts +++ b/server/src/services/vls.ts @@ -1,6 +1,5 @@ import path from 'path'; -import fs from 'fs'; -import { getFileFsPath, normalizeFileNameToFsPath } from '../utils/paths'; +import { getFileFsPath, getPathDepth, normalizeFileNameToFsPath, normalizeResolve } from '../utils/paths'; import { DidChangeConfigurationParams, @@ -19,7 +18,6 @@ import { DocumentSymbolParams, CodeActionParams, CompletionParams, - CompletionTriggerKind, ExecuteCommandParams, ApplyWorkspaceEditRequest, FoldingRangeParams @@ -38,8 +36,8 @@ import { SymbolInformation, TextEdit, ColorPresentation, - Range, - FoldingRange + FoldingRange, + DocumentUri } from 'vscode-languageserver-types'; import type { TextDocument } from 'vscode-languageserver-textdocument'; @@ -53,20 +51,25 @@ import { DocumentContext, RefactorAction } from '../types'; import { DocumentService } from './documentService'; import { VueHTMLMode } from '../modes/template'; import { logger } from '../log'; -import { getDefaultVLSConfig, VLSFullConfig, VLSConfig } from '../config'; +import { getDefaultVLSConfig, VLSFullConfig, VLSConfig, getVeturFullConfig, VeturFullConfig } from '../config'; import { LanguageId } from '../embeddedSupport/embeddedSupport'; import { APPLY_REFACTOR_COMMAND } from '../modes/script/javascript'; import { VCancellationToken, VCancellationTokenSource } from '../utils/cancellationToken'; +import { findConfigFile } from '../utils/workspace'; +import { createProjectService, ProjectService } from './projectService'; export class VLS { // @Todo: Remove this and DocumentContext private workspacePath: string | undefined; - + private veturConfig: VeturFullConfig; private documentService: DocumentService; - private vueInfoService: VueInfoService; + private rootPathForConfig: string; + private globalSnippetDir: string; + private projects: Map; + // private vueInfoService: VueInfoService; private dependencyService: DependencyService; - private languageModes: LanguageModes; + // private languageModes: LanguageModes; private pendingValidationRequests: { [uri: string]: NodeJS.Timer } = {}; private cancellationTokenValidationRequests: { [uri: string]: VCancellationTokenSource } = {}; @@ -88,15 +91,13 @@ export class VLS { constructor(private lspConnection: Connection) { this.documentService = new DocumentService(this.lspConnection); - this.vueInfoService = new VueInfoService(); + // this.vueInfoService = new VueInfoService(); this.dependencyService = createDependencyService(); - this.languageModes = new LanguageModes(); + // this.languageModes = new LanguageModes(); } async init(params: InitializeParams) { - const config = this.getFullConfig(params.initializationOptions?.config); - const workspacePath = params.rootPath; if (!workspacePath) { console.error('No workspace path found. Vetur initialization failed.'); @@ -107,30 +108,23 @@ export class VLS { this.workspacePath = normalizeFileNameToFsPath(workspacePath); - // Enable Yarn PnP support https://yarnpkg.com/features/pnp - if (!process.versions.pnp) { - if (fs.existsSync(path.join(this.workspacePath, '.pnp.js'))) { - require(path.join(workspacePath, '.pnp.js')).setup(); - } else if (fs.existsSync(path.join(this.workspacePath, '.pnp.cjs'))) { - require(path.join(workspacePath, '.pnp.cjs')).setup(); - } - } + this.globalSnippetDir = params.initializationOptions?.globalSnippetDir; + const veturConfigPath = findConfigFile(this.workspacePath, 'vetur.config.js'); + this.rootPathForConfig = normalizeFileNameToFsPath(veturConfigPath ? path.dirname(veturConfigPath) : workspacePath); + this.veturConfig = await getVeturFullConfig( + this.rootPathForConfig, + this.workspacePath, + veturConfigPath ? require(veturConfigPath) : {} + ); + const config = this.getFullConfig(params.initializationOptions?.config); - this.vueInfoService.init(this.languageModes); await this.dependencyService.init( + this.rootPathForConfig, this.workspacePath, config.vetur.useWorkspaceDependencies, config.typescript.tsdk ); - - await this.languageModes.init( - this.workspacePath, - { - infoService: this.vueInfoService, - dependencyService: this.dependencyService - }, - params.initializationOptions?.globalSnippetDir - ); + this.projects = new Map(); this.configure(config); this.setupConfigListeners(); @@ -148,7 +142,11 @@ export class VLS { } private getFullConfig(config: any | undefined): VLSFullConfig { - return config ? _.merge(getDefaultVLSConfig(), config) : getDefaultVLSConfig(); + const result = config ? _.merge(getDefaultVLSConfig(), config) : getDefaultVLSConfig(); + Object.keys(this.veturConfig.settings).forEach(key => { + _.set(result, key, this.veturConfig.settings[key]); + }); + return result; } private setupConfigListeners() { @@ -158,7 +156,39 @@ export class VLS { this.setupDynamicFormatters(config); }); - this.documentService.getAllDocuments().forEach(this.triggerValidation); + // this.documentService.getAllDocuments().forEach(this.triggerValidation); + } + + private async getProjectService(uri: DocumentUri): Promise { + const projectRootPaths = this.veturConfig.projects + .map(project => ({ + rootFsPath: normalizeResolve(this.rootPathForConfig, project.root), + tsconfigPath: project.tsconfig, + packagePath: project.package + })) + .sort((a, b) => getPathDepth(b.rootFsPath, '/') - getPathDepth(a.rootFsPath, '/')); + const docFsPath = getFileFsPath(uri); + const projectConfig = projectRootPaths.find(projectConfig => docFsPath.startsWith(projectConfig.rootFsPath)); + if (!projectConfig) { + return undefined; + } + if (this.projects.has(projectConfig.rootFsPath)) { + return this.projects.get(projectConfig.rootFsPath); + } + + const project = await createProjectService( + this.rootPathForConfig, + this.workspacePath ?? this.rootPathForConfig, + projectConfig.rootFsPath, + projectConfig.tsconfigPath, + projectConfig.packagePath, + this.documentService, + this.config, + this.globalSnippetDir, + this.dependencyService + ); + this.projects.set(projectConfig.rootFsPath, project); + return project; } private setupLSPHandlers() { @@ -183,9 +213,9 @@ export class VLS { } private setupCustomLSPHandlers() { - this.lspConnection.onRequest('$/queryVirtualFileInfo', ({ fileName, currFileText }) => { - return (this.languageModes.getMode('vue-html') as VueHTMLMode).queryVirtualFileInfo(fileName, currFileText); - }); + // this.lspConnection.onRequest('$/queryVirtualFileInfo', ({ fileName, currFileText }) => { + // return (this.languageModes.getMode('vue-html') as VueHTMLMode).queryVirtualFileInfo(fileName, currFileText); + // }); this.lspConnection.onRequest('$/getDiagnostics', async params => { const doc = this.documentService.getDocument(params.uri); @@ -220,17 +250,18 @@ export class VLS { this.lspConnection.sendDiagnostics({ uri: e.document.uri, diagnostics: [] }); }); this.lspConnection.onDidChangeWatchedFiles(({ changes }) => { - const jsMode = this.languageModes.getMode('javascript'); - if (!jsMode) { - throw Error(`Can't find JS mode.`); - } - - changes.forEach(c => { - if (c.type === FileChangeType.Changed) { - const fsPath = getFileFsPath(c.uri); - jsMode.onDocumentChanged!(fsPath); - } - }); + // const jsMode = this.languageModes.getMode('javascript'); + // if (!jsMode) { + // throw Error(`Can't find JS mode.`); + // } + + // changes.forEach(async c => { + // if (c.type === FileChangeType.Changed) { + // const project = await this.getProjectService(c.uri) + // const fsPath = getFileFsPath(c.uri); + // jsMode.onDocumentChanged!(fsPath); + // } + // }); this.documentService.getAllDocuments().forEach(d => { this.triggerValidation(d); @@ -251,10 +282,8 @@ export class VLS { this.templateInterpolationValidation = config.vetur.experimental.templateInterpolationService; - this.languageModes.getAllModes().forEach(m => { - if (m.configure) { - m.configure(config); - } + this.projects.forEach(project => { + project.configure(config); }); logger.setLevel(config.vetur.dev.logLevel); @@ -278,244 +307,94 @@ export class VLS { * Language Features */ - onDocumentFormatting({ textDocument, options }: DocumentFormattingParams): TextEdit[] { - const doc = this.documentService.getDocument(textDocument.uri)!; - - const modeRanges = this.languageModes.getAllLanguageModeRangesInDocument(doc); - const allEdits: TextEdit[] = []; + async onDocumentFormatting(params: DocumentFormattingParams): Promise { + const project = await this.getProjectService(params.textDocument.uri); - const errMessages: string[] = []; - - modeRanges.forEach(modeRange => { - if (modeRange.mode && modeRange.mode.format) { - try { - const edits = modeRange.mode.format(doc, this.toSimpleRange(modeRange), options); - for (const edit of edits) { - allEdits.push(edit); - } - } catch (err) { - errMessages.push(err.toString()); - } - } - }); - - if (errMessages.length !== 0) { - this.displayErrorMessage('Formatting failed: "' + errMessages.join('\n') + '"'); - return []; - } - - return allEdits; + return project?.onDocumentFormatting(params) ?? []; } - private toSimpleRange(modeRange: LanguageModeRange): Range { - return { - start: modeRange.start, - end: modeRange.end - }; - } - - onCompletion({ textDocument, position, context }: CompletionParams): CompletionList { - const doc = this.documentService.getDocument(textDocument.uri)!; - const mode = this.languageModes.getModeAtPosition(doc, position); - if (mode && mode.doComplete) { - /** - * Only use space as trigger character in `vue-html` mode - */ - if ( - mode.getId() !== 'vue-html' && - context && - context?.triggerKind === CompletionTriggerKind.TriggerCharacter && - context.triggerCharacter === ' ' - ) { - return NULL_COMPLETION; - } - - return mode.doComplete(doc, position); - } + async onCompletion(params: CompletionParams): Promise { + const project = await this.getProjectService(params.textDocument.uri); - return NULL_COMPLETION; + return project?.onCompletion(params) ?? NULL_COMPLETION; } - onCompletionResolve(item: CompletionItem): CompletionItem { - if (item.data) { - const uri: string = item.data.uri; - const languageId: LanguageId = item.data.languageId; + async onCompletionResolve(item: CompletionItem): Promise { + const project = await this.getProjectService(item.data.uri); - /** - * Template files need to go through HTML-template service - */ - if (uri.endsWith('.template')) { - const doc = this.documentService.getDocument(uri.slice(0, -'.template'.length)); - const mode = this.languageModes.getMode(languageId); - if (doc && mode && mode.doResolve) { - return mode.doResolve(doc, item); - } - } + return project?.onCompletionResolve(item) ?? item; + } - if (uri && languageId) { - const doc = this.documentService.getDocument(uri); - const mode = this.languageModes.getMode(languageId); - if (doc && mode && mode.doResolve) { - return mode.doResolve(doc, item); - } - } - } + async onHover(params: TextDocumentPositionParams): Promise { + const project = await this.getProjectService(params.textDocument.uri); - return item; + return project?.onHover(params) ?? NULL_HOVER; } - onHover({ textDocument, position }: TextDocumentPositionParams): Hover { - const doc = this.documentService.getDocument(textDocument.uri)!; - const mode = this.languageModes.getModeAtPosition(doc, position); - if (mode && mode.doHover) { - return mode.doHover(doc, position); - } - return NULL_HOVER; - } + async onDocumentHighlight(params: TextDocumentPositionParams): Promise { + const project = await this.getProjectService(params.textDocument.uri); - onDocumentHighlight({ textDocument, position }: TextDocumentPositionParams): DocumentHighlight[] { - const doc = this.documentService.getDocument(textDocument.uri)!; - const mode = this.languageModes.getModeAtPosition(doc, position); - if (mode && mode.findDocumentHighlight) { - return mode.findDocumentHighlight(doc, position); - } - return []; + return project?.onDocumentHighlight(params) ?? []; } - onDefinition({ textDocument, position }: TextDocumentPositionParams): Definition { - const doc = this.documentService.getDocument(textDocument.uri)!; - const mode = this.languageModes.getModeAtPosition(doc, position); - if (mode && mode.findDefinition) { - return mode.findDefinition(doc, position); - } - return []; + async onDefinition(params: TextDocumentPositionParams): Promise { + const project = await this.getProjectService(params.textDocument.uri); + + return project?.onDefinition(params) ?? []; } - onReferences({ textDocument, position }: TextDocumentPositionParams): Location[] { - const doc = this.documentService.getDocument(textDocument.uri)!; - const mode = this.languageModes.getModeAtPosition(doc, position); - if (mode && mode.findReferences) { - return mode.findReferences(doc, position); - } - return []; - } - - onDocumentLinks({ textDocument }: DocumentLinkParams): DocumentLink[] { - const doc = this.documentService.getDocument(textDocument.uri)!; - const documentContext: DocumentContext = { - resolveReference: ref => { - if (this.workspacePath && ref[0] === '/') { - return URI.file(path.resolve(this.workspacePath, ref)).toString(); - } - const fsPath = getFileFsPath(doc.uri); - return URI.file(path.resolve(fsPath, '..', ref)).toString(); - } - }; + async onReferences(params: TextDocumentPositionParams): Promise { + const project = await this.getProjectService(params.textDocument.uri); - const links: DocumentLink[] = []; - this.languageModes.getAllLanguageModeRangesInDocument(doc).forEach(m => { - if (m.mode.findDocumentLinks) { - pushAll(links, m.mode.findDocumentLinks(doc, documentContext)); - } - }); - return links; + return project?.onReferences(params) ?? []; } - onDocumentSymbol({ textDocument }: DocumentSymbolParams): SymbolInformation[] { - const doc = this.documentService.getDocument(textDocument.uri)!; - const symbols: SymbolInformation[] = []; + async onDocumentLinks(params: DocumentLinkParams): Promise { + const project = await this.getProjectService(params.textDocument.uri); - this.languageModes.getAllLanguageModeRangesInDocument(doc).forEach(m => { - if (m.mode.findDocumentSymbols) { - pushAll(symbols, m.mode.findDocumentSymbols(doc)); - } - }); - return symbols; + return project?.onDocumentLinks(params) ?? []; } - onDocumentColors({ textDocument }: DocumentColorParams): ColorInformation[] { - const doc = this.documentService.getDocument(textDocument.uri)!; - const colors: ColorInformation[] = []; + async onDocumentSymbol(params: DocumentSymbolParams): Promise { + const project = await this.getProjectService(params.textDocument.uri); - const distinctModes: Set = new Set(); - this.languageModes.getAllLanguageModeRangesInDocument(doc).forEach(m => { - distinctModes.add(m.mode); - }); + return project?.onDocumentSymbol(params) ?? []; + } - for (const mode of distinctModes) { - if (mode.findDocumentColors) { - pushAll(colors, mode.findDocumentColors(doc)); - } - } + async onDocumentColors(params: DocumentColorParams): Promise { + const project = await this.getProjectService(params.textDocument.uri); - return colors; + return project?.onDocumentColors(params) ?? []; } - onColorPresentations({ textDocument, color, range }: ColorPresentationParams): ColorPresentation[] { - const doc = this.documentService.getDocument(textDocument.uri)!; - const mode = this.languageModes.getModeAtPosition(doc, range.start); - if (mode && mode.getColorPresentations) { - return mode.getColorPresentations(doc, color, range); - } - return []; - } + async onColorPresentations(params: ColorPresentationParams): Promise { + const project = await this.getProjectService(params.textDocument.uri); - onSignatureHelp({ textDocument, position }: TextDocumentPositionParams): SignatureHelp | null { - const doc = this.documentService.getDocument(textDocument.uri)!; - const mode = this.languageModes.getModeAtPosition(doc, position); - if (mode && mode.doSignatureHelp) { - return mode.doSignatureHelp(doc, position); - } - return NULL_SIGNATURE; + return project?.onColorPresentations(params) ?? []; } - onFoldingRanges({ textDocument }: FoldingRangeParams): FoldingRange[] { - const doc = this.documentService.getDocument(textDocument.uri)!; - const lmrs = this.languageModes.getAllLanguageModeRangesInDocument(doc); + async onSignatureHelp(params: TextDocumentPositionParams): Promise { + const project = await this.getProjectService(params.textDocument.uri); - const result: FoldingRange[] = []; - - lmrs.forEach(lmr => { - if (lmr.mode.getFoldingRanges) { - lmr.mode.getFoldingRanges(doc).forEach(r => result.push(r)); - } + return project?.onSignatureHelp(params) ?? NULL_SIGNATURE; + } - result.push({ - startLine: lmr.start.line, - startCharacter: lmr.start.character, - endLine: lmr.end.line, - endCharacter: lmr.end.character - }); - }); + async onFoldingRanges(params: FoldingRangeParams): Promise { + const project = await this.getProjectService(params.textDocument.uri); - return result; + return project?.onFoldingRanges(params) ?? []; } - onCodeAction({ textDocument, range, context }: CodeActionParams) { - if (!this.config.vetur.languageFeatures.codeActions) { - return []; - } + async onCodeAction(params: CodeActionParams) { + const project = await this.getProjectService(params.textDocument.uri); - const doc = this.documentService.getDocument(textDocument.uri)!; - const mode = this.languageModes.getModeAtPosition(doc, range.start); - if (this.languageModes.getModeAtPosition(doc, range.end) !== mode) { - return []; - } - if (mode && mode.getCodeActions) { - return mode.getCodeActions(doc, range, /*formatParams*/ {} as any, context); - } - return []; + return project?.onCodeAction(params) ?? []; } - getRefactorEdits(refactorAction: RefactorAction) { - const uri = URI.file(refactorAction.fileName).toString(); - const doc = this.documentService.getDocument(uri)!; - const startPos = doc.positionAt(refactorAction.textRange.pos); - const mode = this.languageModes.getModeAtPosition(doc, startPos); - if (mode && mode.getRefactorEdits) { - return mode.getRefactorEdits(doc, refactorAction); - } - return undefined; + async getRefactorEdits(refactorAction: RefactorAction) { + const project = await this.getProjectService(URI.file(refactorAction.fileName).toString()); + + return project?.getRefactorEdits(refactorAction) ?? undefined; } private triggerValidation(textDocument: TextDocument): void { @@ -558,30 +437,16 @@ export class VLS { } async doValidate(doc: TextDocument, cancellationToken?: VCancellationToken) { - const diagnostics: Diagnostic[] = []; - if (doc.languageId === 'vue') { - for (const lmr of this.languageModes.getAllLanguageModeRangesInDocument(doc)) { - if (lmr.mode.doValidation) { - if (this.validation[lmr.mode.getId()]) { - pushAll(diagnostics, await lmr.mode.doValidation(doc, cancellationToken)); - } - // Special case for template type checking - else if (lmr.mode.getId() === 'vue-html' && this.templateInterpolationValidation) { - pushAll(diagnostics, await lmr.mode.doValidation(doc, cancellationToken)); - } - } - } - } - if (cancellationToken?.isCancellationRequested) { - return null; - } - return diagnostics; + const project = await this.getProjectService(doc.uri); + + return project?.doValidate(doc, cancellationToken) ?? null; } async executeCommand(arg: ExecuteCommandParams) { if (arg.command === APPLY_REFACTOR_COMMAND && arg.arguments) { const edit = this.getRefactorEdits(arg.arguments[0] as RefactorAction); if (edit) { + // @ts-expect-error this.lspConnection.sendRequest(ApplyWorkspaceEditRequest.type, { edit }); } return; @@ -591,11 +456,13 @@ export class VLS { } removeDocument(doc: TextDocument): void { - this.languageModes.onDocumentRemoved(doc); + // this.languageModes.onDocumentRemoved(doc); } dispose(): void { - this.languageModes.dispose(); + this.projects.forEach(project => { + project.dispose(); + }); } get capabilities(): ServerCapabilities { @@ -621,11 +488,3 @@ export class VLS { }; } } - -function pushAll(to: T[], from: T[]) { - if (from) { - for (let i = 0; i < from.length; i++) { - to.push(from[i]); - } - } -} diff --git a/server/src/utils/paths.ts b/server/src/utils/paths.ts index 9f279e21cb..fc68989d44 100644 --- a/server/src/utils/paths.ts +++ b/server/src/utils/paths.ts @@ -1,4 +1,5 @@ import { platform } from 'os'; +import { resolve } from 'path'; import { URI } from 'vscode-uri'; /** @@ -62,3 +63,11 @@ export function getFilePath(documentUri: string): string { export function normalizeFileNameToFsPath(fileName: string) { return URI.file(fileName).fsPath; } + +export function normalizeResolve(...paths: string[]) { + return normalizeFileNameToFsPath(resolve(...paths)); +} + +export function getPathDepth(filePath: string, sep: string) { + return filePath.split(sep).length; +} diff --git a/server/src/utils/prettier/index.ts b/server/src/utils/prettier/index.ts index 3580dbfb41..96a166ca4d 100644 --- a/server/src/utils/prettier/index.ts +++ b/server/src/utils/prettier/index.ts @@ -147,10 +147,6 @@ function getPrettierOptions( prettierrcOptions.tabWidth = prettierrcOptions.tabWidth || vlsFormatConfig.options.tabSize; prettierrcOptions.useTabs = prettierrcOptions.useTabs || vlsFormatConfig.options.useTabs; prettierrcOptions.parser = parser; - if (dependencyService.useWorkspaceDependencies) { - // For loading plugins such as @prettier/plugin-pug - (prettierrcOptions as { pluginSearchDirs: string[] }).pluginSearchDirs = [dependencyService.workspacePath]; - } return prettierrcOptions; } else { @@ -158,10 +154,6 @@ function getPrettierOptions( vscodePrettierOptions.tabWidth = vscodePrettierOptions.tabWidth || vlsFormatConfig.options.tabSize; vscodePrettierOptions.useTabs = vscodePrettierOptions.useTabs || vlsFormatConfig.options.useTabs; vscodePrettierOptions.parser = parser; - if (dependencyService.useWorkspaceDependencies) { - // For loading plugins such as @prettier/plugin-pug - vscodePrettierOptions.pluginSearchDirs = [dependencyService.workspacePath]; - } return vscodePrettierOptions; } diff --git a/server/src/vueServerMain.ts b/server/src/vueServerMain.ts index 59ef4cf900..ba26c11f11 100644 --- a/server/src/vueServerMain.ts +++ b/server/src/vueServerMain.ts @@ -3,8 +3,8 @@ import { VLS } from './services/vls'; const connection = process.argv.length <= 2 ? createConnection(process.stdin, process.stdout) : createConnection(); -console.log = connection.console.log.bind(connection.console); -console.error = connection.console.error.bind(connection.console); +console.log = (...args: any[]) => connection.console.log(args.join(' ')); +console.error = (...args: any[]) => connection.console.error(args.join(' ')); const vls = new VLS(connection); connection.onInitialize( From 85eecb3ada0a1d1bcdd916e2fa378f3fd708063d Mon Sep 17 00:00:00 2001 From: yoyo930021 Date: Wed, 25 Nov 2020 18:16:17 +0800 Subject: [PATCH 02/33] Support global components --- server/src/config.ts | 80 ++++++++++--------- server/src/embeddedSupport/languageModes.ts | 4 +- server/src/modes/script/childComponents.ts | 2 +- server/src/modes/script/componentInfo.ts | 18 +++++ server/src/modes/script/globalComponents.ts | 53 ++++++++++++ server/src/modes/script/javascript.ts | 5 +- server/src/services/autoImportVueService.ts | 6 +- server/src/services/projectService.ts | 8 +- .../services/typescriptService/serviceHost.ts | 1 + server/src/services/vls.ts | 52 ++++++------ server/src/services/vueInfoService.ts | 1 + server/src/utils/paths.ts | 4 + 12 files changed, 162 insertions(+), 72 deletions(-) create mode 100644 server/src/modes/script/globalComponents.ts diff --git a/server/src/config.ts b/server/src/config.ts index 9d6da20b73..a3e76b307a 100644 --- a/server/src/config.ts +++ b/server/src/config.ts @@ -2,6 +2,7 @@ import path from 'path'; import { getPathDepth, normalizeFileNameToFsPath, normalizeResolve } from './utils/paths'; import fg from 'fast-glob'; import { findConfigFile } from './utils/workspace'; +import { flatten } from 'lodash'; export interface VLSFormatConfig { defaultFormatter: { @@ -147,14 +148,14 @@ export function getDefaultVLSConfig(): VLSFullConfig { }; } -export interface Component { +export interface BasicComponentInfo { name: string; path: string; } export type Glob = string; -export interface VeturProject { +export interface VeturProject { root: string; package?: string; tsconfig?: string; @@ -163,55 +164,60 @@ export interface VeturProject { export interface VeturFullConfig { settings: Record; - projects: VeturProject[]; + projects: VeturProject[]; } export type VeturConfig = Partial> & { - projects?: Array & Partial)> + projects?: Array & Partial)>; }; -export async function getVeturFullConfig ( +export async function getVeturFullConfig( rootPathForConfig: string, workspacePath: string, veturConfig: VeturConfig ): Promise { const oldProjects = veturConfig.projects ?? [workspacePath]; - const projects = oldProjects.map((project) => { - const getFallbackPackagePath = (projectRoot: string) => { - const fallbackPackage = findConfigFile(projectRoot, 'package.json'); - return (fallbackPackage ? normalizeFileNameToFsPath(fallbackPackage) : undefined); - }; + const projects = oldProjects + .map(project => { + const getFallbackPackagePath = (projectRoot: string) => { + const fallbackPackage = findConfigFile(projectRoot, 'package.json'); + return fallbackPackage ? normalizeFileNameToFsPath(fallbackPackage) : undefined; + }; - if (typeof project === 'string') { - const projectRoot = normalizeResolve(rootPathForConfig, project); - const tsconfigPath = findConfigFile(projectRoot, 'tsconfig.json') ?? findConfigFile(projectRoot, 'jsconfig.json'); + if (typeof project === 'string') { + const projectRoot = normalizeResolve(rootPathForConfig, project); + const tsconfigPath = + findConfigFile(projectRoot, 'tsconfig.json') ?? findConfigFile(projectRoot, 'jsconfig.json'); + return { + root: projectRoot, + package: getFallbackPackagePath(projectRoot), + tsconfig: tsconfigPath ? normalizeFileNameToFsPath(tsconfigPath) : undefined, + globalComponents: [] + } as VeturProject; + } + + const projectRoot = normalizeResolve(rootPathForConfig, project.root); return { root: projectRoot, - package: getFallbackPackagePath(projectRoot), - tsconfig: tsconfigPath ? normalizeFileNameToFsPath(tsconfigPath) : undefined, - globalComponents: [] - } as VeturProject; - } - - const projectRoot = normalizeResolve(rootPathForConfig, project.root); - return { - root: projectRoot, - package: project.package ?? getFallbackPackagePath(projectRoot), - tsconfig: project.tsconfig ?? undefined, - globalComponents: project.globalComponents - ?.map(comp => { - if (typeof comp === 'string') { - return fg.sync(comp, { cwd: normalizeResolve(rootPathForConfig, projectRoot) }) - .map(fileName => ({ - name: path.basename(fileName, path.extname(fileName)), - path: normalizeFileNameToFsPath(fileName) - })); - } - return comp; - }) ?? [] - } as VeturProject; - }).sort((a, b) => getPathDepth(b.root, '/') - getPathDepth(a.root, '/')); + package: project.package ?? getFallbackPackagePath(projectRoot), + tsconfig: project.tsconfig ?? undefined, + globalComponents: flatten( + project.globalComponents?.map(comp => { + if (typeof comp === 'string') { + return fg + .sync(comp, { cwd: normalizeResolve(rootPathForConfig, projectRoot), absolute: true }) + .map(fileName => ({ + name: path.basename(fileName, path.extname(fileName)), + path: normalizeFileNameToFsPath(fileName) + })); + } + return comp; + }) ?? [] + ) + } as VeturProject; + }) + .sort((a, b) => getPathDepth(b.root, '/') - getPathDepth(a.root, '/')); return { settings: veturConfig.settings ?? {}, diff --git a/server/src/embeddedSupport/languageModes.ts b/server/src/embeddedSupport/languageModes.ts index cd1523e99e..7b856e870f 100644 --- a/server/src/embeddedSupport/languageModes.ts +++ b/server/src/embeddedSupport/languageModes.ts @@ -35,7 +35,7 @@ import { VueInfoService } from '../services/vueInfoService'; import { DependencyService } from '../services/dependencyService'; import { nullMode } from '../modes/nullMode'; import { getServiceHost, IServiceHost } from '../services/typescriptService/serviceHost'; -import { VLSFullConfig } from '../config'; +import { BasicComponentInfo, VLSFullConfig } from '../config'; import { SassLanguageMode } from '../modes/style/sass/sassLanguageMode'; import { getPugMode } from '../modes/pug'; import { VCancellationToken } from '../utils/cancellationToken'; @@ -116,6 +116,7 @@ export class LanguageModes { projectPath: string, tsconfigPath: string | undefined, packagePath: string | undefined, + globalComponentInfos: BasicComponentInfo[], services: VLSServices, globalSnippetDir?: string ) { @@ -150,6 +151,7 @@ export class LanguageModes { this.serviceHost, this.documentRegions, services.dependencyService, + globalComponentInfos, services.infoService ); autoImportVueService.setGetJSResolve(jsMode.doResolve!); diff --git a/server/src/modes/script/childComponents.ts b/server/src/modes/script/childComponents.ts index 2e0846b57d..b0b85fa18f 100644 --- a/server/src/modes/script/childComponents.ts +++ b/server/src/modes/script/childComponents.ts @@ -9,7 +9,7 @@ import { import { kebabCase } from 'lodash'; import { RuntimeLibrary } from '../../services/dependencyService'; -interface InternalChildComponent { +export interface InternalChildComponent { name: string; documentation?: string; definition?: { diff --git a/server/src/modes/script/componentInfo.ts b/server/src/modes/script/componentInfo.ts index b3d35249e1..48c0ad889d 100644 --- a/server/src/modes/script/componentInfo.ts +++ b/server/src/modes/script/componentInfo.ts @@ -1,4 +1,5 @@ import type ts from 'typescript'; +import { BasicComponentInfo } from '../../config'; import { RuntimeLibrary } from '../../services/dependencyService'; import { VueFileInfo, @@ -9,11 +10,13 @@ import { ChildComponent } from '../../services/vueInfoService'; import { analyzeComponentsDefine } from './childComponents'; +import { getGlobalComponents } from './globalComponents'; export function getComponentInfo( tsModule: RuntimeLibrary['typescript'], service: ts.LanguageService, fileFsPath: string, + globalComponentInfos: BasicComponentInfo[], config: any ): VueFileInfo | undefined { const program = service.getProgram(); @@ -51,6 +54,7 @@ export function getComponentInfo( name: c.name, documentation: c.documentation, definition: c.definition, + global: false, info: c.defaultExportNode ? analyzeDefaultExportExpr(tsModule, c.defaultExportNode, checker) : undefined }); }); @@ -58,6 +62,20 @@ export function getComponentInfo( vueFileInfo.componentInfo.componentsDefine = defineInfo; } + const globalComponents = getGlobalComponents(tsModule, service, globalComponentInfos); + if (globalComponents.length > 0) { + vueFileInfo.componentInfo.childComponents = [ + ...(vueFileInfo.componentInfo.childComponents ?? []), + ...globalComponents.map(c => ({ + name: c.name, + documentation: c.documentation, + definition: c.definition, + global: true, + info: c.defaultExportNode ? analyzeDefaultExportExpr(tsModule, c.defaultExportNode, checker) : undefined + })) + ]; + } + return vueFileInfo; } diff --git a/server/src/modes/script/globalComponents.ts b/server/src/modes/script/globalComponents.ts new file mode 100644 index 0000000000..04e41baf95 --- /dev/null +++ b/server/src/modes/script/globalComponents.ts @@ -0,0 +1,53 @@ +import { kebabCase } from 'lodash'; +import type ts from 'typescript'; +import { BasicComponentInfo } from '../../config'; +import { RuntimeLibrary } from '../../services/dependencyService'; +import { InternalChildComponent } from './childComponents'; +import { buildDocumentation, getDefaultExportNode } from './componentInfo'; + +export function getGlobalComponents( + tsModule: RuntimeLibrary['typescript'], + service: ts.LanguageService, + componentInfos: BasicComponentInfo[], + tagCasing = 'kebab' +): InternalChildComponent[] { + const program = service.getProgram(); + if (!program) { + return []; + } + + const checker = program.getTypeChecker(); + + const result: InternalChildComponent[] = []; + componentInfos.forEach(info => { + const sourceFile = program.getSourceFile(info.path); + if (!sourceFile) { + return; + } + + const defaultExportNode = getDefaultExportNode(tsModule, sourceFile); + if (!defaultExportNode) { + return; + } + + const defaultExportSymbol = checker.getTypeAtLocation(defaultExportNode); + if (!defaultExportSymbol) { + return; + } + + const name = tagCasing === 'kebab' ? kebabCase(info.name) : info.name; + + result.push({ + name, + documentation: buildDocumentation(tsModule, defaultExportSymbol.symbol, checker), + definition: { + path: sourceFile.fileName, + start: defaultExportNode.getStart(sourceFile, true), + end: defaultExportNode.getEnd() + }, + defaultExportNode + }); + }); + + return result; +} diff --git a/server/src/modes/script/javascript.ts b/server/src/modes/script/javascript.ts index 5ec24caa14..d207017d43 100644 --- a/server/src/modes/script/javascript.ts +++ b/server/src/modes/script/javascript.ts @@ -38,7 +38,7 @@ import type ts from 'typescript'; import _ from 'lodash'; import { nullMode, NULL_SIGNATURE } from '../nullMode'; -import { VLSFormatConfig } from '../../config'; +import { BasicComponentInfo, VLSFormatConfig } from '../../config'; import { VueInfoService } from '../../services/vueInfoService'; import { getComponentInfo } from './componentInfo'; import { DependencyService, RuntimeLibrary } from '../../services/dependencyService'; @@ -58,6 +58,7 @@ export async function getJavascriptMode( serviceHost: IServiceHost, documentRegions: LanguageModelCache, dependencyService: DependencyService, + globalComponentInfos: BasicComponentInfo[], vueInfoService?: VueInfoService ): Promise { const jsDocuments = getLanguageModelCache(10, 60, document => { @@ -128,7 +129,7 @@ export async function getJavascriptMode( const { service } = updateCurrentVueTextDocument(doc); const fileFsPath = getFileFsPath(doc.uri); - const info = getComponentInfo(tsModule, service, fileFsPath, config); + const info = getComponentInfo(tsModule, service, fileFsPath, globalComponentInfos, config); if (info) { vueInfoService.updateInfo(doc, info); } diff --git a/server/src/services/autoImportVueService.ts b/server/src/services/autoImportVueService.ts index 00668d26af..41eacc64d0 100644 --- a/server/src/services/autoImportVueService.ts +++ b/server/src/services/autoImportVueService.ts @@ -14,7 +14,7 @@ export interface AutoImportVueService { setGetConfigure(fn: () => VLSFullConfig): void; setGetFilesFn(fn: () => string[]): void; setGetJSResolve(fn: (doc: TextDocument, item: CompletionItem) => CompletionItem): void; - setGetTSScriptTarget (fn: () => ts.ScriptTarget | undefined): void; + setGetTSScriptTarget(fn: () => ts.ScriptTarget | undefined): void; doComplete(document: TextDocument): CompletionItem[]; isMyResolve(item: CompletionItem): boolean; doResolve(document: TextDocument, item: CompletionItem): CompletionItem; @@ -98,7 +98,7 @@ export function createAutoImportVueService( setGetJSResolve(fn) { getJSResolve = fn; }, - setGetTSScriptTarget (fn) { + setGetTSScriptTarget(fn) { getTSScriptTarget = fn; }, doComplete(document): CompletionItem[] { @@ -149,7 +149,7 @@ import ${upperFirst(camelCase(tagName))} from '${fileName}' } const componentDefine = componentInfo?.componentsDefine; - const childComponents = componentInfo?.childComponents; + const childComponents = componentInfo?.childComponents?.filter(c => !c.global); const nameForTriggerResolveInTs = modulePathToValidIdentifier( tsModule, item.data.path, diff --git a/server/src/services/projectService.ts b/server/src/services/projectService.ts index 5483849825..b5311e230f 100644 --- a/server/src/services/projectService.ts +++ b/server/src/services/projectService.ts @@ -29,19 +29,20 @@ import { } from 'vscode-languageserver'; import { TextDocument } from 'vscode-languageserver-textdocument'; import { URI } from 'vscode-uri'; -import { VLSConfig, VLSFullConfig } from '../config'; +import { BasicComponentInfo, VLSConfig, VLSFullConfig } from '../config'; import { LanguageId } from '../embeddedSupport/embeddedSupport'; import { LanguageMode, LanguageModes } from '../embeddedSupport/languageModes'; import { logger } from '../log'; import { NULL_COMPLETION, NULL_HOVER, NULL_SIGNATURE } from '../modes/nullMode'; import { DocumentContext, RefactorAction } from '../types'; import { VCancellationToken, VCancellationTokenSource } from '../utils/cancellationToken'; -import { getFileFsPath } from '../utils/paths'; +import { getFileFsPath, getFsPathToUri } from '../utils/paths'; import { DependencyService } from './dependencyService'; import { DocumentService } from './documentService'; import { VueInfoService } from './vueInfoService'; export interface ProjectService { + languageModes: LanguageModes; configure(config: VLSFullConfig): void; onDocumentFormatting(params: DocumentFormattingParams): Promise; onCompletion(params: CompletionParams): Promise; @@ -68,6 +69,7 @@ export async function createProjectService( projectPath: string, tsconfigPath: string | undefined, packagePath: string | undefined, + globalComponentInfos: BasicComponentInfo[], documentService: DocumentService, initialConfig: VLSConfig, globalSnippetDir: string | undefined, @@ -97,6 +99,7 @@ export async function createProjectService( projectPath, tsconfigPath, packagePath, + globalComponentInfos, { infoService: vueInfoService, dependencyService @@ -116,6 +119,7 @@ export async function createProjectService( return { configure, + languageModes, async onDocumentFormatting({ textDocument, options }) { const doc = documentService.getDocument(textDocument.uri)!; diff --git a/server/src/services/typescriptService/serviceHost.ts b/server/src/services/typescriptService/serviceHost.ts index 1488c812c5..d82ec28252 100644 --- a/server/src/services/typescriptService/serviceHost.ts +++ b/server/src/services/typescriptService/serviceHost.ts @@ -518,6 +518,7 @@ function getParsedConfig( ) { const configFilename = tsconfigPath; const configJson = (configFilename && tsModule.readConfigFile(configFilename, tsModule.sys.readFile).config) || { + include: ['**/*.vue'], exclude: defaultIgnorePatterns(tsModule, projectPath) }; // existingOptions should be empty since it always takes priority diff --git a/server/src/services/vls.ts b/server/src/services/vls.ts index ef0af191ff..7536d92865 100644 --- a/server/src/services/vls.ts +++ b/server/src/services/vls.ts @@ -1,5 +1,11 @@ import path from 'path'; -import { getFileFsPath, getPathDepth, normalizeFileNameToFsPath, normalizeResolve } from '../utils/paths'; +import { + getFileFsPath, + getFsPathToUri, + getPathDepth, + normalizeFileNameToFsPath, + normalizeResolve +} from '../utils/paths'; import { DidChangeConfigurationParams, @@ -66,11 +72,8 @@ export class VLS { private rootPathForConfig: string; private globalSnippetDir: string; private projects: Map; - // private vueInfoService: VueInfoService; private dependencyService: DependencyService; - // private languageModes: LanguageModes; - private pendingValidationRequests: { [uri: string]: NodeJS.Timer } = {}; private cancellationTokenValidationRequests: { [uri: string]: VCancellationTokenSource } = {}; private validationDelayMs = 200; @@ -91,10 +94,7 @@ export class VLS { constructor(private lspConnection: Connection) { this.documentService = new DocumentService(this.lspConnection); - // this.vueInfoService = new VueInfoService(); this.dependencyService = createDependencyService(); - - // this.languageModes = new LanguageModes(); } async init(params: InitializeParams) { @@ -156,7 +156,7 @@ export class VLS { this.setupDynamicFormatters(config); }); - // this.documentService.getAllDocuments().forEach(this.triggerValidation); + this.documentService.getAllDocuments().forEach(this.triggerValidation); } private async getProjectService(uri: DocumentUri): Promise { @@ -164,7 +164,8 @@ export class VLS { .map(project => ({ rootFsPath: normalizeResolve(this.rootPathForConfig, project.root), tsconfigPath: project.tsconfig, - packagePath: project.package + packagePath: project.package, + globalComponents: project.globalComponents })) .sort((a, b) => getPathDepth(b.rootFsPath, '/') - getPathDepth(a.rootFsPath, '/')); const docFsPath = getFileFsPath(uri); @@ -182,6 +183,7 @@ export class VLS { projectConfig.rootFsPath, projectConfig.tsconfigPath, projectConfig.packagePath, + projectConfig.globalComponents, this.documentService, this.config, this.globalSnippetDir, @@ -213,9 +215,10 @@ export class VLS { } private setupCustomLSPHandlers() { - // this.lspConnection.onRequest('$/queryVirtualFileInfo', ({ fileName, currFileText }) => { - // return (this.languageModes.getMode('vue-html') as VueHTMLMode).queryVirtualFileInfo(fileName, currFileText); - // }); + this.lspConnection.onRequest('$/queryVirtualFileInfo', async ({ fileName, currFileText }) => { + const project = await this.getProjectService(getFsPathToUri(fileName)); + return (project?.languageModes.getMode('vue-html') as VueHTMLMode).queryVirtualFileInfo(fileName, currFileText); + }); this.lspConnection.onRequest('$/getDiagnostics', async params => { const doc = this.documentService.getDocument(params.uri); @@ -250,18 +253,14 @@ export class VLS { this.lspConnection.sendDiagnostics({ uri: e.document.uri, diagnostics: [] }); }); this.lspConnection.onDidChangeWatchedFiles(({ changes }) => { - // const jsMode = this.languageModes.getMode('javascript'); - // if (!jsMode) { - // throw Error(`Can't find JS mode.`); - // } - - // changes.forEach(async c => { - // if (c.type === FileChangeType.Changed) { - // const project = await this.getProjectService(c.uri) - // const fsPath = getFileFsPath(c.uri); - // jsMode.onDocumentChanged!(fsPath); - // } - // }); + changes.forEach(async c => { + if (c.type === FileChangeType.Changed) { + const project = await this.getProjectService(c.uri); + const jsMode = project?.languageModes.getMode('javascript'); + const fsPath = getFileFsPath(c.uri); + jsMode?.onDocumentChanged!(fsPath); + } + }); this.documentService.getAllDocuments().forEach(d => { this.triggerValidation(d); @@ -455,8 +454,9 @@ export class VLS { logger.logInfo(`Unknown command ${arg.command}.`); } - removeDocument(doc: TextDocument): void { - // this.languageModes.onDocumentRemoved(doc); + async removeDocument(doc: TextDocument): Promise { + const project = await this.getProjectService(doc.uri); + project?.languageModes.onDocumentRemoved(doc); } dispose(): void { diff --git a/server/src/services/vueInfoService.ts b/server/src/services/vueInfoService.ts index 429aa0a5b5..d0816941e3 100644 --- a/server/src/services/vueInfoService.ts +++ b/server/src/services/vueInfoService.ts @@ -46,6 +46,7 @@ export interface ChildComponent { start: number; end: number; }; + global: boolean; info?: VueFileInfo; } diff --git a/server/src/utils/paths.ts b/server/src/utils/paths.ts index fc68989d44..74ff3b44e3 100644 --- a/server/src/utils/paths.ts +++ b/server/src/utils/paths.ts @@ -71,3 +71,7 @@ export function normalizeResolve(...paths: string[]) { export function getPathDepth(filePath: string, sep: string) { return filePath.split(sep).length; } + +export function getFsPathToUri(fsPath: string) { + return URI.file(fsPath).toString(); +} From 18fa390b72c342f4dc4de1fe06289f598e98a1d4 Mon Sep 17 00:00:00 2001 From: yoyo930021 Date: Wed, 25 Nov 2020 18:28:33 +0800 Subject: [PATCH 03/33] Inject getChildComponents in template service --- .../src/modes/template/interpolationMode.ts | 37 ++++++++++++++----- 1 file changed, 27 insertions(+), 10 deletions(-) diff --git a/server/src/modes/template/interpolationMode.ts b/server/src/modes/template/interpolationMode.ts index 1321ac248d..4567f008ed 100644 --- a/server/src/modes/template/interpolationMode.ts +++ b/server/src/modes/template/interpolationMode.ts @@ -54,6 +54,12 @@ export class VueInterpolationMode implements LanguageMode { return this.serviceHost.queryVirtualFileInfo(fileName, currFileText); } + private getChildComponents(document: TextDocument) { + return this.config.vetur.validation.templateProps + ? this.vueInfoService && this.vueInfoService.getInfo(document)?.componentInfo.childComponents + : []; + } + async doValidation(document: TextDocument, cancellationToken?: VCancellationToken): Promise { if ( !_.get(this.config, ['vetur', 'experimental', 'templateInterpolationService'], true) || @@ -74,13 +80,9 @@ export class VueInterpolationMode implements LanguageMode { document.getText() ); - const childComponents = this.config.vetur.validation.templateProps - ? this.vueInfoService && this.vueInfoService.getInfo(document)?.componentInfo.childComponents - : []; - const { templateService, templateSourceMap } = this.serviceHost.updateCurrentVirtualVueTextDocument( templateDoc, - childComponents + this.getChildComponents(document) ); if (!languageServiceIncludesFile(templateService, templateDoc.uri)) { @@ -131,7 +133,10 @@ export class VueInterpolationMode implements LanguageMode { document.getText() ); - const { templateService, templateSourceMap } = this.serviceHost.updateCurrentVirtualVueTextDocument(templateDoc); + const { templateService, templateSourceMap } = this.serviceHost.updateCurrentVirtualVueTextDocument( + templateDoc, + this.getChildComponents(document) + ); if (!languageServiceIncludesFile(templateService, templateDoc.uri)) { return NULL_COMPLETION; } @@ -221,7 +226,10 @@ export class VueInterpolationMode implements LanguageMode { document.getText() ); - const { templateService, templateSourceMap } = this.serviceHost.updateCurrentVirtualVueTextDocument(templateDoc); + const { templateService, templateSourceMap } = this.serviceHost.updateCurrentVirtualVueTextDocument( + templateDoc, + this.getChildComponents(document) + ); if (!languageServiceIncludesFile(templateService, templateDoc.uri)) { return item; } @@ -282,7 +290,10 @@ export class VueInterpolationMode implements LanguageMode { document.getText() ); - const { templateService, templateSourceMap } = this.serviceHost.updateCurrentVirtualVueTextDocument(templateDoc); + const { templateService, templateSourceMap } = this.serviceHost.updateCurrentVirtualVueTextDocument( + templateDoc, + this.getChildComponents(document) + ); if (!languageServiceIncludesFile(templateService, templateDoc.uri)) { return { contents: [] @@ -337,7 +348,10 @@ export class VueInterpolationMode implements LanguageMode { document.getText() ); - const { templateService, templateSourceMap } = this.serviceHost.updateCurrentVirtualVueTextDocument(templateDoc); + const { templateService, templateSourceMap } = this.serviceHost.updateCurrentVirtualVueTextDocument( + templateDoc, + this.getChildComponents(document) + ); if (!languageServiceIncludesFile(templateService, templateDoc.uri)) { return []; } @@ -385,7 +399,10 @@ export class VueInterpolationMode implements LanguageMode { document.getText() ); - const { templateService, templateSourceMap } = this.serviceHost.updateCurrentVirtualVueTextDocument(templateDoc); + const { templateService, templateSourceMap } = this.serviceHost.updateCurrentVirtualVueTextDocument( + templateDoc, + this.getChildComponents(document) + ); if (!languageServiceIncludesFile(templateService, templateDoc.uri)) { return []; } From 365b3eb5fc5b9ae53ad713e4abeb8187d3c27984 Mon Sep 17 00:00:00 2001 From: yoyo930021 Date: Wed, 25 Nov 2020 20:05:15 +0800 Subject: [PATCH 04/33] Rename normalizeResolve to normalizeFileNameResolve --- server/src/config.ts | 8 ++++---- server/src/services/vls.ts | 4 ++-- server/src/utils/paths.ts | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/server/src/config.ts b/server/src/config.ts index a3e76b307a..9254a6c519 100644 --- a/server/src/config.ts +++ b/server/src/config.ts @@ -1,5 +1,5 @@ import path from 'path'; -import { getPathDepth, normalizeFileNameToFsPath, normalizeResolve } from './utils/paths'; +import { getPathDepth, normalizeFileNameToFsPath, normalizeFileNameResolve } from './utils/paths'; import fg from 'fast-glob'; import { findConfigFile } from './utils/workspace'; import { flatten } from 'lodash'; @@ -185,7 +185,7 @@ export async function getVeturFullConfig( }; if (typeof project === 'string') { - const projectRoot = normalizeResolve(rootPathForConfig, project); + const projectRoot = normalizeFileNameResolve(rootPathForConfig, project); const tsconfigPath = findConfigFile(projectRoot, 'tsconfig.json') ?? findConfigFile(projectRoot, 'jsconfig.json'); @@ -197,7 +197,7 @@ export async function getVeturFullConfig( } as VeturProject; } - const projectRoot = normalizeResolve(rootPathForConfig, project.root); + const projectRoot = normalizeFileNameResolve(rootPathForConfig, project.root); return { root: projectRoot, package: project.package ?? getFallbackPackagePath(projectRoot), @@ -206,7 +206,7 @@ export async function getVeturFullConfig( project.globalComponents?.map(comp => { if (typeof comp === 'string') { return fg - .sync(comp, { cwd: normalizeResolve(rootPathForConfig, projectRoot), absolute: true }) + .sync(comp, { cwd: normalizeFileNameResolve(rootPathForConfig, projectRoot), absolute: true }) .map(fileName => ({ name: path.basename(fileName, path.extname(fileName)), path: normalizeFileNameToFsPath(fileName) diff --git a/server/src/services/vls.ts b/server/src/services/vls.ts index 7536d92865..979a51b114 100644 --- a/server/src/services/vls.ts +++ b/server/src/services/vls.ts @@ -4,7 +4,7 @@ import { getFsPathToUri, getPathDepth, normalizeFileNameToFsPath, - normalizeResolve + normalizeFileNameResolve } from '../utils/paths'; import { @@ -162,7 +162,7 @@ export class VLS { private async getProjectService(uri: DocumentUri): Promise { const projectRootPaths = this.veturConfig.projects .map(project => ({ - rootFsPath: normalizeResolve(this.rootPathForConfig, project.root), + rootFsPath: normalizeFileNameResolve(this.rootPathForConfig, project.root), tsconfigPath: project.tsconfig, packagePath: project.package, globalComponents: project.globalComponents diff --git a/server/src/utils/paths.ts b/server/src/utils/paths.ts index 74ff3b44e3..fabb852f78 100644 --- a/server/src/utils/paths.ts +++ b/server/src/utils/paths.ts @@ -64,7 +64,7 @@ export function normalizeFileNameToFsPath(fileName: string) { return URI.file(fileName).fsPath; } -export function normalizeResolve(...paths: string[]) { +export function normalizeFileNameResolve(...paths: string[]) { return normalizeFileNameToFsPath(resolve(...paths)); } From 0ea03bfeccf9536217f1fa72c6c6aff28b309365 Mon Sep 17 00:00:00 2001 From: yoyo930021 Date: Thu, 26 Nov 2020 17:25:54 +0800 Subject: [PATCH 05/33] Support snippetFolder --- server/src/config.ts | 3 +++ server/src/embeddedSupport/languageModes.ts | 4 ++-- server/src/modes/template/index.ts | 2 +- .../tagProviders/externalTagProviders.ts | 12 +++++------ .../src/modes/template/tagProviders/index.ts | 19 +++++++++--------- .../modes/template/tagProviders/nuxtTags.ts | 20 +++++++++---------- server/src/modes/vue/index.ts | 9 +++------ server/src/modes/vue/snippets.ts | 4 ++-- server/src/services/projectService.ts | 3 ++- .../services/typescriptService/serviceHost.ts | 6 +++--- .../services/typescriptService/vueVersion.ts | 10 +++++++--- server/src/services/vls.ts | 2 ++ server/src/utils/workspace.ts | 4 ++-- 13 files changed, 52 insertions(+), 46 deletions(-) diff --git a/server/src/config.ts b/server/src/config.ts index 9254a6c519..eb21159912 100644 --- a/server/src/config.ts +++ b/server/src/config.ts @@ -159,6 +159,7 @@ export interface VeturProject { root: string; package?: string; tsconfig?: string; + snippetFolder: string; globalComponents: C[]; } @@ -193,6 +194,7 @@ export async function getVeturFullConfig( root: projectRoot, package: getFallbackPackagePath(projectRoot), tsconfig: tsconfigPath ? normalizeFileNameToFsPath(tsconfigPath) : undefined, + snippetFolder: normalizeFileNameResolve(projectRoot, '.vscode/vetur/snippets'), globalComponents: [] } as VeturProject; } @@ -202,6 +204,7 @@ export async function getVeturFullConfig( root: projectRoot, package: project.package ?? getFallbackPackagePath(projectRoot), tsconfig: project.tsconfig ?? undefined, + snippetFolder: normalizeFileNameResolve(projectRoot, '.vscode/vetur/snippets'), globalComponents: flatten( project.globalComponents?.map(comp => { if (typeof comp === 'string') { diff --git a/server/src/embeddedSupport/languageModes.ts b/server/src/embeddedSupport/languageModes.ts index 7b856e870f..8b17ebe2b6 100644 --- a/server/src/embeddedSupport/languageModes.ts +++ b/server/src/embeddedSupport/languageModes.ts @@ -112,10 +112,10 @@ export class LanguageModes { } async init( - workspacePath: string, projectPath: string, tsconfigPath: string | undefined, packagePath: string | undefined, + snippetFolder: string, globalComponentInfos: BasicComponentInfo[], services: VLSServices, globalSnippetDir?: string @@ -156,7 +156,7 @@ export class LanguageModes { ); autoImportVueService.setGetJSResolve(jsMode.doResolve!); - this.modes['vue'] = getVueMode(workspacePath, globalSnippetDir); + this.modes['vue'] = getVueMode(snippetFolder, globalSnippetDir); this.modes['vue-html'] = vueHtmlMode; this.modes['pug'] = getPugMode(services.dependencyService); this.modes['css'] = getCSSMode(this.documentRegions, services.dependencyService); diff --git a/server/src/modes/template/index.ts b/server/src/modes/template/index.ts index 3deb06f4d4..d44dacde3f 100644 --- a/server/src/modes/template/index.ts +++ b/server/src/modes/template/index.ts @@ -32,7 +32,7 @@ export class VueHTMLMode implements LanguageMode { vueInfoService?: VueInfoService ) { const vueDocuments = getLanguageModelCache(10, 60, document => parseHTMLDocument(document)); - const vueVersion = inferVueVersion(projectPath, packagePath); + const vueVersion = inferVueVersion(packagePath); this.htmlMode = new HTMLMode( documentRegions, projectPath, diff --git a/server/src/modes/template/tagProviders/externalTagProviders.ts b/server/src/modes/template/tagProviders/externalTagProviders.ts index 15144b93f2..b3a32533e7 100644 --- a/server/src/modes/template/tagProviders/externalTagProviders.ts +++ b/server/src/modes/template/tagProviders/externalTagProviders.ts @@ -25,12 +25,12 @@ export const gridsomeTagProvider = getExternalTagProvider('gridsome', gridsomeTa /** * Get tag providers specified in workspace root's packaage.json */ -export function getWorkspaceTagProvider(workspacePath: string, rootPkgJson: any): IHTMLTagProvider | null { +export function getWorkspaceTagProvider(packageRoot: string, rootPkgJson: any): IHTMLTagProvider | null { if (!rootPkgJson.vetur) { return null; } - const tagsPath = findConfigFile(workspacePath, rootPkgJson.vetur.tags); - const attrsPath = findConfigFile(workspacePath, rootPkgJson.vetur.attributes); + const tagsPath = findConfigFile(packageRoot, rootPkgJson.vetur.tags); + const attrsPath = findConfigFile(packageRoot, rootPkgJson.vetur.attributes); try { if (tagsPath && attrsPath) { @@ -47,15 +47,15 @@ export function getWorkspaceTagProvider(workspacePath: string, rootPkgJson: any) /** * Get tag providers specified in packaage.json's `vetur` key */ -export function getDependencyTagProvider(workspacePath: string, depPkgJson: any): IHTMLTagProvider | null { +export function getDependencyTagProvider(packageRoot: string, depPkgJson: any): IHTMLTagProvider | null { if (!depPkgJson.vetur) { return null; } try { - const tagsPath = require.resolve(path.join(depPkgJson.name, depPkgJson.vetur.tags), { paths: [workspacePath] }); + const tagsPath = require.resolve(path.join(depPkgJson.name, depPkgJson.vetur.tags), { paths: [packageRoot] }); const attrsPath = require.resolve(path.join(depPkgJson.name, depPkgJson.vetur.attributes), { - paths: [workspacePath] + paths: [packageRoot] }); const tagsJson = JSON.parse(fs.readFileSync(tagsPath, 'utf-8')); diff --git a/server/src/modes/template/tagProviders/index.ts b/server/src/modes/template/tagProviders/index.ts index 9c5e4d3b63..1dcf1d8a14 100644 --- a/server/src/modes/template/tagProviders/index.ts +++ b/server/src/modes/template/tagProviders/index.ts @@ -16,6 +16,7 @@ import fs from 'fs'; import { join } from 'path'; import { getNuxtTagProvider } from './nuxtTags'; import { findConfigFile } from '../../../utils/workspace'; +import { normalizeFileNameResolve } from '../../../utils/paths'; export let allTagProviders: IHTMLTagProvider[] = [ getHTML5TagProvider(), @@ -47,16 +48,14 @@ export function getTagProviderSettings(projectPath: string | null | undefined, p nuxt: false, gridsome: false }; - if (!projectPath) { - return settings; - } try { - const packageJSONPath = packagePath ?? findConfigFile(projectPath, 'package.json'); - if (!packageJSONPath) { + if (!packagePath) { return settings; } - const rootPkgJson = JSON.parse(fs.readFileSync(packageJSONPath, 'utf-8')); + const packageRoot = normalizeFileNameResolve(packagePath, '../'); + + const rootPkgJson = JSON.parse(fs.readFileSync(packagePath, 'utf-8')); const dependencies = rootPkgJson.dependencies || {}; const devDependencies = rootPkgJson.devDependencies || {}; @@ -99,7 +98,7 @@ export function getTagProviderSettings(projectPath: string | null | undefined, p dependencies['quasar-framework'] = '^0.0.17'; } if (dependencies['nuxt'] || dependencies['nuxt-edge'] || devDependencies['nuxt'] || devDependencies['nuxt-edge']) { - const nuxtTagProvider = getNuxtTagProvider(projectPath); + const nuxtTagProvider = getNuxtTagProvider(packageRoot); if (nuxtTagProvider) { settings['nuxt'] = true; allTagProviders.push(nuxtTagProvider); @@ -109,7 +108,7 @@ export function getTagProviderSettings(projectPath: string | null | undefined, p settings['gridsome'] = true; } - const workspaceTagProvider = getWorkspaceTagProvider(projectPath, rootPkgJson); + const workspaceTagProvider = getWorkspaceTagProvider(packageRoot, rootPkgJson); if (workspaceTagProvider) { allTagProviders.push(workspaceTagProvider); } @@ -117,7 +116,7 @@ export function getTagProviderSettings(projectPath: string | null | undefined, p for (const dep of [...Object.keys(dependencies), ...Object.keys(devDependencies)]) { let runtimePkgJsonPath; try { - runtimePkgJsonPath = require.resolve(join(dep, 'package.json'), { paths: [projectPath] }); + runtimePkgJsonPath = require.resolve(join(dep, 'package.json'), { paths: [packageRoot] }); } catch { continue; } @@ -127,7 +126,7 @@ export function getTagProviderSettings(projectPath: string | null | undefined, p continue; } - const depTagProvider = getDependencyTagProvider(projectPath, runtimePkgJson); + const depTagProvider = getDependencyTagProvider(packageRoot, runtimePkgJson); if (!depTagProvider) { continue; } diff --git a/server/src/modes/template/tagProviders/nuxtTags.ts b/server/src/modes/template/tagProviders/nuxtTags.ts index edf39d6b30..16982902c6 100644 --- a/server/src/modes/template/tagProviders/nuxtTags.ts +++ b/server/src/modes/template/tagProviders/nuxtTags.ts @@ -3,20 +3,20 @@ import { getExternalTagProvider } from './externalTagProviders'; const NUXT_JSON_SOURCES = ['@nuxt/vue-app-edge', '@nuxt/vue-app', 'nuxt-helper-json']; -export function getNuxtTagProvider(workspacePath: string) { +export function getNuxtTagProvider(packageRoot: string) { let nuxtTags, nuxtAttributes; for (const source of NUXT_JSON_SOURCES) { - if (tryResolve(join(source, 'package.json'), workspacePath)) { - nuxtTags = tryRequire(join(source, 'vetur/nuxt-tags.json'), workspacePath); - nuxtAttributes = tryRequire(join(source, 'vetur/nuxt-attributes.json'), workspacePath); + if (tryResolve(join(source, 'package.json'), packageRoot)) { + nuxtTags = tryRequire(join(source, 'vetur/nuxt-tags.json'), packageRoot); + nuxtAttributes = tryRequire(join(source, 'vetur/nuxt-attributes.json'), packageRoot); if (nuxtTags) { break; } } } - const componentsTags = tryRequire(join(workspacePath, '.nuxt/vetur/tags.json'), workspacePath); - const componentsAttributes = tryRequire(join(workspacePath, '.nuxt/vetur/attributes.json'), workspacePath); + const componentsTags = tryRequire(join(packageRoot, '.nuxt/vetur/tags.json'), packageRoot); + const componentsAttributes = tryRequire(join(packageRoot, '.nuxt/vetur/attributes.json'), packageRoot); return getExternalTagProvider( 'nuxt', @@ -25,17 +25,17 @@ export function getNuxtTagProvider(workspacePath: string) { ); } -function tryRequire(modulePath: string, workspacePath: string) { +function tryRequire(modulePath: string, findPath: string) { try { - const resolved = tryResolve(modulePath, workspacePath); + const resolved = tryResolve(modulePath, findPath); return resolved ? require(resolved) : undefined; } catch (_err) {} } -function tryResolve(modulePath: string, workspacePath: string) { +function tryResolve(modulePath: string, findPath: string) { try { return require.resolve(modulePath, { - paths: [workspacePath, __dirname] + paths: [findPath, __dirname] }); } catch (_err) {} } diff --git a/server/src/modes/vue/index.ts b/server/src/modes/vue/index.ts index 205d4814c4..db26640030 100644 --- a/server/src/modes/vue/index.ts +++ b/server/src/modes/vue/index.ts @@ -2,10 +2,10 @@ import { LanguageMode } from '../../embeddedSupport/languageModes'; import { SnippetManager, ScaffoldSnippetSources } from './snippets'; import { Range } from 'vscode-css-languageservice'; -export function getVueMode(workspacePath: string, globalSnippetDir?: string): LanguageMode { +export function getVueMode(snippetFolder: string, globalSnippetDir?: string): LanguageMode { let config: any = {}; - const snippetManager = new SnippetManager(workspacePath, globalSnippetDir); + const snippetManager = new SnippetManager(snippetFolder, globalSnippetDir); let scaffoldSnippetSources: ScaffoldSnippetSources = { workspace: '💼', user: '🗒️', @@ -32,10 +32,7 @@ export function getVueMode(workspacePath: string, globalSnippetDir?: string): La } const offset = document.offsetAt(position); - const lines = document - .getText() - .slice(0, offset) - .split('\n'); + const lines = document.getText().slice(0, offset).split('\n'); const currentLine = lines[position.line]; const items = snippetManager ? snippetManager.completeSnippets(scaffoldSnippetSources) : []; diff --git a/server/src/modes/vue/snippets.ts b/server/src/modes/vue/snippets.ts index 8fe8c8efab..f77827815c 100644 --- a/server/src/modes/vue/snippets.ts +++ b/server/src/modes/vue/snippets.ts @@ -21,8 +21,8 @@ export interface ScaffoldSnippetSources { export class SnippetManager { private _snippets: Snippet[] = []; - constructor(workspacePath: string, globalSnippetDir?: string) { - const workspaceSnippets = loadAllSnippets(path.resolve(workspacePath, '.vscode/vetur/snippets'), 'workspace'); + constructor(snippetFolder: string, globalSnippetDir?: string) { + const workspaceSnippets = loadAllSnippets(snippetFolder, 'workspace'); const userSnippets = globalSnippetDir ? loadAllSnippets(globalSnippetDir, 'user') : []; const veturSnippets = loadAllSnippets(path.resolve(__dirname, './veturSnippets'), 'vetur'); diff --git a/server/src/services/projectService.ts b/server/src/services/projectService.ts index b5311e230f..a4176487af 100644 --- a/server/src/services/projectService.ts +++ b/server/src/services/projectService.ts @@ -69,6 +69,7 @@ export async function createProjectService( projectPath: string, tsconfigPath: string | undefined, packagePath: string | undefined, + snippetFolder: string, globalComponentInfos: BasicComponentInfo[], documentService: DocumentService, initialConfig: VLSConfig, @@ -95,10 +96,10 @@ export async function createProjectService( vueInfoService.init(languageModes); await languageModes.init( - workspacePath, projectPath, tsconfigPath, packagePath, + snippetFolder, globalComponentInfos, { infoService: vueInfoService, diff --git a/server/src/services/typescriptService/serviceHost.ts b/server/src/services/typescriptService/serviceHost.ts index d82ec28252..137e2f0374 100644 --- a/server/src/services/typescriptService/serviceHost.ts +++ b/server/src/services/typescriptService/serviceHost.ts @@ -113,7 +113,7 @@ export function getServiceHost( const vueSys = getVueSys(tsModule, scriptFileNameSet); - const vueVersion = inferVueVersion(projectPath, packagePath); + const vueVersion = inferVueVersion(packagePath); const compilerOptions = { ...getDefaultCompilerOptions(tsModule), ...parsedConfig.options @@ -492,9 +492,9 @@ function patchTemplateService(original: ts.LanguageService): ts.LanguageService }; } -function defaultIgnorePatterns(tsModule: RuntimeLibrary['typescript'], workspacePath: string) { +function defaultIgnorePatterns(tsModule: RuntimeLibrary['typescript'], projectPath: string) { const nodeModules = ['node_modules', '**/node_modules/*']; - const gitignore = tsModule.findConfigFile(workspacePath, tsModule.sys.fileExists, '.gitignore'); + const gitignore = tsModule.findConfigFile(projectPath, tsModule.sys.fileExists, '.gitignore'); if (!gitignore) { return nodeModules; } diff --git a/server/src/services/typescriptService/vueVersion.ts b/server/src/services/typescriptService/vueVersion.ts index 0f15e73b23..1db8b4d76e 100644 --- a/server/src/services/typescriptService/vueVersion.ts +++ b/server/src/services/typescriptService/vueVersion.ts @@ -1,3 +1,4 @@ +import { throws } from 'assert'; import { readFileSync } from 'fs'; import { findConfigFile } from '../../utils/workspace'; @@ -17,9 +18,12 @@ function floatVersionToEnum(v: number) { } } -export function inferVueVersion(workspacePath: string, packagePath: string | undefined): VueVersion { - const packageJSONPath = packagePath ?? findConfigFile(workspacePath, 'package.json'); +export function inferVueVersion(packagePath: string | undefined): VueVersion { + const packageJSONPath = packagePath; try { + if (!packageJSONPath) { + throw new Error(`Can't find package.json in project`); + } const packageJSON = packageJSONPath && JSON.parse(readFileSync(packageJSONPath, { encoding: 'utf-8' })); const vueDependencyVersion = packageJSON.dependencies.vue || packageJSON.devDependencies.vue; @@ -30,7 +34,7 @@ export function inferVueVersion(workspacePath: string, packagePath: string | und return floatVersionToEnum(sloppyVersion); } - const nodeModulesVuePackagePath = require.resolve('vue/package.json', { paths: [workspacePath] }); + const nodeModulesVuePackagePath = require.resolve('vue/package.json', { paths: [packageJSONPath] }); const nodeModulesVuePackageJSON = JSON.parse(readFileSync(nodeModulesVuePackagePath, { encoding: 'utf-8' })!); const nodeModulesVueVersion = parseFloat(nodeModulesVuePackageJSON.version.match(/\d+\.\d+/)[0]); diff --git a/server/src/services/vls.ts b/server/src/services/vls.ts index 979a51b114..2edeb7ea8c 100644 --- a/server/src/services/vls.ts +++ b/server/src/services/vls.ts @@ -165,6 +165,7 @@ export class VLS { rootFsPath: normalizeFileNameResolve(this.rootPathForConfig, project.root), tsconfigPath: project.tsconfig, packagePath: project.package, + snippetFolder: project.snippetFolder, globalComponents: project.globalComponents })) .sort((a, b) => getPathDepth(b.rootFsPath, '/') - getPathDepth(a.rootFsPath, '/')); @@ -183,6 +184,7 @@ export class VLS { projectConfig.rootFsPath, projectConfig.tsconfigPath, projectConfig.packagePath, + projectConfig.snippetFolder, projectConfig.globalComponents, this.documentService, this.config, diff --git a/server/src/utils/workspace.ts b/server/src/utils/workspace.ts index 7b26863ef4..b37a2b8a97 100644 --- a/server/src/utils/workspace.ts +++ b/server/src/utils/workspace.ts @@ -1,5 +1,5 @@ import ts from 'typescript'; -export function findConfigFile(workspacePath: string, configName: string) { - return ts.findConfigFile(workspacePath, ts.sys.fileExists, configName); +export function findConfigFile(findPath: string, configName: string) { + return ts.findConfigFile(findPath, ts.sys.fileExists, configName); } From 3363b9939f802e36d82a49597eb67c5337b8e60b Mon Sep 17 00:00:00 2001 From: yoyo930021 Date: Thu, 26 Nov 2020 17:44:15 +0800 Subject: [PATCH 06/33] Refactor to autoImportSfcPlugin --- server/src/embeddedSupport/languageModes.ts | 12 ++++++------ .../plugins/autoImportSfcPlugin.ts} | 14 +++++++------- server/src/modes/template/htmlMode.ts | 8 ++++---- server/src/modes/template/index.ts | 14 +++++++------- 4 files changed, 24 insertions(+), 24 deletions(-) rename server/src/{services/autoImportVueService.ts => modes/plugins/autoImportSfcPlugin.ts} (96%) diff --git a/server/src/embeddedSupport/languageModes.ts b/server/src/embeddedSupport/languageModes.ts index 8b17ebe2b6..e1cb8b12c9 100644 --- a/server/src/embeddedSupport/languageModes.ts +++ b/server/src/embeddedSupport/languageModes.ts @@ -39,7 +39,7 @@ import { BasicComponentInfo, VLSFullConfig } from '../config'; import { SassLanguageMode } from '../modes/style/sass/sassLanguageMode'; import { getPugMode } from '../modes/pug'; import { VCancellationToken } from '../utils/cancellationToken'; -import { createAutoImportVueService } from '../services/autoImportVueService'; +import { createAutoImportSfcPlugin } from '../modes/plugins/autoImportSfcPlugin'; export interface VLSServices { dependencyService: DependencyService; @@ -130,9 +130,9 @@ export class LanguageModes { return vueDocument.getSingleTypeDocument('script'); }); this.serviceHost = getServiceHost(tsModule, projectPath, tsconfigPath, packagePath, scriptRegionDocuments); - const autoImportVueService = createAutoImportVueService(tsModule, services.infoService); - autoImportVueService.setGetTSScriptTarget(() => this.serviceHost.getComplierOptions().target); - autoImportVueService.setGetFilesFn(() => + const autoImportSfcPlugin = createAutoImportSfcPlugin(tsModule, services.infoService); + autoImportSfcPlugin.setGetTSScriptTarget(() => this.serviceHost.getComplierOptions().target); + autoImportSfcPlugin.setGetFilesFn(() => this.serviceHost.getFileNames().filter(fileName => fileName.endsWith('.vue')) ); @@ -142,7 +142,7 @@ export class LanguageModes { this.documentRegions, projectPath, packagePath, - autoImportVueService, + autoImportSfcPlugin, services.dependencyService, services.infoService ); @@ -154,7 +154,7 @@ export class LanguageModes { globalComponentInfos, services.infoService ); - autoImportVueService.setGetJSResolve(jsMode.doResolve!); + autoImportSfcPlugin.setGetJSResolve(jsMode.doResolve!); this.modes['vue'] = getVueMode(snippetFolder, globalSnippetDir); this.modes['vue-html'] = vueHtmlMode; diff --git a/server/src/services/autoImportVueService.ts b/server/src/modes/plugins/autoImportSfcPlugin.ts similarity index 96% rename from server/src/services/autoImportVueService.ts rename to server/src/modes/plugins/autoImportSfcPlugin.ts index 41eacc64d0..f0306285fb 100644 --- a/server/src/services/autoImportVueService.ts +++ b/server/src/modes/plugins/autoImportSfcPlugin.ts @@ -5,12 +5,12 @@ import type ts from 'typescript'; import { CompletionItem } from 'vscode-languageserver'; import { TextDocument } from 'vscode-languageserver-textdocument'; import { TextEdit } from 'vscode-languageserver-types'; -import { VLSFullConfig } from '../config'; -import { modulePathToValidIdentifier, toMarkupContent } from '../utils/strings'; -import { RuntimeLibrary } from './dependencyService'; -import { ChildComponent, VueInfoService } from './vueInfoService'; +import { VLSFullConfig } from '../../config'; +import { modulePathToValidIdentifier, toMarkupContent } from '../../utils/strings'; +import { RuntimeLibrary } from '../../services/dependencyService'; +import { ChildComponent, VueInfoService } from '../../services/vueInfoService'; -export interface AutoImportVueService { +export interface AutoImportSfcPlugin { setGetConfigure(fn: () => VLSFullConfig): void; setGetFilesFn(fn: () => string[]): void; setGetJSResolve(fn: (doc: TextDocument, item: CompletionItem) => CompletionItem): void; @@ -41,10 +41,10 @@ export interface AutoImportVueService { * } * ``` */ -export function createAutoImportVueService( +export function createAutoImportSfcPlugin( tsModule: RuntimeLibrary['typescript'], vueInfoService?: VueInfoService -): AutoImportVueService { +): AutoImportSfcPlugin { let getConfigure: () => VLSFullConfig; let getVueFiles: () => string[]; let getJSResolve: (doc: TextDocument, item: CompletionItem) => CompletionItem; diff --git a/server/src/modes/template/htmlMode.ts b/server/src/modes/template/htmlMode.ts index 5a7f217080..9d3730a64f 100644 --- a/server/src/modes/template/htmlMode.ts +++ b/server/src/modes/template/htmlMode.ts @@ -29,7 +29,7 @@ import { doPropValidation } from './services/vuePropValidation'; import { getFoldingRanges } from './services/htmlFolding'; import { DependencyService } from '../../services/dependencyService'; import { isVCancellationRequested, VCancellationToken } from '../../utils/cancellationToken'; -import { AutoImportVueService } from '../../services/autoImportVueService'; +import { AutoImportSfcPlugin } from '../plugins/autoImportSfcPlugin'; export class HTMLMode implements LanguageMode { private tagProviderSettings: CompletionConfiguration; @@ -47,7 +47,7 @@ export class HTMLMode implements LanguageMode { vueVersion: VueVersion, private dependencyService: DependencyService, private vueDocuments: LanguageModelCache, - private autoImportVueService: AutoImportVueService, + private autoImportSfcPlugin: AutoImportSfcPlugin, private vueInfoService?: VueInfoService ) { this.tagProviderSettings = getTagProviderSettings(projectPath, packagePath); @@ -65,7 +65,7 @@ export class HTMLMode implements LanguageMode { configure(c: VLSFullConfig) { this.enabledTagProviders = getEnabledTagProviders(this.tagProviderSettings); this.config = c; - this.autoImportVueService.setGetConfigure(() => c); + this.autoImportSfcPlugin.setGetConfigure(() => c); } async doValidation(document: TextDocument, cancellationToken?: VCancellationToken) { @@ -106,7 +106,7 @@ export class HTMLMode implements LanguageMode { this.vueDocuments.refreshAndGet(embedded), tagProviders, this.config.emmet, - this.autoImportVueService.doComplete(document) + this.autoImportSfcPlugin.doComplete(document) ); } doHover(document: TextDocument, position: Position) { diff --git a/server/src/modes/template/index.ts b/server/src/modes/template/index.ts index d44dacde3f..60cf690856 100644 --- a/server/src/modes/template/index.ts +++ b/server/src/modes/template/index.ts @@ -12,14 +12,14 @@ import { HTMLDocument, parseHTMLDocument } from './parser/htmlParser'; import { inferVueVersion } from '../../services/typescriptService/vueVersion'; import { DependencyService, RuntimeLibrary } from '../../services/dependencyService'; import { VCancellationToken } from '../../utils/cancellationToken'; -import { AutoImportVueService } from '../../services/autoImportVueService'; +import { AutoImportSfcPlugin } from '../plugins/autoImportSfcPlugin'; type DocumentRegionCache = LanguageModelCache; export class VueHTMLMode implements LanguageMode { private htmlMode: HTMLMode; private vueInterpolationMode: VueInterpolationMode; - private autoImportVueService: AutoImportVueService; + private autoImportSfcPlugin: AutoImportSfcPlugin; constructor( tsModule: RuntimeLibrary['typescript'], @@ -27,7 +27,7 @@ export class VueHTMLMode implements LanguageMode { documentRegions: DocumentRegionCache, projectPath: string, packagePath: string | undefined, - autoImportVueService: AutoImportVueService, + autoImportSfcPlugin: AutoImportSfcPlugin, dependencyService: DependencyService, vueInfoService?: VueInfoService ) { @@ -40,11 +40,11 @@ export class VueHTMLMode implements LanguageMode { vueVersion, dependencyService, vueDocuments, - autoImportVueService, + autoImportSfcPlugin, vueInfoService ); this.vueInterpolationMode = new VueInterpolationMode(tsModule, serviceHost, vueDocuments, vueInfoService); - this.autoImportVueService = autoImportVueService; + this.autoImportSfcPlugin = autoImportSfcPlugin; } getId() { return 'vue-html'; @@ -71,8 +71,8 @@ export class VueHTMLMode implements LanguageMode { }; } doResolve(document: TextDocument, item: CompletionItem): CompletionItem { - if (this.autoImportVueService.isMyResolve(item)) { - return this.autoImportVueService.doResolve(document, item); + if (this.autoImportSfcPlugin.isMyResolve(item)) { + return this.autoImportSfcPlugin.doResolve(document, item); } return this.vueInterpolationMode.doResolve(document, item); } From 4a90ad543ed194a720ddcf30c081bb009820132b Mon Sep 17 00:00:00 2001 From: yoyo930021 Date: Sun, 29 Nov 2020 16:40:11 +0800 Subject: [PATCH 07/33] Add basic mulit-root workspaces support --- server/src/services/dependencyService.ts | 8 +- server/src/services/projectService.ts | 7 +- server/src/services/vls.ts | 178 +++++++++++++---------- server/src/utils/cancellationToken.ts | 7 +- 4 files changed, 109 insertions(+), 91 deletions(-) diff --git a/server/src/services/dependencyService.ts b/server/src/services/dependencyService.ts index 27928c9e83..a9b1582885 100644 --- a/server/src/services/dependencyService.ts +++ b/server/src/services/dependencyService.ts @@ -17,9 +17,9 @@ import { getPathDepth } from '../utils/paths'; const readFileAsync = util.promisify(fs.readFile); const accessFileAsync = util.promisify(fs.access); -async function createNodeModulesPaths(rootPath: string) { +export function createNodeModulesPaths(rootPath: string) { const startTime = performance.now(); - const nodeModules = await fg('**/node_modules', { + const nodeModules = fg.sync('**/node_modules', { cwd: rootPath.replace(/\\/g, '/'), absolute: true, unique: true, @@ -93,6 +93,7 @@ export interface DependencyService { rootPathForConfig: string, workspacePath: string, useWorkspaceDependencies: boolean, + nodeModulesPaths: string[], tsSDKPath?: string ): Promise; get(lib: L, filePath?: string): Dependency; @@ -119,10 +120,9 @@ export const createDependencyService = (): DependencyService => { rootPathForConfig: string, workspacePath: string, useWorkspaceDependencies: boolean, + nodeModulesPaths: string[], tsSDKPath?: string ) { - const nodeModulesPaths = useWorkspaceDependencies ? await createNodeModulesPaths(rootPathForConfig) : []; - const loadTypeScript = async (): Promise[]> => { try { if (useWorkspaceDependencies && tsSDKPath) { diff --git a/server/src/services/projectService.ts b/server/src/services/projectService.ts index a4176487af..bf71d30238 100644 --- a/server/src/services/projectService.ts +++ b/server/src/services/projectService.ts @@ -42,6 +42,7 @@ import { DocumentService } from './documentService'; import { VueInfoService } from './vueInfoService'; export interface ProjectService { + readonly rootPathForConfig: string; languageModes: LanguageModes; configure(config: VLSFullConfig): void; onDocumentFormatting(params: DocumentFormattingParams): Promise; @@ -65,7 +66,6 @@ export interface ProjectService { export async function createProjectService( rootPathForConfig: string, - workspacePath: string, projectPath: string, tsconfigPath: string | undefined, packagePath: string | undefined, @@ -119,9 +119,14 @@ export async function createProjectService( configure(initialConfig); return { + rootPathForConfig, configure, languageModes, async onDocumentFormatting({ textDocument, options }) { + if (!$config.vetur.format.enable) { + return []; + } + const doc = documentService.getDocument(textDocument.uri)!; const modeRanges = languageModes.getAllLanguageModeRangesInDocument(doc); diff --git a/server/src/services/vls.ts b/server/src/services/vls.ts index 2edeb7ea8c..205c437100 100644 --- a/server/src/services/vls.ts +++ b/server/src/services/vls.ts @@ -26,14 +26,14 @@ import { CompletionParams, ExecuteCommandParams, ApplyWorkspaceEditRequest, - FoldingRangeParams + FoldingRangeParams, + DidChangeWorkspaceFoldersNotification } from 'vscode-languageserver'; import { ColorInformation, CompletionItem, CompletionList, Definition, - Diagnostic, DocumentHighlight, DocumentLink, Hover, @@ -48,17 +48,14 @@ import { import type { TextDocument } from 'vscode-languageserver-textdocument'; import { URI } from 'vscode-uri'; -import { LanguageModes, LanguageModeRange, LanguageMode } from '../embeddedSupport/languageModes'; import { NULL_COMPLETION, NULL_HOVER, NULL_SIGNATURE } from '../modes/nullMode'; -import { VueInfoService } from './vueInfoService'; -import { createDependencyService, DependencyService } from './dependencyService'; +import { createDependencyService, createNodeModulesPaths } from './dependencyService'; import _ from 'lodash'; -import { DocumentContext, RefactorAction } from '../types'; +import { RefactorAction } from '../types'; import { DocumentService } from './documentService'; import { VueHTMLMode } from '../modes/template'; import { logger } from '../log'; -import { getDefaultVLSConfig, VLSFullConfig, VLSConfig, getVeturFullConfig, VeturFullConfig } from '../config'; -import { LanguageId } from '../embeddedSupport/embeddedSupport'; +import { getDefaultVLSConfig, VLSFullConfig, getVeturFullConfig, VeturFullConfig } from '../config'; import { APPLY_REFACTOR_COMMAND } from '../modes/script/javascript'; import { VCancellationToken, VCancellationTokenSource } from '../utils/cancellationToken'; import { findConfigFile } from '../utils/workspace'; @@ -66,67 +63,53 @@ import { createProjectService, ProjectService } from './projectService'; export class VLS { // @Todo: Remove this and DocumentContext - private workspacePath: string | undefined; - private veturConfig: VeturFullConfig; + // private workspacePath: string | undefined; + private workspaces: Map; + private nodeModulesMap: Map; + // private veturConfig: VeturFullConfig; private documentService: DocumentService; - private rootPathForConfig: string; + // private rootPathForConfig: string; private globalSnippetDir: string; private projects: Map; - private dependencyService: DependencyService; + // private dependencyService: DependencyService; private pendingValidationRequests: { [uri: string]: NodeJS.Timer } = {}; private cancellationTokenValidationRequests: { [uri: string]: VCancellationTokenSource } = {}; private validationDelayMs = 200; - private validation: { [k: string]: boolean } = { - 'vue-html': true, - html: true, - css: true, - scss: true, - less: true, - postcss: true, - javascript: true - }; - private templateInterpolationValidation = false; private documentFormatterRegistration: Disposable | undefined; - private config: VLSFullConfig; + private workspaceConfig: VLSFullConfig; constructor(private lspConnection: Connection) { this.documentService = new DocumentService(this.lspConnection); - this.dependencyService = createDependencyService(); + this.projects = new Map(); + this.nodeModulesMap = new Map(); } async init(params: InitializeParams) { - const workspacePath = params.rootPath; - if (!workspacePath) { + let workspaceFolders = + !Array.isArray(params.workspaceFolders) && params.rootPath + ? [{ name: '', fsPath: normalizeFileNameToFsPath(params.rootPath) }] + : params.workspaceFolders?.map(el => ({ name: el.name, fsPath: getFileFsPath(el.uri) })) ?? []; + + if (workspaceFolders.length === 0) { console.error('No workspace path found. Vetur initialization failed.'); return { capabilities: {} }; } - this.workspacePath = normalizeFileNameToFsPath(workspacePath); - + this.workspaces = new Map(); this.globalSnippetDir = params.initializationOptions?.globalSnippetDir; - const veturConfigPath = findConfigFile(this.workspacePath, 'vetur.config.js'); - this.rootPathForConfig = normalizeFileNameToFsPath(veturConfigPath ? path.dirname(veturConfigPath) : workspacePath); - this.veturConfig = await getVeturFullConfig( - this.rootPathForConfig, - this.workspacePath, - veturConfigPath ? require(veturConfigPath) : {} - ); - const config = this.getFullConfig(params.initializationOptions?.config); - await this.dependencyService.init( - this.rootPathForConfig, - this.workspacePath, - config.vetur.useWorkspaceDependencies, - config.typescript.tsdk - ); - this.projects = new Map(); + await Promise.all(workspaceFolders.map(workspace => this.addWorkspace(workspace))); + + this.workspaceConfig = this.getVLSFullConfig({}, params.initializationOptions?.config); - this.configure(config); + if (params.capabilities.workspace?.workspaceFolders) { + this.setupWorkspaceListeners(); + } this.setupConfigListeners(); this.setupLSPHandlers(); this.setupCustomLSPHandlers(); @@ -141,28 +124,70 @@ export class VLS { this.lspConnection.listen(); } - private getFullConfig(config: any | undefined): VLSFullConfig { + private getVLSFullConfig(settings: VeturFullConfig['settings'], config: any | undefined): VLSFullConfig { const result = config ? _.merge(getDefaultVLSConfig(), config) : getDefaultVLSConfig(); - Object.keys(this.veturConfig.settings).forEach(key => { - _.set(result, key, this.veturConfig.settings[key]); + Object.keys(settings).forEach(key => { + _.set(result, key, settings[key]); }); return result; } + private async addWorkspace(workspace: { name: string; fsPath: string }) { + const veturConfigPath = findConfigFile(workspace.fsPath, 'vetur.config.js'); + const rootPathForConfig = normalizeFileNameToFsPath( + veturConfigPath ? path.dirname(veturConfigPath) : workspace.fsPath + ); + if (!this.workspaces.has(rootPathForConfig)) { + this.workspaces.set(rootPathForConfig, { + ...(await getVeturFullConfig( + rootPathForConfig, + workspace.fsPath, + veturConfigPath ? require(veturConfigPath) : {} + )), + workspaceFsPath: workspace.fsPath + }); + } + } + + private setupWorkspaceListeners() { + this.lspConnection.client.register(DidChangeWorkspaceFoldersNotification.type); + this.lspConnection.workspace.onDidChangeWorkspaceFolders(async e => { + await Promise.all(e.added.map(el => this.addWorkspace({ name: el.name, fsPath: getFileFsPath(el.uri) }))); + }); + } + private setupConfigListeners() { this.lspConnection.onDidChangeConfiguration(async ({ settings }: DidChangeConfigurationParams) => { - const config = this.getFullConfig(settings); - this.configure(config); - this.setupDynamicFormatters(config); + let isFormatEnable = false; + this.projects.forEach(project => { + const veturConfig = this.workspaces.get(project.rootPathForConfig); + if (!veturConfig) return; + const fullConfig = this.getVLSFullConfig(veturConfig.settings, settings); + project.configure(fullConfig); + isFormatEnable = isFormatEnable || fullConfig.vetur.format.enable; + }); + this.setupDynamicFormatters(isFormatEnable); }); this.documentService.getAllDocuments().forEach(this.triggerValidation); } private async getProjectService(uri: DocumentUri): Promise { - const projectRootPaths = this.veturConfig.projects + const projectRootPaths = _.flatten( + Array.from(this.workspaces.entries()).map(([rootPathForConfig, veturConfig]) => + veturConfig.projects.map(project => ({ + ...project, + rootPathForConfig, + vlsFullConfig: this.getVLSFullConfig(veturConfig.settings, this.workspaceConfig), + workspaceFsPath: veturConfig.workspaceFsPath + })) + ) + ) .map(project => ({ - rootFsPath: normalizeFileNameResolve(this.rootPathForConfig, project.root), + vlsFullConfig: project.vlsFullConfig, + rootPathForConfig: project.rootPathForConfig, + workspaceFsPath: project.workspaceFsPath, + rootFsPath: normalizeFileNameResolve(project.rootPathForConfig, project.root), tsconfigPath: project.tsconfig, packagePath: project.package, snippetFolder: project.snippetFolder, @@ -178,18 +203,31 @@ export class VLS { return this.projects.get(projectConfig.rootFsPath); } + const dependencyService = createDependencyService(); + const nodeModulePaths = + this.nodeModulesMap.get(projectConfig.rootPathForConfig) ?? + createNodeModulesPaths(projectConfig.rootPathForConfig); + if (this.nodeModulesMap.has(projectConfig.rootPathForConfig)) { + this.nodeModulesMap.set(projectConfig.rootPathForConfig, nodeModulePaths); + } + await dependencyService.init( + projectConfig.rootPathForConfig, + projectConfig.workspaceFsPath, + projectConfig.vlsFullConfig.vetur.useWorkspaceDependencies, + nodeModulePaths, + projectConfig.vlsFullConfig.typescript.tsdk + ); const project = await createProjectService( - this.rootPathForConfig, - this.workspacePath ?? this.rootPathForConfig, + projectConfig.rootPathForConfig, projectConfig.rootFsPath, projectConfig.tsconfigPath, projectConfig.packagePath, projectConfig.snippetFolder, projectConfig.globalComponents, this.documentService, - this.config, + this.workspaceConfig, this.globalSnippetDir, - this.dependencyService + dependencyService ); this.projects.set(projectConfig.rootFsPath, project); return project; @@ -232,8 +270,8 @@ export class VLS { }); } - private async setupDynamicFormatters(settings: VLSFullConfig) { - if (settings.vetur.format.enable) { + private async setupDynamicFormatters(enable: boolean) { + if (enable) { if (!this.documentFormatterRegistration) { this.documentFormatterRegistration = await this.lspConnection.client.register(DocumentFormattingRequest.type, { documentSelector: [{ language: 'vue' }] @@ -270,26 +308,6 @@ export class VLS { }); } - configure(config: VLSConfig): void { - this.config = config; - - const veturValidationOptions = config.vetur.validation; - this.validation['vue-html'] = veturValidationOptions.template; - this.validation.css = veturValidationOptions.style; - this.validation.postcss = veturValidationOptions.style; - this.validation.scss = veturValidationOptions.style; - this.validation.less = veturValidationOptions.style; - this.validation.javascript = veturValidationOptions.script; - - this.templateInterpolationValidation = config.vetur.experimental.templateInterpolationService; - - this.projects.forEach(project => { - project.configure(config); - }); - - logger.setLevel(config.vetur.dev.logLevel); - } - /** * Custom Notifications */ @@ -407,8 +425,7 @@ export class VLS { this.cancelPastValidation(textDocument); this.pendingValidationRequests[textDocument.uri] = setTimeout(() => { delete this.pendingValidationRequests[textDocument.uri]; - const tsDep = this.dependencyService.get('typescript'); - this.cancellationTokenValidationRequests[textDocument.uri] = new VCancellationTokenSource(tsDep.module); + this.cancellationTokenValidationRequests[textDocument.uri] = new VCancellationTokenSource(); this.validateTextDocument(textDocument, this.cancellationTokenValidationRequests[textDocument.uri].token); }, this.validationDelayMs); } @@ -470,6 +487,7 @@ export class VLS { get capabilities(): ServerCapabilities { return { textDocumentSync: TextDocumentSyncKind.Incremental, + workspace: { workspaceFolders: { supported: true, changeNotifications: true } }, completionProvider: { resolveProvider: true, triggerCharacters: ['.', ':', '<', '"', "'", '/', '@', '*', ' '] }, signatureHelpProvider: { triggerCharacters: ['('] }, documentFormattingProvider: false, diff --git a/server/src/utils/cancellationToken.ts b/server/src/utils/cancellationToken.ts index 13adc5cfcf..a0c056af9e 100644 --- a/server/src/utils/cancellationToken.ts +++ b/server/src/utils/cancellationToken.ts @@ -7,12 +7,7 @@ export interface VCancellationToken extends LSPCancellationToken { } export class VCancellationTokenSource extends CancellationTokenSource { - constructor(private tsModule: RuntimeLibrary['typescript']) { - super(); - } - get token(): VCancellationToken { - const operationCancelException = this.tsModule.OperationCanceledException; const token = super.token as VCancellationToken; token.tsToken = { isCancellationRequested() { @@ -20,7 +15,7 @@ export class VCancellationTokenSource extends CancellationTokenSource { }, throwIfCancellationRequested() { if (token.isCancellationRequested) { - throw new operationCancelException(); + throw new Error('OperationCanceledException'); } } }; From 5f686f275c1b38203fd33e3763614e0f22012662 Mon Sep 17 00:00:00 2001 From: yoyo930021 Date: Mon, 30 Nov 2020 20:05:51 +0800 Subject: [PATCH 08/33] Remove unused code --- server/src/services/dependencyService.ts | 6 +++--- server/src/services/projectService.ts | 5 ++--- server/src/services/vls.ts | 10 ++-------- 3 files changed, 7 insertions(+), 14 deletions(-) diff --git a/server/src/services/dependencyService.ts b/server/src/services/dependencyService.ts index a9b1582885..7defd14217 100644 --- a/server/src/services/dependencyService.ts +++ b/server/src/services/dependencyService.ts @@ -2,6 +2,9 @@ import path from 'path'; import fg from 'fast-glob'; import fs from 'fs'; import util from 'util'; +import { performance } from 'perf_hooks'; +import { logger } from '../log'; +import { getPathDepth } from '../utils/paths'; // dependencies import ts from 'typescript'; import prettier from 'prettier'; @@ -10,9 +13,6 @@ import prettierEslint from 'prettier-eslint'; import * as prettierTslint from 'prettier-tslint'; import stylusSupremacy from 'stylus-supremacy'; import * as prettierPluginPug from '@prettier/plugin-pug'; -import { performance } from 'perf_hooks'; -import { logger } from '../log'; -import { getPathDepth } from '../utils/paths'; const readFileAsync = util.promisify(fs.readFile); const accessFileAsync = util.promisify(fs.access); diff --git a/server/src/services/projectService.ts b/server/src/services/projectService.ts index bf71d30238..a4be6d01c7 100644 --- a/server/src/services/projectService.ts +++ b/server/src/services/projectService.ts @@ -32,11 +32,10 @@ import { URI } from 'vscode-uri'; import { BasicComponentInfo, VLSConfig, VLSFullConfig } from '../config'; import { LanguageId } from '../embeddedSupport/embeddedSupport'; import { LanguageMode, LanguageModes } from '../embeddedSupport/languageModes'; -import { logger } from '../log'; import { NULL_COMPLETION, NULL_HOVER, NULL_SIGNATURE } from '../modes/nullMode'; import { DocumentContext, RefactorAction } from '../types'; -import { VCancellationToken, VCancellationTokenSource } from '../utils/cancellationToken'; -import { getFileFsPath, getFsPathToUri } from '../utils/paths'; +import { VCancellationToken } from '../utils/cancellationToken'; +import { getFileFsPath } from '../utils/paths'; import { DependencyService } from './dependencyService'; import { DocumentService } from './documentService'; import { VueInfoService } from './vueInfoService'; diff --git a/server/src/services/vls.ts b/server/src/services/vls.ts index 205c437100..460d59fd66 100644 --- a/server/src/services/vls.ts +++ b/server/src/services/vls.ts @@ -62,17 +62,11 @@ import { findConfigFile } from '../utils/workspace'; import { createProjectService, ProjectService } from './projectService'; export class VLS { - // @Todo: Remove this and DocumentContext - // private workspacePath: string | undefined; private workspaces: Map; private nodeModulesMap: Map; - // private veturConfig: VeturFullConfig; private documentService: DocumentService; - // private rootPathForConfig: string; private globalSnippetDir: string; private projects: Map; - // private dependencyService: DependencyService; - private pendingValidationRequests: { [uri: string]: NodeJS.Timer } = {}; private cancellationTokenValidationRequests: { [uri: string]: VCancellationTokenSource } = {}; private validationDelayMs = 200; @@ -88,7 +82,7 @@ export class VLS { } async init(params: InitializeParams) { - let workspaceFolders = + const workspaceFolders = !Array.isArray(params.workspaceFolders) && params.rootPath ? [{ name: '', fsPath: normalizeFileNameToFsPath(params.rootPath) }] : params.workspaceFolders?.map(el => ({ name: el.name, fsPath: getFileFsPath(el.uri) })) ?? []; @@ -161,7 +155,7 @@ export class VLS { let isFormatEnable = false; this.projects.forEach(project => { const veturConfig = this.workspaces.get(project.rootPathForConfig); - if (!veturConfig) return; + if (!veturConfig) { return; } const fullConfig = this.getVLSFullConfig(veturConfig.settings, settings); project.configure(fullConfig); isFormatEnable = isFormatEnable || fullConfig.vetur.format.enable; From 60b579b6749bbfa0a0c4863f3b004f42e4d478b0 Mon Sep 17 00:00:00 2001 From: yoyo930021 Date: Mon, 30 Nov 2020 20:30:56 +0800 Subject: [PATCH 09/33] Fix client register capabilities error --- server/src/services/vls.ts | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/server/src/services/vls.ts b/server/src/services/vls.ts index 460d59fd66..526ba78b82 100644 --- a/server/src/services/vls.ts +++ b/server/src/services/vls.ts @@ -77,6 +77,7 @@ export class VLS { constructor(private lspConnection: Connection) { this.documentService = new DocumentService(this.lspConnection); + this.workspaces = new Map(); this.projects = new Map(); this.nodeModulesMap = new Map(); } @@ -94,7 +95,6 @@ export class VLS { }; } - this.workspaces = new Map(); this.globalSnippetDir = params.initializationOptions?.globalSnippetDir; await Promise.all(workspaceFolders.map(workspace => this.addWorkspace(workspace))); @@ -144,9 +144,10 @@ export class VLS { } private setupWorkspaceListeners() { - this.lspConnection.client.register(DidChangeWorkspaceFoldersNotification.type); - this.lspConnection.workspace.onDidChangeWorkspaceFolders(async e => { - await Promise.all(e.added.map(el => this.addWorkspace({ name: el.name, fsPath: getFileFsPath(el.uri) }))); + this.lspConnection.onInitialized(() => { + this.lspConnection.workspace.onDidChangeWorkspaceFolders(async e => { + await Promise.all(e.added.map(el => this.addWorkspace({ name: el.name, fsPath: getFileFsPath(el.uri) }))); + }); }); } @@ -155,7 +156,9 @@ export class VLS { let isFormatEnable = false; this.projects.forEach(project => { const veturConfig = this.workspaces.get(project.rootPathForConfig); - if (!veturConfig) { return; } + if (!veturConfig) { + return; + } const fullConfig = this.getVLSFullConfig(veturConfig.settings, settings); project.configure(fullConfig); isFormatEnable = isFormatEnable || fullConfig.vetur.format.enable; From 5325e8735bbf4f1e64fa73f35d25062f4f9e31ed Mon Sep 17 00:00:00 2001 From: yoyo930021 Date: Mon, 30 Nov 2020 21:08:08 +0800 Subject: [PATCH 10/33] Watch ts and vetur config changed --- .../services/typescriptService/serviceHost.ts | 100 +++++++++++------- server/src/services/vls.ts | 22 +++- server/src/utils/workspace.ts | 5 + 3 files changed, 87 insertions(+), 40 deletions(-) diff --git a/server/src/services/typescriptService/serviceHost.ts b/server/src/services/typescriptService/serviceHost.ts index 137e2f0374..d9b9777b7c 100644 --- a/server/src/services/typescriptService/serviceHost.ts +++ b/server/src/services/typescriptService/serviceHost.ts @@ -94,31 +94,68 @@ export function getServiceHost( let currentScriptDoc: TextDocument; - let projectVersion = 1; - const versions = new Map(); - const localScriptRegionDocuments = new Map(); - const nodeModuleSnapshots = new Map(); - const projectFileSnapshots = new Map(); - const moduleResolutionCache = new ModuleResolutionCache(); - - const parsedConfig = getParsedConfig(tsModule, projectPath, tsconfigPath); - /** - * Only js/ts files in local project - */ - const initialProjectFiles = parsedConfig.fileNames; - logger.logDebug( - `Initializing ServiceHost with ${initialProjectFiles.length} files: ${JSON.stringify(initialProjectFiles)}` - ); - const scriptFileNameSet = new Set(initialProjectFiles); + const vueVersion = inferVueVersion(packagePath); - const vueSys = getVueSys(tsModule, scriptFileNameSet); + // host variable + let projectVersion = 1; + let versions = new Map(); + let localScriptRegionDocuments = new Map(); + let nodeModuleSnapshots = new Map(); + let projectFileSnapshots = new Map(); + let moduleResolutionCache = new ModuleResolutionCache(); + + let parsedConfig: ts.ParsedCommandLine; + let scriptFileNameSet: Set; + + let vueSys: ts.System; + let compilerOptions: ts.CompilerOptions; + + let jsHost: ts.LanguageServiceHost; + let templateHost: ts.LanguageServiceHost; + + let registry: ts.DocumentRegistry; + let jsLanguageService: ts.LanguageService; + let templateLanguageService: ts.LanguageService; + init(); + + function getCompilerOptions () { + const compilerOptions = { + ...getDefaultCompilerOptions(tsModule), + ...parsedConfig.options + }; + compilerOptions.allowNonTsExtensions = true; + return compilerOptions; + } - const vueVersion = inferVueVersion(packagePath); - const compilerOptions = { - ...getDefaultCompilerOptions(tsModule), - ...parsedConfig.options - }; - compilerOptions.allowNonTsExtensions = true; + function init () { + projectVersion = 1; + versions = new Map(); + localScriptRegionDocuments = new Map(); + nodeModuleSnapshots = new Map(); + projectFileSnapshots = new Map(); + moduleResolutionCache = new ModuleResolutionCache(); + + parsedConfig = getParsedConfig(tsModule, projectPath, tsconfigPath); + const initialProjectFiles = parsedConfig.fileNames; + logger.logDebug( + `Initializing ServiceHost with ${initialProjectFiles.length} files: ${JSON.stringify(initialProjectFiles)}` + ); + scriptFileNameSet = new Set(initialProjectFiles); + vueSys = getVueSys(tsModule, scriptFileNameSet); + compilerOptions = getCompilerOptions(); + + jsHost = createLanguageServiceHost(compilerOptions); + templateHost = createLanguageServiceHost({ + ...compilerOptions, + noUnusedLocals: false, + noUnusedParameters: false, + allowJs: true, + checkJs: true + }); + registry = tsModule.createDocumentRegistry(true); + jsLanguageService = tsModule.createLanguageService(jsHost, registry); + templateLanguageService = patchTemplateService(tsModule.createLanguageService(templateHost, registry)); + } function queryVirtualFileInfo( fileName: string, @@ -202,6 +239,10 @@ export function getServiceHost( // External Documents: JS/TS, non Vue documents function updateExternalDocument(fileFsPath: string) { + if (fileFsPath === tsconfigPath) { + init(); + } + // respect tsconfig // use *internal* function const configFileSpecs = (parsedConfig as any).configFileSpecs; @@ -439,19 +480,6 @@ export function getServiceHost( }; } - const jsHost = createLanguageServiceHost(compilerOptions); - const templateHost = createLanguageServiceHost({ - ...compilerOptions, - noUnusedLocals: false, - noUnusedParameters: false, - allowJs: true, - checkJs: true - }); - - const registry = tsModule.createDocumentRegistry(true); - let jsLanguageService = tsModule.createLanguageService(jsHost, registry); - const templateLanguageService = patchTemplateService(tsModule.createLanguageService(templateHost, registry)); - return { queryVirtualFileInfo, updateCurrentVirtualVueTextDocument, diff --git a/server/src/services/vls.ts b/server/src/services/vls.ts index 526ba78b82..6b6cf85423 100644 --- a/server/src/services/vls.ts +++ b/server/src/services/vls.ts @@ -58,11 +58,11 @@ import { logger } from '../log'; import { getDefaultVLSConfig, VLSFullConfig, getVeturFullConfig, VeturFullConfig } from '../config'; import { APPLY_REFACTOR_COMMAND } from '../modes/script/javascript'; import { VCancellationToken, VCancellationTokenSource } from '../utils/cancellationToken'; -import { findConfigFile } from '../utils/workspace'; +import { findConfigFile, requireUncached } from '../utils/workspace'; import { createProjectService, ProjectService } from './projectService'; export class VLS { - private workspaces: Map; + private workspaces: Map; private nodeModulesMap: Map; private documentService: DocumentService; private globalSnippetDir: string; @@ -133,10 +133,11 @@ export class VLS { ); if (!this.workspaces.has(rootPathForConfig)) { this.workspaces.set(rootPathForConfig, { + name: workspace.name, ...(await getVeturFullConfig( rootPathForConfig, workspace.fsPath, - veturConfigPath ? require(veturConfigPath) : {} + veturConfigPath ? requireUncached(veturConfigPath) : {} )), workspaceFsPath: workspace.fsPath }); @@ -292,9 +293,22 @@ export class VLS { this.lspConnection.onDidChangeWatchedFiles(({ changes }) => { changes.forEach(async c => { if (c.type === FileChangeType.Changed) { + const fsPath = getFileFsPath(c.uri); + if (this.workspaces.has(fsPath)) { + const name = this.workspaces.get(fsPath)?.name ?? ''; + this.workspaces.delete(fsPath); + await this.addWorkspace({ name, fsPath }); + this.projects.forEach((project, projectPath) => { + if (project.rootPathForConfig === fsPath) { + project.dispose(); + this.projects.delete(projectPath); + } + }); + return; + } + const project = await this.getProjectService(c.uri); const jsMode = project?.languageModes.getMode('javascript'); - const fsPath = getFileFsPath(c.uri); jsMode?.onDocumentChanged!(fsPath); } }); diff --git a/server/src/utils/workspace.ts b/server/src/utils/workspace.ts index b37a2b8a97..b81e004ca2 100644 --- a/server/src/utils/workspace.ts +++ b/server/src/utils/workspace.ts @@ -3,3 +3,8 @@ import ts from 'typescript'; export function findConfigFile(findPath: string, configName: string) { return ts.findConfigFile(findPath, ts.sys.fileExists, configName); } + +export function requireUncached(module: string) { + delete require.cache[require.resolve(module)]; + return require(module); +} From e2e882f8d9b265324698a759afbc148eea16b395 Mon Sep 17 00:00:00 2001 From: yoyo930021 Date: Mon, 30 Nov 2020 21:39:18 +0800 Subject: [PATCH 11/33] Fix passing wrong config --- server/src/services/vls.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server/src/services/vls.ts b/server/src/services/vls.ts index 6b6cf85423..d2647d1f48 100644 --- a/server/src/services/vls.ts +++ b/server/src/services/vls.ts @@ -73,7 +73,7 @@ export class VLS { private documentFormatterRegistration: Disposable | undefined; - private workspaceConfig: VLSFullConfig; + private workspaceConfig: unknown; constructor(private lspConnection: Connection) { this.documentService = new DocumentService(this.lspConnection); @@ -223,7 +223,7 @@ export class VLS { projectConfig.snippetFolder, projectConfig.globalComponents, this.documentService, - this.workspaceConfig, + projectConfig.vlsFullConfig, this.globalSnippetDir, dependencyService ); From ef331ac0b4ff0abb72942d9fa9e06d2186a4efba Mon Sep 17 00:00:00 2001 From: yoyo930021 Date: Tue, 1 Dec 2020 21:52:39 +0800 Subject: [PATCH 12/33] Fix wrong tsconfig or jsconfig path --- server/src/config.ts | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/server/src/config.ts b/server/src/config.ts index eb21159912..362eb527f2 100644 --- a/server/src/config.ts +++ b/server/src/config.ts @@ -184,16 +184,27 @@ export async function getVeturFullConfig( const fallbackPackage = findConfigFile(projectRoot, 'package.json'); return fallbackPackage ? normalizeFileNameToFsPath(fallbackPackage) : undefined; }; + const getFallbackTsconfigPath = (projectRoot: string) => { + const jsconfigPath = findConfigFile(projectRoot, 'jsconfig.json'); + const tsconfigPath = findConfigFile(projectRoot, 'tsconfig.json'); + if (jsconfigPath && tsconfigPath) { + const tsconfigFsPath = normalizeFileNameToFsPath(tsconfigPath); + const jsconfigFsPath = normalizeFileNameToFsPath(jsconfigPath); + return getPathDepth(tsconfigPath, '/') >= getPathDepth(jsconfigFsPath, '/') + ? tsconfigFsPath + : jsconfigFsPath; + } + const configPath = tsconfigPath || jsconfigPath; + return configPath ? normalizeFileNameToFsPath(configPath) : undefined; + }; if (typeof project === 'string') { const projectRoot = normalizeFileNameResolve(rootPathForConfig, project); - const tsconfigPath = - findConfigFile(projectRoot, 'tsconfig.json') ?? findConfigFile(projectRoot, 'jsconfig.json'); return { root: projectRoot, package: getFallbackPackagePath(projectRoot), - tsconfig: tsconfigPath ? normalizeFileNameToFsPath(tsconfigPath) : undefined, + tsconfig: getFallbackTsconfigPath(projectRoot), snippetFolder: normalizeFileNameResolve(projectRoot, '.vscode/vetur/snippets'), globalComponents: [] } as VeturProject; @@ -203,7 +214,7 @@ export async function getVeturFullConfig( return { root: projectRoot, package: project.package ?? getFallbackPackagePath(projectRoot), - tsconfig: project.tsconfig ?? undefined, + tsconfig: project.tsconfig ?? getFallbackTsconfigPath(projectRoot), snippetFolder: normalizeFileNameResolve(projectRoot, '.vscode/vetur/snippets'), globalComponents: flatten( project.globalComponents?.map(comp => { From 590e22e6a751bb789353161b41bbedd0d71f7fde Mon Sep 17 00:00:00 2001 From: yoyo930021 Date: Tue, 1 Dec 2020 22:32:53 +0800 Subject: [PATCH 13/33] Fix error when vue3 project startup --- server/src/services/typescriptService/preprocess.ts | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/server/src/services/typescriptService/preprocess.ts b/server/src/services/typescriptService/preprocess.ts index 6dacb59d29..05444b4ec9 100644 --- a/server/src/services/typescriptService/preprocess.ts +++ b/server/src/services/typescriptService/preprocess.ts @@ -60,10 +60,6 @@ export function createUpdater( const modificationTracker = new WeakSet(); const printer = tsModule.createPrinter(); - function isTSLike(scriptKind: ts.ScriptKind | undefined) { - return scriptKind === tsModule.ScriptKind.TS || scriptKind === tsModule.ScriptKind.TSX; - } - function modifySourceFile( fileName: string, sourceFile: ts.SourceFile, @@ -75,7 +71,7 @@ export function createUpdater( return; } - if (isVueFile(fileName) && !isTSLike(scriptKind)) { + if (isVueFile(fileName)) { modifyVueScript(tsModule, sourceFile); modificationTracker.add(sourceFile); return; From 0e951fc3feff685b2034c789a927d4e6e12cc84f Mon Sep 17 00:00:00 2001 From: yoyo930021 Date: Tue, 1 Dec 2020 22:44:45 +0800 Subject: [PATCH 14/33] Add monorepo tests --- .vscode/launch.json | 18 +++ package.json | 1 + test/monorepo/data-dir/User/settings.json | 3 + .../features/beforeAll/beforeAll.test.ts | 5 + .../features/completion/alias.test.ts | 11 ++ .../features/diagnostics/eslint.test.ts | 33 ++++++ .../diagnostics/globalComponent.test.ts | 20 ++++ test/monorepo/fixture/package.json | 6 + .../packages/vue2/completion/Alias.vue | 3 + .../packages/vue2/components/AppSpinner.vue | 3 + .../packages/vue2/diagnostics/ESLint.vue | 8 ++ .../fixture/packages/vue2/jsconfig.json | 11 ++ .../fixture/packages/vue2/package.json | 10 ++ .../fixture/packages/vue3/package.json | 7 ++ .../fixture/packages/vue3/src/App.vue | 10 ++ .../vue3/src/components/AppButton.vue | 23 ++++ .../fixture/packages/vue3/src/tsconfig.json | 33 ++++++ test/monorepo/fixture/vetur.config.js | 9 ++ test/monorepo/fixture/yarn.lock | 109 ++++++++++++++++++ test/monorepo/index.ts | 46 ++++++++ test/monorepo/path.ts | 9 ++ 21 files changed, 378 insertions(+) create mode 100644 test/monorepo/data-dir/User/settings.json create mode 100644 test/monorepo/features/beforeAll/beforeAll.test.ts create mode 100644 test/monorepo/features/completion/alias.test.ts create mode 100644 test/monorepo/features/diagnostics/eslint.test.ts create mode 100644 test/monorepo/features/diagnostics/globalComponent.test.ts create mode 100644 test/monorepo/fixture/package.json create mode 100644 test/monorepo/fixture/packages/vue2/completion/Alias.vue create mode 100644 test/monorepo/fixture/packages/vue2/components/AppSpinner.vue create mode 100644 test/monorepo/fixture/packages/vue2/diagnostics/ESLint.vue create mode 100644 test/monorepo/fixture/packages/vue2/jsconfig.json create mode 100644 test/monorepo/fixture/packages/vue2/package.json create mode 100644 test/monorepo/fixture/packages/vue3/package.json create mode 100644 test/monorepo/fixture/packages/vue3/src/App.vue create mode 100644 test/monorepo/fixture/packages/vue3/src/components/AppButton.vue create mode 100644 test/monorepo/fixture/packages/vue3/src/tsconfig.json create mode 100644 test/monorepo/fixture/vetur.config.js create mode 100644 test/monorepo/fixture/yarn.lock create mode 100644 test/monorepo/index.ts create mode 100644 test/monorepo/path.ts diff --git a/.vscode/launch.json b/.vscode/launch.json index 5a2e25418d..011a916e1d 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -121,6 +121,24 @@ "smartStep": true, "skipFiles": ["/**"] }, + { + "name": "E2E Test (Monorepo)", + "type": "extensionHost", + "request": "launch", + "runtimeExecutable": "${execPath}", + "args": [ + "--extensionDevelopmentPath=${workspaceFolder}", + "--extensionTestsPath=${workspaceFolder}/dist-test/test/monorepo", + "--user-data-dir=${workspaceFolder}/test/monorepo/data-dir", + "--disable-extensions", + "${workspaceFolder}/test/monorepo/fixture" + ], + "stopOnEntry": false, + "sourceMaps": true, + "outFiles": ["${workspaceFolder}/dist-test/test/**/*.js"], + "smartStep": true, + "skipFiles": ["/**"] + }, { "type": "node", "request": "launch", diff --git a/package.json b/package.json index 67f40e6bd1..544462be3f 100644 --- a/package.json +++ b/package.json @@ -23,6 +23,7 @@ "test:int": "node ./dist-test/test/codeTestRunner.js interpolation", "test:vue3": "node ./dist-test/test/codeTestRunner.js vue3", "test:componentData": "node ./dist-test/test/codeTestRunner.js componentData", + "test:monorepo": "node ./dist-test/test/codeTestRunner.js monorepo", "test": "run-s test:server test:e2e", "docs": "bash ./build/update-docs.sh", "prepare-publish": "./build/release-cleanup.sh" diff --git a/test/monorepo/data-dir/User/settings.json b/test/monorepo/data-dir/User/settings.json new file mode 100644 index 0000000000..756721312a --- /dev/null +++ b/test/monorepo/data-dir/User/settings.json @@ -0,0 +1,3 @@ +{ + "update.mode": "none" +} diff --git a/test/monorepo/features/beforeAll/beforeAll.test.ts b/test/monorepo/features/beforeAll/beforeAll.test.ts new file mode 100644 index 0000000000..e464c29363 --- /dev/null +++ b/test/monorepo/features/beforeAll/beforeAll.test.ts @@ -0,0 +1,5 @@ +import { activateLS } from '../../../editorHelper'; + +before(async () => { + await activateLS(); +}); diff --git a/test/monorepo/features/completion/alias.test.ts b/test/monorepo/features/completion/alias.test.ts new file mode 100644 index 0000000000..0357d98e4b --- /dev/null +++ b/test/monorepo/features/completion/alias.test.ts @@ -0,0 +1,11 @@ +import { position } from '../../../util'; +import { testCompletion } from '../../../completionHelper'; +import { getDocUri } from '../../path'; + +describe('Should autocomplete for diff --git a/test/monorepo/fixture/packages/vue2/components/AppSpinner.vue b/test/monorepo/fixture/packages/vue2/components/AppSpinner.vue new file mode 100644 index 0000000000..7b8b46cb04 --- /dev/null +++ b/test/monorepo/fixture/packages/vue2/components/AppSpinner.vue @@ -0,0 +1,3 @@ + diff --git a/test/monorepo/fixture/packages/vue2/diagnostics/ESLint.vue b/test/monorepo/fixture/packages/vue2/diagnostics/ESLint.vue new file mode 100644 index 0000000000..0e5ef1511d --- /dev/null +++ b/test/monorepo/fixture/packages/vue2/diagnostics/ESLint.vue @@ -0,0 +1,8 @@ + diff --git a/test/monorepo/fixture/packages/vue2/jsconfig.json b/test/monorepo/fixture/packages/vue2/jsconfig.json new file mode 100644 index 0000000000..4533977a8a --- /dev/null +++ b/test/monorepo/fixture/packages/vue2/jsconfig.json @@ -0,0 +1,11 @@ +{ + "compilerOptions": { + "target": "es2015", + "module": "esnext", + "strict": true, + "baseUrl": "./", + "paths": { + "@/*": ["components/*"] + } + } +} diff --git a/test/monorepo/fixture/packages/vue2/package.json b/test/monorepo/fixture/packages/vue2/package.json new file mode 100644 index 0000000000..7363b26c19 --- /dev/null +++ b/test/monorepo/fixture/packages/vue2/package.json @@ -0,0 +1,10 @@ +{ + "name": "vue2", + "version": "1.0.0", + "dependencies": { + "vue": "^2.6.11" + }, + "vetur": { + "tags": "./tags.json" + } +} diff --git a/test/monorepo/fixture/packages/vue3/package.json b/test/monorepo/fixture/packages/vue3/package.json new file mode 100644 index 0000000000..64236a420f --- /dev/null +++ b/test/monorepo/fixture/packages/vue3/package.json @@ -0,0 +1,7 @@ +{ + "name": "vue3", + "version": "1.0.0", + "dependencies": { + "vue": "^3.0.0" + } +} diff --git a/test/monorepo/fixture/packages/vue3/src/App.vue b/test/monorepo/fixture/packages/vue3/src/App.vue new file mode 100644 index 0000000000..ed40c83af1 --- /dev/null +++ b/test/monorepo/fixture/packages/vue3/src/App.vue @@ -0,0 +1,10 @@ + + + diff --git a/test/monorepo/fixture/packages/vue3/src/components/AppButton.vue b/test/monorepo/fixture/packages/vue3/src/components/AppButton.vue new file mode 100644 index 0000000000..f6071b3dff --- /dev/null +++ b/test/monorepo/fixture/packages/vue3/src/components/AppButton.vue @@ -0,0 +1,23 @@ + + + diff --git a/test/monorepo/fixture/packages/vue3/src/tsconfig.json b/test/monorepo/fixture/packages/vue3/src/tsconfig.json new file mode 100644 index 0000000000..185240c371 --- /dev/null +++ b/test/monorepo/fixture/packages/vue3/src/tsconfig.json @@ -0,0 +1,33 @@ +{ + "compilerOptions": { + "target": "es2015", + "module": "esnext", + "strict": true, + "moduleResolution": "node", + "esModuleInterop": true, + "resolveJsonModule": true, + "allowSyntheticDefaultImports": true, + "baseUrl": ".", + "paths": { + "@/*": [ + "src/*" + ] + }, + "lib": [ + "esnext", + "dom", + "dom.iterable", + "scripthost" + ] + }, + "include": [ + "src/**/*.ts", + "src/**/*.tsx", + "src/**/*.vue", + "tests/**/*.ts", + "tests/**/*.tsx" + ], + "exclude": [ + "node_modules" + ] +} diff --git a/test/monorepo/fixture/vetur.config.js b/test/monorepo/fixture/vetur.config.js new file mode 100644 index 0000000000..4c1c1177d5 --- /dev/null +++ b/test/monorepo/fixture/vetur.config.js @@ -0,0 +1,9 @@ +module.exports = { + settings: { + 'vetur.validation.templateProps': true + }, + projects: [ + './packages/vue2', + { root: './packages/vue3', tsconfig: './src/tsconfig.json', globalComponents: ['./src/components/**/*.vue'] } + ] +}; diff --git a/test/monorepo/fixture/yarn.lock b/test/monorepo/fixture/yarn.lock new file mode 100644 index 0000000000..96d383c920 --- /dev/null +++ b/test/monorepo/fixture/yarn.lock @@ -0,0 +1,109 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@babel/helper-validator-identifier@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.4.tgz#a78c7a7251e01f616512d31b10adcf52ada5e0d2" + integrity sha512-3U9y+43hz7ZM+rzG24Qe2mufW5KhvFg/NhnNph+i9mgCtdTCtMJuI1TMkrIUiK7Ix4PYlRF9I5dhqaLYA/ADXw== + +"@babel/parser@^7.12.0": + version "7.12.7" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.12.7.tgz#fee7b39fe809d0e73e5b25eecaf5780ef3d73056" + integrity sha512-oWR02Ubp4xTLCAqPRiNIuMVgNO5Aif/xpXtabhzW2HWUD47XJsAB4Zd/Rg30+XeQA3juXigV7hlquOTmwqLiwg== + +"@babel/types@^7.12.0": + version "7.12.7" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.12.7.tgz#6039ff1e242640a29452c9ae572162ec9a8f5d13" + integrity sha512-MNyI92qZq6jrQkXvtIiykvl4WtoRrVV9MPn+ZfsoEENjiWcBQ3ZSHrkxnJWgWtLX3XXqX5hrSQ+X69wkmesXuQ== + dependencies: + "@babel/helper-validator-identifier" "^7.10.4" + lodash "^4.17.19" + to-fast-properties "^2.0.0" + +"@vue/compiler-core@3.0.3": + version "3.0.3" + resolved "https://registry.yarnpkg.com/@vue/compiler-core/-/compiler-core-3.0.3.tgz#dbb4d5eb91f294038f0bed170a1c25f59f7dc74f" + integrity sha512-iWlRT8RYLmz7zkg84pTOriNUzjH7XACWN++ImFkskWXWeev29IKi7p76T9jKDaMZoPiGcUZ0k9wayuASWVxOwg== + dependencies: + "@babel/parser" "^7.12.0" + "@babel/types" "^7.12.0" + "@vue/shared" "3.0.3" + estree-walker "^2.0.1" + source-map "^0.6.1" + +"@vue/compiler-dom@3.0.3": + version "3.0.3" + resolved "https://registry.yarnpkg.com/@vue/compiler-dom/-/compiler-dom-3.0.3.tgz#582ba30bc82da8409868bc1153ff0e0e2be617e5" + integrity sha512-6GdUbDPjsc0MDZGAgpi4lox+d+aW9/brscwBOLOFfy9wcI9b6yLPmBbjdIsJq3pYdJWbdvACdJ77avBBdHEP8A== + dependencies: + "@vue/compiler-core" "3.0.3" + "@vue/shared" "3.0.3" + +"@vue/reactivity@3.0.3": + version "3.0.3" + resolved "https://registry.yarnpkg.com/@vue/reactivity/-/reactivity-3.0.3.tgz#681ee01ceff9219bc4da6bbb7d9c97d452e44d1d" + integrity sha512-t39Qmc42MX7wJtf8L6tHlu17eP9Rc5w4aRnxpLHNWoaRxddv/7FBhWqusJ2Bwkk8ixFHOQeejcLMt5G469WYJw== + dependencies: + "@vue/shared" "3.0.3" + +"@vue/runtime-core@3.0.3": + version "3.0.3" + resolved "https://registry.yarnpkg.com/@vue/runtime-core/-/runtime-core-3.0.3.tgz#edab3c9ad122cf8afd034b174cd20c073fbf950a" + integrity sha512-Fd1JVnYI6at0W/2ERwJuTSq4S22gNt8bKEbICcvCAac7hJUZ1rylThlrhsvrgA+DVkWU01r0niNZQ4UddlNw7g== + dependencies: + "@vue/reactivity" "3.0.3" + "@vue/shared" "3.0.3" + +"@vue/runtime-dom@3.0.3": + version "3.0.3" + resolved "https://registry.yarnpkg.com/@vue/runtime-dom/-/runtime-dom-3.0.3.tgz#5e3e5e5418b9defcac988d2be0cf65596fa2cc03" + integrity sha512-ytTvSlRaEYvLQUkkpruIBizWIwuIeHER0Ch/evO6kUaPLjZjX3NerVxA40cqJx8rRjb9keQso21U2Jcpk8GsTg== + dependencies: + "@vue/runtime-core" "3.0.3" + "@vue/shared" "3.0.3" + csstype "^2.6.8" + +"@vue/shared@3.0.3": + version "3.0.3" + resolved "https://registry.yarnpkg.com/@vue/shared/-/shared-3.0.3.tgz#ef12ebff93a446df281e8a0fd765b5aea8e7745b" + integrity sha512-yGgkF7u4W0Dmwri9XdeY50kOowN4UIX7aBQ///jbxx37itpzVjK7QzvD3ltQtPfWaJDGBfssGL0wpAgwX9OJpQ== + +csstype@^2.6.8: + version "2.6.14" + resolved "https://registry.yarnpkg.com/csstype/-/csstype-2.6.14.tgz#004822a4050345b55ad4dcc00be1d9cf2f4296de" + integrity sha512-2mSc+VEpGPblzAxyeR+vZhJKgYg0Og0nnRi7pmRXFYYxSfnOnW8A5wwQb4n4cE2nIOzqKOAzLCaEX6aBmNEv8A== + +estree-walker@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-2.0.1.tgz#f8e030fb21cefa183b44b7ad516b747434e7a3e0" + integrity sha512-tF0hv+Yi2Ot1cwj9eYHtxC0jB9bmjacjQs6ZBTj82H8JwUywFuc+7E83NWfNMwHXZc11mjfFcVXPe9gEP4B8dg== + +lodash@^4.17.19: + version "4.17.20" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.20.tgz#b44a9b6297bcb698f1c51a3545a2b3b368d59c52" + integrity sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA== + +source-map@^0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" + integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== + +to-fast-properties@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e" + integrity sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4= + +vue@^2.6.11: + version "2.6.12" + resolved "https://registry.yarnpkg.com/vue/-/vue-2.6.12.tgz#f5ebd4fa6bd2869403e29a896aed4904456c9123" + integrity sha512-uhmLFETqPPNyuLLbsKz6ioJ4q7AZHzD8ZVFNATNyICSZouqP2Sz0rotWQC8UNBF6VGSCs5abnKJoStA6JbCbfg== + +vue@^3.0.0: + version "3.0.3" + resolved "https://registry.yarnpkg.com/vue/-/vue-3.0.3.tgz#ad94a475e6ebbf3904673b6a0ae46e47b957bd72" + integrity sha512-BZG5meD5vLWdvfnRL5WqfDy+cnXO1X/SweModGUna78bdFPZW6+ZO1tU9p0acrskX3DKFcfSp2s4SZnMjABx6w== + dependencies: + "@vue/compiler-dom" "3.0.3" + "@vue/runtime-dom" "3.0.3" + "@vue/shared" "3.0.3" diff --git a/test/monorepo/index.ts b/test/monorepo/index.ts new file mode 100644 index 0000000000..5f328763e2 --- /dev/null +++ b/test/monorepo/index.ts @@ -0,0 +1,46 @@ +import path from 'path'; +import Mocha from 'mocha'; +import glob from 'glob'; + +export function run(): Promise { + const args = {}; + + Object.keys(process.env) + .filter(k => k.startsWith('MOCHA_')) + .forEach(k => { + args[k.slice('MOCHA_'.length)] = process.env[k]; + }); + + const mocha = new Mocha({ + ui: 'bdd', + timeout: 100000, + color: true, + ...args + }); + + const testsRoot = __dirname; + + return new Promise((c, e) => { + glob('**/**.test.js', { cwd: testsRoot }, (err, files) => { + if (err) { + return e(err); + } + + // Add files to the test suite + files.forEach(f => mocha.addFile(path.resolve(testsRoot, f))); + + try { + // Run the mocha test + mocha.run(failures => { + if (failures > 0) { + e(new Error(`${failures} tests failed.`)); + } else { + c(); + } + }); + } catch (err) { + e(err); + } + }); + }); +} diff --git a/test/monorepo/path.ts b/test/monorepo/path.ts new file mode 100644 index 0000000000..a1c31cd5b4 --- /dev/null +++ b/test/monorepo/path.ts @@ -0,0 +1,9 @@ +import { Uri } from 'vscode'; +import { resolve } from 'path'; + +export const getDocPath = (p: string) => { + return resolve(__dirname, `../../../test/monorepo/fixture`, p); +}; +export const getDocUri = (p: string) => { + return Uri.file(getDocPath(p)); +}; From d6707cab7009fb7d17d3d6770d237a72d72003b5 Mon Sep 17 00:00:00 2001 From: yoyo930021 Date: Tue, 1 Dec 2020 22:47:53 +0800 Subject: [PATCH 15/33] Add vue3 test in monorepo --- .../monorepo/features/completion/vue3.test.ts | 20 +++++++++++++++++++ .../fixture/packages/vue3/src/App.vue | 3 ++- 2 files changed, 22 insertions(+), 1 deletion(-) create mode 100644 test/monorepo/features/completion/vue3.test.ts diff --git a/test/monorepo/features/completion/vue3.test.ts b/test/monorepo/features/completion/vue3.test.ts new file mode 100644 index 0000000000..3cafb560c6 --- /dev/null +++ b/test/monorepo/features/completion/vue3.test.ts @@ -0,0 +1,20 @@ +import { CompletionItemKind } from 'vscode'; +import { getDocUri } from '../../path'; +import { position } from '../../../util'; +import { testCompletion } from '../../../completionHelper'; + +describe('Vue 3 integration test', () => { + const fileUri = getDocUri('packages/vue3/src/App.vue'); + + describe('Should complete Vue 3 options', () => { + it('complete `setup`', async () => { + await testCompletion(fileUri, position(8, 2), [ + { + label: 'setup?', + kind: CompletionItemKind.Field, + insertText: 'setup' + } + ]); + }); + }); +}); diff --git a/test/monorepo/fixture/packages/vue3/src/App.vue b/test/monorepo/fixture/packages/vue3/src/App.vue index ed40c83af1..eaf00e378a 100644 --- a/test/monorepo/fixture/packages/vue3/src/App.vue +++ b/test/monorepo/fixture/packages/vue3/src/App.vue @@ -5,6 +5,7 @@ From c6763674e5ba9b5957558e67319ffe9c496f2fbe Mon Sep 17 00:00:00 2001 From: yoyo930021 Date: Thu, 3 Dec 2020 20:46:20 +0800 Subject: [PATCH 16/33] Add more refresh log --- server/src/services/typescriptService/serviceHost.ts | 2 ++ server/src/services/vls.ts | 1 + 2 files changed, 3 insertions(+) diff --git a/server/src/services/typescriptService/serviceHost.ts b/server/src/services/typescriptService/serviceHost.ts index d9b9777b7c..bbbe81a032 100644 --- a/server/src/services/typescriptService/serviceHost.ts +++ b/server/src/services/typescriptService/serviceHost.ts @@ -240,7 +240,9 @@ export function getServiceHost( // External Documents: JS/TS, non Vue documents function updateExternalDocument(fileFsPath: string) { if (fileFsPath === tsconfigPath) { + logger.logInfo(`refresh ts language service when ${fileFsPath} changed.`); init(); + return; } // respect tsconfig diff --git a/server/src/services/vls.ts b/server/src/services/vls.ts index d2647d1f48..3cf2816816 100644 --- a/server/src/services/vls.ts +++ b/server/src/services/vls.ts @@ -295,6 +295,7 @@ export class VLS { if (c.type === FileChangeType.Changed) { const fsPath = getFileFsPath(c.uri); if (this.workspaces.has(fsPath)) { + logger.logInfo(`refresh vetur config when ${fsPath} changed.`); const name = this.workspaces.get(fsPath)?.name ?? ''; this.workspaces.delete(fsPath); await this.addWorkspace({ name, fsPath }); From cff8892c290b8897e0c6d4876212cf4433bf5f39 Mon Sep 17 00:00:00 2001 From: yoyo930021 Date: Thu, 3 Dec 2020 21:47:26 +0800 Subject: [PATCH 17/33] Add friendly warning when open project --- client/vueMain.ts | 3 ++ package.json | 6 +++ server/src/config.ts | 6 +-- server/src/services/vls.ts | 78 +++++++++++++++++++++++++++++++++++--- 4 files changed, 84 insertions(+), 9 deletions(-) diff --git a/client/vueMain.ts b/client/vueMain.ts index 665fc58528..845c72221b 100644 --- a/client/vueMain.ts +++ b/client/vueMain.ts @@ -95,6 +95,9 @@ function registerCustomClientNotificationHandlers(client: LanguageClient) { client.onNotification('$/displayError', (msg: string) => { vscode.window.showErrorMessage(msg); }); + client.onNotification('$/openWebsite', (url: string) => { + vscode.env.openExternal(vscode.Uri.parse(url)); + }); client.onNotification('$/showVirtualFile', (virtualFileSource: string, prettySourceMap: string) => { setVirtualContents(virtualFileSource, prettySourceMap); }); diff --git a/package.json b/package.json index 544462be3f..683d15d274 100644 --- a/package.json +++ b/package.json @@ -184,6 +184,12 @@ "configuration": { "title": "Vetur", "properties": { + "vetur.ignoreProjectWarning": { + "type": "boolean", + "default": false, + "description": "Vetur will warn about not setup correctly for the project. You can disable it.", + "scope": "application" + }, "vetur.useWorkspaceDependencies": { "type": "boolean", "default": false, diff --git a/server/src/config.ts b/server/src/config.ts index 362eb527f2..e3d48daf30 100644 --- a/server/src/config.ts +++ b/server/src/config.ts @@ -21,6 +21,7 @@ export interface VLSFormatConfig { export interface VLSConfig { vetur: { + ignoreProjectWarning: boolean; useWorkspaceDependencies: boolean; completion: { autoImport: boolean; @@ -86,6 +87,7 @@ export interface VLSFullConfig extends VLSConfig { export function getDefaultVLSConfig(): VLSFullConfig { return { vetur: { + ignoreProjectWarning: false, useWorkspaceDependencies: false, validation: { template: true, @@ -190,9 +192,7 @@ export async function getVeturFullConfig( if (jsconfigPath && tsconfigPath) { const tsconfigFsPath = normalizeFileNameToFsPath(tsconfigPath); const jsconfigFsPath = normalizeFileNameToFsPath(jsconfigPath); - return getPathDepth(tsconfigPath, '/') >= getPathDepth(jsconfigFsPath, '/') - ? tsconfigFsPath - : jsconfigFsPath; + return getPathDepth(tsconfigPath, '/') >= getPathDepth(jsconfigFsPath, '/') ? tsconfigFsPath : jsconfigFsPath; } const configPath = tsconfigPath || jsconfigPath; return configPath ? normalizeFileNameToFsPath(configPath) : undefined; diff --git a/server/src/services/vls.ts b/server/src/services/vls.ts index 3cf2816816..60b1b1ec98 100644 --- a/server/src/services/vls.ts +++ b/server/src/services/vls.ts @@ -55,14 +55,29 @@ import { RefactorAction } from '../types'; import { DocumentService } from './documentService'; import { VueHTMLMode } from '../modes/template'; import { logger } from '../log'; -import { getDefaultVLSConfig, VLSFullConfig, getVeturFullConfig, VeturFullConfig } from '../config'; +import { getDefaultVLSConfig, VLSFullConfig, getVeturFullConfig, VeturFullConfig, BasicComponentInfo } from '../config'; import { APPLY_REFACTOR_COMMAND } from '../modes/script/javascript'; import { VCancellationToken, VCancellationTokenSource } from '../utils/cancellationToken'; import { findConfigFile, requireUncached } from '../utils/workspace'; import { createProjectService, ProjectService } from './projectService'; +interface ProjectConfig { + vlsFullConfig: VLSFullConfig; + isExistVeturConfig: boolean; + rootPathForConfig: string; + workspaceFsPath: string; + rootFsPath: string; + tsconfigPath: string | undefined; + packagePath: string | undefined; + snippetFolder: string; + globalComponents: BasicComponentInfo[]; +} + export class VLS { - private workspaces: Map; + private workspaces: Map< + string, + VeturFullConfig & { name: string; workspaceFsPath: string; isExistVeturConfig: boolean } + >; private nodeModulesMap: Map; private documentService: DocumentService; private globalSnippetDir: string; @@ -139,6 +154,7 @@ export class VLS { workspace.fsPath, veturConfigPath ? requireUncached(veturConfigPath) : {} )), + isExistVeturConfig: !!veturConfigPath, workspaceFsPath: workspace.fsPath }); } @@ -170,19 +186,21 @@ export class VLS { this.documentService.getAllDocuments().forEach(this.triggerValidation); } - private async getProjectService(uri: DocumentUri): Promise { - const projectRootPaths = _.flatten( + private getAllProjectConfigs(): ProjectConfig[] { + return _.flatten( Array.from(this.workspaces.entries()).map(([rootPathForConfig, veturConfig]) => veturConfig.projects.map(project => ({ ...project, rootPathForConfig, vlsFullConfig: this.getVLSFullConfig(veturConfig.settings, this.workspaceConfig), - workspaceFsPath: veturConfig.workspaceFsPath + workspaceFsPath: veturConfig.workspaceFsPath, + isExistVeturConfig: veturConfig.isExistVeturConfig })) ) ) .map(project => ({ vlsFullConfig: project.vlsFullConfig, + isExistVeturConfig: project.isExistVeturConfig, rootPathForConfig: project.rootPathForConfig, workspaceFsPath: project.workspaceFsPath, rootFsPath: normalizeFileNameResolve(project.rootPathForConfig, project.root), @@ -192,8 +210,51 @@ export class VLS { globalComponents: project.globalComponents })) .sort((a, b) => getPathDepth(b.rootFsPath, '/') - getPathDepth(a.rootFsPath, '/')); + } + + private warnProjectIfNeed(projectConfig: ProjectConfig) { + if (projectConfig.vlsFullConfig.vetur.ignoreProjectWarning) return; + if (projectConfig.isExistVeturConfig) return; + + const showWarningAndLearnMore = (message: string, url: string) => { + this.lspConnection.window.showWarningMessage(message, { title: 'Learn More' }).then(() => { + this.openWebsite(url); + }); + }; + + const getCantFindMessage = (fileNames: string[]) => + `Vetur can't find ${fileNames.map(el => `\`${el}\``).join(' or ')} in ${projectConfig.rootPathForConfig}.`; + if (!projectConfig.tsconfigPath) { + showWarningAndLearnMore( + getCantFindMessage(['tsconfig.json', 'jsconfig.json']), + 'https://vuejs.github.io/vetur/setup.html#project-setup' + ); + } + if (!projectConfig.packagePath) { + showWarningAndLearnMore(getCantFindMessage(['package.json']), ''); + } + + if ( + ![ + normalizeFileNameResolve(projectConfig.rootPathForConfig, 'tsconfig.json'), + normalizeFileNameResolve(projectConfig.rootPathForConfig, 'jsconfig.json') + ].includes(projectConfig.tsconfigPath ?? '') + ) { + showWarningAndLearnMore( + `Vetur find \`tsconfig.json\`/\`jsconfig.json\`, but they aren\'t in the project root.`, + 'https://vuejs.github.io/vetur/setup.html#project-setup' + ); + } + + if (normalizeFileNameResolve(projectConfig.rootPathForConfig, 'package.json') !== projectConfig.packagePath) { + showWarningAndLearnMore(`Vetur find \`package.json\`/, but they aren\'t in the project root.`, ''); + } + } + + private async getProjectService(uri: DocumentUri): Promise { + const projectConfigs = this.getAllProjectConfigs(); const docFsPath = getFileFsPath(uri); - const projectConfig = projectRootPaths.find(projectConfig => docFsPath.startsWith(projectConfig.rootFsPath)); + const projectConfig = projectConfigs.find(projectConfig => docFsPath.startsWith(projectConfig.rootFsPath)); if (!projectConfig) { return undefined; } @@ -201,6 +262,7 @@ export class VLS { return this.projects.get(projectConfig.rootFsPath); } + // init project const dependencyService = createDependencyService(); const nodeModulePaths = this.nodeModulesMap.get(projectConfig.rootPathForConfig) ?? @@ -215,6 +277,7 @@ export class VLS { nodeModulePaths, projectConfig.vlsFullConfig.typescript.tsdk ); + this.warnProjectIfNeed(projectConfig); const project = await createProjectService( projectConfig.rootPathForConfig, projectConfig.rootFsPath, @@ -333,6 +396,9 @@ export class VLS { displayErrorMessage(msg: string): void { this.lspConnection.sendNotification('$/displayError', msg); } + openWebsite(url: string): void { + this.lspConnection.sendNotification('$/openWebsite', url); + } /** * Language Features From 0570cded8b6ace5f94812fbb95db9469dfe07d02 Mon Sep 17 00:00:00 2001 From: yoyo930021 Date: Thu, 3 Dec 2020 21:49:41 +0800 Subject: [PATCH 18/33] Remove unused client noti --- client/vueMain.ts | 9 --------- server/src/services/vls.ts | 14 ++------------ 2 files changed, 2 insertions(+), 21 deletions(-) diff --git a/client/vueMain.ts b/client/vueMain.ts index 845c72221b..8565a75635 100644 --- a/client/vueMain.ts +++ b/client/vueMain.ts @@ -86,15 +86,6 @@ function registerRestartVLSCommand(context: vscode.ExtensionContext, client: Lan } function registerCustomClientNotificationHandlers(client: LanguageClient) { - client.onNotification('$/displayInfo', (msg: string) => { - vscode.window.showInformationMessage(msg); - }); - client.onNotification('$/displayWarning', (msg: string) => { - vscode.window.showWarningMessage(msg); - }); - client.onNotification('$/displayError', (msg: string) => { - vscode.window.showErrorMessage(msg); - }); client.onNotification('$/openWebsite', (url: string) => { vscode.env.openExternal(vscode.Uri.parse(url)); }); diff --git a/server/src/services/vls.ts b/server/src/services/vls.ts index 60b1b1ec98..5a137a3277 100644 --- a/server/src/services/vls.ts +++ b/server/src/services/vls.ts @@ -213,8 +213,8 @@ export class VLS { } private warnProjectIfNeed(projectConfig: ProjectConfig) { - if (projectConfig.vlsFullConfig.vetur.ignoreProjectWarning) return; - if (projectConfig.isExistVeturConfig) return; + if (projectConfig.vlsFullConfig.vetur.ignoreProjectWarning) { return; } + if (projectConfig.isExistVeturConfig) { return; } const showWarningAndLearnMore = (message: string, url: string) => { this.lspConnection.window.showWarningMessage(message, { title: 'Learn More' }).then(() => { @@ -386,16 +386,6 @@ export class VLS { /** * Custom Notifications */ - - displayInfoMessage(msg: string): void { - this.lspConnection.sendNotification('$/displayInfo', msg); - } - displayWarningMessage(msg: string): void { - this.lspConnection.sendNotification('$/displayWarning', msg); - } - displayErrorMessage(msg: string): void { - this.lspConnection.sendNotification('$/displayError', msg); - } openWebsite(url: string): void { this.lspConnection.sendNotification('$/openWebsite', url); } From d466ba9921bd4498cf7a075e450ab43ca3013772 Mon Sep 17 00:00:00 2001 From: yoyo930021 Date: Fri, 4 Dec 2020 13:47:16 +0800 Subject: [PATCH 19/33] Add show output panel and doctor commands --- client/commands/doctorCommand.ts | 21 +++++++++++++++++++ client/vueMain.ts | 5 ++++- package.json | 22 ++++++++++++++++---- server/src/services/projectService.ts | 2 ++ server/src/services/vls.ts | 30 +++++++++++++++++++++++---- 5 files changed, 71 insertions(+), 9 deletions(-) create mode 100644 client/commands/doctorCommand.ts diff --git a/client/commands/doctorCommand.ts b/client/commands/doctorCommand.ts new file mode 100644 index 0000000000..1b42551743 --- /dev/null +++ b/client/commands/doctorCommand.ts @@ -0,0 +1,21 @@ +import vscode from 'vscode'; +import { LanguageClient } from 'vscode-languageclient'; + +export function generateDoctorCommand (client: LanguageClient) { + return async () => { + if (!vscode.window.activeTextEditor || !vscode.window.activeTextEditor.document.fileName.endsWith('.vue')) { + return vscode.window.showInformationMessage( + 'Failed to doctor. Make sure the current file is a .vue file.' + ); + } + + const fileName = vscode.window.activeTextEditor.document.fileName; + + const result = await client.sendRequest('$/doctor', { fileName }) as string; + const showText = result.slice(0, 1000) + '....'; + const action = await vscode.window.showInformationMessage(showText, { modal: true }, 'Ok', 'Copy'); + if (action === 'Copy') { + await vscode.env.clipboard.writeText(result); + } + }; +} diff --git a/client/vueMain.ts b/client/vueMain.ts index 8565a75635..6cec587cbc 100644 --- a/client/vueMain.ts +++ b/client/vueMain.ts @@ -11,6 +11,7 @@ import { } from './commands/virtualFileCommand'; import { getGlobalSnippetDir } from './userSnippetDir'; import { generateOpenUserScaffoldSnippetFolderCommand } from './commands/openUserScaffoldSnippetFolderCommand'; +import { generateDoctorCommand } from './commands/doctorCommand'; export async function activate(context: vscode.ExtensionContext) { const isInsiders = vscode.env.appName.includes('Insiders'); @@ -96,6 +97,8 @@ function registerCustomClientNotificationHandlers(client: LanguageClient) { function registerCustomLSPCommands(context: vscode.ExtensionContext, client: LanguageClient) { context.subscriptions.push( - vscode.commands.registerCommand('vetur.showCorrespondingVirtualFile', generateShowVirtualFileCommand(client)) + vscode.commands.registerCommand('vetur.showCorrespondingVirtualFile', generateShowVirtualFileCommand(client)), + vscode.commands.registerCommand('vetur.showOutputChannel', () => client.outputChannel.show()), + vscode.commands.registerCommand('vetur.showDoctorInfo', generateDoctorCommand(client)) ); } diff --git a/package.json b/package.json index 683d15d274..37343129cb 100644 --- a/package.json +++ b/package.json @@ -65,19 +65,33 @@ "commands": [ { "command": "vetur.restartVLS", - "title": "Vetur: Restart VLS (Vue Language Server)" + "category": "Vetur", + "title": "Restart VLS (Vue Language Server)" }, { "command": "vetur.generateGrammar", - "title": "Vetur: Generate grammar from `vetur.grammar.customBlocks`" + "category": "Vetur", + "title": "Generate grammar from `vetur.grammar.customBlocks`" }, { "command": "vetur.showCorrespondingVirtualFile", - "title": "Vetur: Show corresponding virtual file and sourcemap" + "category": "Vetur", + "title": "Show corresponding virtual file and sourcemap" }, { "command": "vetur.openUserScaffoldSnippetFolder", - "title": "Vetur: Open user scaffold snippet folder" + "category": "Vetur", + "title": "Open user scaffold snippet folder" + }, + { + "command": "vetur.showOutputChannel", + "category": "Vetur", + "title": "Show Output Channel" + }, + { + "command": "vetur.showDoctorInfo", + "category": "Vetur", + "title": "Show Doctor info" } ], "breakpoints": [ diff --git a/server/src/services/projectService.ts b/server/src/services/projectService.ts index a4be6d01c7..1a8879fdca 100644 --- a/server/src/services/projectService.ts +++ b/server/src/services/projectService.ts @@ -42,6 +42,7 @@ import { VueInfoService } from './vueInfoService'; export interface ProjectService { readonly rootPathForConfig: string; + readonly projectPath: string | undefined; languageModes: LanguageModes; configure(config: VLSFullConfig): void; onDocumentFormatting(params: DocumentFormattingParams): Promise; @@ -119,6 +120,7 @@ export async function createProjectService( return { rootPathForConfig, + projectPath, configure, languageModes, async onDocumentFormatting({ textDocument, options }) { diff --git a/server/src/services/vls.ts b/server/src/services/vls.ts index 5a137a3277..c6273623b8 100644 --- a/server/src/services/vls.ts +++ b/server/src/services/vls.ts @@ -213,12 +213,16 @@ export class VLS { } private warnProjectIfNeed(projectConfig: ProjectConfig) { - if (projectConfig.vlsFullConfig.vetur.ignoreProjectWarning) { return; } - if (projectConfig.isExistVeturConfig) { return; } + if (projectConfig.vlsFullConfig.vetur.ignoreProjectWarning) { + return; + } + if (projectConfig.isExistVeturConfig) { + return; + } const showWarningAndLearnMore = (message: string, url: string) => { - this.lspConnection.window.showWarningMessage(message, { title: 'Learn More' }).then(() => { - this.openWebsite(url); + this.lspConnection.window.showWarningMessage(message, { title: 'Learn More' }).then(action => { + if (action) this.openWebsite(url); }); }; @@ -316,6 +320,24 @@ export class VLS { } private setupCustomLSPHandlers() { + this.lspConnection.onRequest('$/doctor', async ({ fileName }) => { + const uri = getFsPathToUri(fileName); + const projectConfigs = this.getAllProjectConfigs(); + const project = await this.getProjectService(uri); + + return JSON.stringify( + { + name: 'Vetur doctor info', + fileName, + currentProject: { rootPathForConfig: project?.rootPathForConfig, projectRootFsPath: project?.projectPath }, + activeProjects: Array.from(this.projects.keys()), + projectConfigs + }, + null, + 2 + ); + }); + this.lspConnection.onRequest('$/queryVirtualFileInfo', async ({ fileName, currFileText }) => { const project = await this.getProjectService(getFsPathToUri(fileName)); return (project?.languageModes.getMode('vue-html') as VueHTMLMode).queryVirtualFileInfo(fileName, currFileText); From e6ef15229dbfeceb127f40d39f2e5bc1533f53ae Mon Sep 17 00:00:00 2001 From: yoyo930021 Date: Sun, 6 Dec 2020 10:45:38 +0800 Subject: [PATCH 20/33] Refactor to EnvironmentService and watch package json --- server/src/config.ts | 1 + server/src/embeddedSupport/languageModes.ts | 37 +++++------ server/src/modes/pug/index.ts | 12 ++-- server/src/modes/script/javascript.ts | 28 ++++---- server/src/modes/style/index.ts | 47 ++++++++++---- .../src/modes/style/sass/sassLanguageMode.ts | 15 ++--- server/src/modes/style/stylus/index.ts | 22 +++---- server/src/modes/template/htmlMode.ts | 37 +++++------ server/src/modes/template/index.ts | 17 ++--- .../src/modes/template/interpolationMode.ts | 24 +++---- .../template/services/htmlEslintValidation.ts | 2 +- .../src/modes/template/tagProviders/index.ts | 2 +- server/src/modes/vue/index.ts | 17 ++--- server/src/services/EnvironmentService.ts | 40 ++++++++++++ server/src/services/projectService.ts | 65 ++++++------------- .../services/typescriptService/serviceHost.ts | 30 ++++----- server/src/services/vls.ts | 45 ++++++++----- .../typescriptService => utils}/vueVersion.ts | 2 - 18 files changed, 229 insertions(+), 214 deletions(-) create mode 100644 server/src/services/EnvironmentService.ts rename server/src/{services/typescriptService => utils}/vueVersion.ts (94%) diff --git a/server/src/config.ts b/server/src/config.ts index e3d48daf30..c8f6ecebd0 100644 --- a/server/src/config.ts +++ b/server/src/config.ts @@ -78,6 +78,7 @@ export interface VLSFullConfig extends VLSConfig { emmet?: any; html?: any; css?: any; + sass?: any; javascript?: any; typescript?: any; prettier?: any; diff --git a/server/src/embeddedSupport/languageModes.ts b/server/src/embeddedSupport/languageModes.ts index e1cb8b12c9..ae6183a2d9 100644 --- a/server/src/embeddedSupport/languageModes.ts +++ b/server/src/embeddedSupport/languageModes.ts @@ -40,6 +40,7 @@ import { SassLanguageMode } from '../modes/style/sass/sassLanguageMode'; import { getPugMode } from '../modes/pug'; import { VCancellationToken } from '../utils/cancellationToken'; import { createAutoImportSfcPlugin } from '../modes/plugins/autoImportSfcPlugin'; +import { EnvironmentService } from '../services/EnvironmentService'; export interface VLSServices { dependencyService: DependencyService; @@ -48,7 +49,6 @@ export interface VLSServices { export interface LanguageMode { getId(): string; - configure?(options: VLSFullConfig): void; updateFileInfo?(doc: TextDocument): void; doValidation?(document: TextDocument, cancellationToken?: VCancellationToken): Promise; @@ -111,15 +111,7 @@ export class LanguageModes { this.modelCaches.push(this.documentRegions); } - async init( - projectPath: string, - tsconfigPath: string | undefined, - packagePath: string | undefined, - snippetFolder: string, - globalComponentInfos: BasicComponentInfo[], - services: VLSServices, - globalSnippetDir?: string - ) { + async init(env: EnvironmentService, services: VLSServices, globalSnippetDir?: string) { const tsModule = services.dependencyService.get('typescript').module; /** @@ -129,7 +121,7 @@ export class LanguageModes { const vueDocument = this.documentRegions.refreshAndGet(document); return vueDocument.getSingleTypeDocument('script'); }); - this.serviceHost = getServiceHost(tsModule, projectPath, tsconfigPath, packagePath, scriptRegionDocuments); + this.serviceHost = getServiceHost(tsModule, env, scriptRegionDocuments); const autoImportSfcPlugin = createAutoImportSfcPlugin(tsModule, services.infoService); autoImportSfcPlugin.setGetTSScriptTarget(() => this.serviceHost.getComplierOptions().target); autoImportSfcPlugin.setGetFilesFn(() => @@ -139,9 +131,8 @@ export class LanguageModes { const vueHtmlMode = new VueHTMLMode( tsModule, this.serviceHost, + env, this.documentRegions, - projectPath, - packagePath, autoImportSfcPlugin, services.dependencyService, services.infoService @@ -149,22 +140,24 @@ export class LanguageModes { const jsMode = await getJavascriptMode( this.serviceHost, + env, this.documentRegions, services.dependencyService, - globalComponentInfos, + env.getGlobalComponentInfos(), services.infoService ); + autoImportSfcPlugin.setGetConfigure(env.getConfig); autoImportSfcPlugin.setGetJSResolve(jsMode.doResolve!); - this.modes['vue'] = getVueMode(snippetFolder, globalSnippetDir); + this.modes['vue'] = getVueMode(env, globalSnippetDir); this.modes['vue-html'] = vueHtmlMode; - this.modes['pug'] = getPugMode(services.dependencyService); - this.modes['css'] = getCSSMode(this.documentRegions, services.dependencyService); - this.modes['postcss'] = getPostCSSMode(this.documentRegions, services.dependencyService); - this.modes['scss'] = getSCSSMode(this.documentRegions, services.dependencyService); - this.modes['sass'] = new SassLanguageMode(); - this.modes['less'] = getLESSMode(this.documentRegions, services.dependencyService); - this.modes['stylus'] = getStylusMode(this.documentRegions, services.dependencyService); + this.modes['pug'] = getPugMode(env, services.dependencyService); + this.modes['css'] = getCSSMode(env, this.documentRegions, services.dependencyService); + this.modes['postcss'] = getPostCSSMode(env, this.documentRegions, services.dependencyService); + this.modes['scss'] = getSCSSMode(env, this.documentRegions, services.dependencyService); + this.modes['sass'] = new SassLanguageMode(env); + this.modes['less'] = getLESSMode(env, this.documentRegions, services.dependencyService); + this.modes['stylus'] = getStylusMode(env, this.documentRegions, services.dependencyService); this.modes['javascript'] = jsMode; this.modes['typescript'] = jsMode; this.modes['tsx'] = jsMode; diff --git a/server/src/modes/pug/index.ts b/server/src/modes/pug/index.ts index 112552aec0..03761832bd 100644 --- a/server/src/modes/pug/index.ts +++ b/server/src/modes/pug/index.ts @@ -5,19 +5,15 @@ import { prettierPluginPugify } from '../../utils/prettier'; import { VLSFormatConfig } from '../../config'; import { getFileFsPath } from '../../utils/paths'; import { DependencyService } from '../../services/dependencyService'; +import { EnvironmentService } from '../../services/EnvironmentService'; -export function getPugMode(dependencyService: DependencyService): LanguageMode { - let config: any = {}; - +export function getPugMode(env: EnvironmentService, dependencyService: DependencyService): LanguageMode { return { getId() { return 'pug'; }, - configure(c) { - config = c; - }, format(document, currRange, formattingOptions) { - if (config.vetur.format.defaultFormatter['pug'] === 'none') { + if (env.getConfig().vetur.format.defaultFormatter['pug'] === 'none') { return []; } @@ -28,7 +24,7 @@ export function getPugMode(dependencyService: DependencyService): LanguageMode { value, getFileFsPath(document.uri), range, - config.vetur.format as VLSFormatConfig, + env.getConfig().vetur.format as VLSFormatConfig, // @ts-expect-error 'pug', false diff --git a/server/src/modes/script/javascript.ts b/server/src/modes/script/javascript.ts index d207017d43..0db673d275 100644 --- a/server/src/modes/script/javascript.ts +++ b/server/src/modes/script/javascript.ts @@ -47,6 +47,7 @@ import { IServiceHost } from '../../services/typescriptService/serviceHost'; import { toCompletionItemKind, toSymbolKind } from '../../services/typescriptService/util'; import * as Previewer from './previewer'; import { isVCancellationRequested, VCancellationToken } from '../../utils/cancellationToken'; +import { EnvironmentService } from '../../services/EnvironmentService'; // Todo: After upgrading to LS server 4.0, use CompletionContext for filtering trigger chars // https://microsoft.github.io/language-server-protocol/specification#completion-request-leftwards_arrow_with_hook @@ -56,6 +57,7 @@ export const APPLY_REFACTOR_COMMAND = 'vetur.applyRefactorCommand'; export async function getJavascriptMode( serviceHost: IServiceHost, + env: EnvironmentService, documentRegions: LanguageModelCache, dependencyService: DependencyService, globalComponentInfos: BasicComponentInfo[], @@ -75,11 +77,10 @@ export async function getJavascriptMode( const tsModule: RuntimeLibrary['typescript'] = dependencyService.get('typescript').module; const { updateCurrentVueTextDocument } = serviceHost; - let config: any = {}; let supportedCodeFixCodes: Set; function getUserPreferences(scriptDoc: TextDocument): ts.UserPreferences { - const baseConfig = config[scriptDoc.languageId === 'javascript' ? 'javascript' : 'typescript']; + const baseConfig = env.getConfig()[scriptDoc.languageId === 'javascript' ? 'javascript' : 'typescript']; const preferencesConfig = baseConfig?.preferences; if (!baseConfig || !preferencesConfig) { @@ -119,9 +120,6 @@ export async function getJavascriptMode( getId() { return 'javascript'; }, - configure(c) { - config = c; - }, updateFileInfo(doc: TextDocument): void { if (!vueInfoService) { return; @@ -129,7 +127,7 @@ export async function getJavascriptMode( const { service } = updateCurrentVueTextDocument(doc); const fileFsPath = getFileFsPath(doc.uri); - const info = getComponentInfo(tsModule, service, fileFsPath, globalComponentInfos, config); + const info = getComponentInfo(tsModule, service, fileFsPath, globalComponentInfos, env.getConfig()); if (info) { vueInfoService.updateInfo(doc, info); } @@ -202,7 +200,7 @@ export async function getJavascriptMode( ...getUserPreferences(scriptDoc), triggerCharacter: getTsTriggerCharacter(triggerChar), includeCompletionsWithInsertText: true, - includeCompletionsForModuleExports: config.vetur.completion.autoImport + includeCompletionsForModuleExports: env.getConfig().vetur.completion.autoImport }); if (!completions) { return { isIncomplete: false, items: [] }; @@ -293,7 +291,7 @@ export async function getJavascriptMode( fileFsPath, item.data.offset, item.label, - getFormatCodeSettings(config), + getFormatCodeSettings(env.getConfig()), item.data.source, getUserPreferences(scriptDoc) ); @@ -316,7 +314,7 @@ export async function getJavascriptMode( } } - if (details.codeActions && config.vetur.completion.autoImport) { + if (details.codeActions && env.getConfig().vetur.completion.autoImport) { const textEdits = convertCodeAction(doc, details.codeActions, firstScriptRegion); item.additionalTextEdits = textEdits; @@ -588,7 +586,7 @@ export async function getJavascriptMode( return []; } - const formatSettings: ts.FormatCodeSettings = getFormatCodeSettings(config); + const formatSettings: ts.FormatCodeSettings = getFormatCodeSettings(env.getConfig()); const result: CodeAction[] = []; const fixes = service.getCodeFixesAtPosition( @@ -628,16 +626,16 @@ export async function getJavascriptMode( const defaultFormatter = scriptDoc.languageId === 'javascript' - ? config.vetur.format.defaultFormatter.js - : config.vetur.format.defaultFormatter.ts; + ? env.getConfig().vetur.format.defaultFormatter.js + : env.getConfig().vetur.format.defaultFormatter.ts; if (defaultFormatter === 'none') { return []; } const parser = scriptDoc.languageId === 'javascript' ? 'babel' : 'typescript'; - const needInitialIndent = config.vetur.format.scriptInitialIndent; - const vlsFormatConfig: VLSFormatConfig = config.vetur.format; + const needInitialIndent = env.getConfig().vetur.format.scriptInitialIndent; + const vlsFormatConfig: VLSFormatConfig = env.getConfig().vetur.format; if ( defaultFormatter === 'prettier' || @@ -658,7 +656,7 @@ export async function getJavascriptMode( } else { const initialIndentLevel = needInitialIndent ? 1 : 0; const formatSettings: ts.FormatCodeSettings = - scriptDoc.languageId === 'javascript' ? config.javascript.format : config.typescript.format; + scriptDoc.languageId === 'javascript' ? env.getConfig().javascript.format : env.getConfig().typescript.format; const convertedFormatSettings = convertOptions( formatSettings, { diff --git a/server/src/modes/style/index.ts b/server/src/modes/style/index.ts index 1eaa0a90e8..e288710279 100644 --- a/server/src/modes/style/index.ts +++ b/server/src/modes/style/index.ts @@ -19,39 +19,45 @@ import { NULL_HOVER } from '../nullMode'; import { VLSFormatConfig } from '../../config'; import { DependencyService } from '../../services/dependencyService'; import { BuiltInParserName } from 'prettier'; +import { EnvironmentService } from '../../services/EnvironmentService'; export function getCSSMode( + env: EnvironmentService, documentRegions: LanguageModelCache, dependencyService: DependencyService ): LanguageMode { const languageService = getCSSLanguageService(); - return getStyleMode('css', languageService, documentRegions, dependencyService); + return getStyleMode(env, 'css', languageService, documentRegions, dependencyService); } export function getPostCSSMode( + env: EnvironmentService, documentRegions: LanguageModelCache, dependencyService: DependencyService ): LanguageMode { const languageService = getCSSLanguageService(); - return getStyleMode('postcss', languageService, documentRegions, dependencyService); + return getStyleMode(env, 'postcss', languageService, documentRegions, dependencyService); } export function getSCSSMode( + env: EnvironmentService, documentRegions: LanguageModelCache, dependencyService: DependencyService ): LanguageMode { const languageService = getSCSSLanguageService(); - return getStyleMode('scss', languageService, documentRegions, dependencyService); + return getStyleMode(env, 'scss', languageService, documentRegions, dependencyService); } export function getLESSMode( + env: EnvironmentService, documentRegions: LanguageModelCache, dependencyService: DependencyService ): LanguageMode { const languageService = getLESSLanguageService(); - return getStyleMode('less', languageService, documentRegions, dependencyService); + return getStyleMode(env, 'less', languageService, documentRegions, dependencyService); } function getStyleMode( + env: EnvironmentService, languageId: LanguageId, languageService: LanguageService, documentRegions: LanguageModelCache, @@ -61,17 +67,22 @@ function getStyleMode( documentRegions.refreshAndGet(document).getSingleLanguageDocument(languageId) ); const stylesheets = getLanguageModelCache(10, 60, document => languageService.parseStylesheet(document)); - let config: any = {}; + + let latestConfig = env.getConfig().css; + function syncConfig() { + if (_.isEqual(latestConfig, env.getConfig().css)) { + return; + } + latestConfig = env.getConfig().css; + languageService.configure(env.getConfig().css); + } return { getId() { return languageId; }, - configure(c) { - languageService.configure(c && c.css); - config = c; - }, async doValidation(document) { + syncConfig(); if (languageId === 'postcss') { return []; } else { @@ -80,6 +91,7 @@ function getStyleMode( } }, doComplete(document, position) { + syncConfig(); const embedded = embeddedDocuments.refreshAndGet(document); const emmetSyntax = languageId === 'postcss' ? 'css' : languageId; const lsCompletions = languageService.doComplete(embedded, position, stylesheets.refreshAndGet(embedded)); @@ -92,7 +104,7 @@ function getStyleMode( }) : []; - const emmetCompletions = emmet.doComplete(document, position, emmetSyntax, config.emmet); + const emmetCompletions = emmet.doComplete(document, position, emmetSyntax, env.getConfig().emmet); if (!emmetCompletions) { return { isIncomplete: false, items: lsItems }; } else { @@ -109,18 +121,22 @@ function getStyleMode( } }, doHover(document, position) { + syncConfig(); const embedded = embeddedDocuments.refreshAndGet(document); return languageService.doHover(embedded, position, stylesheets.refreshAndGet(embedded)) || NULL_HOVER; }, findDocumentHighlight(document, position) { + syncConfig(); const embedded = embeddedDocuments.refreshAndGet(document); return languageService.findDocumentHighlights(embedded, position, stylesheets.refreshAndGet(embedded)); }, findDocumentSymbols(document) { + syncConfig(); const embedded = embeddedDocuments.refreshAndGet(document); return languageService.findDocumentSymbols(embedded, stylesheets.refreshAndGet(embedded)); }, findDefinition(document, position) { + syncConfig(); const embedded = embeddedDocuments.refreshAndGet(document); const definition = languageService.findDefinition(embedded, position, stylesheets.refreshAndGet(embedded)); if (!definition) { @@ -129,28 +145,33 @@ function getStyleMode( return definition; }, findReferences(document, position) { + syncConfig(); const embedded = embeddedDocuments.refreshAndGet(document); return languageService.findReferences(embedded, position, stylesheets.refreshAndGet(embedded)); }, findDocumentColors(document) { + syncConfig(); const embedded = embeddedDocuments.refreshAndGet(document); return languageService.findDocumentColors(embedded, stylesheets.refreshAndGet(embedded)); }, getFoldingRanges(document) { + syncConfig(); const embedded = embeddedDocuments.refreshAndGet(document); return languageService.getFoldingRanges(embedded); }, getColorPresentations(document, color, range) { + syncConfig(); const embedded = embeddedDocuments.refreshAndGet(document); return languageService.getColorPresentations(embedded, stylesheets.refreshAndGet(embedded), color, range); }, format(document, currRange, formattingOptions) { - if (config.vetur.format.defaultFormatter[languageId] === 'none') { + if (env.getConfig().vetur.format.defaultFormatter[languageId] === 'none') { return []; } + syncConfig(); const { value, range } = getValueAndRange(document, currRange); - const needIndent = config.vetur.format.styleInitialIndent; + const needIndent = env.getConfig().vetur.format.styleInitialIndent; const parserMap: { [k: string]: BuiltInParserName } = { css: 'css', postcss: 'css', @@ -162,7 +183,7 @@ function getStyleMode( value, getFileFsPath(document.uri), range, - config.vetur.format as VLSFormatConfig, + env.getConfig().vetur.format as VLSFormatConfig, parserMap[languageId], needIndent ); diff --git a/server/src/modes/style/sass/sassLanguageMode.ts b/server/src/modes/style/sass/sassLanguageMode.ts index a2280a6afc..e3572fe451 100644 --- a/server/src/modes/style/sass/sassLanguageMode.ts +++ b/server/src/modes/style/sass/sassLanguageMode.ts @@ -9,22 +9,17 @@ import { SassFormatter, SassFormatterConfig } from 'sass-formatter'; import * as emmet from 'vscode-emmet-helper'; import { Priority } from '../emmet'; +import { EnvironmentService } from '../../../services/EnvironmentService'; export class SassLanguageMode implements LanguageMode { - private config: any = {}; - - constructor() {} + constructor(private env: EnvironmentService) {} getId() { return 'sass'; } - configure(c: any) { - this.config = c; - } - doComplete(document: TextDocument, position: Position): CompletionList { - const emmetCompletions = emmet.doComplete(document, position, 'sass', this.config.emmet); + const emmetCompletions = emmet.doComplete(document, position, 'sass', this.env.getConfig().emmet); if (!emmetCompletions) { return { isIncomplete: false, items: [] }; } else { @@ -42,11 +37,11 @@ export class SassLanguageMode implements LanguageMode { } format(document: TextDocument, range: Range, formattingOptions: FormattingOptions) { - if (this.config.vetur.format.defaultFormatter.sass === 'sass-formatter') { + if (this.env.getConfig().vetur.format.defaultFormatter.sass === 'sass-formatter') { return [ TextEdit.replace( range, - SassFormatter.Format(document.getText(range), { ...formattingOptions, ...this.config.sass.format }) + SassFormatter.Format(document.getText(range), { ...formattingOptions, ...this.env.getConfig().sass.format }) ) ]; } diff --git a/server/src/modes/style/stylus/index.ts b/server/src/modes/style/stylus/index.ts index af10bd11f7..c6d0a741f2 100644 --- a/server/src/modes/style/stylus/index.ts +++ b/server/src/modes/style/stylus/index.ts @@ -14,22 +14,20 @@ import { stylusHover } from './stylus-hover'; import { getFileFsPath } from '../../../utils/paths'; import { VLSFormatConfig } from '../../../config'; import { DependencyService } from '../../../services/dependencyService'; +import { EnvironmentService } from '../../../services/EnvironmentService'; +import { sync } from 'glob'; export function getStylusMode( + env: EnvironmentService, documentRegions: LanguageModelCache, dependencyService: DependencyService ): LanguageMode { const embeddedDocuments = getLanguageModelCache(10, 60, document => documentRegions.refreshAndGet(document).getSingleLanguageDocument('stylus') ); - let baseIndentShifted = false; - let config: any = {}; + return { getId: () => 'stylus', - configure(c) { - baseIndentShifted = _.get(c, 'vetur.format.styleInitialIndent', false); - config = c; - }, onDocumentRemoved() {}, dispose() {}, doComplete(document, position) { @@ -43,7 +41,7 @@ export function getStylusMode( }; }); - const emmetCompletions: CompletionList = emmet.doComplete(document, position, 'stylus', config.emmet); + const emmetCompletions: CompletionList = emmet.doComplete(document, position, 'stylus', env.getConfig().emmet); if (!emmetCompletions) { return { isIncomplete: false, items: lsItems }; } else { @@ -68,7 +66,7 @@ export function getStylusMode( return stylusHover(embedded, position); }, format(document, range, formatParams) { - if (config.vetur.format.defaultFormatter.stylus === 'none') { + if (env.getConfig().vetur.format.defaultFormatter.stylus === 'none') { return []; } @@ -77,7 +75,7 @@ export function getStylusMode( const inputText = document.getText(range); - const vlsFormatConfig = config.vetur.format as VLSFormatConfig; + const vlsFormatConfig = env.getConfig().vetur.format as VLSFormatConfig; const tabStopChar = vlsFormatConfig.options.useTabs ? '\t' : ' '.repeat(vlsFormatConfig.options.tabSize); // Note that this would have been `document.eol` ideally @@ -93,13 +91,15 @@ export function getStylusMode( } // Add one more indentation when `vetur.format.styleInitialIndent` is set to `true` - if (baseIndentShifted) { + if (env.getConfig().vetur.format.scriptInitialIndent) { baseIndent += tabStopChar; } // Build the formatting options for Stylus Supremacy // See https://thisismanta.github.io/stylus-supremacy/#options - const stylusSupremacyFormattingOptions = stylusSupremacy.createFormattingOptions(config.stylusSupremacy || {}); + const stylusSupremacyFormattingOptions = stylusSupremacy.createFormattingOptions( + env.getConfig().stylusSupremacy || {} + ); const formattingOptions = { ...stylusSupremacyFormattingOptions, tabStopChar, diff --git a/server/src/modes/template/htmlMode.ts b/server/src/modes/template/htmlMode.ts index 9d3730a64f..cc8711b457 100644 --- a/server/src/modes/template/htmlMode.ts +++ b/server/src/modes/template/htmlMode.ts @@ -24,57 +24,46 @@ import { DocumentContext } from '../../types'; import { VLSFormatConfig, VLSFullConfig } from '../../config'; import { VueInfoService } from '../../services/vueInfoService'; import { getComponentInfoTagProvider } from './tagProviders/componentInfoTagProvider'; -import { VueVersion } from '../../services/typescriptService/vueVersion'; import { doPropValidation } from './services/vuePropValidation'; import { getFoldingRanges } from './services/htmlFolding'; import { DependencyService } from '../../services/dependencyService'; import { isVCancellationRequested, VCancellationToken } from '../../utils/cancellationToken'; import { AutoImportSfcPlugin } from '../plugins/autoImportSfcPlugin'; +import { EnvironmentService } from '../../services/EnvironmentService'; export class HTMLMode implements LanguageMode { private tagProviderSettings: CompletionConfiguration; private enabledTagProviders: IHTMLTagProvider[]; private embeddedDocuments: LanguageModelCache; - - private config: VLSFullConfig; - private lintEngine: any; constructor( documentRegions: LanguageModelCache, - projectPath: string | undefined, - packagePath: string | undefined, - vueVersion: VueVersion, + private env: EnvironmentService, private dependencyService: DependencyService, private vueDocuments: LanguageModelCache, private autoImportSfcPlugin: AutoImportSfcPlugin, private vueInfoService?: VueInfoService ) { - this.tagProviderSettings = getTagProviderSettings(projectPath, packagePath); + this.tagProviderSettings = getTagProviderSettings(env.getPackagePath()); this.enabledTagProviders = getEnabledTagProviders(this.tagProviderSettings); this.embeddedDocuments = getLanguageModelCache(10, 60, document => documentRegions.refreshAndGet(document).getSingleLanguageDocument('vue-html') ); - this.lintEngine = createLintEngine(vueVersion); + this.lintEngine = createLintEngine(env.getVueVersion()); } getId() { return 'html'; } - configure(c: VLSFullConfig) { - this.enabledTagProviders = getEnabledTagProviders(this.tagProviderSettings); - this.config = c; - this.autoImportSfcPlugin.setGetConfigure(() => c); - } - async doValidation(document: TextDocument, cancellationToken?: VCancellationToken) { const diagnostics = []; if (await isVCancellationRequested(cancellationToken)) { return []; } - if (this.config.vetur.validation.templateProps) { + if (this.env.getConfig().vetur.validation.templateProps) { const info = this.vueInfoService ? this.vueInfoService.getInfo(document) : undefined; if (info && info.componentInfo.childComponents) { diagnostics.push(...doPropValidation(document, this.vueDocuments.refreshAndGet(document), info)); @@ -84,7 +73,7 @@ export class HTMLMode implements LanguageMode { if (await isVCancellationRequested(cancellationToken)) { return diagnostics; } - if (this.config.vetur.validation.template) { + if (this.env.getConfig().vetur.validation.template) { const embedded = this.embeddedDocuments.refreshAndGet(document); diagnostics.push(...(await doESLintValidation(embedded, this.lintEngine))); } @@ -105,7 +94,7 @@ export class HTMLMode implements LanguageMode { position, this.vueDocuments.refreshAndGet(embedded), tagProviders, - this.config.emmet, + this.env.getConfig().emmet, this.autoImportSfcPlugin.doComplete(document) ); } @@ -125,7 +114,7 @@ export class HTMLMode implements LanguageMode { return findDocumentSymbols(document, this.vueDocuments.refreshAndGet(document)); } format(document: TextDocument, range: Range, formattingOptions: FormattingOptions) { - return htmlFormat(this.dependencyService, document, range, this.config.vetur.format as VLSFormatConfig); + return htmlFormat(this.dependencyService, document, range, this.env.getConfig().vetur.format as VLSFormatConfig); } findDefinition(document: TextDocument, position: Position) { const embedded = this.embeddedDocuments.refreshAndGet(document); @@ -136,6 +125,16 @@ export class HTMLMode implements LanguageMode { const embedded = this.embeddedDocuments.refreshAndGet(document); return getFoldingRanges(embedded); } + onDocumentChanged(filePath: string) { + if (filePath !== this.env.getPackagePath()) { + return; + } + + // reload package + this.tagProviderSettings = getTagProviderSettings(this.env.getPackagePath()); + this.enabledTagProviders = getEnabledTagProviders(this.tagProviderSettings); + this.lintEngine = createLintEngine(this.env.getVueVersion()); + } onDocumentRemoved(document: TextDocument) { this.vueDocuments.onDocumentRemoved(document); } diff --git a/server/src/modes/template/index.ts b/server/src/modes/template/index.ts index 60cf690856..999aaeb766 100644 --- a/server/src/modes/template/index.ts +++ b/server/src/modes/template/index.ts @@ -9,10 +9,11 @@ import { HTMLMode } from './htmlMode'; import { VueInterpolationMode } from './interpolationMode'; import { IServiceHost } from '../../services/typescriptService/serviceHost'; import { HTMLDocument, parseHTMLDocument } from './parser/htmlParser'; -import { inferVueVersion } from '../../services/typescriptService/vueVersion'; +import { inferVueVersion } from '../../utils/vueVersion'; import { DependencyService, RuntimeLibrary } from '../../services/dependencyService'; import { VCancellationToken } from '../../utils/cancellationToken'; import { AutoImportSfcPlugin } from '../plugins/autoImportSfcPlugin'; +import { EnvironmentService } from '../../services/EnvironmentService'; type DocumentRegionCache = LanguageModelCache; @@ -24,35 +25,27 @@ export class VueHTMLMode implements LanguageMode { constructor( tsModule: RuntimeLibrary['typescript'], serviceHost: IServiceHost, + env: EnvironmentService, documentRegions: DocumentRegionCache, - projectPath: string, - packagePath: string | undefined, autoImportSfcPlugin: AutoImportSfcPlugin, dependencyService: DependencyService, vueInfoService?: VueInfoService ) { const vueDocuments = getLanguageModelCache(10, 60, document => parseHTMLDocument(document)); - const vueVersion = inferVueVersion(packagePath); this.htmlMode = new HTMLMode( documentRegions, - projectPath, - packagePath, - vueVersion, + env, dependencyService, vueDocuments, autoImportSfcPlugin, vueInfoService ); - this.vueInterpolationMode = new VueInterpolationMode(tsModule, serviceHost, vueDocuments, vueInfoService); + this.vueInterpolationMode = new VueInterpolationMode(tsModule, serviceHost, env, vueDocuments, vueInfoService); this.autoImportSfcPlugin = autoImportSfcPlugin; } getId() { return 'vue-html'; } - configure(c: any) { - this.htmlMode.configure(c); - this.vueInterpolationMode.configure(c); - } queryVirtualFileInfo(fileName: string, currFileText: string) { return this.vueInterpolationMode.queryVirtualFileInfo(fileName, currFileText); } diff --git a/server/src/modes/template/interpolationMode.ts b/server/src/modes/template/interpolationMode.ts index 4567f008ed..b16af8fa04 100644 --- a/server/src/modes/template/interpolationMode.ts +++ b/server/src/modes/template/interpolationMode.ts @@ -31,13 +31,13 @@ import * as Previewer from '../script/previewer'; import { HTMLDocument } from './parser/htmlParser'; import { isInsideInterpolation } from './services/isInsideInterpolation'; import { RuntimeLibrary } from '../../services/dependencyService'; +import { EnvironmentService } from '../../services/EnvironmentService'; export class VueInterpolationMode implements LanguageMode { - private config: VLSFullConfig; - constructor( private tsModule: RuntimeLibrary['typescript'], private serviceHost: IServiceHost, + private env: EnvironmentService, private vueDocuments: LanguageModelCache, private vueInfoService?: VueInfoService ) {} @@ -46,24 +46,20 @@ export class VueInterpolationMode implements LanguageMode { return 'vue-html-interpolation'; } - configure(c: any) { - this.config = c; - } - queryVirtualFileInfo(fileName: string, currFileText: string) { return this.serviceHost.queryVirtualFileInfo(fileName, currFileText); } private getChildComponents(document: TextDocument) { - return this.config.vetur.validation.templateProps + return this.env.getConfig().vetur.validation.templateProps ? this.vueInfoService && this.vueInfoService.getInfo(document)?.componentInfo.childComponents : []; } async doValidation(document: TextDocument, cancellationToken?: VCancellationToken): Promise { if ( - !_.get(this.config, ['vetur', 'experimental', 'templateInterpolationService'], true) || - !this.config.vetur.validation.interpolation + !this.env.getConfig().vetur.experimental.templateInterpolationService || + !this.env.getConfig().vetur.validation.interpolation ) { return []; } @@ -113,7 +109,7 @@ export class VueInterpolationMode implements LanguageMode { } doComplete(document: TextDocument, position: Position): CompletionList { - if (!_.get(this.config, ['vetur', 'experimental', 'templateInterpolationService'], true)) { + if (!this.env.getConfig().vetur.experimental.templateInterpolationService) { return NULL_COMPLETION; } @@ -206,7 +202,7 @@ export class VueInterpolationMode implements LanguageMode { } doResolve(document: TextDocument, item: CompletionItem): CompletionItem { - if (!_.get(this.config, ['vetur', 'experimental', 'templateInterpolationService'], true)) { + if (!this.env.getConfig().vetur.experimental.templateInterpolationService) { return item; } @@ -278,7 +274,7 @@ export class VueInterpolationMode implements LanguageMode { contents: MarkedString[]; range?: Range; } { - if (!_.get(this.config, ['vetur', 'experimental', 'templateInterpolationService'], true)) { + if (!this.env.getConfig().vetur.experimental.templateInterpolationService) { return { contents: [] }; } @@ -336,7 +332,7 @@ export class VueInterpolationMode implements LanguageMode { } findDefinition(document: TextDocument, position: Position): Location[] { - if (!_.get(this.config, ['vetur', 'experimental', 'templateInterpolationService'], true)) { + if (!this.env.getConfig().vetur.experimental.templateInterpolationService) { return []; } @@ -387,7 +383,7 @@ export class VueInterpolationMode implements LanguageMode { } findReferences(document: TextDocument, position: Position): Location[] { - if (!_.get(this.config, ['vetur', 'experimental', 'templateInterpolationService'], true)) { + if (!this.env.getConfig().vetur.experimental.templateInterpolationService) { return []; } diff --git a/server/src/modes/template/services/htmlEslintValidation.ts b/server/src/modes/template/services/htmlEslintValidation.ts index 474cabca5b..b68ef570db 100644 --- a/server/src/modes/template/services/htmlEslintValidation.ts +++ b/server/src/modes/template/services/htmlEslintValidation.ts @@ -3,7 +3,7 @@ import { configs } from 'eslint-plugin-vue'; import { Diagnostic, Range, DiagnosticSeverity } from 'vscode-languageserver-types'; import type { TextDocument } from 'vscode-languageserver-textdocument'; import { resolve } from 'path'; -import { VueVersion } from '../../../services/typescriptService/vueVersion'; +import { VueVersion } from '../../../utils/vueVersion'; function toDiagnostic(error: Linter.LintMessage): Diagnostic { const line = error.line - 1; diff --git a/server/src/modes/template/tagProviders/index.ts b/server/src/modes/template/tagProviders/index.ts index 1dcf1d8a14..209625732d 100644 --- a/server/src/modes/template/tagProviders/index.ts +++ b/server/src/modes/template/tagProviders/index.ts @@ -32,7 +32,7 @@ export interface CompletionConfiguration { [provider: string]: boolean; } -export function getTagProviderSettings(projectPath: string | null | undefined, packagePath: string | undefined) { +export function getTagProviderSettings(packagePath: string | undefined) { const settings: CompletionConfiguration = { '__vetur-workspace': true, html5: true, diff --git a/server/src/modes/vue/index.ts b/server/src/modes/vue/index.ts index db26640030..2c8e5f7e1a 100644 --- a/server/src/modes/vue/index.ts +++ b/server/src/modes/vue/index.ts @@ -1,12 +1,11 @@ import { LanguageMode } from '../../embeddedSupport/languageModes'; import { SnippetManager, ScaffoldSnippetSources } from './snippets'; import { Range } from 'vscode-css-languageservice'; +import { EnvironmentService } from '../../services/EnvironmentService'; -export function getVueMode(snippetFolder: string, globalSnippetDir?: string): LanguageMode { - let config: any = {}; - - const snippetManager = new SnippetManager(snippetFolder, globalSnippetDir); - let scaffoldSnippetSources: ScaffoldSnippetSources = { +export function getVueMode(env: EnvironmentService, globalSnippetDir?: string): LanguageMode { + const snippetManager = new SnippetManager(env.getSnippetFolder(), globalSnippetDir); + const scaffoldSnippetSources: ScaffoldSnippetSources = { workspace: '💼', user: '🗒️', vetur: '✌' @@ -16,13 +15,9 @@ export function getVueMode(snippetFolder: string, globalSnippetDir?: string): La getId() { return 'vue'; }, - configure(c) { - config = c; - if (c.vetur.completion['scaffoldSnippetSources']) { - scaffoldSnippetSources = c.vetur.completion['scaffoldSnippetSources']; - } - }, doComplete(document, position) { + const scaffoldSnippetSources: ScaffoldSnippetSources = env.getConfig().vetur.completion.scaffoldSnippetSources; + if ( scaffoldSnippetSources['workspace'] === '' && scaffoldSnippetSources['user'] === '' && diff --git a/server/src/services/EnvironmentService.ts b/server/src/services/EnvironmentService.ts new file mode 100644 index 0000000000..29a89a0352 --- /dev/null +++ b/server/src/services/EnvironmentService.ts @@ -0,0 +1,40 @@ +import { BasicComponentInfo, VLSConfig, VLSFullConfig } from '../config'; +import { inferVueVersion, VueVersion } from '../utils/vueVersion'; + +export interface EnvironmentService { + configure(config: VLSFullConfig): void; + getConfig(): VLSFullConfig; + getRootPathForConfig(): string; + getProjectRoot(): string; + getTsConfigPath(): string | undefined; + getPackagePath(): string | undefined; + getVueVersion(): VueVersion; + getSnippetFolder(): string; + getGlobalComponentInfos(): BasicComponentInfo[]; +} + +export function createEnvironmentService( + rootPathForConfig: string, + projectPath: string, + tsconfigPath: string | undefined, + packagePath: string | undefined, + snippetFolder: string, + globalComponentInfos: BasicComponentInfo[], + initialConfig: VLSConfig +): EnvironmentService { + let $config = initialConfig; + + return { + configure(config: VLSFullConfig) { + $config = config; + }, + getConfig: () => $config, + getRootPathForConfig: () => rootPathForConfig, + getProjectRoot: () => projectPath, + getTsConfigPath: () => tsconfigPath, + getPackagePath: () => packagePath, + getVueVersion: () => inferVueVersion(packagePath), + getSnippetFolder: () => snippetFolder, + getGlobalComponentInfos: () => globalComponentInfos + }; +} diff --git a/server/src/services/projectService.ts b/server/src/services/projectService.ts index 1a8879fdca..3cacf3ff5c 100644 --- a/server/src/services/projectService.ts +++ b/server/src/services/projectService.ts @@ -29,7 +29,6 @@ import { } from 'vscode-languageserver'; import { TextDocument } from 'vscode-languageserver-textdocument'; import { URI } from 'vscode-uri'; -import { BasicComponentInfo, VLSConfig, VLSFullConfig } from '../config'; import { LanguageId } from '../embeddedSupport/embeddedSupport'; import { LanguageMode, LanguageModes } from '../embeddedSupport/languageModes'; import { NULL_COMPLETION, NULL_HOVER, NULL_SIGNATURE } from '../modes/nullMode'; @@ -38,13 +37,12 @@ import { VCancellationToken } from '../utils/cancellationToken'; import { getFileFsPath } from '../utils/paths'; import { DependencyService } from './dependencyService'; import { DocumentService } from './documentService'; +import { EnvironmentService } from './EnvironmentService'; import { VueInfoService } from './vueInfoService'; export interface ProjectService { - readonly rootPathForConfig: string; - readonly projectPath: string | undefined; + env: EnvironmentService; languageModes: LanguageModes; - configure(config: VLSFullConfig): void; onDocumentFormatting(params: DocumentFormattingParams): Promise; onCompletion(params: CompletionParams): Promise; onCompletionResolve(item: CompletionItem): Promise; @@ -65,42 +63,29 @@ export interface ProjectService { } export async function createProjectService( - rootPathForConfig: string, - projectPath: string, - tsconfigPath: string | undefined, - packagePath: string | undefined, - snippetFolder: string, - globalComponentInfos: BasicComponentInfo[], + env: EnvironmentService, documentService: DocumentService, - initialConfig: VLSConfig, globalSnippetDir: string | undefined, dependencyService: DependencyService ): Promise { - let $config = initialConfig; - const vueInfoService = new VueInfoService(); const languageModes = new LanguageModes(); function getValidationFlags(): Record { + const config = env.getConfig(); return { - 'vue-html': $config.vetur.validation.template, - css: $config.vetur.validation.style, - postcss: $config.vetur.validation.style, - scss: $config.vetur.validation.style, - less: $config.vetur.validation.style, - javascript: $config.vetur.validation.script + 'vue-html': config.vetur.validation.template, + css: config.vetur.validation.style, + postcss: config.vetur.validation.style, + scss: config.vetur.validation.style, + less: config.vetur.validation.style, + javascript: config.vetur.validation.script }; } - const validationFlags = getValidationFlags(); - vueInfoService.init(languageModes); await languageModes.init( - projectPath, - tsconfigPath, - packagePath, - snippetFolder, - globalComponentInfos, + env, { infoService: vueInfoService, dependencyService @@ -108,23 +93,11 @@ export async function createProjectService( globalSnippetDir ); - function configure(config: VLSFullConfig) { - $config = config; - languageModes.getAllModes().forEach(m => { - if (m.configure) { - m.configure(config); - } - }); - } - configure(initialConfig); - return { - rootPathForConfig, - projectPath, - configure, + env, languageModes, async onDocumentFormatting({ textDocument, options }) { - if (!$config.vetur.format.enable) { + if (!env.getConfig().vetur.format.enable) { return []; } @@ -239,8 +212,8 @@ export async function createProjectService( const doc = documentService.getDocument(textDocument.uri)!; const documentContext: DocumentContext = { resolveReference: ref => { - if (projectPath && ref[0] === '/') { - return URI.file(path.resolve(projectPath, ref)).toString(); + if (ref[0] === '/') { + return URI.file(path.resolve(env.getProjectRoot(), ref)).toString(); } const fsPath = getFileFsPath(doc.uri); return URI.file(path.resolve(fsPath, '..', ref)).toString(); @@ -321,7 +294,7 @@ export async function createProjectService( return result; }, async onCodeAction({ textDocument, range, context }: CodeActionParams) { - if (!$config.vetur.languageFeatures.codeActions) { + if (!env.getConfig().vetur.languageFeatures.codeActions) { return []; } @@ -338,13 +311,17 @@ export async function createProjectService( async doValidate(doc: TextDocument, cancellationToken?: VCancellationToken) { const diagnostics: Diagnostic[] = []; if (doc.languageId === 'vue') { + const validationFlags = getValidationFlags(); for (const lmr of languageModes.getAllLanguageModeRangesInDocument(doc)) { if (lmr.mode.doValidation) { if (validationFlags[lmr.mode.getId()]) { diagnostics.push.apply(diagnostics, await lmr.mode.doValidation(doc, cancellationToken)); } // Special case for template type checking - else if (lmr.mode.getId() === 'vue-html' && $config.vetur.experimental.templateInterpolationService) { + else if ( + lmr.mode.getId() === 'vue-html' && + env.getConfig().vetur.experimental.templateInterpolationService + ) { diagnostics.push.apply(diagnostics, await lmr.mode.doValidation(doc, cancellationToken)); } } diff --git a/server/src/services/typescriptService/serviceHost.ts b/server/src/services/typescriptService/serviceHost.ts index bbbe81a032..00e892245c 100644 --- a/server/src/services/typescriptService/serviceHost.ts +++ b/server/src/services/typescriptService/serviceHost.ts @@ -14,9 +14,10 @@ import { isVirtualVueTemplateFile, isVueFile } from './util'; import { logger } from '../../log'; import { ModuleResolutionCache } from './moduleResolutionCache'; import { globalScope } from './transformTemplate'; -import { inferVueVersion, VueVersion } from './vueVersion'; import { ChildComponent } from '../vueInfoService'; import { RuntimeLibrary } from '../dependencyService'; +import { EnvironmentService } from '../EnvironmentService'; +import { VueVersion } from '../../utils/vueVersion'; const NEWLINE = process.platform === 'win32' ? '\r\n' : '\n'; @@ -85,18 +86,15 @@ export interface IServiceHost { */ export function getServiceHost( tsModule: RuntimeLibrary['typescript'], - projectPath: string, - tsconfigPath: string | undefined, - packagePath: string | undefined, + env: EnvironmentService, updatedScriptRegionDocuments: LanguageModelCache ): IServiceHost { patchTS(tsModule); let currentScriptDoc: TextDocument; - const vueVersion = inferVueVersion(packagePath); - // host variable + let vueVersion = env.getVueVersion(); let projectVersion = 1; let versions = new Map(); let localScriptRegionDocuments = new Map(); @@ -118,7 +116,7 @@ export function getServiceHost( let templateLanguageService: ts.LanguageService; init(); - function getCompilerOptions () { + function getCompilerOptions() { const compilerOptions = { ...getDefaultCompilerOptions(tsModule), ...parsedConfig.options @@ -127,7 +125,8 @@ export function getServiceHost( return compilerOptions; } - function init () { + function init() { + vueVersion = env.getVueVersion(); projectVersion = 1; versions = new Map(); localScriptRegionDocuments = new Map(); @@ -135,7 +134,7 @@ export function getServiceHost( projectFileSnapshots = new Map(); moduleResolutionCache = new ModuleResolutionCache(); - parsedConfig = getParsedConfig(tsModule, projectPath, tsconfigPath); + parsedConfig = getParsedConfig(tsModule, env.getProjectRoot(), env.getTsConfigPath()); const initialProjectFiles = parsedConfig.fileNames; logger.logDebug( `Initializing ServiceHost with ${initialProjectFiles.length} files: ${JSON.stringify(initialProjectFiles)}` @@ -239,7 +238,8 @@ export function getServiceHost( // External Documents: JS/TS, non Vue documents function updateExternalDocument(fileFsPath: string) { - if (fileFsPath === tsconfigPath) { + // reload `tsconfig.json` or vue version changed + if (fileFsPath === env.getTsConfigPath() || vueVersion !== env.getVueVersion()) { logger.logInfo(`refresh ts language service when ${fileFsPath} changed.`); init(); return; @@ -252,7 +252,7 @@ export function getServiceHost( if ( isExcludedFile && configFileSpecs && - isExcludedFile(fileFsPath, configFileSpecs, projectPath, true, projectPath) + isExcludedFile(fileFsPath, configFileSpecs, env.getProjectRoot(), true, env.getProjectRoot()) ) { return; } @@ -475,7 +475,7 @@ export function getServiceHost( getChangeRange: () => void 0 }; }, - getCurrentDirectory: () => projectPath, + getCurrentDirectory: () => env.getProjectRoot(), getDefaultLibFileName: tsModule.getDefaultLibFilePath, getNewLine: () => NEWLINE, useCaseSensitiveFileNames: () => true @@ -543,19 +543,19 @@ function getScriptKind(tsModule: RuntimeLibrary['typescript'], langId: string): function getParsedConfig( tsModule: RuntimeLibrary['typescript'], - projectPath: string, + projectRoot: string, tsconfigPath: string | undefined ) { const configFilename = tsconfigPath; const configJson = (configFilename && tsModule.readConfigFile(configFilename, tsModule.sys.readFile).config) || { include: ['**/*.vue'], - exclude: defaultIgnorePatterns(tsModule, projectPath) + exclude: defaultIgnorePatterns(tsModule, projectRoot) }; // existingOptions should be empty since it always takes priority return tsModule.parseJsonConfigFileContent( configJson, tsModule.sys, - projectPath, + projectRoot, /*existingOptions*/ {}, configFilename, /*resolutionStack*/ undefined, diff --git a/server/src/services/vls.ts b/server/src/services/vls.ts index c6273623b8..6930c22f94 100644 --- a/server/src/services/vls.ts +++ b/server/src/services/vls.ts @@ -60,6 +60,7 @@ import { APPLY_REFACTOR_COMMAND } from '../modes/script/javascript'; import { VCancellationToken, VCancellationTokenSource } from '../utils/cancellationToken'; import { findConfigFile, requireUncached } from '../utils/workspace'; import { createProjectService, ProjectService } from './projectService'; +import { createEnvironmentService } from './EnvironmentService'; interface ProjectConfig { vlsFullConfig: VLSFullConfig; @@ -172,12 +173,12 @@ export class VLS { this.lspConnection.onDidChangeConfiguration(async ({ settings }: DidChangeConfigurationParams) => { let isFormatEnable = false; this.projects.forEach(project => { - const veturConfig = this.workspaces.get(project.rootPathForConfig); + const veturConfig = this.workspaces.get(project.env.getRootPathForConfig()); if (!veturConfig) { return; } const fullConfig = this.getVLSFullConfig(veturConfig.settings, settings); - project.configure(fullConfig); + project.env.configure(fullConfig); isFormatEnable = isFormatEnable || fullConfig.vetur.format.enable; }); this.setupDynamicFormatters(isFormatEnable); @@ -222,7 +223,9 @@ export class VLS { const showWarningAndLearnMore = (message: string, url: string) => { this.lspConnection.window.showWarningMessage(message, { title: 'Learn More' }).then(action => { - if (action) this.openWebsite(url); + if (action) { + this.openWebsite(url); + } }); }; @@ -283,14 +286,16 @@ export class VLS { ); this.warnProjectIfNeed(projectConfig); const project = await createProjectService( - projectConfig.rootPathForConfig, - projectConfig.rootFsPath, - projectConfig.tsconfigPath, - projectConfig.packagePath, - projectConfig.snippetFolder, - projectConfig.globalComponents, + createEnvironmentService( + projectConfig.rootPathForConfig, + projectConfig.rootFsPath, + projectConfig.tsconfigPath, + projectConfig.packagePath, + projectConfig.snippetFolder, + projectConfig.globalComponents, + projectConfig.vlsFullConfig + ), this.documentService, - projectConfig.vlsFullConfig, this.globalSnippetDir, dependencyService ); @@ -329,7 +334,10 @@ export class VLS { { name: 'Vetur doctor info', fileName, - currentProject: { rootPathForConfig: project?.rootPathForConfig, projectRootFsPath: project?.projectPath }, + currentProject: { + rootPathForConfig: project?.env.getRootPathForConfig(), + projectRootFsPath: project?.env.getProjectRoot() + }, activeProjects: Array.from(this.projects.keys()), projectConfigs }, @@ -379,23 +387,28 @@ export class VLS { changes.forEach(async c => { if (c.type === FileChangeType.Changed) { const fsPath = getFileFsPath(c.uri); + + // when `vetur.config.js` changed if (this.workspaces.has(fsPath)) { logger.logInfo(`refresh vetur config when ${fsPath} changed.`); const name = this.workspaces.get(fsPath)?.name ?? ''; this.workspaces.delete(fsPath); await this.addWorkspace({ name, fsPath }); - this.projects.forEach((project, projectPath) => { - if (project.rootPathForConfig === fsPath) { + this.projects.forEach((project, projectRoot) => { + if (project.env.getRootPathForConfig() === fsPath) { project.dispose(); - this.projects.delete(projectPath); + this.projects.delete(projectRoot); } }); return; } const project = await this.getProjectService(c.uri); - const jsMode = project?.languageModes.getMode('javascript'); - jsMode?.onDocumentChanged!(fsPath); + project?.languageModes.getAllModes().forEach(m => { + if (m.onDocumentChanged) { + m.onDocumentChanged(fsPath); + } + }); } }); diff --git a/server/src/services/typescriptService/vueVersion.ts b/server/src/utils/vueVersion.ts similarity index 94% rename from server/src/services/typescriptService/vueVersion.ts rename to server/src/utils/vueVersion.ts index 1db8b4d76e..2f23debaaf 100644 --- a/server/src/services/typescriptService/vueVersion.ts +++ b/server/src/utils/vueVersion.ts @@ -1,6 +1,4 @@ -import { throws } from 'assert'; import { readFileSync } from 'fs'; -import { findConfigFile } from '../../utils/workspace'; export enum VueVersion { VPre25, From 00baf2a1992faadb9d3346e3c9614d524aa2a705 Mon Sep 17 00:00:00 2001 From: yoyo930021 Date: Sun, 6 Dec 2020 12:03:02 +0800 Subject: [PATCH 21/33] Add vetur.config.js docs and refactor docs --- docs/.vuepress/config.js | 51 +++++--- docs/credits.md | 8 ++ docs/framework.md | 3 - docs/{ => guide}/FAQ.md | 0 docs/guide/Readme.md | 48 +++++++ docs/{ => guide}/component-data.md | 0 docs/{ => guide}/debugging.md | 0 docs/{ => guide}/emmet.md | 0 docs/{ => guide}/formatting.md | 0 docs/guide/global-components.md | 35 +++++ docs/{ => guide}/highlighting.md | 0 docs/{ => guide}/intellisense.md | 0 docs/{ => guide}/interpolation.md | 0 docs/{ => guide}/linting-error.md | 0 docs/guide/setup.md | 151 ++++++++++++++++++++++ docs/{ => guide}/snippet.md | 0 docs/{ => guide}/vti.md | 2 + docs/reference/Readme.md | 201 +++++++++++++++++++++++++++++ docs/reference/package.md | 4 + docs/reference/tsconfig.md | 3 + docs/roadmap.md | 3 - docs/setup.md | 95 -------------- 22 files changed, 484 insertions(+), 120 deletions(-) delete mode 100644 docs/framework.md rename docs/{ => guide}/FAQ.md (100%) create mode 100644 docs/guide/Readme.md rename docs/{ => guide}/component-data.md (100%) rename docs/{ => guide}/debugging.md (100%) rename docs/{ => guide}/emmet.md (100%) rename docs/{ => guide}/formatting.md (100%) create mode 100644 docs/guide/global-components.md rename docs/{ => guide}/highlighting.md (100%) rename docs/{ => guide}/intellisense.md (100%) rename docs/{ => guide}/interpolation.md (100%) rename docs/{ => guide}/linting-error.md (100%) create mode 100644 docs/guide/setup.md rename docs/{ => guide}/snippet.md (100%) rename docs/{ => guide}/vti.md (93%) create mode 100644 docs/reference/Readme.md create mode 100644 docs/reference/package.md create mode 100644 docs/reference/tsconfig.md delete mode 100644 docs/roadmap.md delete mode 100644 docs/setup.md diff --git a/docs/.vuepress/config.js b/docs/.vuepress/config.js index 813b0c7f0e..7e1f22c494 100644 --- a/docs/.vuepress/config.js +++ b/docs/.vuepress/config.js @@ -9,24 +9,37 @@ module.exports = { repo: 'vuejs/vetur', editLinks: true, docsDir: 'docs', - sidebar: [ - '/setup', - { - title: 'Features', - collapsable: false, - children: [ - '/highlighting', - '/snippet', - '/emmet', - '/linting-error', - '/formatting', - '/intellisense', - '/debugging', - '/component-data', - '/interpolation', - '/vti' - ] - } - ] + nav: [ + { text: 'Guide', link: '/guide/' }, + { text: 'Reference', link: '/reference/' }, + { text: 'Roadmap', link: 'https://github.com/vuejs/vetur/issues/873' }, + { text: 'Credits', link: '/credits' }, + { text: 'Contribution Guide', link: 'https://github.com/vuejs/vetur/wiki#contribution-guide' } + ], + sidebar: { + '/guide/': [ + '', + 'setup', + { + title: 'Features', + collapsable: false, + children: [ + 'highlighting', + 'snippet', + 'emmet', + 'linting-error', + 'formatting', + 'intellisense', + 'debugging', + 'component-data', + 'interpolation', + 'vti', + 'global-components' + ] + }, + 'FAQ' + ], + '/reference/': ['', 'tsconfig'] + } } }; diff --git a/docs/credits.md b/docs/credits.md index 41f4aecacb..a91a680dc5 100644 --- a/docs/credits.md +++ b/docs/credits.md @@ -5,6 +5,12 @@ Main Developer: [@octref](https://github.com/octref) Contributors: - [@HerringtonDarkholme](https://github.com/HerringtonDarkholme) - [@sandersn](https://github.com/sandersn) +- [@yoyo930021](https://github.com/yoyo930021) +- [@ktsn](https://github.com/ktsn) +- [@rchl](https://github.com/rchl) +- [@Uninen](https://github.com/Uninen) + +Others: https://github.com/vuejs/vetur/graphs/contributors ### Attributions @@ -15,3 +21,5 @@ Others: - Grammar based on [vuejs/vue-syntax-highlight](https://github.com/vuejs/vue-syntax-highlight) - Sass grammar based on [TheRealSyler/vscode-sass-indented](https://github.com/TheRealSyler/vscode-sass-indented) - PostCSS grammar based on [hudochenkov/Syntax-highlighting-for-PostCSS](https://github.com/hudochenkov/Syntax-highlighting-for-PostCSS) +- TypeScript/JavaScript based on [TypeScript](https://github.com/microsoft/TypeScript/#readme) +- CSS/SCSS/LESS feature based on [vscode-css-languageservice](https://github.com/microsoft/vscode-css-languageservice) diff --git a/docs/framework.md b/docs/framework.md deleted file mode 100644 index b00b01be57..0000000000 --- a/docs/framework.md +++ /dev/null @@ -1,3 +0,0 @@ -# Framework Support - -Renamed as [Component Data Support](./component-data.md) \ No newline at end of file diff --git a/docs/FAQ.md b/docs/guide/FAQ.md similarity index 100% rename from docs/FAQ.md rename to docs/guide/FAQ.md diff --git a/docs/guide/Readme.md b/docs/guide/Readme.md new file mode 100644 index 0000000000..9a75609555 --- /dev/null +++ b/docs/guide/Readme.md @@ -0,0 +1,48 @@ +# Quick start + +Here are five common case. + +- [Vue CLI](#vue-cli) +- [Veturpack]($veturpack) +- [Laravel](#laravel-custom-project) +- [Custom project](#laravel-custom-project) +- [Monorepo](#monorepo) + +## Vue CLI +[Offical Website](https://cli.vuejs.org/) +When you create project with Vue CLI, +If no use typescript, please add `jsconfig.json` at opened project root. +```json +{ + "compilerOptions": { + "target": "es2015", + "module": "esnext", + "baseUrl": "./", + "paths": { + "@/*": ["components/*"] + } + }, + "include": [ + "src/**/*.vue", + "src/**/*.js" + ] +} +``` + +If use typescript, you don't need to do any thing in your project. + +## Veturpack +[Github](https://github.com/octref/veturpack) +It is out of box. + +## Laravel / Custom project +Please keep `package.json` and `tsconfig.json`/`jsconfig.json` at opened project root. +If you can't do it, please add `vetur.config.js` for set config file path. + +- [Read more `tsconfig.json`/`jsconfig.json`](). +- [Read more `vetur.config.js` doc](). + +## Monorepo +please add `vetur.config.js` for define projects. + +- [Read more `vetur.config.js` doc](). diff --git a/docs/component-data.md b/docs/guide/component-data.md similarity index 100% rename from docs/component-data.md rename to docs/guide/component-data.md diff --git a/docs/debugging.md b/docs/guide/debugging.md similarity index 100% rename from docs/debugging.md rename to docs/guide/debugging.md diff --git a/docs/emmet.md b/docs/guide/emmet.md similarity index 100% rename from docs/emmet.md rename to docs/guide/emmet.md diff --git a/docs/formatting.md b/docs/guide/formatting.md similarity index 100% rename from docs/formatting.md rename to docs/guide/formatting.md diff --git a/docs/guide/global-components.md b/docs/guide/global-components.md new file mode 100644 index 0000000000..ad65b30bbb --- /dev/null +++ b/docs/guide/global-components.md @@ -0,0 +1,35 @@ +# Global components + +Vetur support define global components. +You can register template interpolation for that components anywhere in the project. + +Please add `projects.globalComponents` in `vetur.config.js`. + +## Example +When your project isn't a monorepo and `package.json/(ts|js)config.json` at project root. +```javascript +// vetur.config.js +/** @type {import('vti').VeturConfig} */ +module.exports = { + projects: [ + { + root: './', + // **optional** default: `[]` + // Register globally Vue component glob. + // If you set it, you can get completion by that components. + // It is relative to root property. + // Notice: It won't actually do it. You need to use `require.context` or `Vue.component` + globalComponents: [ + './src/components/**/*.vue', + { + // Component name + name: 'FakeButton', + // Component file path, please use '/'. + path: './src/app/components/AppButton.vue' + } + ] + } + ] +} +``` + diff --git a/docs/highlighting.md b/docs/guide/highlighting.md similarity index 100% rename from docs/highlighting.md rename to docs/guide/highlighting.md diff --git a/docs/intellisense.md b/docs/guide/intellisense.md similarity index 100% rename from docs/intellisense.md rename to docs/guide/intellisense.md diff --git a/docs/interpolation.md b/docs/guide/interpolation.md similarity index 100% rename from docs/interpolation.md rename to docs/guide/interpolation.md diff --git a/docs/linting-error.md b/docs/guide/linting-error.md similarity index 100% rename from docs/linting-error.md rename to docs/guide/linting-error.md diff --git a/docs/guide/setup.md b/docs/guide/setup.md new file mode 100644 index 0000000000..ee0c150552 --- /dev/null +++ b/docs/guide/setup.md @@ -0,0 +1,151 @@ +# Setup + +## Extensions + +- Install [Sass](https://marketplace.visualstudio.com/items?itemName=Syler.sass-indented) for sass syntax highlighting. +- Install [language-stylus](https://marketplace.visualstudio.com/items?itemName=sysoev.language-stylus) for stylus syntax highlighting. +- Install [ESLint extension](https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint) for linting vue and js files. + +## VS Code Config + +- Add `vue` to your `eslint.validate` setting, for example: + + ```json + "eslint.validate": [ + "javascript", + "javascriptreact", + "vue" + ] + ``` + +## Project Setup + +- At project root exist `package.json` file, Vetur use it for infer vue version and get component date. +- At project root create a `jsconfig.json` or `tsconfig.json` that `include` all vue files and files that they import from, for example: + +- `jsconfig.json` + + ```json + { + "include": [ + "./src/**/*" + ] + } + ``` + +- `tsconfig.json` + + ```json + { + "include": [ + "./src/**/*" + ], + "compilerOptions": { + "module": "es2015", + "moduleResolution": "node", + "target": "es5", + "sourceMap": true, + "allowJs": true + } + } + ``` +- [What is a tsconfig.json](https://www.typescriptlang.org/docs/handbook/tsconfig-json.html) +- [Reference](https://www.typescriptlang.org/tsconfig) + +#### jsconfig vs tsconfig + +- Use `tsconfig` for pure TS project. +- Use `jsconfig` for pure JS project. +- Use `jsconfig` or `tsconfig` with `allowJs: true` for mixed JS / TS project. + +### Path mapping + +If you are using [Webpack's alias](https://webpack.js.org/configuration/resolve/) or [TypeScript's path mapping](https://www.typescriptlang.org/docs/handbook/module-resolution.html) to resolve components, you need to update Vetur's `tsconfig.json` or `jsconfig.json`. + +For example: + +```html +└── src + ├── components + │ ├── a.vue + │ └── b.vue + ├── containers + │ └── index.vue + ├── index.js + └── jsconfig.json +``` + +jsconfig.json: + +```json +{ + "compilerOptions": { + "baseUrl": ".", + "paths": { + "components/*": [ + "src/components/*" + ] + } + } +} +``` + +index.vue + +```javascript +import a from 'components/a.vue' +import b from 'components/b.vue' +``` + +## Advanced +If you use monorepo or VTI or not exist `package.json` and `tsconfig.json/jsconfig.json` at project root, +You can use `vetur.config.js` for advanced setting. + +Please add `vetur.config.js` at project root or monorepo project root. +```javascript +// vetur.config.js +/** @type {import('vti').VeturConfig} */ +module.exports = { + // **optional** default: `{}` + // override vscode settings + // Notice: It only affects the settings used by Vetur. + settings: { + "vetur.useWorkspaceDependencies": true, + "vetur.experimental.templateInterpolationService": true + }, + // **optional** default: `[{ root: './' }]` + // support monorepos + projects: [ + './packages/repo2', // shorthand for only root. + { + // **required** + // Where is your project? + // It is relative to `vetur.config.js`. + root: './packages/repo1', + // **optional** default: `'package.json'` + // Where is `package.json` in the project? + // We use it to determine the version of vue. + // It is relative to root property. + package: './package.json', + // **optional** + // Where is TypeScript config file in the project? + // It is relative to root property. + tsconfig: './tsconfig.json', + // **optional** default: `'./.vscode/vetur/snippets'` + // Where is vetur custom snippets folders? + snippetFolder: './.vscode/vetur/snippets' + // **optional** default: `[]` + // Register globally Vue component glob. + // If you set it, you can get completion by that components. + // It is relative to root property. + // Notice: It won't actually do it. You need to use `require.context` or `Vue.component` + globalComponents: [ + './src/components/**/*.vue' + ] + } + ] +} +``` + +- [Read more `vetur.config.js` reference](). +- [Read RFC](https://github.com/vuejs/vetur/blob/master/rfcs/001-vetur-config-file.md). diff --git a/docs/snippet.md b/docs/guide/snippet.md similarity index 100% rename from docs/snippet.md rename to docs/guide/snippet.md diff --git a/docs/vti.md b/docs/guide/vti.md similarity index 93% rename from docs/vti.md rename to docs/guide/vti.md index 0de1fda0c7..188f0ccf62 100644 --- a/docs/vti.md +++ b/docs/guide/vti.md @@ -28,6 +28,8 @@ vti diagnostics ![VTI demo](https://user-images.githubusercontent.com/4033249/72225084-911ef580-3581-11ea-9943-e7165126ace9.gif). +You also can use [`vetur.config.js`](/reference/) for setting VTI. + Currently, this is only used for generating interpolation type-checking errors on CLI, which neither Vue's compiler nor Webpack would catch. diff --git a/docs/reference/Readme.md b/docs/reference/Readme.md new file mode 100644 index 0000000000..be0ebc4eca --- /dev/null +++ b/docs/reference/Readme.md @@ -0,0 +1,201 @@ +# `vetur.config.js` + +A new configuration file for Vetur and VTI + +## Example + +```javascript +// vetur.config.js +/** @type {import('vti').VeturConfig} */ +module.exports = { + // **optional** default: `{}` + // override vscode settings part + // Notice: It only affects the settings used by Vetur. + settings: { + "vetur.useWorkspaceDependencies": true, + "vetur.experimental.templateInterpolationService": true + }, + // **optional** default: `[{ root: './' }]` + // support monorepos + projects: [ + './packages/repo2', // shorthand for only root. + { + // **required** + // Where is your project? + // It is relative to `vetur.config.js`. + root: './packages/repo1', + // **optional** default: `'package.json'` + // Where is `package.json` in the project? + // We use it to determine the version of vue. + // It is relative to root property. + package: './package.json', + // **optional** + // Where is TypeScript config file in the project? + // It is relative to root property. + tsconfig: './tsconfig.json', + // **optional** default: `'./.vscode/vetur/snippets'` + // Where is vetur custom snippets folders? + snippetFolder: './.vscode/vetur/snippets' + // **optional** default: `[]` + // Register globally Vue component glob. + // If you set it, you can get completion by that components. + // It is relative to root property. + // Notice: It won't actually do it. You need to use `require.context` or `Vue.component` + globalComponents: [ + './src/components/**/*.vue' + ] + } + ] +} +``` + +## Noun +- Vetur: a VSCode extension for Vue support. +- VTI: a CLI for Vue file type-check, diagnostics or some feature. +- VLS: vue language server, The core of everything. It is base on [language server protocol](https://microsoft.github.io/language-server-protocol/). + +## Spec +- All path formats are used with `/`. + > Helpful for cross-platform use project. +- Only support commonjs format. + > We can use it quickly and directly. +- Use pure JavaScript. + > Same as above. + > You can get typings like `@JSDoc`. +- UTF-8 charset + +## How to use + +### VTI +You can use it to override VTI default settings. +```bash +vti `action` +vti -c vetur.config.js `action` +vti --config vetur.config.js `action` +``` + +### Vetur +This profile takes precedence over vscode setting. +It will find it when Vetur initialization. +If it isn't exist, It will use `{ settings: {}, projects: ['./'] }`. +This will ensure consistency with past behavior. + +### How to find `vetur.config.js` +- Start from the root and work your way up until the file is found. +- The root is set `process.cwd()` value in VTI and you can set file path in CLI params. + +PS. Each root can have its own vetur.config.js in VSCode Multi root feature. + +## Detail + +### Definition +```typescript +type Glob = string + +export interface VeturConfig { + settings?: { [key: string]: boolean | string | Enum }, + projects?: Array + }> +} +``` + +### `settings` +Incoming to vue language server config. + +In VLS, it will merge (vscode setting or VTL default config) and vetur.config.js `settings`. +```typescript +import _ from 'lodash' + +// original vscode config or VTI default config +const config: VLSFullConfig = params.initializationOptions?.config + ? _.merge(getDefaultVLSConfig(), params.initializationOptions.config) + : getDefaultVLSConfig(); + +// From vetur.config.js +const veturConfig = getVeturConfigInWorkspace() +// Merge vetur.config.js +Object.keys(veturConfig.setting).forEach((key) => { + _.set(config, key, veturConfig.setting[key]) +}) +``` + +Notice: It only affects the settings used by Vetur. +For example, we use `typescript.preferences.quoteStyle` in Vetur. so you can set it. +But it don't affect original TypeScript support in VSCode. + +### `projects` +The monorepo need a baseline or logic. +Possible options are `package.json` or `tsconfig.js`. +But both are used for node and typescript projects. +We're likely to waste unnecessary resources on things we don't need. +So I figured the best way to do it was through the setup. + +For detailed discussion, see this [RFC](https://github.com/vuejs/vetur/pull/2377). + +if `projects[]` is only a string, It is a shorthand when you only need to define `root`. + +### `projects[].root` +All runtime dependencies is base on value of this property. +Like `typescript`, `prettier`, `@prettier/pug`. +Also Vetur find `./package.json` and `./tsconfig.js` by default. + +### `projects[].package` +We can get the project name or dependency info from here. +But We only use it to determine the version of vue now. +But it doesn't rule out the use of more. + +### `projects[].tsconfig` +Typescript project profile. +It's the key to helping us support JavaScript and TypeScript. +We also use it for support template interpolation. + +#### Why isn't array? +If you are familiar with typescript, You know TypeScript allow support multiple discrete `tsconfig`. +But in the vue ecosystem, It's almost completely unsupported. +For example, We often use webpack to compile Vue projects. +The `vue-loader` call `ts-loader` for support typescript. +But `ts-loader` is only support only one `tsconfig.json`. + +For these reasons, we also don't support it. +It can reduce development and maintenance costs. + +PS. `jsconfig.json` is also support it. + +### `projects[].snippetFolder` +Vetur Custom snippets folder path + +### `projects[].globalComponents` +We have some amazing features, Like `template interpolation`. +But it only work when register component in component. +For example: +```javascript +import Comp from '@/components/Comp.vue' + +export default { + components: { + Comp + } +} +``` + +With this property available, we will parse vue component files that match the glob on vls startup. +You can support `template interpolation` for that components anywhere in the project. + +This property allow two type values in array. +- Glob (`string`) [format](https://github.com/mrmlnc/fast-glob#pattern-syntax) + Vetur will call glob lib with `projects[].root` for loading component when value is string. + It use `path.basename(fileName, path.extname(fileName))` as component name. +- Object (`{ name: string, path: string }`) + Vetur use this data directly. + It's the most flexible way. + If this is a relative path, It is based on `projects[].root`. + +Notice: It won't actually do it. You need to use `require.context` and `Vue.component` in your project. [more](https://vuejs.org/v2/guide/components-registration.html#Automatic-Global-Registration-of-Base-Components) + + +- [Read RFC](https://github.com/vuejs/vetur/blob/master/rfcs/001-vetur-config-file.md). diff --git a/docs/reference/package.md b/docs/reference/package.md new file mode 100644 index 0000000000..3525bd67f2 --- /dev/null +++ b/docs/reference/package.md @@ -0,0 +1,4 @@ +## package.json + +Same as `package.json` in nodejs project. +Vetur infer vue version and support other libs from this file. diff --git a/docs/reference/tsconfig.md b/docs/reference/tsconfig.md new file mode 100644 index 0000000000..b6324aaee1 --- /dev/null +++ b/docs/reference/tsconfig.md @@ -0,0 +1,3 @@ +# Javascript / Typescript config + +See https://www.typescriptlang.org/tsconfig diff --git a/docs/roadmap.md b/docs/roadmap.md deleted file mode 100644 index d6a97488cf..0000000000 --- a/docs/roadmap.md +++ /dev/null @@ -1,3 +0,0 @@ -# Roadmap - -See https://github.com/vuejs/vetur/issues/873. \ No newline at end of file diff --git a/docs/setup.md b/docs/setup.md deleted file mode 100644 index 04e5db8350..0000000000 --- a/docs/setup.md +++ /dev/null @@ -1,95 +0,0 @@ -# Setup - -## Extensions - -- Install [Sass](https://marketplace.visualstudio.com/items?itemName=Syler.sass-indented) for sass syntax highlighting. -- Install [language-stylus](https://marketplace.visualstudio.com/items?itemName=sysoev.language-stylus) for stylus syntax highlighting. -- Install [ESLint extension](https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint) for linting vue and js files. - -## VS Code Config - -- Add `vue` to your `eslint.validate` setting, for example: - - ```json - "eslint.validate": [ - "javascript", - "javascriptreact", - "vue" - ] - ``` - -## Project Setup - -- At project root create a `jsconfig.json` or `tsconfig.json` that `include` all vue files and files that they import from, for example: - -- `jsconfig.json` - - ```json - { - "include": [ - "./src/**/*" - ] - } - ``` - -- `tsconfig.json` - - ```json - { - "include": [ - "./src/**/*" - ], - "compilerOptions": { - "module": "es2015", - "moduleResolution": "node", - "target": "es5", - "sourceMap": true, - "allowJs": true - } - } - ``` - -#### jsconfig vs tsconfig - -- Use `tsconfig` for pure TS project. -- Use `jsconfig` for pure JS project. -- Use `jsconfig` or `tsconfig` with `allowJs: true` for mixed JS / TS project. - -### Path mapping - -If you are using [Webpack's alias](https://webpack.js.org/configuration/resolve/) or [TypeScript's path mapping](https://www.typescriptlang.org/docs/handbook/module-resolution.html) to resolve components, you need to update Vetur's `tsconfig.json` or `jsconfig.json`. - -For example: - -```html -└── src - ├── components - │ ├── a.vue - │ └── b.vue - ├── containers - │ └── index.vue - ├── index.js - └── jsconfig.json -``` - -jsconfig.json: - -```json -{ - "compilerOptions": { - "baseUrl": ".", - "paths": { - "components/*": [ - "src/components/*" - ] - } - } -} -``` - -index.vue - -```javascript -import a from 'components/a.vue' -import b from 'components/b.vue' -``` From 8d6845a3f2fda4608bf274fc2562f2589b170403 Mon Sep 17 00:00:00 2001 From: yoyo930021 Date: Sun, 6 Dec 2020 12:08:23 +0800 Subject: [PATCH 22/33] CHANGELOG --- CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 55685b335f..74112ef6d8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,13 @@ - Fix pug format. #2460 - Fix scss autocompletion. #2522 - Fix templates in custom blocks are parsed as root elements. #1336 +- Support multi-root workspace +- Support monorepo +- Register global components +- Support `vetur.config.js` for monorepo, global components. +- Watch more config file changed, Like: `package.json`, `tsconfig.json` +- Warn some probably problem when open project. +- Add `Vetur: doctor` command. ### 0.30.3 | 2020-11-26 | [VSIX](https://marketplace.visualstudio.com/_apis/public/gallery/publishers/octref/vsextensions/vetur/0.30.3/vspackage) From dfbabe699ff9be66e62874401dddc1df8e5f7d6e Mon Sep 17 00:00:00 2001 From: yoyo930021 Date: Mon, 7 Dec 2020 08:06:49 +0800 Subject: [PATCH 23/33] Add vueVersion in doctor info --- server/src/services/vls.ts | 6 ++++-- server/src/utils/vueVersion.ts | 4 ++++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/server/src/services/vls.ts b/server/src/services/vls.ts index 6930c22f94..d330732ecb 100644 --- a/server/src/services/vls.ts +++ b/server/src/services/vls.ts @@ -61,6 +61,7 @@ import { VCancellationToken, VCancellationTokenSource } from '../utils/cancellat import { findConfigFile, requireUncached } from '../utils/workspace'; import { createProjectService, ProjectService } from './projectService'; import { createEnvironmentService } from './EnvironmentService'; +import { getVueVersionKey } from '../utils/vueVersion'; interface ProjectConfig { vlsFullConfig: VLSFullConfig; @@ -335,8 +336,9 @@ export class VLS { name: 'Vetur doctor info', fileName, currentProject: { - rootPathForConfig: project?.env.getRootPathForConfig(), - projectRootFsPath: project?.env.getProjectRoot() + vueVersion: project ? getVueVersionKey(project?.env.getVueVersion()) : null, + rootPathForConfig: project?.env.getRootPathForConfig() ?? null, + projectRootFsPath: project?.env.getProjectRoot() ?? null }, activeProjects: Array.from(this.projects.keys()), projectConfigs diff --git a/server/src/utils/vueVersion.ts b/server/src/utils/vueVersion.ts index 2f23debaaf..7fbfdfa10a 100644 --- a/server/src/utils/vueVersion.ts +++ b/server/src/utils/vueVersion.ts @@ -16,6 +16,10 @@ function floatVersionToEnum(v: number) { } } +export function getVueVersionKey(version: VueVersion) { + return Object.keys(VueVersion)?.[Object.values(VueVersion).indexOf(version)]; +} + export function inferVueVersion(packagePath: string | undefined): VueVersion { const packageJSONPath = packagePath; try { From 51ee1d080b396074149faea8e80a7857e0586ed0 Mon Sep 17 00:00:00 2001 From: yoyo930021 Date: Mon, 7 Dec 2020 10:57:53 +0800 Subject: [PATCH 24/33] Add can't access config file error --- server/src/services/vls.ts | 38 +++++++++++++++++++++++++++++--------- 1 file changed, 29 insertions(+), 9 deletions(-) diff --git a/server/src/services/vls.ts b/server/src/services/vls.ts index d330732ecb..bc778ebf23 100644 --- a/server/src/services/vls.ts +++ b/server/src/services/vls.ts @@ -26,8 +26,7 @@ import { CompletionParams, ExecuteCommandParams, ApplyWorkspaceEditRequest, - FoldingRangeParams, - DidChangeWorkspaceFoldersNotification + FoldingRangeParams } from 'vscode-languageserver'; import { ColorInformation, @@ -62,6 +61,7 @@ import { findConfigFile, requireUncached } from '../utils/workspace'; import { createProjectService, ProjectService } from './projectService'; import { createEnvironmentService } from './EnvironmentService'; import { getVueVersionKey } from '../utils/vueVersion'; +import { accessSync, constants } from 'fs'; interface ProjectConfig { vlsFullConfig: VLSFullConfig; @@ -218,9 +218,14 @@ export class VLS { if (projectConfig.vlsFullConfig.vetur.ignoreProjectWarning) { return; } - if (projectConfig.isExistVeturConfig) { - return; - } + + const showErrorIfCantAccess = (name: string, fsPath: string) => { + try { + accessSync(fsPath, constants.R_OK); + } catch { + this.lspConnection.window.showErrorMessage(`Vetur can't access ${projectConfig.tsconfigPath} for ${name}.`); + } + }; const showWarningAndLearnMore = (message: string, url: string) => { this.lspConnection.window.showWarningMessage(message, { title: 'Learn More' }).then(action => { @@ -235,11 +240,23 @@ export class VLS { if (!projectConfig.tsconfigPath) { showWarningAndLearnMore( getCantFindMessage(['tsconfig.json', 'jsconfig.json']), - 'https://vuejs.github.io/vetur/setup.html#project-setup' + 'https://vuejs.github.io/vetur/guide/FAQ.html#vetur-can-t-find-tsconfig-json-jsconfig-json-in-xxxx-xxxxxx' ); + } else { + showErrorIfCantAccess('ts/js config', projectConfig.tsconfigPath); } if (!projectConfig.packagePath) { - showWarningAndLearnMore(getCantFindMessage(['package.json']), ''); + showWarningAndLearnMore( + getCantFindMessage(['package.json']), + 'https://vuejs.github.io/vetur/guide/FAQ.html#vetur-can-t-find-package-json-in-xxxx-xxxxxx' + ); + } else { + showErrorIfCantAccess('ts/js config', projectConfig.packagePath); + } + + // ignore not in project root warning when vetur config file is exist. + if (projectConfig.isExistVeturConfig) { + return; } if ( @@ -250,12 +267,15 @@ export class VLS { ) { showWarningAndLearnMore( `Vetur find \`tsconfig.json\`/\`jsconfig.json\`, but they aren\'t in the project root.`, - 'https://vuejs.github.io/vetur/setup.html#project-setup' + 'https://vuejs.github.io/vetur/guide/FAQ.html#vetur-find-xxx-but-they-aren-t-in-the-project-root' ); } if (normalizeFileNameResolve(projectConfig.rootPathForConfig, 'package.json') !== projectConfig.packagePath) { - showWarningAndLearnMore(`Vetur find \`package.json\`/, but they aren\'t in the project root.`, ''); + showWarningAndLearnMore( + `Vetur find \`package.json\`/, but they aren\'t in the project root.`, + 'https://vuejs.github.io/vetur/guide/FAQ.html#vetur-find-xxx-but-they-aren-t-in-the-project-root' + ); } } From 6a7536f2127d2655ad2127343dcaa42652663b9e Mon Sep 17 00:00:00 2001 From: yoyo930021 Date: Mon, 7 Dec 2020 11:00:58 +0800 Subject: [PATCH 25/33] Add warning and link in docs --- docs/.vuepress/config.js | 1 + docs/guide/FAQ.md | 56 ++++++++++++++++++++++++++++++---------- docs/guide/Readme.md | 6 ++--- docs/guide/setup.md | 2 +- 4 files changed, 48 insertions(+), 17 deletions(-) diff --git a/docs/.vuepress/config.js b/docs/.vuepress/config.js index 7e1f22c494..68f32291ff 100644 --- a/docs/.vuepress/config.js +++ b/docs/.vuepress/config.js @@ -12,6 +12,7 @@ module.exports = { nav: [ { text: 'Guide', link: '/guide/' }, { text: 'Reference', link: '/reference/' }, + { text: 'FAQ', link: '/guide/FAQ' }, { text: 'Roadmap', link: 'https://github.com/vuejs/vetur/issues/873' }, { text: 'Credits', link: '/credits' }, { text: 'Contribution Guide', link: 'https://github.com/vuejs/vetur/wiki#contribution-guide' } diff --git a/docs/guide/FAQ.md b/docs/guide/FAQ.md index 17ffc49eec..a1188286f6 100644 --- a/docs/guide/FAQ.md +++ b/docs/guide/FAQ.md @@ -1,17 +1,5 @@ # FAQ -- [Install an old version of Vetur](#install-an-old-version-of-vetur) -- [No Syntax Highlighting & No Language Features working](#no-syntax-highlighting--no-language-features-working) -- [Vetur Crash](#vetur-crash) -- [Vetur can't recognize components imported using webpack's alias](#vetur-cant-recognize-components-imported-using-webpacks-alias) -- [Property 'xxx' does not exist on type 'CombinedVueInstance'](#property-xxx-does-not-exist-on-type-combinedvueinstance) -- [Vetur cannot recognize my Vue component import, such as `import Comp from './comp'`](#vetur-cannot-recognize-my-vue-component-import-such-as-import-comp-from-comp) -- [Template Interpolation auto completion does not work](#template-interpolation-auto-completion-does-not-work) -- [.vue file cannot be imported in TS file](#vue-file-cannot-be-imported-in-ts-file) -- [How to build and install from source](#how-to-build-and-install-from-source) -- [Vetur uses different version of TypeScript in .vue files to what I installed in `node_modules`.](#vetur-uses-different-version-of-typescript-in-vue-files-to-what-i-installed-in-node_modules) -- [Vetur is slow](#vetur-is-slow) - ## Install an old version of Vetur Sometimes new releases have bugs that you want to avoid. Here's an easy way to downgrade Vetur to a working version: @@ -131,4 +119,46 @@ NB: It will use `typescript.tsdk` setting as the path to look for if defined, de You can run the command `Vetur: Restart VLS (Vue Language Server)` to restart VLS. -However, we'd appreciate it if you can file a [performance issue report with a profile](https://github.com/vuejs/vetur/blob/master/.github/PERF_ISSUE.md) to help us find the cause of the issue. \ No newline at end of file +However, we'd appreciate it if you can file a [performance issue report with a profile](https://github.com/vuejs/vetur/blob/master/.github/PERF_ISSUE.md) to help us find the cause of the issue. + +## Vetur can't find `tsconfig.json`, `jsconfig.json` in /xxxx/xxxxxx. + +If you don't have any `tsconfig.json`, `jsconfig.json` in project, +Vetur will use fallback settings. Some feature isn't work. +Like: path alias, decorator, import json. + +You can add this config in correct position in project. +Or use `vetur.config.js` to set file path in project. + +- [Read more project setup](/guide/setup.html#project-setup) +- [Read more `vetur.config.js`](/guide/setup.html#advanced) + +If you want debug info, you can use `Vetur: show doctor info` command. +You can use `vetur.ignoreProjectWarning: false` in vscode setting to close this warning. + +## Vetur can't find `package.json` in /xxxx/xxxxxx. + +If you don't have any `package.json` in project, Vetur can't know vue version and component data from other libs. +Vetur assume that vue version is less than 2.5 in your project. +If the version is wrong, you will get wrong diagnostic from typescript and eslint template validation. + +You can add this config in correct position in project. +Or use `vetur.config.js` to set file path in project. + +- [Read more `vetur.config.js`](/guide/setup.html#advanced) + +If you want debug info, you can use `Vetur: show doctor info` command. +You can use `vetur.ignoreProjectWarning: false` in vscode setting to close this warning. + +## Vetur find xxx, but they aren\'t in the project root. +Vetur find the file, but it may not actually be what you want. +If it is wrong, it will cause same result as the previous two. [ref1](/guide/FAQ.html#vetur-can-t-find-tsconfig-json-jsconfig-json-in-xxxx-xxxxxx), [ref2](/guide/FAQ.html#vetur-can-t-find-package-json-in-xxxx-xxxxxx) + +You can add this config in correct position in project. +Or use `vetur.config.js` to set file path in project. + +- [Read more `vetur.config.js`](/guide/setup.html#advanced) + +If you want debug info, you can use `Vetur: show doctor info` command. +You can use `vetur.ignoreProjectWarning: false` in vscode setting to close this warning. + diff --git a/docs/guide/Readme.md b/docs/guide/Readme.md index 9a75609555..f8d0f4b255 100644 --- a/docs/guide/Readme.md +++ b/docs/guide/Readme.md @@ -39,10 +39,10 @@ It is out of box. Please keep `package.json` and `tsconfig.json`/`jsconfig.json` at opened project root. If you can't do it, please add `vetur.config.js` for set config file path. -- [Read more `tsconfig.json`/`jsconfig.json`](). -- [Read more `vetur.config.js` doc](). +- [Read more `tsconfig.json`/`jsconfig.json`](/guide/setup.html#project-setup). +- [Read more `vetur.config.js` doc](/guide/setup.html#advanced). ## Monorepo please add `vetur.config.js` for define projects. -- [Read more `vetur.config.js` doc](). +- [Read more `vetur.config.js` doc](/guide/setup.html#advanced). diff --git a/docs/guide/setup.md b/docs/guide/setup.md index ee0c150552..4b3d94e68e 100644 --- a/docs/guide/setup.md +++ b/docs/guide/setup.md @@ -147,5 +147,5 @@ module.exports = { } ``` -- [Read more `vetur.config.js` reference](). +- [Read more `vetur.config.js` reference](/reference/). - [Read RFC](https://github.com/vuejs/vetur/blob/master/rfcs/001-vetur-config-file.md). From b20d5e5be6db8298d95a9e7bc29c8aa4da2b83c6 Mon Sep 17 00:00:00 2001 From: yoyo930021 Date: Mon, 7 Dec 2020 19:34:53 +0800 Subject: [PATCH 26/33] CHANGELOG --- CHANGELOG.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 74112ef6d8..54b1c0f0df 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,9 +8,11 @@ - Support monorepo - Register global components - Support `vetur.config.js` for monorepo, global components. -- Watch more config file changed, Like: `package.json`, `tsconfig.json` +- Watch config file changed, Like: `package.json`, `tsconfig.json` - Warn some probably problem when open project. -- Add `Vetur: doctor` command. +- Add `Vetur: doctor` command for debug. +- Improve docs. + ### 0.30.3 | 2020-11-26 | [VSIX](https://marketplace.visualstudio.com/_apis/public/gallery/publishers/octref/vsextensions/vetur/0.30.3/vspackage) From 6f7fc70bf2d70584c8706651dad996129d397680 Mon Sep 17 00:00:00 2001 From: yoyo930021 Date: Tue, 8 Dec 2020 10:05:12 +0800 Subject: [PATCH 27/33] Add shime type in setup doc --- docs/guide/setup.md | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/docs/guide/setup.md b/docs/guide/setup.md index 4b3d94e68e..b182eb36a6 100644 --- a/docs/guide/setup.md +++ b/docs/guide/setup.md @@ -97,6 +97,28 @@ import a from 'components/a.vue' import b from 'components/b.vue' ``` +## Typescript + +You need to add a shim type file for import vue SFC in typescript file. +### Vue2 +```typescript +// shims-vue.d.ts +declare module '*.vue' { + import Vue from 'vue' + export default Vue +} +``` +### Vue3 +```typescript +// shims-vue.d.ts +declare module '*.vue' { + import type { DefineComponent } from 'vue' + const component: DefineComponent<{}, {}, any> + export default component +} +``` + + ## Advanced If you use monorepo or VTI or not exist `package.json` and `tsconfig.json/jsconfig.json` at project root, You can use `vetur.config.js` for advanced setting. From 2d76859d9fd07050659f3a9c4a812858130605a7 Mon Sep 17 00:00:00 2001 From: yoyo930021 Date: Tue, 8 Dec 2020 21:25:14 +0800 Subject: [PATCH 28/33] CHANGELOG --- CHANGELOG.md | 13 +++++++++++++ docs/guide/Readme.md | 4 +++- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 54b1c0f0df..ec335655c3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,19 @@ # Changelog ### 0.31.0 + +---- + +#### 🎉 RFC release 🎉 + +We support monorepo and multi-root workspace in this version. +We have also added a new config file called `vetur.config.js`. + +See more: https://vetur.github.io/vetur/guide/setup.html#advanced +Reference: https://vetur.github.io/vetur/reference/ + +---- + - Fix pug format. #2460 - Fix scss autocompletion. #2522 - Fix templates in custom blocks are parsed as root elements. #1336 diff --git a/docs/guide/Readme.md b/docs/guide/Readme.md index f8d0f4b255..3a3d466d85 100644 --- a/docs/guide/Readme.md +++ b/docs/guide/Readme.md @@ -28,6 +28,7 @@ If no use typescript, please add `jsconfig.json` at opened project root. ] } ``` +[Add shim-types file for import Vue SFC in typescript file.](/guide/setup.html#typescript) If use typescript, you don't need to do any thing in your project. @@ -37,7 +38,8 @@ It is out of box. ## Laravel / Custom project Please keep `package.json` and `tsconfig.json`/`jsconfig.json` at opened project root. -If you can't do it, please add `vetur.config.js` for set config file path. +If you can't do it, please add `vetur.config.js` for set config file path. +[Add shim-types file for import Vue SFC in typescript file.](/guide/setup.html#typescript) - [Read more `tsconfig.json`/`jsconfig.json`](/guide/setup.html#project-setup). - [Read more `vetur.config.js` doc](/guide/setup.html#advanced). From a0132c021fc09b291e3472db9cb1b7ba64b428fc Mon Sep 17 00:00:00 2001 From: yoyo930021 Date: Tue, 8 Dec 2020 21:38:15 +0800 Subject: [PATCH 29/33] Remove unused import --- server/src/modes/template/tagProviders/index.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/server/src/modes/template/tagProviders/index.ts b/server/src/modes/template/tagProviders/index.ts index 209625732d..bb96a5161a 100644 --- a/server/src/modes/template/tagProviders/index.ts +++ b/server/src/modes/template/tagProviders/index.ts @@ -15,7 +15,6 @@ export { IHTMLTagProvider } from './common'; import fs from 'fs'; import { join } from 'path'; import { getNuxtTagProvider } from './nuxtTags'; -import { findConfigFile } from '../../../utils/workspace'; import { normalizeFileNameResolve } from '../../../utils/paths'; export let allTagProviders: IHTMLTagProvider[] = [ From 1507e9fe7030106270b4cbd00ca2f2b274a746c6 Mon Sep 17 00:00:00 2001 From: yoyo930021 Date: Tue, 8 Dec 2020 21:50:50 +0800 Subject: [PATCH 30/33] Support yarn pnp in monorepo --- CHANGELOG.md | 1 + server/src/services/dependencyService.ts | 15 ++++++++++++++- server/src/services/vls.ts | 19 +++++++++++++++---- 3 files changed, 30 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ec335655c3..8eac2e9fa8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,6 +25,7 @@ Reference: https://vetur.github.io/vetur/reference/ - Warn some probably problem when open project. - Add `Vetur: doctor` command for debug. - Improve docs. +- Support yarn PnP support. Thanks to contribution from [@merceyz](https://github.com/merceyz). #2478. ### 0.30.3 | 2020-11-26 | [VSIX](https://marketplace.visualstudio.com/_apis/public/gallery/publishers/octref/vsextensions/vetur/0.30.3/vspackage) diff --git a/server/src/services/dependencyService.ts b/server/src/services/dependencyService.ts index 7defd14217..d9889bc0ff 100644 --- a/server/src/services/dependencyService.ts +++ b/server/src/services/dependencyService.ts @@ -108,7 +108,7 @@ export const createDependencyService = (): DependencyService => { const bundledModules = { typescript: ts, - prettier: prettier, + prettier, '@starptech/prettyhtml': prettyHTML, 'prettier-eslint': prettierEslint, 'prettier-tslint': prettierTslint, @@ -217,6 +217,8 @@ export const createDependencyService = (): DependencyService => { $useWorkspaceDependencies = useWorkspaceDependencies; $workspacePath = workspacePath; $rootPathForConfig = rootPathForConfig; + // We don't need loaded when yarn pnp. https://yarnpkg.com/features/pnp + if (process.versions.pnp) { return; } loaded = { typescript: await loadTypeScript(), prettier: await loadCommonDep('prettier', bundledModules['prettier']), @@ -229,6 +231,17 @@ export const createDependencyService = (): DependencyService => { } const get = (lib: L, filePath?: string): Dependency => { + // We find it when yarn pnp. https://yarnpkg.com/features/pnp + if (process.versions.pnp) { + const pkgPath = require.resolve(lib, { paths: [filePath ?? $workspacePath] }); + + return { + dir: path.dirname(pkgPath), + version: '', + bundled: false, + module: require(pkgPath) + }; + } if (!loaded) { throw new Error('Please call init function before get dependency.'); } diff --git a/server/src/services/vls.ts b/server/src/services/vls.ts index bc778ebf23..0a7e0182b9 100644 --- a/server/src/services/vls.ts +++ b/server/src/services/vls.ts @@ -61,7 +61,7 @@ import { findConfigFile, requireUncached } from '../utils/workspace'; import { createProjectService, ProjectService } from './projectService'; import { createEnvironmentService } from './EnvironmentService'; import { getVueVersionKey } from '../utils/vueVersion'; -import { accessSync, constants } from 'fs'; +import { accessSync, constants, existsSync } from 'fs'; interface ProjectConfig { vlsFullConfig: VLSFullConfig; @@ -144,6 +144,15 @@ export class VLS { } private async addWorkspace(workspace: { name: string; fsPath: string }) { + // Enable Yarn PnP support https://yarnpkg.com/features/pnp + if (!process.versions.pnp) { + if (existsSync(path.join(workspace.fsPath, '.pnp.js'))) { + require(path.join(workspace.fsPath, '.pnp.js')).setup(); + } else if (existsSync(path.join(workspace.fsPath, '.pnp.cjs'))) { + require(path.join(workspace.fsPath, '.pnp.cjs')).setup(); + } + } + const veturConfigPath = findConfigFile(workspace.fsPath, 'vetur.config.js'); const rootPathForConfig = normalizeFileNameToFsPath( veturConfigPath ? path.dirname(veturConfigPath) : workspace.fsPath @@ -292,9 +301,11 @@ export class VLS { // init project const dependencyService = createDependencyService(); - const nodeModulePaths = - this.nodeModulesMap.get(projectConfig.rootPathForConfig) ?? - createNodeModulesPaths(projectConfig.rootPathForConfig); + // Yarn Pnp don't need this. https://yarnpkg.com/features/pnp + const nodeModulePaths = !process.versions.pnp + ? this.nodeModulesMap.get(projectConfig.rootPathForConfig) ?? + createNodeModulesPaths(projectConfig.rootPathForConfig) + : []; if (this.nodeModulesMap.has(projectConfig.rootPathForConfig)) { this.nodeModulesMap.set(projectConfig.rootPathForConfig, nodeModulePaths); } From 8866f416eba2ff8189e17d0f2d0c27fc88a99229 Mon Sep 17 00:00:00 2001 From: yoyo930021 Date: Tue, 8 Dec 2020 21:57:56 +0800 Subject: [PATCH 31/33] Add yarn pnp docs --- README.md | 23 ++++++++++++----------- docs/README.md | 21 +++++++++++---------- docs/guide/setup.md | 6 ++++++ 3 files changed, 29 insertions(+), 21 deletions(-) diff --git a/README.md b/README.md index fa2d069e62..fcfbb40187 100644 --- a/README.md +++ b/README.md @@ -52,15 +52,17 @@ Thanks to the following companies for supporting Vetur's development: ## Features -- Syntax-highlighting -- Snippet -- Emmet -- Linting / Error Checking -- Formatting -- Auto Completion -- [Component Data](https://vuejs.github.io/vetur/component-data.html): auto-completion and hover-information for popular Vue frameworks and your own custom components -- [Experimental Interpolation Features](https://vuejs.github.io/vetur/interpolation.html): auto-completion, hover information and type-checking in Vue template -- [VTI](https://vuejs.github.io/vetur/vti.html): Surface template type-checking errors on CLI +- [Syntax-highlighting](/guide/highlighting.md) +- [Snippet](/guide/snippet.md) +- [Emmet](/guide/emmet.md) +- [Linting / Error Checking](/guide/linting-error.md) +- [Formatting](/guide/formatting.md) +- [IntelliSense](/guide/intellisense.md) +- [Debugging](/guide/debugging.md) +- [Component Data](/guide/framework.md): auto-completion and hover-information for popular Vue frameworks and your own custom components +- [Experimental Interpolation Features](/guide/interpolation.md): auto-completion, hover information and type-checking in Vue template +- [VTI](/guide/vti.md): Surface template type-checking errors on CLI +- [Global components](/guide/global-components.md): support define global components. ## Quick Start @@ -75,9 +77,8 @@ Thanks to the following companies for supporting Vetur's development: ## Limitations -- No multi root suppport yet ([#424](https://github.com/vuejs/vetur/issues/424)) -- Cannot handle tsconfig from non-top-level folder ([#815](https://github.com/vuejs/vetur/issues/815)) - You can restart Vue language service when Vetur slow ([#2192](https://github.com/vuejs/vetur/issues/2192)) +- yarn pnp (https://vuejs.github.io/vetur/guide/setup.html#yarn-pnp) ## Roadmap diff --git a/docs/README.md b/docs/README.md index 9a8d33402e..bdab945c7f 100644 --- a/docs/README.md +++ b/docs/README.md @@ -15,16 +15,17 @@ You can [open an issue](https://github.com/vuejs/vetur/issues/new) for bugs or f ## Features -- [Syntax-highlighting](highlighting.md) -- [Snippet](snippet.md) -- [Emmet](emmet.md) -- [Linting / Error Checking](linting-error.md) -- [Formatting](formatting.md) -- [IntelliSense](intellisense.md) -- [Debugging](debugging.md) -- [Component Data](framework.md): auto-completion and hover-information for popular Vue frameworks and your own custom components -- [Experimental Interpolation Features](interpolation.md): auto-completion, hover information and type-checking in Vue template -- [VTI](vti.md): Surface template type-checking errors on CLI +- [Syntax-highlighting](/guide/highlighting.md) +- [Snippet](/guide/snippet.md) +- [Emmet](/guide/emmet.md) +- [Linting / Error Checking](/guide/linting-error.md) +- [Formatting](/guide/formatting.md) +- [IntelliSense](/guide/intellisense.md) +- [Debugging](/guide/debugging.md) +- [Component Data](/guide/framework.md): auto-completion and hover-information for popular Vue frameworks and your own custom components +- [Experimental Interpolation Features](/guide/interpolation.md): auto-completion, hover information and type-checking in Vue template +- [VTI](/guide/vti.md): Surface template type-checking errors on CLI +- [Global components](/guide/global-components.md): support define global components. ## Quick Start diff --git a/docs/guide/setup.md b/docs/guide/setup.md index b182eb36a6..8192e15825 100644 --- a/docs/guide/setup.md +++ b/docs/guide/setup.md @@ -171,3 +171,9 @@ module.exports = { - [Read more `vetur.config.js` reference](/reference/). - [Read RFC](https://github.com/vuejs/vetur/blob/master/rfcs/001-vetur-config-file.md). + +## Yarn pnp +Vetur support this project now, but have some limits. + +- Don't mix common project and pnp project in multi-root/monorepo +- Prettier don't support yarn pnp, so can't load plugin automatic. From e6e498ca0be94d641fe00267a12fa4803a982b35 Mon Sep 17 00:00:00 2001 From: yoyo930021 Date: Tue, 8 Dec 2020 22:06:36 +0800 Subject: [PATCH 32/33] Refactor DependencyService --- server/src/services/dependencyService.ts | 224 +++++++++++------------ server/src/services/vls.ts | 3 +- 2 files changed, 105 insertions(+), 122 deletions(-) diff --git a/server/src/services/dependencyService.ts b/server/src/services/dependencyService.ts index d9889bc0ff..997118a737 100644 --- a/server/src/services/dependencyService.ts +++ b/server/src/services/dependencyService.ts @@ -88,137 +88,123 @@ export interface RuntimeLibrary { } export interface DependencyService { - useWorkspaceDependencies: boolean; - init( - rootPathForConfig: string, - workspacePath: string, - useWorkspaceDependencies: boolean, - nodeModulesPaths: string[], - tsSDKPath?: string - ): Promise; + readonly useWorkspaceDependencies: boolean; + readonly nodeModulesPaths: string[]; get(lib: L, filePath?: string): Dependency; getBundled(lib: L): Dependency; } -export const createDependencyService = (): DependencyService => { - let $useWorkspaceDependencies: boolean; - let $rootPathForConfig: string; - let $workspacePath: string; - let loaded: { [K in keyof RuntimeLibrary]: Dependency[] }; - - const bundledModules = { - typescript: ts, - prettier, - '@starptech/prettyhtml': prettyHTML, - 'prettier-eslint': prettierEslint, - 'prettier-tslint': prettierTslint, - 'stylus-supremacy': stylusSupremacy, - '@prettier/plugin-pug': prettierPluginPug - }; - - async function init( - rootPathForConfig: string, - workspacePath: string, - useWorkspaceDependencies: boolean, - nodeModulesPaths: string[], - tsSDKPath?: string - ) { - const loadTypeScript = async (): Promise[]> => { - try { - if (useWorkspaceDependencies && tsSDKPath) { - const dir = path.isAbsolute(tsSDKPath) - ? path.resolve(tsSDKPath, '..') - : path.resolve(workspacePath, tsSDKPath, '..'); - const tsModule = require(dir); - logger.logInfo(`Loaded typescript@${tsModule.version} from ${dir} for tsdk.`); - - return [ - { - dir, - version: tsModule.version as string, - bundled: false, - module: tsModule as typeof ts - } - ]; - } - - if (useWorkspaceDependencies) { - const packages = await findAllPackages(nodeModulesPaths, 'typescript'); - if (packages.length === 0) { - throw new Error(`No find any packages in ${rootPathForConfig}.`); - } +const bundledModules = { + typescript: ts, + prettier, + '@starptech/prettyhtml': prettyHTML, + 'prettier-eslint': prettierEslint, + 'prettier-tslint': prettierTslint, + 'stylus-supremacy': stylusSupremacy, + '@prettier/plugin-pug': prettierPluginPug +}; - return packages - .map(pkg => { - logger.logInfo(`Loaded typescript@${pkg.version} from ${pkg.dir}.`); +export const createDependencyService = async ( + rootPathForConfig: string, + workspacePath: string, + useWorkspaceDependencies: boolean, + nodeModulesPaths: string[], + tsSDKPath?: string +): Promise => { + let loaded: { [K in keyof RuntimeLibrary]: Dependency[] }; - return { - dir: pkg.dir, - version: pkg.version as string, - bundled: false, - module: pkg.module as typeof ts - }; - }) - .sort(compareDependency); - } + const loadTypeScript = async (): Promise[]> => { + try { + if (useWorkspaceDependencies && tsSDKPath) { + const dir = path.isAbsolute(tsSDKPath) + ? path.resolve(tsSDKPath, '..') + : path.resolve(workspacePath, tsSDKPath, '..'); + const tsModule = require(dir); + logger.logInfo(`Loaded typescript@${tsModule.version} from ${dir} for tsdk.`); - throw new Error('No useWorkspaceDependencies.'); - } catch (e) { - logger.logDebug(e.message); - logger.logInfo(`Loaded bundled typescript@${ts.version}.`); return [ { - dir: '', - version: ts.version, - bundled: true, - module: ts + dir, + version: tsModule.version as string, + bundled: false, + module: tsModule as typeof ts } ]; } - }; - const loadCommonDep = async (name: N, bundleModule: BM): Promise[]> => { - try { - if (useWorkspaceDependencies) { - const packages = await findAllPackages(nodeModulesPaths, name); - if (packages.length === 0) { - throw new Error(`No find ${name} packages in ${rootPathForConfig}.`); - } + if (useWorkspaceDependencies) { + const packages = await findAllPackages(nodeModulesPaths, 'typescript'); + if (packages.length === 0) { + throw new Error(`No find any packages in ${rootPathForConfig}.`); + } - return packages - .map(pkg => { - logger.logInfo(`Loaded ${name}@${pkg.version} from ${pkg.dir}.`); + return packages + .map(pkg => { + logger.logInfo(`Loaded typescript@${pkg.version} from ${pkg.dir}.`); - return { - dir: pkg.dir, - version: pkg.version as string, - bundled: false, - module: pkg.module as BM - }; - }) - .sort(compareDependency); + return { + dir: pkg.dir, + version: pkg.version as string, + bundled: false, + module: pkg.module as typeof ts + }; + }) + .sort(compareDependency); + } + + throw new Error('No useWorkspaceDependencies.'); + } catch (e) { + logger.logDebug(e.message); + logger.logInfo(`Loaded bundled typescript@${ts.version}.`); + return [ + { + dir: '', + version: ts.version, + bundled: true, + module: ts } - throw new Error('No useWorkspaceDependencies.'); - } catch (e) { - logger.logDebug(e.message); - // TODO: Get bundle package version - logger.logInfo(`Loaded bundled ${name}.`); - return [ - { - dir: '', - version: '', - bundled: true, - module: bundleModule as BM - } - ]; + ]; + } + }; + + const loadCommonDep = async (name: N, bundleModule: BM): Promise[]> => { + try { + if (useWorkspaceDependencies) { + const packages = await findAllPackages(nodeModulesPaths, name); + if (packages.length === 0) { + throw new Error(`No find ${name} packages in ${rootPathForConfig}.`); + } + + return packages + .map(pkg => { + logger.logInfo(`Loaded ${name}@${pkg.version} from ${pkg.dir}.`); + + return { + dir: pkg.dir, + version: pkg.version as string, + bundled: false, + module: pkg.module as BM + }; + }) + .sort(compareDependency); } - }; + throw new Error('No useWorkspaceDependencies.'); + } catch (e) { + logger.logDebug(e.message); + // TODO: Get bundle package version + logger.logInfo(`Loaded bundled ${name}.`); + return [ + { + dir: '', + version: '', + bundled: true, + module: bundleModule as BM + } + ]; + } + }; - $useWorkspaceDependencies = useWorkspaceDependencies; - $workspacePath = workspacePath; - $rootPathForConfig = rootPathForConfig; - // We don't need loaded when yarn pnp. https://yarnpkg.com/features/pnp - if (process.versions.pnp) { return; } + if (!process.versions.pnp) { loaded = { typescript: await loadTypeScript(), prettier: await loadCommonDep('prettier', bundledModules['prettier']), @@ -233,7 +219,7 @@ export const createDependencyService = (): DependencyService => { const get = (lib: L, filePath?: string): Dependency => { // We find it when yarn pnp. https://yarnpkg.com/features/pnp if (process.versions.pnp) { - const pkgPath = require.resolve(lib, { paths: [filePath ?? $workspacePath] }); + const pkgPath = require.resolve(lib, { paths: [filePath ?? workspacePath] }); return { dir: path.dirname(pkgPath), @@ -259,8 +245,8 @@ export const createDependencyService = (): DependencyService => { const possiblePaths: string[] = []; let tempPath = path.dirname(filePath); while ( - $rootPathForConfig === tempPath || - getPathDepth($rootPathForConfig, path.sep) > getPathDepth(tempPath, path.sep) + rootPathForConfig === tempPath || + getPathDepth(rootPathForConfig, path.sep) > getPathDepth(tempPath, path.sep) ) { possiblePaths.push(path.resolve(tempPath, `node_modules/${lib}`)); tempPath = path.resolve(tempPath, '../'); @@ -280,10 +266,8 @@ export const createDependencyService = (): DependencyService => { }; return { - get useWorkspaceDependencies() { - return $useWorkspaceDependencies; - }, - init, + useWorkspaceDependencies, + nodeModulesPaths, get, getBundled }; diff --git a/server/src/services/vls.ts b/server/src/services/vls.ts index 0a7e0182b9..5584d75157 100644 --- a/server/src/services/vls.ts +++ b/server/src/services/vls.ts @@ -300,7 +300,6 @@ export class VLS { } // init project - const dependencyService = createDependencyService(); // Yarn Pnp don't need this. https://yarnpkg.com/features/pnp const nodeModulePaths = !process.versions.pnp ? this.nodeModulesMap.get(projectConfig.rootPathForConfig) ?? @@ -309,7 +308,7 @@ export class VLS { if (this.nodeModulesMap.has(projectConfig.rootPathForConfig)) { this.nodeModulesMap.set(projectConfig.rootPathForConfig, nodeModulePaths); } - await dependencyService.init( + const dependencyService = await createDependencyService( projectConfig.rootPathForConfig, projectConfig.workspaceFsPath, projectConfig.vlsFullConfig.vetur.useWorkspaceDependencies, From b72b4049b5269bb816d729945b74fe2df5435fff Mon Sep 17 00:00:00 2001 From: yoyo930021 Date: Tue, 8 Dec 2020 22:07:00 +0800 Subject: [PATCH 33/33] Loading plugins for prettier --- server/src/utils/prettier/index.ts | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/server/src/utils/prettier/index.ts b/server/src/utils/prettier/index.ts index 96a166ca4d..dd7d6f4acc 100644 --- a/server/src/utils/prettier/index.ts +++ b/server/src/utils/prettier/index.ts @@ -147,6 +147,12 @@ function getPrettierOptions( prettierrcOptions.tabWidth = prettierrcOptions.tabWidth || vlsFormatConfig.options.tabSize; prettierrcOptions.useTabs = prettierrcOptions.useTabs || vlsFormatConfig.options.useTabs; prettierrcOptions.parser = parser; + if (dependencyService.useWorkspaceDependencies) { + // For loading plugins such as @prettier/plugin-pug + (prettierrcOptions as { + pluginSearchDirs: string[]; + }).pluginSearchDirs = dependencyService.nodeModulesPaths.map(el => path.dirname(el)); + } return prettierrcOptions; } else { @@ -154,6 +160,10 @@ function getPrettierOptions( vscodePrettierOptions.tabWidth = vscodePrettierOptions.tabWidth || vlsFormatConfig.options.tabSize; vscodePrettierOptions.useTabs = vscodePrettierOptions.useTabs || vlsFormatConfig.options.useTabs; vscodePrettierOptions.parser = parser; + if (dependencyService.useWorkspaceDependencies) { + // For loading plugins such as @prettier/plugin-pug + vscodePrettierOptions.pluginSearchDirs = dependencyService.nodeModulesPaths.map(el => path.dirname(el)); + } return vscodePrettierOptions; }