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);
- }
- }
|