You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
203 lines
6.0 KiB
203 lines
6.0 KiB
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); |
|
} |
|
}
|
|
|