polymer-analyzer
Version:
Static analysis for Web Components
135 lines • 5.66 kB
JavaScript
/**
* @license
* Copyright (c) 2016 The Polymer Project Authors. All rights reserved.
* This code may only be used under the BSD style license found at
* http://polymer.github.io/LICENSE.txt
* The complete set of authors may be found at
* http://polymer.github.io/AUTHORS.txt
* The complete set of contributors may be found at
* http://polymer.github.io/CONTRIBUTORS.txt
* Code distributed by Google as part of the polymer project is also
* subject to an additional IP rights grant found at
* http://polymer.github.io/PATENTS.txt
*/
Object.defineProperty(exports, "__esModule", { value: true });
const generator_1 = require("@babel/generator");
const indent = require("indent");
const document_1 = require("../parser/document");
const estraverse_shim_1 = require("./estraverse-shim");
/**
* babel.Node#type is one of around a hundred string literals. We don't have
* a direct reference to the type that represents any of those string literals
* though. We can get a reference by taking a Node and using the `typeof`
* operator, and it doesn't need to be a real Node as all of this happens at
* analysis time, and nothing happens at runtime.
*/
// tslint:disable-next-line: no-any
const __exampleNode = null;
class JavaScriptDocument extends document_1.ParsedDocument {
constructor(from) {
super(from);
this.type = 'js';
this.visitorSkips = new Map();
this.parsedAsSourceType = from.parsedAsSourceType;
}
visit(visitors) {
/**
* Applies all visiting callbacks from `visitors`.
*/
const applyScanners = (callbackName, node, parent, path) => {
for (const visitor of visitors) {
if (_shouldSkip(visitor, callbackName, node.type)) {
continue;
}
if (callbackName in visitor) {
// TODO(rictic): is there a maintainable way to enforce the
// mapping between callback names and the types of the first
// arg?
const result =
// tslint:disable-next-line: no-any
visitor[callbackName](node, parent, path);
if (result) {
handleVisitorResult(result, callbackName, visitor, node.type);
}
}
}
};
// a visitor to break early, or to skip a subtree of the AST. We need to
// track this ourselves because we're running all the visitors at once.
const _shouldSkip = (visitor, callbackName, nodeType) => {
const skipRecord = this.visitorSkips.get(visitor);
if (!skipRecord) {
return false;
}
if (callbackName === `enter${nodeType}`) {
skipRecord.depth += 1;
return true;
}
else if (callbackName === `leave${nodeType}`) {
skipRecord.depth -= 1;
if (skipRecord.depth === 0) {
this.visitorSkips.delete(visitor);
// Note that we don't `continue` here. This is deliberate so that
// we call the leave handler for the node where we started
// skipping.
}
else {
return true;
}
}
else {
return true;
}
};
const handleVisitorResult = (visitorOption, callbackName, visitor, nodeType) => {
switch (visitorOption) {
case estraverse_shim_1.VisitorOption.Remove:
throw new Error(`estraverse.VisitorOption.Remove not ` +
`supported by JavascriptDocument`);
case estraverse_shim_1.VisitorOption.Break:
visitors = visitors.filter((v) => v !== visitor);
break;
case estraverse_shim_1.VisitorOption.Skip:
if (callbackName.startsWith('leave')) {
throw new Error(`estraverse.VisitorOption.Skip was returned from ` +
`${callbackName} but it's not supported in a leave method`);
}
this.visitorSkips.set(visitor, { type: nodeType, depth: 1 });
break;
}
};
estraverse_shim_1.traverse(this.ast, {
enter(node, parent, path) {
applyScanners(`enter${node.type}`, node, parent, path);
},
leave(node, parent, path) {
applyScanners(`leave${node.type}`, node, parent, path);
}
});
}
_sourceRangeForNode(node) {
if (!node || !node.loc) {
return;
}
return {
file: this.url,
// Note: estree uses 1-indexed lines, but SourceRange uses 0 indexed.
start: { line: (node.loc.start.line - 1), column: node.loc.start.column },
end: { line: (node.loc.end.line - 1), column: node.loc.end.column }
};
}
stringify(options = {}) {
const { prettyPrint = true } = options;
const formatOptions = {
comments: prettyPrint,
retainLines: false,
quotes: 'single',
minified: !prettyPrint,
};
const code = generator_1.default(this.ast, formatOptions).code + '\n';
return options.indent != null ? indent(code, options.indent * 2) : code;
}
}
exports.JavaScriptDocument = JavaScriptDocument;
//# sourceMappingURL=javascript-document.js.map
;