@atomist/automation-client
Version:
Atomist API for software low-level client
142 lines • 4.67 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const tree_path_1 = require("@atomist/tree-path");
const _ = require("lodash");
const ts = require("typescript");
const logger_1 = require("../../../util/logger");
/**
* Allow path expressions against ASTs from the TypeScript parser.
* For reference material on the grammar, and which productions are legal
* names in path expressions, see the grammar at
* https://github.com/Microsoft/TypeScript/blob/master/doc/spec.md#A.
* See also the ES6 grammar of which TypeScript is a superset:
* http://www.ecma-international.org/ecma-262/6.0/#sec-grammar-summary.
* and the SyntaxKind type defined by
* the TypeScript compiler. Invalid production names will be rejected
* with a runtime error.
*
* Will try to determine TypeScript ScriptKind from the file extension.
*/
class TypeScriptFileParser {
constructor(scriptTarget) {
this.scriptTarget = scriptTarget;
this.rootName = ts.SyntaxKind[ts.SyntaxKind.SourceFile];
}
toAst(f) {
return f.getContent()
.then(content => {
const sourceFile = ts.createSourceFile(f.name, content, this.scriptTarget, false, scriptKindFor(f));
const root = new TypeScriptAstNodeTreeNode(sourceFile, sourceFile, undefined);
return root;
});
}
/**
* Check that this path expression uses only valid TypeScript constructs
* @param {PathExpression} pex
*/
validate(pex) {
for (const ls of locationSteps(pex)) {
if (tree_path_1.isNamedNodeTest(ls.test) && ls.test !== tree_path_1.AllNodeTest) {
if (!ts.SyntaxKind[ls.test.name]) {
throw new Error(`Invalid path expression '${tree_path_1.stringify(pex)}': ` +
`No such TypeScript element: '${ls.test.name}'`);
}
}
}
}
}
exports.TypeScriptFileParser = TypeScriptFileParser;
/**
* Determine the script kind of the file from its extension
* @param {File} f
* @return {ts.ScriptKind}
*/
function scriptKindFor(f) {
switch (f.extension) {
case "js":
return ts.ScriptKind.JS;
case "jsx":
return ts.ScriptKind.JSX;
case "ts":
return ts.ScriptKind.TS;
case "tsx":
return ts.ScriptKind.TSX;
default:
return ts.ScriptKind.Unknown;
}
}
function locationSteps(pex) {
return tree_path_1.isUnionPathExpression(pex) ?
_.flatten(pex.unions.map(locationSteps)) :
pex.locationSteps;
}
/**
* TreeNode implementation backed by a node from the TypeScript parser
*/
class TypeScriptAstNodeTreeNode {
constructor(sourceFile, node, $parent) {
this.sourceFile = sourceFile;
this.node = node;
this.$parent = $parent;
this.$children = [];
this.$name = extractName(node);
try {
this.$offset = node.getStart(sourceFile, true);
}
catch (e) {
// Ignore and continue
if (!!node.pos) {
this.$offset = node.pos;
}
else {
logger_1.logger.debug("Cannot get start for node with kind %s", ts.SyntaxKind[node.kind]);
}
}
for (const n of node.getChildren(sourceFile)) {
if (!!n) {
this.$children.push(new TypeScriptAstNodeTreeNode(sourceFile, n, this));
}
}
if (this.$children.length === 0) {
// Get it off the JSON if it doesn't matter
this.$children = undefined;
}
else {
// It's a non-terminal, so the name needs to be the kind
this.$name = ts.SyntaxKind[node.kind];
}
}
get $value() {
return extractValue(this.sourceFile, this.node);
}
}
function extractName(node) {
if (!!node.name && node.name.escapedText) {
return node.name.escapedText;
}
else {
return ts.SyntaxKind[node.kind];
}
}
function extractValue(sourceFile, node) {
if (!!node.text) {
return node.text;
}
try {
return node.getText(sourceFile);
}
catch (te) {
const start = node.getStart(sourceFile, true);
const end = node.getEnd(sourceFile, true);
if (!!start && !!end) {
return sourceFile.text.substr(start, end - start);
}
return undefined;
}
}
/**
* Parser for TypeScript and JavaScript
* @type {TypeScriptFileParser}
*/
exports.TypeScriptES6FileParser = new TypeScriptFileParser(ts.ScriptTarget.ES2016);
//# sourceMappingURL=TypeScriptFileParser.js.map