Skip to content

Instantly share code, notes, and snippets.

@veged
Last active March 24, 2018 16:19
Show Gist options
  • Save veged/f4a47e6ff84d14dc01377fbf6958b2da to your computer and use it in GitHub Desktop.
Save veged/f4a47e6ff84d14dc01377fbf6958b2da to your computer and use it in GitHub Desktop.
typescript-bem-compiler
import * as ts from "typescript";
import * as path from "path";
import * as bemImport from '@bem/import-notation';
import * as bemFs from '@bem/fs-scheme';
import * as BemEntityName from '@bem/entity-name';
import * as BemCell from '@bem/cell';
const bemConfig = require('bem-config')();
const bemNaming = require('@bem/naming')('react');
function createCompilerHost(options: ts.CompilerOptions, moduleSearchLocations: string[]): ts.CompilerHost {
//const levelsMap = bemConfig.levelMapSync();
const levelsMap = {
'../typescript-bem/src/blocks': { scheme: 'nested' },
'../typescript-bem/src/blocks2': { scheme: 'nested' }
};
const levels = Array.isArray(levelsMap) ? levelsMap : Object.keys(levelsMap);
return {
getSourceFile,
getDefaultLibFileName: (options) => ts.getDefaultLibFilePath(options),
writeFile: (fileName, content) => ts.sys.writeFile(fileName, content),
getCurrentDirectory: () => ts.sys.getCurrentDirectory(),
getDirectories: (path) => ts.sys.getDirectories(path),
getCanonicalFileName: fileName => ts.sys.useCaseSensitiveFileNames ? fileName : fileName.toLowerCase(),
getNewLine: () => ts.sys.newLine,
useCaseSensitiveFileNames: () => ts.sys.useCaseSensitiveFileNames,
fileExists,
readFile,
resolveModuleNames
}
function fileExists(fileName: string): boolean {
return ts.sys.fileExists(fileName);
}
function readFile(fileName: string): string | undefined {
return ts.sys.readFile(fileName);
}
function getSourceFile(fileName: string, languageVersion: ts.ScriptTarget, onError?: (message: string) => void) {
const sourceText = ts.sys.readFile(fileName);
return sourceText !== undefined ? ts.createSourceFile(fileName, sourceText, languageVersion) : undefined;
}
function resolveModuleNames(moduleNames: string[], containingFile: string): ts.ResolvedModule[] {
const currentEntity = bemNaming.parse(path.basename(containingFile).split('.')[0]);
const resolvedModules: ts.ResolvedModule[] = [];
for (const moduleName of moduleNames) {
// try to match b:MyBlock
let bemImports = bemImport.parse(moduleName, currentEntity);
if (bemImports.length) {
bemImports = bemImports.reduce((acc, entity) => {
levels.forEach(layer => {
acc.push(BemCell.create({ entity, layer }));
});
return acc;
}, []);
// find path for every entity and check it existance
let bemCell, i = 0, resolvedModule;
while(bemCell = bemImports[i++]) {
const localNamingOpts = (levelsMap[bemCell.layer] && levelsMap[bemCell.layer].naming) || 'react';
const fsScheme = (levelsMap[bemCell.layer] && levelsMap[bemCell.layer].scheme) || 'nested';
const entityPath = path.resolve(bemFs(fsScheme).path(bemCell, localNamingOpts));
if (entityPath === containingFile.replace(/\.[^.]+$/, '')) break;
let result = ts.resolveModuleName(entityPath, containingFile, options, { fileExists, readFile });
if (result.resolvedModule) {
resolvedModule = result.resolvedModule;
}
}
if (resolvedModule) {
resolvedModules.push(resolvedModule);
} else {
throw Error(`Can not find module ${moduleName} from ${containingFile}`);
}
} else {
let result = ts.resolveModuleName(moduleName, containingFile, options, { fileExists, readFile });
if (result.resolvedModule) {
resolvedModules.push(result.resolvedModule);
} // TODO: assert unknown module
}
}
return resolvedModules;
}
}
function compile(sourceFiles: string[], moduleSearchLocations: string[]): void {
const options: ts.CompilerOptions = {
noEmitOnError: true,
noImplicitAny: true,
target: ts.ScriptTarget.ES2015,
module: ts.ModuleKind.CommonJS,
jsx: 'react',
jsxFactory: 'React.createElement',
traceResolution: true
};
const host = createCompilerHost(options, moduleSearchLocations);
const program = ts.createProgram(sourceFiles, options, host);
let emitResult = program.emit();
let allDiagnostics = ts.getPreEmitDiagnostics(program).concat(emitResult.diagnostics);
allDiagnostics.forEach(diagnostic => {
if (diagnostic.file) {
let { line, character } = diagnostic.file.getLineAndCharacterOfPosition(diagnostic.start!);
let message = ts.flattenDiagnosticMessageText(diagnostic.messageText, '\n');
console.log(`${diagnostic.file.fileName} (${line + 1},${character + 1}): ${message}`);
}
else {
console.log(`${ts.flattenDiagnosticMessageText(diagnostic.messageText, '\n')}`);
}
});
let exitCode = emitResult.emitSkipped ? 1 : 0;
console.log(`Process exiting with code '${exitCode}'.`);
process.exit(exitCode);
}
compile(process.argv.slice(2));
{
"name": "typescript-bem-compiler",
"version": "1.0.0",
"description": "",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC",
"dependencies": {
"@bem/fs-scheme": "^2.1.0",
"@bem/import-notation": "^1.1.3",
"bem-config": "^3.2.3",
"typescript": "^2.7.2"
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment