mark(node) { let loc = node.loc; if (!loc) return; // no location info let map = this.map; if (!map) return; // no source map if (t.isProgram(node) || t.isFile(node)) return; // illegal mapping nodes let position = this.position; let generated = { line: position.line, column: position.column }; let original = loc.start; // Avoid emitting duplicates on either side. Duplicated // original values creates unnecesssarily large source maps // and increases compile time. Duplicates on the generated // side can lead to incorrect mappings. if (comparePosition(original, this.last.original) || comparePosition(generated, this.last.generated)) { return; } this.last = { source: loc.filename || this.opts.sourceFileName, generated: generated, original: original }; map.addMapping(this.last); }
run() { let block = this.block; if (block._letDone) return; block._letDone = true; let needsClosure = this.getLetReferences(); // this is a block within a `Function/Program` so we can safely leave it be if (t.isFunction(this.parent) || t.isProgram(this.block)) { this.updateScopeInfo(); return; } // we can skip everything if (!this.hasLetReferences) return; if (needsClosure) { this.wrapClosure(); } else { this.remap(); } this.updateScopeInfo(); if (this.loopLabel && !t.isLabeledStatement(this.loopParent)) { return t.labeledStatement(this.loopLabel, this.loop); } }
print(node, parent, opts = {}) { if (!node) return; this._lastPrintedIsEmptyStatement = false; if (parent && parent._compact) { node._compact = true; } let oldInAux = this.insideAux; this.insideAux = !node.loc; let oldConcise = this.format.concise; if (node._compact) { this.format.concise = true; } let printMethod = this[node.type]; if (!printMethod) { throw new ReferenceError(`unknown node of type ${JSON.stringify(node.type)} with constructor ${JSON.stringify(node && node.constructor.name)}`); } this._printStack.push(node); if (node.loc) this.printAuxAfterComment(); this.printAuxBeforeComment(oldInAux); let needsParens = n.needsParens(node, parent, this._printStack); if (needsParens) this.push("("); this.printLeadingComments(node, parent); this.catchUp(node); this._printNewline(true, node, parent, opts); if (opts.before) opts.before(); let loc = (t.isProgram(node) || t.isFile(node)) ? null : node.loc; this.withSource("start", loc, () => { this._print(node, parent); }); // Check again if any of our children may have left an aux comment on the stack if (node.loc) this.printAuxAfterComment(); this.printTrailingComments(node, parent); if (needsParens) this.push(")"); // end this._printStack.pop(); if (opts.after) opts.after(); this.format.concise = oldConcise; this.insideAux = oldInAux; this._printNewline(false, node, parent, opts); }
export function replaceWith(replacement) { this.resync(); if (this.removed) { throw new Error("You can't replace this node, we've already removed it"); } if (replacement instanceof NodePath) { replacement = replacement.node; } if (!replacement) { throw new Error("You passed `path.replaceWith()` a falsy node, use `path.remove()` instead"); } if (this.node === replacement) { return; } if (this.isProgram() && !t.isProgram(replacement)) { throw new Error("You can only replace a Program root node with another Program node"); } if (Array.isArray(replacement)) { throw new Error("Don't use `path.replaceWith()` with an array of nodes, use `path.replaceWithMultiple()`"); } if (typeof replacement === "string") { throw new Error("Don't use `path.replaceWith()` with a source string, use `path.replaceWithSourceString()`"); } // replacing a statement with an expression so wrap it in an expression statement if (this.isNodeType("Statement") && t.isExpression(replacement) && !this.canHaveVariableDeclarationOrExpression()) { replacement = t.expressionStatement(replacement); } // replacing an expression with a statement so let's explode it if (this.isNodeType("Expression") && t.isStatement(replacement)) { return this.replaceExpressionWithStatements([replacement]); } let oldNode = this.node; if (oldNode) { t.inheritsComments(replacement, oldNode); t.removeComments(oldNode); } // replace the node this._replaceWith(replacement); this.type = replacement.type; // potentially create new scope this.setScope(); // requeue for visiting this.requeue(); }
function getCommentPool(path) { var commentPool; // Comments at the Program scope: if (t.isProgram(path.node) && path.node.innerComments && path.node.innerComments.length) { commentPool = path.node.innerComments; } else if (t.isProgram(path.node) && path.node.body.leadingComments && path.node.body.leadingComments.length) { commentPool = path.node.body.leadingComments; } else if (path.node.leadingComments && path.node.leadingComments.length) { commentPool = path.node.leadingComments; } if (commentPool) { // Handle an edge-case with babel against ES6 destructured identifiers; // it seems to attach the leading comments to both the VariableDeclaration // node as well as the first identifier inside of it, so for the following // snippet: // // /** // * @module // */ // var { Assertion } = require('chai'); // // leadingComments will be set both on VariableDeclaration as well as // Identifier(Assertion). // // We'll handle the VariableDeclaration and forget about the // destructured variable identifier altogether. if (ASTUtils.isCommentedDestructuredProperty(path)) { debugLog('Ignoring comment pool on destructured property at %s', dumpLocation(path.node, 'line')) return; } const withoutDuplicates = discardDuplicateComments(commentPool) if (withoutDuplicates.length !== commentPool.length) { debugLog('Discarded %d comments identified as duplicate', commentPool.length - withoutDuplicates.length) } return withoutDuplicates; } }
/** * transform function which operates on each discovered example block * @callback transformCallback * @param {Object} options * @param {ast} options.comment * @param {string} options.name * @param {string} options.filename * @param {string} options.type */ /** * Extracts comment blocks from the source code in the specified file * @param {transformCallback} transform * @param {string} filename * @returns {Array<ast>} */ export default function extract(transform, filename) { // eslint-disable-next-line no-sync const code = fs.readFileSync(filename, {encoding: `utf8`}); const ast = parse(code, {sourceFilename: filename}); const results = []; let done = false; traverse(ast, { enter(path) { if (path.node.leadingComments) { path.node.leadingComments .filter(isJSDocComment) .forEach((comment) => { const result = doctrine.parse(comment.value, { unwrap: true, sloppy: true, recoverable: true, lineNumbers: true }); if (result.tags) { result.tags.forEach((tag) => { if (tag.title === `example`) { results.push(transform({ comment: tag.description, name: getNodeName(path.node), filename: path.node.loc.filename, type: path.node.type })); } }); } }); } }, Program: { exit(path) { if (isProgram(path)) { if (done) { return; } path.pushContainer(`body`, results); done = true; } } } }); return ast; }
/** * Should we pull this component path? * Currently: is it considered "top level"? Ie. not in closures/constructors etc. */ function shouldPullComponent(path) { let tPath = path.parentPath; while (tPath) { if (T.isProgram(tPath)) { return true; } if ( !T.isExportDefaultDeclaration(tPath) && !T.isExportNamedDeclaration(tPath) && !T.isVariableDeclaration(tPath) ) { break; } tPath = tPath.parentPath; } return false; }
/** Pull TOP LEVEL symbols out of our ast */ function pullSymbols(ast) { const symbols = []; // TODO: Need to think hard about how we store each symbol "type" to allow easy resolution later function pushVariableDeclarator(node) { symbols.push({ name: node.id.name, type: resolveType(node.init), }); } function pushVariableDeclaration(node) { node.declarations.forEach(declNode => pushVariableDeclarator(declNode)); } function pushFunctionDeclaration(node) { symbols.push({ name: node.id.name, type: resolveType(node), }); } function pushClassDeclaration(node) { symbols.push({ name: node.id.name, type: resolveType(node), }); } function pushExportSpecifier(node, {source, sourceName} = {}) { symbols.push({ name: `export::${node.exported.name}`, type: resolveType(node, {source, sourceName}), }); } function pushExportDeclaration(node) { // We'll collect two symbols for export declarations. One is // the declared value/type and the other will simply be a pointer // with name `export::symbolName` if (T.isFunctionDeclaration(node)) { pushFunctionDeclaration(node); symbols.push({ name: `export::${node.id.name}`, type: resolveType(node.id), }); } else if (T.isVariableDeclaration(node)) { pushVariableDeclaration(node); node.declarations.forEach(declNode => symbols.push({ name: `export::${declNode.id.name}`, type: resolveType(declNode.id), })); } else if (T.isClassDeclaration(node)) { pushClassDeclaration(node); symbols.push({ name: `export::${node.id.name}`, type: resolveType(node.id), }); } } function pushExportNamedDeclaration(node) { if (node.declaration) { pushExportDeclaration(node.declaration); } else { const source = node.source ? node.source.value : undefined; node.specifiers.forEach(spec => pushExportSpecifier(spec, {source, sourceName: spec.local.name})); } } function pushExportDefaultDeclaration(node) { const decl = node.declaration; if (T.isFunctionDeclaration(decl)) { pushFunctionDeclaration(decl); symbols.push({ name: `export::default`, type: resolveType(decl.id), }); } else if (T.isVariableDeclaration(decl)) { pushVariableDeclaration(decl); decl.declarations.forEach(vDecl => symbols.push({ name: `export::default`, type: resolveType(vDecl.id), })); } else if (T.isClassDeclaration(decl)) { pushClassDeclaration(decl); symbols.push({ name: `export::default`, type: resolveType(decl.id), }); } else { symbols.push({ name: `export::default`, type: resolveType(decl), }); } } function pushImportDeclaration(node) { const source = node.source.value; node.specifiers.forEach(spec => symbols.push({ name: T.isIdentifier(spec.exported) ? spec.exported.value : spec.local.name, type: resolveType(spec, {source, sourceName: spec.local.name}), })); } const visitor = { FunctionDeclaration(path) { if (T.isProgram(path.parent)) { pushFunctionDeclaration(path.node); } path.skip(); }, VariableDeclaration(path) { if (T.isProgram(path.parent)) { pushVariableDeclaration(path.node); } path.skip(); }, ClassDeclaration(path) { if (T.isProgram(path.parent)) { pushClassDeclaration(path.node); } path.skip(); }, ExportNamedDeclaration(path) { pushExportNamedDeclaration(path.node); path.skip(); }, ExportDefaultDeclaration(path) { pushExportDefaultDeclaration(path.node); path.skip(); }, ImportDeclaration(path) { pushImportDeclaration(path.node); path.skip(); }, }; traverse(ast, visitor); return [...symbols]; }
parsedFiles.forEach(function(file) { var namedExports = {}; var localVariableMap = {}; traverse(file.ast, { enter(path) { // replace require calls with the corresponding value var r; if ((r = getRequireValue(path.node, file))) { path.replaceWith(r); return; } // if we see `var foo = require('bar')` we can just inline the variable // representing `require('bar')` wherever `foo` was used. if ( t.isVariableDeclaration(path.node) && path.node.declarations.length === 1 && t.isIdentifier(path.node.declarations[0].id) && (r = getRequireValue(path.node.declarations[0].init, file)) ) { var newName = 'compiled_local_variable_reference_' + getUniqueIndex(); path.scope.rename(path.node.declarations[0].id.name, newName); localVariableMap[newName] = r; path.remove(); return; } // rename all top level functions to keep them local to the module if (t.isFunctionDeclaration(path.node) && t.isProgram(path.parent)) { path.scope.rename(path.node.id.name, file.property + '_local_fn_' + path.node.id.name); return; } // rename all top level variables to keep them local to the module if (t.isVariableDeclaration(path.node) && t.isProgram(path.parent)) { path.node.declarations.forEach(function(declaration) { path.scope.rename( declaration.id.name, file.property + '_local_var_' + declaration.id.name ); }); return; } // replace module.exports.bar with a variable for the named export if ( t.isMemberExpression(path.node, { computed: false }) && isModuleDotExports(path.node.object) ) { var name = path.node.property.name; var identifier = t.identifier(file.property + '_export_' + name); path.replaceWith(identifier); namedExports[name] = identifier; } }, }); traverse(file.ast, { enter(path) { if ( t.isIdentifier(path.node) && Object.prototype.hasOwnProperty.call(localVariableMap, path.node.name) ) { path.replaceWith(localVariableMap[path.node.name]); } }, }); var defaultExports = t.objectExpression( Object.keys(namedExports).map(function(name) { return t.objectProperty(t.identifier(name), namedExports[name]); }) ); moduleExportsByPath[file.filename] = { namedExports: namedExports, defaultExports: defaultExports, }; statements.push( t.variableDeclaration( 'var', Object.keys(namedExports).map(function(name) { return t.variableDeclarator(namedExports[name]); }) ) ); statements.push.apply(statements, file.ast.program.body); });