@atomist/automation-client
Version:
Atomist API for software low-level client
442 lines • 19.5 kB
JavaScript
;
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __asyncValues = (this && this.__asyncValues) || function (o) {
if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined.");
var m = o[Symbol.asyncIterator], i;
return m ? m.call(o) : (o = typeof __values === "function" ? __values(o) : o[Symbol.iterator](), i = {}, verb("next"), verb("throw"), verb("return"), i[Symbol.asyncIterator] = function () { return this; }, i);
function verb(n) { i[n] = o[n] && function (v) { return new Promise(function (resolve, reject) { v = o[n](v), settle(resolve, reject, v.done, v.value); }); }; }
function settle(resolve, reject, d, v) { Promise.resolve(v).then(function(v) { resolve({ value: v, done: d }); }, reject); }
};
var __await = (this && this.__await) || function (v) { return this instanceof __await ? (this.v = v, this) : new __await(v); }
var __asyncGenerator = (this && this.__asyncGenerator) || function (thisArg, _arguments, generator) {
if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined.");
var g = generator.apply(thisArg, _arguments || []), i, q = [];
return i = {}, verb("next"), verb("throw"), verb("return"), i[Symbol.asyncIterator] = function () { return this; }, i;
function verb(n) { if (g[n]) i[n] = function (v) { return new Promise(function (a, b) { q.push([n, v, a, b]) > 1 || resume(n, v); }); }; }
function resume(n, v) { try { step(g[n](v)); } catch (e) { settle(q[0][3], e); } }
function step(r) { r.value instanceof __await ? Promise.resolve(r.value.v).then(fulfill, reject) : settle(q[0][2], r); }
function fulfill(value) { resume("next", value); }
function reject(value) { resume("throw", value); }
function settle(f, v) { if (f(v), q.shift(), q.length) resume(q[0][0], q[0][1]); }
};
Object.defineProperty(exports, "__esModule", { value: true });
const tree_path_1 = require("@atomist/tree-path");
const _ = require("lodash");
const logger_1 = require("../../util/logger");
const HasCache_1 = require("../../project/HasCache");
const projectUtils_1 = require("../../project/util/projectUtils");
const sourceLocationUtils_1 = require("../../project/util/sourceLocationUtils");
const FileHits_1 = require("./FileHits");
const FileParser_1 = require("./FileParser");
/**
* Integrate path expressions with project operations to find all matches
* @param p project
* @param parseWith parser or parsers to use to parse files
* @param globPatterns file glob patterns
* @param pathExpression path expression string or parsed
* @param functionRegistry registry to look for path expression functions in
* @return {Promise<MatchResult[]>} matches
* @type T match type to mix in
* @deprecated use matches
*/
function findMatches(p, parseWith, globPatterns, pathExpression, functionRegistry) {
return __awaiter(this, void 0, void 0, function* () {
const fileHits = yield fileMatches(p, { parseWith, globPatterns, pathExpression, functionRegistry });
return _.flatten(fileHits.map(f => f.matches));
});
}
exports.findMatches = findMatches;
/**
* Integrate path expressions with project operations to find all matches
* @param p project
* @param peqo query options
* @return {Promise<MatchResult[]>} matches
* @type T match type to mix in
*/
function matches(p, peqo) {
return __awaiter(this, void 0, void 0, function* () {
const fileHits = yield fileMatches(p, peqo);
return _.flatten(fileHits.map(f => f.matches));
});
}
exports.matches = matches;
/**
* Generator style iteration over matches in a project.
* Modifications made to matches will be made on the project.
* @param p project
* @param opts options for query
* @type T match type to mix in
*/
function matchIterator(p, opts) {
return __asyncGenerator(this, arguments, function* matchIterator_1() {
var e_1, _a;
const fileHits = fileHitIterator(p, opts);
try {
for (var fileHits_1 = __asyncValues(fileHits), fileHits_1_1; fileHits_1_1 = yield __await(fileHits_1.next()), !fileHits_1_1.done;) {
const fileHit = fileHits_1_1.value;
try {
for (const match of fileHit.matches) {
yield yield __await(match);
}
}
finally {
// Even if the user jumps out of the generator, ensure that we make the changes to the present file
yield __await(p.flush());
}
}
}
catch (e_1_1) { e_1 = { error: e_1_1 }; }
finally {
try {
if (fileHits_1_1 && !fileHits_1_1.done && (_a = fileHits_1.return)) yield __await(_a.call(fileHits_1));
}
finally { if (e_1) throw e_1.error; }
}
});
}
exports.matchIterator = matchIterator;
/**
* Integrate path expressions with project operations to gather mapped values from all matches.
* Choose the files with globPatterns; choose the portions of code to match with the pathExpression.
* Choose what to return based on each match with the mapper function.
* Returns all of the values returned by the mapper (except undefined).
* @param p project
* @param parserOrRegistry parser or parsers to use to parse files
* @param globPatterns file glob patterns
* @param mapper mapping function from match result to result
* @param pathExpression path expression string or parsed
* @param functionRegistry registry to look for path expression functions in
* @return {Promise<MatchResult[]>} matches
* @deprecated use gather
*/
function gatherFromMatches(p, parserOrRegistry, globPatterns, pathExpression, mapper, functionRegistry) {
return __awaiter(this, void 0, void 0, function* () {
return gather(p, { parseWith: parserOrRegistry, globPatterns, pathExpression, mapper, functionRegistry });
});
}
exports.gatherFromMatches = gatherFromMatches;
/**
* Integrate path expressions with project operations to gather mapped values from all matches.
* Choose the files with globPatterns; choose the portions of code to match with the pathExpression.
* Choose what to return based on each match with the mapper function.
* Returns all of the values returned by the mapper (except undefined).
* @param p project
* @param peqo options
* @return {Promise<MatchResult[]>} matches
*/
function gather(p, peqo) {
return __awaiter(this, void 0, void 0, function* () {
const fileHits = yield fileMatches(p, peqo);
return _.flatten(fileHits.map(f => f.matches.map(peqo.mapper).filter(x => !!x)));
});
}
exports.gather = gather;
/**
* Like gather but keep location
*/
function gatherWithLocation(p, peqo) {
return __awaiter(this, void 0, void 0, function* () {
const fileHits = yield fileMatches(p, peqo);
const result = [];
for (const fileHit of fileHits) {
const values = fileHit.matches.map(peqo.mapper).filter(x => !!x);
values.forEach((value, index) => result.push({ value, file: fileHit.file, matchResult: fileHit.matches[index] }));
}
return result;
});
}
exports.gatherWithLocation = gatherWithLocation;
/**
* Integrate path expressions with project operations to find all matches
* and their files
* @param p project
* @param parseWith parser or parsers to use to parse files
* @param globPatterns file glob patterns
* @param pathExpression path expression string or parsed
* @param functionRegistry registry to look for path expression functions in
* @return hits for each file
* @deprecated use fileMatches
*/
function findFileMatches(p, parseWith, globPatterns, pathExpression, functionRegistry) {
return __awaiter(this, void 0, void 0, function* () {
return fileMatches(p, { parseWith, globPatterns, pathExpression, functionRegistry });
});
}
exports.findFileMatches = findFileMatches;
/**
* Integrate path expressions with project operations to find all matches
* and their files
* @param p project
* @param peqo query options
* @return hits for each file
*/
function fileMatches(p, peqo) {
return __awaiter(this, void 0, void 0, function* () {
const parsed = tree_path_1.toPathExpression(peqo.pathExpression);
const parser = findParser(parsed, peqo.parseWith);
if (!parser) {
throw new Error(`Cannot find parser for path expression [${peqo.pathExpression}]: Using ${peqo.parseWith}`);
}
const valuesToCheckFor = literalValues(parsed);
const files = yield projectUtils_1.gatherFromFiles(p, peqo.globPatterns, file => parseFile(parser, parsed, peqo.functionRegistry, p, file, valuesToCheckFor, undefined, peqo.cacheAst !== false));
return files.filter(x => !!x);
});
}
exports.fileMatches = fileMatches;
/**
* Iterate over provided path expression query options and return
* [[FileHit]]s for each path expression.
* @param p project
* @param peqos Array of query options
* @return hits for each file for each query option
*/
function pathExpressionFileMatches(p, peqos) {
return __awaiter(this, void 0, void 0, function* () {
const pefhs = [];
const fileCache = {};
for (const peqo of peqos) {
const parsed = tree_path_1.toPathExpression(peqo.pathExpression);
const parser = findParser(parsed, peqo.parseWith);
if (!parser) {
throw new Error(`Cannot find parser for path expression [${peqo.pathExpression}]: Using ${peqo.parseWith}`);
}
const matchFiles = yield p.getFiles(peqo.globPatterns);
const checkFiles = matchFiles.map(f => {
if (!fileCache[f.path]) {
fileCache[f.path] = f;
}
return fileCache[f.path];
});
const valuesToCheckFor = literalValues(parsed);
const files = [];
for (const file of checkFiles) {
const hit = yield parseFile(parser, parsed, peqo.functionRegistry, p, file, valuesToCheckFor, undefined, peqo.cacheAst !== false);
if (hit) {
files.push(hit);
}
}
if (files.length > 0) {
pefhs.push({
pathExpression: tree_path_1.stringify(parsed),
fileHits: files,
});
}
}
return pefhs;
});
}
exports.pathExpressionFileMatches = pathExpressionFileMatches;
/**
* Generator style iteration over file matches
* @param p project
* @param opts options for query
*/
function fileHitIterator(p, opts) {
return __asyncGenerator(this, arguments, function* fileHitIterator_1() {
var e_2, _a;
const parsed = tree_path_1.toPathExpression(opts.pathExpression);
const parser = findParser(parsed, opts.parseWith);
if (!parser) {
throw new Error(`Cannot find parser for path expression [${opts.pathExpression}]: Using ${opts.parseWith}`);
}
const valuesToCheckFor = literalValues(parsed);
try {
for (var _b = __asyncValues(projectUtils_1.fileIterator(p, opts.globPatterns, opts.fileFilter)), _c; _c = yield __await(_b.next()), !_c.done;) {
const file = _c.value;
const fileHit = yield __await(parseFile(parser, parsed, opts.functionRegistry, p, file, valuesToCheckFor, opts.testWith, opts.cacheAst !== false));
if (!!fileHit) {
yield yield __await(fileHit);
}
}
}
catch (e_2_1) { e_2 = { error: e_2_1 }; }
finally {
try {
if (_c && !_c.done && (_a = _b.return)) yield __await(_a.call(_b));
}
finally { if (e_2) throw e_2.error; }
}
});
}
exports.fileHitIterator = fileHitIterator;
function parseFile(parser, pex, functionRegistry, p, file, valuesToCheckFor, matchTester, cacheAst) {
return __awaiter(this, void 0, void 0, function* () {
// First, apply optimizations
if (valuesToCheckFor.length > 0) {
const content = yield file.getContent();
if (valuesToCheckFor.every(literal => !content.includes(literal))) {
return undefined;
}
}
if (!!parser.couldBeMatchesInThisFile && !(yield parser.couldBeMatchesInThisFile(pex, file))) {
// Skip parsing as we know there can never be matches
return undefined;
}
// If we get here, we need to parse the file
try {
// Use a cached AST if appropriate
const topLevelProduction = yield HasCache_1.retrieveOrCompute(file, `ast_${parser.rootName}`, (f) => __awaiter(this, void 0, void 0, function* () {
const prod = yield parser.toAst(f);
tree_path_1.defineDynamicProperties(prod);
return prod;
}), cacheAst);
logger_1.logger.debug("Successfully parsed file '%s' to AST with root node named '%s'. Will execute '%s'", file.path, topLevelProduction.$name, tree_path_1.stringify(pex));
const fileNode = {
path: file.path,
name: file.name,
$name: file.name,
$children: [topLevelProduction],
};
const r = tree_path_1.evaluateExpression(fileNode, pex, functionRegistry);
if (tree_path_1.isSuccessResult(r)) {
logger_1.logger.debug("%d matches in file '%s'", r.length, file.path);
return fillInSourceLocations(file, r)
.then(locatedNodes => {
if (matchTester) {
return matchTester(file)
.then(test => new FileHits_1.FileHit(p, file, fileNode, locatedNodes.filter(test)));
}
return new FileHits_1.FileHit(p, file, fileNode, locatedNodes);
});
}
else {
logger_1.logger.debug("No matches in file '%s'", file.path);
return undefined;
}
}
catch (err) {
logger_1.logger.debug("Failed to parse file '%s': %s", file.path, err);
return undefined;
}
});
}
/**
* Use file content to fill in LocatedTreeNode.sourceLocation
* @param {File} f
* @param {TreeNode[]} nodes
* @return {Promise<LocatedTreeNode[]>}
*/
function fillInSourceLocations(f, nodes) {
if (nodes.length === 0) {
// Optimization.
// In this case, let's not read the file content and leave source locations undefined
return Promise.resolve(nodes);
}
return f.getContent()
.then(content => {
nodes.forEach(n => {
n.sourceLocation = sourceLocationUtils_1.toSourceLocation(f, content, n.$offset);
});
return nodes;
});
}
/**
* Convenient method to find all values of matching nodes--
* typically, terminals such as identifiers
* @param p project
* @param globPatterns file glob pattern
* @param parserOrRegistry parser for files
* @param pathExpression path expression string or parsed
* @param functionRegistry registry to look for path expression functions in
* @return {Promise<TreeNode[]>} hit record for each matching file
*/
function findValues(p, parserOrRegistry, globPatterns, pathExpression, functionRegistry) {
return fileMatches(p, { parseWith: parserOrRegistry, globPatterns, pathExpression, functionRegistry })
.then(fileHits => _.flatten(fileHits.map(f => f.matches))
.map(m => m.$value));
}
exports.findValues = findValues;
/**
* Integrate path expressions with project operations to find all matches
* of a path expression and zap them. Use with care!
* @param p project
* @param globPatterns file glob pattern
* @param parserOrRegistry parser for files
* @param pathExpression path expression string or parsed
* @param opts options for handling whitespace
* @return {Promise<TreeNode[]>} hit record for each matching file
*/
function zapAllMatches(p, parserOrRegistry, globPatterns, pathExpression, opts = {}) {
return doWithAllMatches(p, parserOrRegistry, globPatterns, pathExpression, m => m.zap(opts));
}
exports.zapAllMatches = zapAllMatches;
/**
* Integrate path expressions with project operations to find all matches
* of a path expression and perform a mutation on them them. Use with care!
* @param p project
* @param globPatterns file glob pattern
* @param parserOrRegistry parser for files
* @param pathExpression path expression string or parsed
* @param action what to do with matches
* @return {Promise<TreeNode[]>} hit record for each matching file
*/
function doWithAllMatches(p, parserOrRegistry, globPatterns, pathExpression, action) {
return __awaiter(this, void 0, void 0, function* () {
const fileHits = yield fileMatches(p, { parseWith: parserOrRegistry, globPatterns, pathExpression });
fileHits.forEach(fh => applyActionToMatches(fh, action));
return p.flush();
});
}
exports.doWithAllMatches = doWithAllMatches;
function applyActionToMatches(fh, action) {
// Sort file hits in reverse order so that offsets aren't upset by applications
const sorted = fh.matches.sort((m1, m2) => m1.$offset - m2.$offset);
sorted.forEach(action);
}
function findParser(pathExpression, fp) {
if (FileParser_1.isFileParser(fp)) {
if (!!fp.validate) {
fp.validate(pathExpression);
}
return fp;
}
else {
return fp.parserFor(pathExpression);
}
}
exports.findParser = findParser;
/**
* Return literal values that must be present in a file for this path expression to
* match. Return the empty array if there are no literal @values or if we cannot
* determine whether there may be for this path expression.
* @param {PathExpression} pex
* @return {string[]}
*/
function literalValues(pex) {
return _.uniq(allPredicates(pex)
.filter(isAttributeEqualityPredicate)
.map(p => p.value));
}
exports.literalValues = literalValues;
function allPredicates(pe) {
if (tree_path_1.isUnionPathExpression(pe)) {
return _.flatten(pe.unions.map(allPredicates));
}
return _.flatten(pe.locationSteps.map(s => {
return _.flatten(s.predicates.map(p => {
if (isNestedPredicate(p)) {
return allPredicates(p.pathExpression);
}
else {
return [p];
}
}));
}));
}
function isAttributeEqualityPredicate(p) {
const maybe = p;
return !!maybe.value;
}
function isNestedPredicate(p) {
const maybe = p;
return !!maybe.pathExpression;
}
//# sourceMappingURL=astUtils.js.map