Skip to content

Commit

Permalink
Merge pull request #39 from lifeart/improve-lint-api
Browse files Browse the repository at this point in the history
Improve lint api
  • Loading branch information
lifeart authored Dec 21, 2019
2 parents 22dea04 + 6986681 commit bc833c6
Show file tree
Hide file tree
Showing 7 changed files with 133 additions and 30 deletions.
17 changes: 16 additions & 1 deletion src/builtin-addons/core/template-completion-provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,10 @@ import { emberBlockItems, emberMustacheItems, emberSubExpressionItems, emberModi
import { templateContextLookup } from './template-context-provider';
import { provideComponentTemplatePaths } from './template-definition-provider';

import { log } from '../../utils/logger';
import { log, logInfo, logError } from '../../utils/logger';
import ASTPath, { getLocalScope } from '../../glimmer-utils';
import Server from '../../server';
import { Project } from '../../project-roots';
import {
isLinkToTarget,
isComponentArgumentName,
Expand Down Expand Up @@ -80,6 +82,19 @@ function isArgumentName(name: string) {

export default class TemplateCompletionProvider {
constructor() {}
async initRegistry(_: Server, project: Project) {
try {
let initStartTime = Date.now();
mListHelpers(project.root);
mListModifiers(project.root);
mListRoutes(project.root);
mListComponents(project.root);
mGetProjectAddonsInfo(project.root);
logInfo(project.root + ': registry initialized in ' + (Date.now() - initStartTime) + 'ms');
} catch (e) {
logError(e);
}
}
getAllAngleBracketComponents(root: string, uri: string) {
const items: CompletionItem[] = [];
return uniqBy(
Expand Down
22 changes: 20 additions & 2 deletions src/builtin-addons/core/template-definition-provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { Definition, Location } from 'vscode-languageserver';
import { DefinitionFunctionParams } from './../../utils/addon-api';
import { isLinkToTarget, isLinkComponentRouteTarget } from './../../utils/ast-helpers';
import ASTPath from './../../glimmer-utils';
import { getGlobalRegistry } from './../../utils/layout-helpers';

import { isTemplatePath, getComponentNameFromURI, isModuleUnificationApp, getPodModulePrefix } from './../../utils/layout-helpers';

Expand All @@ -29,8 +30,23 @@ function normalizeAngleTagName(tagName: string) {
.join('/');
}

export function getPathsFromRegistry(type: 'helper' | 'modifier' | 'component', name: string, root: string): string[] {
const absRoot = path.normalize(root);
const registry = getGlobalRegistry();
const bucket: any = registry[type].get(name) || new Set();
return Array.from(bucket).filter((el: string) => path.normalize(el).includes(absRoot) && fs.existsSync(el)) as string[];
}

export function provideComponentTemplatePaths(root: string, rawComponentName: string) {
const maybeComponentName = normalizeAngleTagName(rawComponentName);
const items = getPathsFromRegistry('component', maybeComponentName, root);
if (items.length) {
const results = items.filter((el) => el.endsWith('.hbs'));
if (results.length) {
return results;
}
}

let paths = [...getPathsForComponentTemplates(root, maybeComponentName)].filter(fs.existsSync);

if (!paths.length) {
Expand Down Expand Up @@ -142,8 +158,10 @@ export default class TemplateDefinitionProvider {
}
_provideLikelyRawComponentTemplatePaths(root: string, rawComponentName: string) {
const maybeComponentName = normalizeAngleTagName(rawComponentName);
let paths = [...getPathsForComponentScripts(root, maybeComponentName), ...getPathsForComponentTemplates(root, maybeComponentName)].filter(fs.existsSync);

let paths = getPathsFromRegistry('component', maybeComponentName, root);
if (!paths.length) {
paths = [...getPathsForComponentScripts(root, maybeComponentName), ...getPathsForComponentTemplates(root, maybeComponentName)].filter(fs.existsSync);
}
if (!paths.length) {
paths = mAddonPathsForComponentTemplates(root, maybeComponentName);
}
Expand Down
23 changes: 21 additions & 2 deletions src/project-roots.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,21 +7,40 @@ import * as walkSync from 'walk-sync';
import { isGlimmerNativeProject, isGlimmerXProject } from './utils/layout-helpers';
import { ProjectProviders, collectProjectProviders, initBuiltinProviders } from './utils/addon-api';
import Server from './server';
import { TextDocument, Diagnostic } from 'vscode-languageserver';

export type Eexcutor = (server: Server, command: string, args: any[]) => any;
export type Linter = (document: TextDocument) => Diagnostic[];
export interface Executors {
[key: string]: (server: Server, command: string, args: any[]) => any;
[key: string]: Eexcutor;
}

export class Project {
providers!: ProjectProviders;
builtinProviders!: ProjectProviders;
executors: Executors = {};
linters: Linter[] = [];
addCommandExecutor(key: string, cb: Eexcutor) {
this.executors[key] = cb;
}
addLinter(cb: Linter) {
this.linters.push(cb);
}
constructor(public readonly root: string) {
this.providers = collectProjectProviders(root);
this.builtinProviders = initBuiltinProviders();
}
init(server: Server) {
this.builtinProviders.initFunctions.forEach((initFn) => initFn(server, this));
this.providers.initFunctions.forEach((initFn) => initFn(server, this));
if (this.providers.info.length) {
logInfo('--------------------');
logInfo('loded language server addons:');
this.providers.info.forEach((addonName) => {
logInfo(' ' + addonName);
});
logInfo('--------------------');
}
}
}

Expand Down Expand Up @@ -68,8 +87,8 @@ export default class ProjectRoots {
try {
const project = new Project(path);
this.projects.set(path, project);
project.init(this.server);
logInfo(`Ember CLI project added at ${path}`);
project.init(this.server);
} catch (e) {
logError(e);
}
Expand Down
48 changes: 34 additions & 14 deletions src/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,14 @@ import {
TextDocumentPositionParams,
CompletionItem,
StreamMessageReader,
WorkspaceFoldersChangeEvent,
StreamMessageWriter,
ReferenceParams,
Location
Location,
TextDocument
} from 'vscode-languageserver';

import ProjectRoots, { Executors } from './project-roots';
import ProjectRoots, { Project, Executors } from './project-roots';
import DefinitionProvider from './definition-providers/entry';
import TemplateLinter from './template-linter';
import DocumentSymbolProvider from './symbols/document-symbol-provider';
Expand Down Expand Up @@ -92,7 +94,9 @@ export default class Server {
templateLinter: TemplateLinter = new TemplateLinter(this);

referenceProvider: ReferenceProvider = new ReferenceProvider(this);

private onInitilized() {
this.connection.workspace.onDidChangeWorkspaceFolders(this.onDidChangeWorkspaceFolders.bind(this));
}
constructor() {
// Make the text document manager listen on the connection
// for open, change and close text document events
Expand All @@ -103,6 +107,7 @@ export default class Server {

// Bind event handlers
this.connection.onInitialize(this.onInitialize.bind(this));
this.connection.onInitialized(this.onInitilized.bind(this));
this.documents.onDidChangeContent(this.onDidChangeContent.bind(this));
this.connection.onDidChangeWatchedFiles(this.onDidChangeWatchedFiles.bind(this));
this.connection.onDocumentSymbol(this.onDocumentSymbol.bind(this));
Expand Down Expand Up @@ -172,6 +177,13 @@ export default class Server {
this.connection.listen();
}

private onDidChangeWorkspaceFolders(event: WorkspaceFoldersChangeEvent) {
if (event.added.length) {
event.added.forEach((folder) => {
this.projectRoots.findProjectsInsideRoot(folder.uri);
});
}
}
// After the server has started the client sends an initilize request. The server receives
// in the passed params the rootPath of the workspace plus the client capabilites.
private onInitialize({ rootUri, rootPath, workspaceFolders }: InitializeParams): InitializeResult {
Expand Down Expand Up @@ -205,6 +217,12 @@ export default class Server {
},
documentSymbolProvider: true,
referencesProvider: true,
workspace: {
workspaceFolders: {
supported: true,
changeNotifications: true
}
},
completionProvider: {
resolveProvider: true,
triggerCharacters: ['.', '::', '=', '/', '{{', '(', '<', '@', 'this.']
Expand All @@ -213,7 +231,6 @@ export default class Server {
};
}

linters: any[] = [];
executors: Executors = {};

private async onDidChangeContent(change: any) {
Expand All @@ -226,17 +243,20 @@ export default class Server {
results.push(result);
});
}
for (let linter of this.linters) {
try {
let tempResult = await linter(change.document);
// API must return array
if (Array.isArray(tempResult)) {
tempResult.forEach((el) => {
results.push(el as Diagnostic);
});
const project: Project | undefined = this.projectRoots.projectForUri(change.document.uri);
if (project) {
for (let linter of project.linters) {
try {
let tempResults = await linter(change.document as TextDocument);
// API must return array
if (Array.isArray(tempResults)) {
tempResults.forEach((el) => {
results.push(el as Diagnostic);
});
}
} catch (e) {
logError(e);
}
} catch (e) {
logError(e);
}
}

Expand Down
25 changes: 17 additions & 8 deletions src/utils/addon-api.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
import { Location, TextDocumentIdentifier, Position, CompletionItem } from 'vscode-languageserver';
import { getProjectAddonsRoots, getPackageJSON, getProjectInRepoAddonsRoots } from './layout-helpers';
import {
getProjectAddonsRoots,
getPackageJSON,
getProjectInRepoAddonsRoots,
PackageInfo,
ADDON_CONFIG_KEY,
hasEmberLanguageServerExtension
} from './layout-helpers';
import * as path from 'path';
import { log, logInfo, logError } from './logger';
import Server from '../server';
Expand All @@ -11,7 +18,6 @@ import ScriptCompletionProvider from './../builtin-addons/core/script-completion
import TemplateCompletionProvider from './../builtin-addons/core/template-completion-provider';
import { Project } from '../project-roots';

const ADDON_CONFIG_KEY = 'ember-language-server';
interface BaseAPIParams {
server: Server;
textDocument: TextDocumentIdentifier;
Expand Down Expand Up @@ -53,7 +59,7 @@ interface HandlerObject {
updateHandler: () => void;
packageRoot: string;
debug: boolean;
packageJSON: any;
packageJSON: PackageInfo;
capabilities: NormalizedCapabilities;
}

Expand Down Expand Up @@ -82,7 +88,8 @@ export function initBuiltinProviders(): ProjectProviders {
return {
definitionProviders: [scriptDefinition.onDefinition.bind(scriptDefinition), templateDefinition.onDefinition.bind(templateDefinition)],
referencesProviders: [],
initFunctions: [],
initFunctions: [templateCompletion.initRegistry.bind(this)],
info: [],
completionProviders: [scriptCompletion.onComplete.bind(scriptCompletion), templateCompletion.onComplete.bind(templateCompletion)]
};
}
Expand Down Expand Up @@ -127,11 +134,13 @@ export function collectProjectProviders(root: string): ProjectProviders {
referencesProviders: ReferenceResolveFunction[];
completionProviders: CompletionResolveFunction[];
initFunctions: InitFunction[];
info: string[];
} = {
definitionProviders: [],
referencesProviders: [],
completionProviders: [],
initFunctions: []
initFunctions: [],
info: []
};

// onReference, onComplete, onDefinition
Expand All @@ -143,6 +152,7 @@ export function collectProjectProviders(root: string): ProjectProviders {

// let's reload files in case of debug mode for each request
if (handlerObject.debug) {
result.info.push('addon-in-debug-mode: ' + _);
logInfo(`els-addon-api: debug mode enabled for ${handlerObject.packageRoot}, for all requests resolvers will be reloaded.`);
result.completionProviders.push(function(root: string, params: CompletionFunctionParams) {
handlerObject.updateHandler();
Expand Down Expand Up @@ -177,6 +187,7 @@ export function collectProjectProviders(root: string): ProjectProviders {
}
} as InitFunction);
} else {
result.info.push('addon: ' + _);
if (handlerObject.capabilities.completionProvider && typeof handlerObject.handler.onComplete === 'function') {
result.completionProviders.push(handlerObject.handler.onComplete);
}
Expand All @@ -200,6 +211,7 @@ export interface ProjectProviders {
referencesProviders: ReferenceResolveFunction[];
completionProviders: CompletionResolveFunction[];
initFunctions: InitFunction[];
info: string[];
}

interface ExtensionCapabilities {
Expand Down Expand Up @@ -236,6 +248,3 @@ export function languageServerHandler(info: any): string {
export function isDebugModeEnabled(info: any): boolean {
return info[ADDON_CONFIG_KEY].debug === true;
}
export function hasEmberLanguageServerExtension(info: any) {
return ADDON_CONFIG_KEY in info;
}
22 changes: 19 additions & 3 deletions src/utils/layout-helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import { CompletionItem, CompletionItemKind } from 'vscode-languageserver';
type GLOBAL_REGISTRY_ITEM = Map<string, Set<string>>;
export type REGISTRY_KIND = 'transform' | 'helper' | 'component' | 'routePath' | 'model' | 'service' | 'modifier';

export const ADDON_CONFIG_KEY = 'ember-language-server';

const GLOBAL_REGISTRY: {
transform: GLOBAL_REGISTRY_ITEM;
helper: GLOBAL_REGISTRY_ITEM;
Expand All @@ -30,6 +32,10 @@ export function getGlobalRegistry() {
return GLOBAL_REGISTRY;
}

export function hasEmberLanguageServerExtension(info: PackageInfo) {
return info[ADDON_CONFIG_KEY] !== undefined;
}

export function addToRegistry(normalizedName: string, kind: REGISTRY_KIND, files: string[]) {
if (!GLOBAL_REGISTRY[kind].has(normalizedName)) {
GLOBAL_REGISTRY[kind].set(normalizedName, new Set());
Expand Down Expand Up @@ -62,11 +68,17 @@ export const isAddonRoot = memoize(isProjectAddonRoot, {
maxAge: 600000
});

interface PackageInfo {
export interface PackageInfo {
keywords?: string[];
name?: string;
'ember-language-server'?: {};
peerDependencies?: {};
devDependencies?: {};
dependencies?: {};
'ember-addon'?: {
version?: number;
before?: string | string[];
after?: string | string[];
};
}

Expand Down Expand Up @@ -223,7 +235,6 @@ export function isGlimmerXProject(root: string) {
}

export function getProjectAddonsRoots(root: string, resolvedItems: string[] = [], packageFolderName = 'node_modules') {
// log('getProjectAddonsInfo', root);
const pack = getPackageJSON(root);
if (resolvedItems.length) {
if (!isEmberAddon(pack)) {
Expand All @@ -245,6 +256,11 @@ export function getProjectAddonsRoots(root: string, resolvedItems: string[] = []
});
const recursiveRoots: string[] = resolvedItems.slice(0);
roots.forEach((rootItem: string) => {
let packInfo = getPackageJSON(rootItem);
// we don't need to go deeper if package itself not an ember-addon or els-extension
if (!isEmberAddon(packInfo) && !hasEmberLanguageServerExtension(packInfo)) {
return;
}
if (!recursiveRoots.includes(rootItem)) {
recursiveRoots.push(rootItem);
getProjectAddonsRoots(rootItem, recursiveRoots, packageFolderName).forEach((item: string) => {
Expand All @@ -257,7 +273,7 @@ export function getProjectAddonsRoots(root: string, resolvedItems: string[] = []
return recursiveRoots;
}

export function getPackageJSON(file: string) {
export function getPackageJSON(file: string): PackageInfo {
try {
const result = JSON.parse(fs.readFileSync(path.join(file, 'package.json'), 'utf8'));
return result;
Expand Down
6 changes: 6 additions & 0 deletions test/__snapshots__/integration-test.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -2683,6 +2683,12 @@ Object {
},
"referencesProvider": true,
"textDocumentSync": 1,
"workspace": Object {
"workspaceFolders": Object {
"changeNotifications": true,
"supported": true,
},
},
},
}
`;

0 comments on commit bc833c6

Please sign in to comment.