react / react-0.13.3 / examples / basic-commonjs / node_modules / reactify / node_modules / react-tools / node_modules / commoner / node_modules / recast / lib / comments.js
81169 viewsvar assert = require("assert");1var types = require("./types");2var n = types.namedTypes;3var isArray = types.builtInTypes.array;4var isObject = types.builtInTypes.object;5var linesModule = require("./lines");6var fromString = linesModule.fromString;7var Lines = linesModule.Lines;8var concat = linesModule.concat;9var comparePos = require("./util").comparePos;10var childNodesCacheKey = require("private").makeUniqueKey();1112// TODO Move a non-caching implementation of this function into ast-types,13// and implement a caching wrapper function here.14function getSortedChildNodes(node, resultArray) {15if (!node) {16return;17}1819if (resultArray) {20if (n.Node.check(node) &&21n.SourceLocation.check(node.loc)) {22// This reverse insertion sort almost always takes constant23// time because we almost always (maybe always?) append the24// nodes in order anyway.25for (var i = resultArray.length - 1; i >= 0; --i) {26if (comparePos(resultArray[i].loc.end,27node.loc.start) <= 0) {28break;29}30}31resultArray.splice(i + 1, 0, node);32return;33}34} else if (node[childNodesCacheKey]) {35return node[childNodesCacheKey];36}3738var names;39if (isArray.check(node)) {40names = Object.keys(node);41} else if (isObject.check(node)) {42names = types.getFieldNames(node);43} else {44return;45}4647if (!resultArray) {48Object.defineProperty(node, childNodesCacheKey, {49value: resultArray = [],50enumerable: false51});52}5354for (var i = 0, nameCount = names.length; i < nameCount; ++i) {55getSortedChildNodes(node[names[i]], resultArray);56}5758return resultArray;59}6061// As efficiently as possible, decorate the comment object with62// .precedingNode, .enclosingNode, and/or .followingNode properties, at63// least one of which is guaranteed to be defined.64function decorateComment(node, comment) {65var childNodes = getSortedChildNodes(node);6667// Time to dust off the old binary search robes and wizard hat.68var left = 0, right = childNodes.length;69while (left < right) {70var middle = (left + right) >> 1;71var child = childNodes[middle];7273if (comparePos(child.loc.start, comment.loc.start) <= 0 &&74comparePos(comment.loc.end, child.loc.end) <= 0) {75// The comment is completely contained by this child node.76decorateComment(comment.enclosingNode = child, comment);77return; // Abandon the binary search at this level.78}7980if (comparePos(child.loc.end, comment.loc.start) <= 0) {81// This child node falls completely before the comment.82// Because we will never consider this node or any nodes83// before it again, this node must be the closest preceding84// node we have encountered so far.85var precedingNode = child;86left = middle + 1;87continue;88}8990if (comparePos(comment.loc.end, child.loc.start) <= 0) {91// This child node falls completely after the comment.92// Because we will never consider this node or any nodes after93// it again, this node must be the closest following node we94// have encountered so far.95var followingNode = child;96right = middle;97continue;98}99100throw new Error("Comment location overlaps with node location");101}102103if (precedingNode) {104comment.precedingNode = precedingNode;105}106107if (followingNode) {108comment.followingNode = followingNode;109}110}111112exports.attach = function(comments, ast, lines) {113if (!isArray.check(comments)) {114return;115}116117var tiesToBreak = [];118119comments.forEach(function(comment) {120comment.loc.lines = lines;121decorateComment(ast, comment);122123var pn = comment.precedingNode;124var en = comment.enclosingNode;125var fn = comment.followingNode;126127if (pn && fn) {128var tieCount = tiesToBreak.length;129if (tieCount > 0) {130var lastTie = tiesToBreak[tieCount - 1];131132assert.strictEqual(133lastTie.precedingNode === comment.precedingNode,134lastTie.followingNode === comment.followingNode135);136137if (lastTie.followingNode !== comment.followingNode) {138breakTies(tiesToBreak, lines);139}140}141142tiesToBreak.push(comment);143144} else if (pn) {145// No contest: we have a trailing comment.146breakTies(tiesToBreak, lines);147Comments.forNode(pn).addTrailing(comment);148149} else if (fn) {150// No contest: we have a leading comment.151breakTies(tiesToBreak, lines);152Comments.forNode(fn).addLeading(comment);153154} else if (en) {155// The enclosing node has no child nodes at all, so what we156// have here is a dangling comment, e.g. [/* crickets */].157breakTies(tiesToBreak, lines);158Comments.forNode(en).addDangling(comment);159160} else {161throw new Error("AST contains no nodes at all?");162}163});164165breakTies(tiesToBreak, lines);166};167168function breakTies(tiesToBreak, lines) {169var tieCount = tiesToBreak.length;170if (tieCount === 0) {171return;172}173174var pn = tiesToBreak[0].precedingNode;175var fn = tiesToBreak[0].followingNode;176var gapEndPos = fn.loc.start;177178// Iterate backwards through tiesToBreak, examining the gaps179// between the tied comments. In order to qualify as leading, a180// comment must be separated from fn by an unbroken series of181// whitespace-only gaps (or other comments).182for (var indexOfFirstLeadingComment = tieCount;183indexOfFirstLeadingComment > 0;184--indexOfFirstLeadingComment) {185var comment = tiesToBreak[indexOfFirstLeadingComment - 1];186assert.strictEqual(comment.precedingNode, pn);187assert.strictEqual(comment.followingNode, fn);188189var gap = lines.sliceString(comment.loc.end, gapEndPos);190if (/\S/.test(gap)) {191// The gap string contained something other than whitespace.192break;193}194195gapEndPos = comment.loc.start;196}197198while (indexOfFirstLeadingComment <= tieCount &&199(comment = tiesToBreak[indexOfFirstLeadingComment]) &&200// If the comment is a //-style comment and indented more201// deeply than the node itself, reconsider it as trailing.202comment.type === "Line" &&203comment.loc.start.column > fn.loc.start.column) {204++indexOfFirstLeadingComment;205}206207tiesToBreak.forEach(function(comment, i) {208if (i < indexOfFirstLeadingComment) {209Comments.forNode(pn).addTrailing(comment);210} else {211Comments.forNode(fn).addLeading(comment);212}213});214215tiesToBreak.length = 0;216}217218function Comments() {219assert.ok(this instanceof Comments);220this.leading = [];221this.dangling = [];222this.trailing = [];223}224225var Cp = Comments.prototype;226227Comments.forNode = function forNode(node) {228var comments = node.comments;229if (!comments) {230Object.defineProperty(node, "comments", {231value: comments = new Comments,232enumerable: false233});234}235return comments;236};237238Cp.forEach = function forEach(callback, context) {239this.leading.forEach(callback, context);240// this.dangling.forEach(callback, context);241this.trailing.forEach(callback, context);242};243244Cp.addLeading = function addLeading(comment) {245this.leading.push(comment);246};247248Cp.addDangling = function addDangling(comment) {249this.dangling.push(comment);250};251252Cp.addTrailing = function addTrailing(comment) {253comment.trailing = true;254if (comment.type === "Block") {255this.trailing.push(comment);256} else {257this.leading.push(comment);258}259};260261/**262* @param {Object} options - Options object that configures printing.263*/264function printLeadingComment(comment, options) {265var loc = comment.loc;266var lines = loc && loc.lines;267var parts = [];268269if (comment.type === "Block") {270parts.push("/*", fromString(comment.value, options), "*/");271} else if (comment.type === "Line") {272parts.push("//", fromString(comment.value, options));273} else assert.fail(comment.type);274275if (comment.trailing) {276// When we print trailing comments as leading comments, we don't277// want to bring any trailing spaces along.278parts.push("\n");279280} else if (lines instanceof Lines) {281var trailingSpace = lines.slice(282loc.end,283lines.skipSpaces(loc.end)284);285286if (trailingSpace.length === 1) {287// If the trailing space contains no newlines, then we want to288// preserve it exactly as we found it.289parts.push(trailingSpace);290} else {291// If the trailing space contains newlines, then replace it292// with just that many newlines, with all other spaces removed.293parts.push(new Array(trailingSpace.length).join("\n"));294}295296} else {297parts.push("\n");298}299300return concat(parts).stripMargin(loc ? loc.start.column : 0);301}302303/**304* @param {Object} options - Options object that configures printing.305*/306function printTrailingComment(comment, options) {307var loc = comment.loc;308var lines = loc && loc.lines;309var parts = [];310311if (lines instanceof Lines) {312var fromPos = lines.skipSpaces(loc.start, true) || lines.firstPos();313var leadingSpace = lines.slice(fromPos, loc.start);314315if (leadingSpace.length === 1) {316// If the leading space contains no newlines, then we want to317// preserve it exactly as we found it.318parts.push(leadingSpace);319} else {320// If the leading space contains newlines, then replace it321// with just that many newlines, sans all other spaces.322parts.push(new Array(leadingSpace.length).join("\n"));323}324}325326if (comment.type === "Block") {327parts.push("/*", fromString(comment.value, options), "*/");328} else if (comment.type === "Line") {329parts.push("//", fromString(comment.value, options), "\n");330} else assert.fail(comment.type);331332return concat(parts).stripMargin(333loc ? loc.start.column : 0,334true // Skip the first line, in case there were leading spaces.335);336}337338/**339* @param {Object} options - Options object that configures printing.340*/341exports.printComments = function(comments, innerLines, options) {342if (innerLines) {343assert.ok(innerLines instanceof Lines);344} else {345innerLines = fromString("");346}347348if (!comments || !(comments.leading.length +349comments.trailing.length)) {350return innerLines;351}352353var parts = [];354355comments.leading.forEach(function(comment) {356parts.push(printLeadingComment(comment, options));357});358359parts.push(innerLines);360361comments.trailing.forEach(function(comment) {362assert.strictEqual(comment.type, "Block");363parts.push(printTrailingComment(comment, options));364});365366return concat(parts);367};368369370