Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

[Not Ready] [typespec-vscode] Improvements for the intellisense of tspconfig.yaml #5186

Open
wants to merge 21 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
---
# Change versionKind to one of: internal, fix, dependencies, feature, deprecation, breaking
changeKind: fix
packages:
- "@typespec/compiler"
---

Improvements for the intellisense of tspconfig.yaml
- Supports auto-completion of the extends and imports paths
- The rule or ruleSets of the linter can be auto-completed
- Emitter optoins autocomplete intelligently handles quotation mark display
- Autocomplete of variable interpolation
- The parameters of emitter's options distinguish whether they are required or optional
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please create two changelog items, one as new feature for:
Support the auto completion for extends, imports, rule, rule sets and variables in tspconfig.yaml
Show required/optional information in the details of emitter's options completion item in tspconfig.yaml
the other as bug fix for:
Fix the issue that emitter option auto completion would extra " while inside "..."

Original file line number Diff line number Diff line change
@@ -1,54 +1,57 @@
import { joinPaths } from "../core/path-utils.js";
import { NpmPackage, NpmPackageProvider } from "./npm-package-provider.js";

export class EmitterProvider {
private isEmitterPackageCache = new Map<string, boolean>();
constructor(private npmPackageProvider: NpmPackageProvider) {}
export class LibraryProvider {
private libPackageFilterResultCache = new Map<string, boolean>();
constructor(
private npmPackageProvider: NpmPackageProvider,
private filter: (libExports: Record<string, any>) => boolean,
) {}

/**
*
* @param startFolder folder starts to search for package.json with emitters defined as dependencies
* @param startFolder folder starts to search for package.json with library defined as dependencies
* @returns
*/
async listEmitters(startFolder: string): Promise<Record<string, NpmPackage>> {
async listLibraries(startFolder: string): Promise<Record<string, NpmPackage>> {
const packageJsonFolder = await this.npmPackageProvider.getPackageJsonFolder(startFolder);
if (!packageJsonFolder) return {};

const pkg = await this.npmPackageProvider.get(packageJsonFolder);
const data = await pkg?.getPackageJsonData();
if (!data) return {};

const emitters: Record<string, NpmPackage> = {};
const libs: Record<string, NpmPackage> = {};
const allDep = {
...(data.dependencies ?? {}),
...(data.devDependencies ?? {}),
};
for (const dep of Object.keys(allDep)) {
const depPkg = await this.getEmitterFromDep(packageJsonFolder, dep);
const depPkg = await this.getLibraryFromDep(packageJsonFolder, dep);
if (depPkg) {
emitters[dep] = depPkg;
libs[dep] = depPkg;
}
}
return emitters;
return libs;
}

/**
*
* @param startFolder folder starts to search for package.json with emitters defined as dependencies
* @param emitterName
* @param startFolder folder starts to search for package.json with library defined as dependencies
* @param libName
* @returns
*/
async getEmitter(startFolder: string, emitterName: string): Promise<NpmPackage | undefined> {
async getLibrary(startFolder: string, libName: string): Promise<NpmPackage | undefined> {
const packageJsonFolder = await this.npmPackageProvider.getPackageJsonFolder(startFolder);
if (!packageJsonFolder) {
return undefined;
}
return this.getEmitterFromDep(packageJsonFolder, emitterName);
return this.getLibraryFromDep(packageJsonFolder, libName);
}

private async isEmitter(depName: string, pkg: NpmPackage) {
if (this.isEmitterPackageCache.has(depName)) {
return this.isEmitterPackageCache.get(depName);
private async getLibFilterResult(depName: string, pkg: NpmPackage) {
if (this.libPackageFilterResultCache.has(depName)) {
return this.libPackageFilterResultCache.get(depName);
}

const data = await pkg.getPackageJsonData();
Expand All @@ -61,19 +64,20 @@ export class EmitterProvider {
const exports = await pkg.getModuleExports();
// don't add to cache when failing to load exports which is unexpected
if (!exports) return false;
const isEmitter = exports.$onEmit !== undefined;
this.isEmitterPackageCache.set(depName, isEmitter);
return isEmitter;

const filterResult = this.filter(exports);
this.libPackageFilterResultCache.set(depName, filterResult);
return filterResult;
} else {
this.isEmitterPackageCache.set(depName, false);
this.libPackageFilterResultCache.set(depName, false);
return false;
}
}

private async getEmitterFromDep(packageJsonFolder: string, depName: string) {
private async getLibraryFromDep(packageJsonFolder: string, depName: string) {
const depFolder = joinPaths(packageJsonFolder, "node_modules", depName);
const depPkg = await this.npmPackageProvider.get(depFolder);
if (depPkg && (await this.isEmitter(depName, depPkg))) {
if (depPkg && (await this.getLibFilterResult(depName, depPkg))) {
return depPkg;
}
return undefined;
Expand Down
13 changes: 11 additions & 2 deletions packages/compiler/src/server/serverlib.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,9 +80,9 @@ import { createCompileService } from "./compile-service.js";
import { resolveCompletion } from "./completion.js";
import { Commands } from "./constants.js";
import { convertDiagnosticToLsp } from "./diagnostics.js";
import { EmitterProvider } from "./emitter-provider.js";
import { createFileService } from "./file-service.js";
import { createFileSystemCache } from "./file-system-cache.js";
import { LibraryProvider } from "./lib-provider.js";
import { NpmPackageProvider } from "./npm-package-provider.js";
import { getSymbolStructure } from "./symbol-structure.js";
import { provideTspconfigCompletionItems } from "./tspconfig/completion.js";
Expand Down Expand Up @@ -114,7 +114,14 @@ export function createServer(host: ServerHost): Server {
});
const compilerHost = createCompilerHost();
const npmPackageProvider = new NpmPackageProvider(compilerHost);
const emitterProvider = new EmitterProvider(npmPackageProvider);
const emitterProvider = new LibraryProvider(
npmPackageProvider,
(exports) => exports.$onEmit !== undefined,
);
const linterProvider = new LibraryProvider(
npmPackageProvider,
(exports) => exports.$linter !== undefined,
);

const compileService = createCompileService({
fileService,
Expand Down Expand Up @@ -703,7 +710,9 @@ export function createServer(host: ServerHost): Server {
if (doc) {
const items = await provideTspconfigCompletionItems(doc, params.position, {
fileService,
compilerHost,
emitterProvider,
linterProvider,
log,
});
return CompletionList.create(items);
Expand Down
Loading
Loading