5 module.exports = function (options, callback) {
6 var source = options.source;
7 var target = options.target;
9 var skipComments = (options.comments) ? options.comments === SKIP : true;
10 var skipStrings = (options.strings) ? options.strings === SKIP : true;
11 var skipFunctionNames = (options.functionNames) ? options.functionNames === SKIP : true;
12 var skipFunctionArguments = options.functionArguments === SKIP;
13 var skipParentheticals = options.parentheticals === SKIP;
15 var onceOptionUsed = false;
16 Object.keys(options).forEach(function(key) {
17 if (options[key] !== ONLY) return;
18 if (!onceOptionUsed) {
19 onceOptionUsed = true;
21 throw new Error('Only one syntax feature option can be the "only" one to check');
25 var onlyComments = options.comments === ONLY;
26 var onlyStrings = options.strings === ONLY;
27 var onlyFunctionNames = options.functionNames === ONLY;
28 var onlyFunctionArguments = options.functionArguments === ONLY;
29 var onlyParentheticals = options.parentheticals === ONLY;
31 var insideString = false;
32 var insideComment = false;
33 var insideSingleLineComment = false;
34 var insideParens = false;
35 var insideFunctionArguments = false;
36 var openingParenCount = 0;
40 var targetIsArray = Array.isArray(target);
42 // If the target is just a string, it is easy to check whether
43 // some index of the source matches it.
44 // If the target is an array of strings, though, we have to
45 // check whether some index of the source matches *any* of
46 // those target strings (stopping after the first match).
47 var getMatch = (function () {
49 return getMatchBase.bind(null, target);
51 return function(index) {
52 for (var ti = 0, tl = target.length; ti < tl; ti++) {
53 var checkResult = getMatchBase(target[ti], index);
54 if (checkResult) return checkResult;
60 function getMatchBase(targetString, index) {
61 var targetStringLength = targetString.length;
63 // Target is a single character
64 if (targetStringLength === 1 && source[index] !== targetString) return false;
66 // Target is multiple characters
67 if (source.substr(index, targetStringLength) !== targetString) return false;
70 insideParens: insideParens,
71 insideFunctionArguments: insideFunctionArguments,
72 insideComment: insideComment,
73 insideString: insideString,
75 endIndex: index + targetStringLength,
80 for (var i = 0, l = source.length; i < l; i++) {
81 var currentChar = source[i];
83 // Register the beginning of a comment
85 !insideString && !insideComment
86 && currentChar === "/"
87 && source[i - 1] !== "\\" // escaping
90 if (source[i + 1] === "*") {
94 // single-line comments
95 if (source[i + 1] === "/") {
97 insideSingleLineComment = true;
103 // Register the end of a standard comment
105 !insideSingleLineComment
106 && currentChar === "*"
107 && source[i - 1] !== "\\" // escaping
108 && source[i + 1] === "/"
109 && source[i - 1] !== "/" // don't end if it's /*/
111 insideComment = false;
115 // Register the end of a single-line comment
117 insideSingleLineComment
118 && currentChar === "\n"
120 insideComment = false;
121 insideSingleLineComment = false;
124 if (skipComments) continue;
127 // Register the beginning of a string
128 if (!insideComment && !insideString && (currentChar === "\"" || currentChar === "'")) {
129 if (source[i - 1] === "\\") continue; // escaping
131 openingQuote = currentChar;
134 // For string-quotes rule
135 if (target === currentChar) handleMatch(getMatch(i));
140 // Register the end of a string
141 if (currentChar === openingQuote) {
142 if (source[i - 1] === "\\") continue; // escaping
143 insideString = false;
147 if (skipStrings) continue;
150 // Register the beginning of parens/functions
151 if (!insideString && !insideComment && currentChar === "(") {
152 // Keep track of opening parentheticals so that we
153 // know when the outermost function (possibly
154 // containing nested functions) is closing
158 // Only inside a function if there is a function name
159 // before the opening paren
160 if (/[a-zA-Z]/.test(source[i - 1])) {
161 insideFunctionArguments = true;
164 if (target === "(") handleMatch(getMatch(i));
169 // Register the end of a function
170 if (currentChar === ")") {
172 // Do this here so the match is still technically inside a function
173 if (target === ")") handleMatch(getMatch(i));
174 if (openingParenCount === 0) {
175 insideParens = false;
176 insideFunctionArguments = false;
182 var isFunctionName = /^[a-zA-Z]*\(/.test(source.slice(i));
183 if (skipFunctionNames && isFunctionName) continue;
184 if (onlyFunctionNames && !isFunctionName) continue;
186 var match = getMatch(i);
188 if (!match) continue;
190 if (options.once) return;
193 function handleMatch(match) {
194 if (onlyParentheticals && !insideParens) return;
195 if (skipParentheticals && insideParens) return;
196 if (onlyFunctionArguments && !insideFunctionArguments) return;
197 if (skipFunctionArguments && insideFunctionArguments) return;
198 if (onlyStrings && !insideString) return;
199 if (onlyComments && !insideComment) return;
201 callback(match, matchCount);