| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203 | var SKIP = 'skip';var CHECK = 'check';var ONLY = 'only';module.exports = function (options, callback) {  var source = options.source;  var target = options.target;  var skipComments = (options.comments) ? options.comments === SKIP : true;  var skipStrings = (options.strings) ? options.strings === SKIP : true;  var skipFunctionNames = (options.functionNames) ? options.functionNames === SKIP : true;  var skipFunctionArguments = options.functionArguments === SKIP;  var skipParentheticals = options.parentheticals === SKIP;  var onceOptionUsed = false;  Object.keys(options).forEach(function(key) {    if (options[key] !== ONLY) return;    if (!onceOptionUsed) {      onceOptionUsed = true;    } else {      throw new Error('Only one syntax feature option can be the "only" one to check');    }  });  var onlyComments = options.comments === ONLY;  var onlyStrings = options.strings === ONLY;  var onlyFunctionNames = options.functionNames === ONLY;  var onlyFunctionArguments = options.functionArguments === ONLY;  var onlyParentheticals = options.parentheticals === ONLY;  var insideString = false;  var insideComment = false;  var insideSingleLineComment = false;  var insideParens = false;  var insideFunctionArguments = false;  var openingParenCount = 0;  var matchCount = 0;  var openingQuote;  var targetIsArray = Array.isArray(target);  // If the target is just a string, it is easy to check whether  // some index of the source matches it.  // If the target is an array of strings, though, we have to  // check whether some index of the source matches *any* of  // those target strings (stopping after the first match).  var getMatch = (function () {    if (!targetIsArray) {      return getMatchBase.bind(null, target);    }    return function(index) {      for (var ti = 0, tl = target.length; ti < tl; ti++) {        var checkResult = getMatchBase(target[ti], index);        if (checkResult) return checkResult;      }      return false;    }  })();  function getMatchBase(targetString, index) {    var targetStringLength = targetString.length;    // Target is a single character    if (targetStringLength === 1 && source[index] !== targetString) return false;    // Target is multiple characters    if (source.substr(index, targetStringLength) !== targetString) return false;    return {      insideParens: insideParens,      insideFunctionArguments: insideFunctionArguments,      insideComment: insideComment,      insideString: insideString,      startIndex: index,      endIndex: index + targetStringLength,      target: targetString,    };  }  for (var i = 0, l = source.length; i < l; i++) {    var currentChar = source[i];    // Register the beginning of a comment    if (      !insideString && !insideComment      && currentChar === "/"      && source[i - 1] !== "\\" // escaping    ) {      // standard comments      if (source[i + 1] === "*") {        insideComment = true;        continue;      }      // single-line comments      if (source[i + 1] === "/") {        insideComment = true;        insideSingleLineComment = true;        continue;      }    }    if (insideComment) {      // Register the end of a standard comment      if (        !insideSingleLineComment        && currentChar === "*"        && source[i - 1] !== "\\" // escaping        && source[i + 1] === "/"        && source[i - 1] !== "/" // don't end if it's /*/      ) {        insideComment = false;        continue;      }      // Register the end of a single-line comment      if (        insideSingleLineComment        && currentChar === "\n"      ) {        insideComment = false;        insideSingleLineComment = false;      }      if (skipComments) continue;    }    // Register the beginning of a string    if (!insideComment && !insideString && (currentChar === "\"" || currentChar === "'")) {      if (source[i - 1] === "\\") continue; // escaping      openingQuote = currentChar;      insideString = true;      // For string-quotes rule      if (target === currentChar) handleMatch(getMatch(i));      continue;    }    if (insideString) {      // Register the end of a string      if (currentChar === openingQuote) {        if (source[i - 1] === "\\") continue; // escaping        insideString = false;        continue;      }      if (skipStrings) continue;    }    // Register the beginning of parens/functions    if (!insideString && !insideComment && currentChar === "(") {      // Keep track of opening parentheticals so that we      // know when the outermost function (possibly      // containing nested functions) is closing      openingParenCount++;      insideParens = true;      // Only inside a function if there is a function name      // before the opening paren      if (/[a-zA-Z]/.test(source[i - 1])) {        insideFunctionArguments = true;      }      if (target === "(") handleMatch(getMatch(i));      continue;    }    if (insideParens) {      // Register the end of a function      if (currentChar === ")") {        openingParenCount--;        // Do this here so the match is still technically inside a function        if (target === ")") handleMatch(getMatch(i));        if (openingParenCount === 0) {          insideParens = false;          insideFunctionArguments = false;        }        continue;      }    }    var isFunctionName = /^[a-zA-Z]*\(/.test(source.slice(i));    if (skipFunctionNames && isFunctionName) continue;    if (onlyFunctionNames && !isFunctionName) continue;    var match = getMatch(i);    if (!match) continue;    handleMatch(match);    if (options.once) return;  }  function handleMatch(match) {    if (onlyParentheticals && !insideParens) return;    if (skipParentheticals && insideParens) return;    if (onlyFunctionArguments && !insideFunctionArguments) return;    if (skipFunctionArguments && insideFunctionArguments) return;    if (onlyStrings && !insideString) return;    if (onlyComments && !insideComment) return;    matchCount++;    callback(match, matchCount);  }}
 |