els-addon-typed-templates
Version:
Ember Language Server Typed Templates
227 lines (206 loc) • 8.28 kB
text/typescript
import { PLACEHOLDER, isHBS, isJS, isTS } from "./../lib/utils";
import * as fs from "fs";
import { withDebug } from '../lib/logger';
import {
findComponentForTemplate,
relativeComponentImport,
relativeAddonImport
} from "./../lib/resolvers";
import { MatchResult, typeForPath, LSRegistry } from './../lib/ts-service';
import { TypescriptTemplateBuilder } from "./../lib/hbs-converter";
import { getClassMeta } from './../lib/ast-parser';
import { Project, Server } from '@lifeart/ember-language-server';
export default class VirtualDocumentProvider {
builder!: TypescriptTemplateBuilder;
constructor(private server: Server, private project: Project) {
this.builder = new TypescriptTemplateBuilder(server, project)
}
unknownComponentTemplate(meta) {
return `export default class ${meta.className}Template {};`;
}
createFullVirtualTemplate(
componentsMap,
templatePath,
fileName,
uri,
content: string | boolean = false,
meta: MatchResult
) {
const projectRoot = this.project.root;
const server = this.server;
const document = server.documents.get(uri);
if (!document && !content) {
if (fs.existsSync(templatePath)) {
content = fs.readFileSync(templatePath, 'utf8');
} else {
return this.unknownComponentTemplate(meta);
}
}
const registry = "getRegistry" in server ? server.getRegistry(projectRoot) : null;
content = content ? content : (document ? document.getText() : "");
const { nodes, comments } = getClassMeta(content);
let scriptForComponent = findComponentForTemplate(templatePath, this.project, (registry as unknown) as LSRegistry)
// console.log('scriptForComponent', scriptForComponent);
let relComponentImport: string | null = null;
if (scriptForComponent) {
relComponentImport = relativeComponentImport(fileName, scriptForComponent);
}
// console.log('scriptForComponent', scriptForComponent);
componentsMap[fileName] = this.builder.getClass(componentsMap,
fileName,
{ nodes, comments, meta },
relComponentImport,
getValidRegistryItems(registry, fileName, projectRoot)
);
withDebug(()=>{
console.log("===============");
fs.writeFileSync(fileName, componentsMap[fileName], "utf8");
console.log(fileName);
console.log("===============");
});
return componentsMap[fileName];
}
createVirtualTemplate(
componentsMap,
fileName,
{ templatePath, realPath, isArg, isArrayCase, isParam }: any
) {
const projectRoot = this.project.root;
const scriptForComponent = findComponentForTemplate(
templatePath,
this.project,
this.server.getRegistry(this.project.root)
);
let isTemplateOnly = false;
let relComponentImport: any = undefined;
let className = 'Template';
const meta = typeForPath(projectRoot, templatePath);
if (meta) {
className = meta.className + 'Template';
}
if (!scriptForComponent) {
isTemplateOnly = true;
} else {
relComponentImport = relativeComponentImport(fileName, scriptForComponent);
}
componentsMap[fileName] = this.getBasicComponent(realPath, {
relComponentImport,
isArg,
isParam,
className,
isTemplateOnly,
isArrayCase
});
let posStart = this.getBasicComponent(PLACEHOLDER, {
relComponentImport,
isParam,
className,
isTemplateOnly,
isArrayCase,
isArg
}).indexOf(PLACEHOLDER);
let pos = posStart + realPath.length;
return { pos, posStart };
}
private getBasicComponent(pathExp = PLACEHOLDER, flags: any = {}) {
let outputType = "string | number | void";
let relImport = flags.relComponentImport || "./component";
let templateOnly = "";
let className = "Template";
if (flags.className) {
className = flags.className;
}
if (flags.isTemplateOnly) {
templateOnly = "args: any;";
}
if (flags.isArrayCase) {
outputType = "any[]";
}
if (flags.isParam) {
outputType = "any";
}
if (flags.isTemplateOnly) {
return [
`export default class ${className} {`,
`constructor(owner, args) {
this.args = args;
};`,
templateOnly,
`_template_PathExpresion(): ${outputType} {`,
"return " + pathExp,
"}",
"}"
].join("");
}
return [
`import Component from "${relImport}";`,
`export default class ${className} extends Component {`,
templateOnly,
`_template_PathExpresion(): ${outputType} {`,
"return " + pathExp,
"}",
"}"
].join("");
}
}
function normalizePath(name: string) {
return name.split('\\').join('/');
}
function getValidRegistryItems(registry: any, templateFile: string, projectRoot: string) {
const items: any = {};
if (!projectRoot) {
return items;
}
if (registry === null) {
return items;
} else {
withDebug(()=>{
fs.writeFileSync(projectRoot + '/registry.json', JSON.stringify(registry), 'utf8');
});
const keys = ["helper", "modifier"];
keys.forEach(keyName => {
Object.keys(registry[keyName]).forEach(name => {
const itemPaths = registry[keyName][name].filter(
p => !isHBS(p) && !normalizePath(name).includes('/tests/') && !normalizePath(name).includes('/dist/')
);
let primaryPath = itemPaths.find(p => isTS(p));
if (primaryPath) {
items[name] = relativeAddonImport(templateFile, primaryPath);
} else {
if (itemPaths.length) {
items[name] = relativeAddonImport(templateFile, itemPaths.sort()[0]);
}
}
if (items[name] === null) {
delete items[name];
}
});
});
const componentKeys = ["component"];
componentKeys.forEach(keyName => {
// @to-do - fix this creepy stuff
Object.keys(registry[keyName]).forEach(name => {
const hasScriptHbs = registry[keyName][name].find(name => isHBS(name));
const componentScripts = registry[keyName][name].filter(
p => !isHBS(p) && !normalizePath(p).includes('/tests/') && !normalizePath(p).includes('/dist/')
).sort();
const hasScriptTs = componentScripts.find(name => isTS(name));
const hasScriptJs = componentScripts.find(name => isJS(name));
const hasAddonTs = componentScripts.find(name => isTS(name) && normalizePath(name).includes('/addon/'));
const hasAddonJs = componentScripts.find(name => isJS(name) && normalizePath(name).includes('/addon/'));
if (hasScriptHbs) {
items[name] = {
template: hasScriptHbs,
script: hasAddonTs || hasAddonJs || hasScriptTs || hasScriptJs || null
}
} else {
items[name] = {
template: null,
script: hasAddonTs || hasAddonJs || hasScriptTs || hasScriptJs || null
}
}
});
});
}
return items;
}