UNPKG

@atomist/automation-client

Version:

Atomist API for software low-level client

442 lines • 19.5 kB
"use strict"; 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