Skip to content

Commit

Permalink
feat: addon-api hover provider (ember-tooling#327)
Browse files Browse the repository at this point in the history
* feat: addon-api hover provider

* fix typings

* attempt to improve coverage

* fix found issues

* update snapshot

* support optional providers in default function,

* rollback package.json changes

* fix missing addons meta

* fix snapshot tests

* increase test timeout
  • Loading branch information
lifeart authored Nov 1, 2021
1 parent 290be29 commit 3b739f5
Show file tree
Hide file tree
Showing 11 changed files with 245 additions and 45 deletions.
1 change: 1 addition & 0 deletions jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ module.exports = {
},
testRegex: '-test\\.ts$',
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'],
testTimeout: 15000,
globals: {
'ts-jest': {
diagnostics: false,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ export default class TemplateLintFixesCodeAction extends BaseCodeActionProvider
async (issue): Promise<null | CodeAction> => {
const { output, isFixed } = await Promise.resolve(
linter.verifyAndFix({
source: meta.selection,
source: meta.selection || '',
moduleId: URI.parse(params.textDocument.uri).fsPath,
filePath: URI.parse(params.textDocument.uri).fsPath,
})
Expand Down
23 changes: 23 additions & 0 deletions src/hover-provider/entry.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { Hover, HoverParams } from 'vscode-languageserver/node';
import Server from '../server';
import { queryELSAddonsAPIChain } from './../utils/addon-api';

export class HoverProvider {
constructor(private server: Server) {}
async provideHovers({ textDocument, position }: HoverParams): Promise<Hover[]> {
const project = this.server.projectRoots.projectForUri(textDocument.uri);

if (!project) {
return [];
}

const addonResults = await queryELSAddonsAPIChain(project.providers.hoverProviders, project.root, {
textDocument,
position,
results: [],
server: this.server,
});

return addonResults;
}
}
21 changes: 3 additions & 18 deletions src/project-roots.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { isGlimmerXProject, isELSAddonRoot, isRootStartingWithFilePath, safeWalk
import Server from './server';

import { Project } from './project';
import { emptyProjectProviders } from './utils/addon-api';

export default class ProjectRoots {
constructor(private server: Server) {}
Expand Down Expand Up @@ -146,15 +147,7 @@ export default class ProjectRoots {

return {
initIssues: [`Unable to create project "${info.name}", because it ignored according to config: [${this.ignoredProjects.join(',')}]`],
providers: {
definitionProviders: [],
referencesProviders: [],
completionProviders: [],
codeActionProviders: [],
initFunctions: [],
info: [],
addonsMeta: [],
},
providers: emptyProjectProviders(),
addonsMeta: [],
name: info.name,
registry: {},
Expand All @@ -181,15 +174,7 @@ export default class ProjectRoots {

return {
initIssues: [e.toString(), e.stack],
providers: {
definitionProviders: [],
referencesProviders: [],
completionProviders: [],
codeActionProviders: [],
initFunctions: [],
info: [],
addonsMeta: [],
},
providers: emptyProjectProviders(),
addonsMeta: [],
name: `[${projectPath}]`,
registry: {},
Expand Down
11 changes: 11 additions & 0 deletions src/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ import {
ExecuteCommandParams,
TextDocumentChangeEvent,
ExecuteCommandRequest,
HoverParams,
Hover,
} from 'vscode-languageserver';

import ProjectRoots from './project-roots';
Expand Down Expand Up @@ -62,6 +64,7 @@ import { debounce } from 'lodash';
import { Config, Initializer } from './types';
import { asyncGetJSON, isFileBelongsToRoots, mGetProjectAddonsInfo, setRequireSupport, setSyncFSSupport } from './utils/layout-helpers';
import FSProvider, { AsyncFsProvider, setFSImplementation } from './fs-provider';
import { HoverProvider } from './hover-provider/entry';

export interface IServerConfig {
local: Config;
Expand Down Expand Up @@ -151,6 +154,7 @@ export default class Server {
templateLinter!: TemplateLinter;

referenceProvider!: ReferenceProvider;
hoverProvider!: HoverProvider;
codeActionProvider!: CodeActionProvider;
async executeInitializers() {
logInfo('UELS: executeInitializers');
Expand Down Expand Up @@ -387,6 +391,7 @@ export default class Server {
this.scriptCompletionProvider = new ScriptCompletionProvider(this);
this.definitionProvider = new DefinitionProvider(this);
this.referenceProvider = new ReferenceProvider(this);
this.hoverProvider = new HoverProvider(this);
this.codeActionProvider = new CodeActionProvider(this);

this.documents.listen(this.connection);
Expand All @@ -407,6 +412,7 @@ export default class Server {
this.connection.onCompletionResolve(this.onCompletionResolve.bind(this));
this.connection.onExecuteCommand(this.onExecute.bind(this));
this.connection.onReferences(this.onReference.bind(this));
this.connection.onHover(this.onHover.bind(this));
this.connection.onCodeAction(this.onCodeAction.bind(this));
this.connection.telemetry.logEvent({ connected: true });
}
Expand Down Expand Up @@ -536,6 +542,7 @@ export default class Server {
documentSymbolProvider: true,
codeActionProvider: true,
referencesProvider: true,
hoverProvider: true,
workspace: {
workspaceFolders: {
supported: true,
Expand Down Expand Up @@ -663,6 +670,10 @@ export default class Server {
return await this.referenceProvider.provideReferences(params);
}

private async onHover(params: HoverParams): Promise<Hover[]> {
return await this.hoverProvider.provideHovers(params);
}

private async onCompletionResolve(item: CompletionItem) {
return item;
}
Expand Down
44 changes: 37 additions & 7 deletions src/template-linter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,32 @@ import { toDiagnostic, toHbsSource } from './utils/diagnostic';
import { getTemplateNodes } from '@lifeart/ember-extract-inline-templates';
import { parseScriptFile } from 'ember-meta-explorer';
import { URI } from 'vscode-uri';
import { log, logError } from './utils/logger';
import { log, logError, logDebugInfo } from './utils/logger';
import * as findUp from 'find-up';
import * as path from 'path';

import Server from './server';
import { Project } from './project';
import { getRequireSupport } from './utils/layout-helpers';

type LinterVerifyArgs = { source: string; moduleId: string; filePath: string };
class Linter {
constructor() {
return this;
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
verify(_params: LinterVerifyArgs): TemplateLinterError[] {
return [];
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
verifyAndFix(_params: LinterVerifyArgs): { isFixed: boolean; output: string } {
return {
output: '',
isFixed: true,
};
}
}

export interface TemplateLinterError {
fatal?: boolean;
moduleId: string;
Expand All @@ -37,7 +55,7 @@ function setCwd(cwd: string) {
}

export default class TemplateLinter {
private _linterCache = new Map<Project, any>();
private _linterCache = new Map<Project, typeof Linter>();
private _isEnabled = true;

constructor(private server: Server) {
Expand Down Expand Up @@ -124,13 +142,21 @@ export default class TemplateLinter {

const TemplateLinterKlass = await this.getLinter(project);

let linter: typeof TemplateLinterKlass | null = null;
if (!TemplateLinterKlass) {
return;
}

let linter: Linter;

try {
setCwd(project.root);
linter = new TemplateLinterKlass();
} catch (e) {
setCwd(cwd);
try {
setCwd(cwd);
} catch (e) {
logDebugInfo(e.stack);
}

return;
}
Expand Down Expand Up @@ -159,7 +185,11 @@ export default class TemplateLinter {
logError(e);
}

setCwd(cwd);
try {
setCwd(cwd);
} catch (e) {
logDebugInfo(e.stack);
}

return diagnostics;
}
Expand All @@ -171,7 +201,7 @@ export default class TemplateLinter {
return await this.getLinter(project);
}
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
private async getLinter(project: Project) {
private async getLinter(project: Project): Promise<typeof Linter | undefined> {
if (this._linterCache.has(project)) {
return this._linterCache.get(project);
}
Expand Down Expand Up @@ -210,7 +240,7 @@ export default class TemplateLinter {
const requireFunc = typeof __webpack_require__ === 'function' ? __non_webpack_require__ : require;

// eslint-disable-next-line @typescript-eslint/no-var-requires
const linter = requireFunc(linterPath);
const linter: typeof Linter = requireFunc(linterPath);

this._linterCache.set(project, linter);

Expand Down
59 changes: 41 additions & 18 deletions src/utils/addon-api.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Location, TextDocumentIdentifier, Command, CodeActionParams, CodeAction, Position, CompletionItem } from 'vscode-languageserver/node';
import { Location, TextDocumentIdentifier, Command, CodeActionParams, CodeAction, Position, CompletionItem, Hover } from 'vscode-languageserver/node';
import {
getProjectAddonsRoots,
getProjectInRepoAddonsRoots,
Expand Down Expand Up @@ -30,6 +30,10 @@ interface ExtendedAPIParams extends BaseAPIParams {
export interface ReferenceFunctionParams extends BaseAPIParams {
results: Location[];
}

export interface HoverFunctionParams extends BaseAPIParams {
results: Hover[];
}
export interface CompletionFunctionParams extends ExtendedAPIParams {
results: CompletionItem[];
}
Expand All @@ -42,6 +46,8 @@ export interface CodeActionFunctionParams extends CodeActionParams {
}

type ReferenceResolveFunction = (root: string, params: ReferenceFunctionParams) => Promise<Location[]>;

type HoverResolveFunction = (root: string, params: HoverFunctionParams) => Promise<Hover[]>;
type CompletionResolveFunction = (root: string, params: CompletionFunctionParams) => Promise<CompletionItem[]>;
type DefinitionResolveFunction = (root: string, params: DefinitionFunctionParams) => Promise<Location[]>;
type CodeActionResolveFunction = (root: string, params: CodeActionFunctionParams) => Promise<(Command | CodeAction)[] | undefined | null>;
Expand All @@ -55,6 +61,7 @@ export interface AddonAPI {
}

interface PublicAddonAPI {
onHover?: HoverResolveFunction;
onReference?: ReferenceResolveFunction;
onComplete?: CompletionResolveFunction;
onDefinition?: DefinitionResolveFunction;
Expand Down Expand Up @@ -159,6 +166,10 @@ function requireUncached(module: string) {
instanceResult.onReference = instance.onReference.bind(instance);
}

if (typeof instance.onHover === 'function') {
instanceResult.onHover = instance.onHover.bind(instance);
}

return instanceResult;
}
} catch (e) {
Expand Down Expand Up @@ -220,19 +231,13 @@ export async function collectProjectProviders(root: string, addons: string[]): P
referencesProviders: ReferenceResolveFunction[];
completionProviders: CompletionResolveFunction[];
codeActionProviders: CodeActionResolveFunction[];
hoverProviders: HoverResolveFunction[];
initFunctions: InitFunction[];
info: string[];
addonsMeta: AddonMeta[];
} = {
definitionProviders: [],
referencesProviders: [],
completionProviders: [],
codeActionProviders: [],
initFunctions: [],
info: [],
} = emptyProjectProviders({
addonsMeta,
};

});
// onReference, onComplete, onDefinition

dagMap.each((_, handlerObject) => {
Expand Down Expand Up @@ -262,6 +267,15 @@ export async function collectProjectProviders(root: string, addons: string[]): P
return params.results;
}
} as ReferenceResolveFunction);
result.hoverProviders.push(function (root: string, params: HoverFunctionParams) {
handlerObject.updateHandler();

if (typeof handlerObject.handler.onHover === 'function') {
return handlerObject.handler.onHover(root, params);
} else {
return params.results;
}
} as HoverResolveFunction);
result.definitionProviders.push(function (root: string, params: DefinitionFunctionParams) {
handlerObject.updateHandler();

Expand Down Expand Up @@ -300,6 +314,10 @@ export async function collectProjectProviders(root: string, addons: string[]): P
result.referencesProviders.push(handlerObject.handler.onReference);
}

if (handlerObject.capabilities.hoverProvider && typeof handlerObject.handler.onHover === 'function') {
result.hoverProviders.push(handlerObject.handler.onHover);
}

if (handlerObject.capabilities.definitionProvider && typeof handlerObject.handler.onDefinition === 'function') {
result.definitionProviders.push(handlerObject.handler.onDefinition);
}
Expand All @@ -320,19 +338,21 @@ export async function collectProjectProviders(root: string, addons: string[]): P
export type AddonMeta = { root: string; name: string; version: null | 1 | 2 };
export type DependencyMeta = { name: string; version: string };

export function emptyProjectProviders(): ProjectProviders {
export function emptyProjectProviders(providers?: Partial<ProjectProviders>): ProjectProviders {
return {
definitionProviders: [],
referencesProviders: [],
completionProviders: [],
codeActionProviders: [],
initFunctions: [],
info: [],
addonsMeta: [],
definitionProviders: providers?.definitionProviders ?? [],
hoverProviders: providers?.hoverProviders ?? [],
referencesProviders: providers?.referencesProviders ?? [],
completionProviders: providers?.completionProviders ?? [],
codeActionProviders: providers?.codeActionProviders ?? [],
initFunctions: providers?.initFunctions ?? [],
info: providers?.info ?? [],
addonsMeta: providers?.addonsMeta ?? [],
};
}

export interface ProjectProviders {
hoverProviders: HoverResolveFunction[];
definitionProviders: DefinitionResolveFunction[];
referencesProviders: ReferenceResolveFunction[];
completionProviders: CompletionResolveFunction[];
Expand All @@ -343,6 +363,7 @@ export interface ProjectProviders {
}

export interface ExtensionCapabilities {
hoverProvider: undefined | true | false;
definitionProvider: undefined | true | false;
codeActionProvider: undefined | true | false;
referencesProvider:
Expand All @@ -355,6 +376,7 @@ export interface ExtensionCapabilities {
}

interface NormalizedCapabilities {
hoverProvider: true | false;
definitionProvider: true | false;
referencesProvider: true | false;
completionProvider: true | false;
Expand All @@ -363,6 +385,7 @@ interface NormalizedCapabilities {

function normalizeCapabilities(raw: ExtensionCapabilities): NormalizedCapabilities {
return {
hoverProvider: raw.hoverProvider === true,
definitionProvider: raw.definitionProvider === true,
codeActionProvider: raw.codeActionProvider === true,
referencesProvider: raw.referencesProvider === true || (typeof raw.referencesProvider === 'object' && raw.referencesProvider.components === true),
Expand Down
1 change: 1 addition & 0 deletions src/utils/builtin-addons-initializer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ export function initBuiltinProviders(addonsMeta: AddonMeta[]): ProjectProviders
definitionProviders,
referencesProviders,
codeActionProviders,
hoverProviders: [],
initFunctions,
info: [],
addonsMeta: [],
Expand Down
Loading

0 comments on commit 3b739f5

Please sign in to comment.