3 const configurationError = require("./configurationError");
4 const isSingleLineString = require("./isSingleLineString");
5 const isWhitespace = require("./isWhitespace");
8 * Create a whitespaceChecker, which exposes the following functions:
10 * - `beforeAllowingIndentation()`
14 * @param {"space"|"newline"} targetWhitespace - This is a keyword instead
15 * of the actual character (e.g. " ") in order to accommodate
16 * different styles of newline ("\n" vs "\r\n")
19 * |"always-single-line"|"always-multi-line"
20 * | "never-single-line"|"never-multi-line"
22 * @param {object} messages - An object of message functions;
23 * calling `before*()` or `after*()` and the `expectation` that is passed
24 * determines which message functions are required
25 * @param {function} [messages.exectedBefore]
26 * @param {function} [messages.rejectedBefore]
27 * @param {function} [messages.expectedAfter]
28 * @param {function} [messages.rejectedAfter]
29 * @param {function} [messages.expectedBeforeSingleLine]
30 * @param {function} [messages.rejectedBeforeSingleLine]
31 * @param {function} [messages.expectedBeforeMultiLine]
32 * @param {function} [messages.rejectedBeforeMultiLine]
33 * @return {object} The checker, with its exposed checking functions
35 module.exports = function(
36 targetWhitespace /*: "space" | "newline"*/,
37 expectation /*: "always" | "never" | "always-single-line"
38 | "always-multi-line" | "never-single-line"|"never-multi-line"*/,
42 beforeAllowingIndentation: Function,
44 afterOneOnly: Function
46 // Keep track of active arguments in order to avoid passing
47 // too much stuff around, making signatures long and confusing.
48 // This variable gets reset anytime a checking function is called.
52 * Check for whitespace *before* a character.
54 * @param {object} args - Named arguments object
55 * @param {string} args.source - The source string
56 * @param {number} args.index - The index of the character to check before
57 * @param {function} args.err - If a violation is found, this callback
58 * will be invoked with the relevant warning message.
59 * Typically this callback will report() the violation.
60 * @param {function} args.errTarget - If a violation is found, this string
61 * will be sent to the relevant warning message.
62 * @param {string} [args.lineCheckStr] - Single- and multi-line checkers
63 * will use this string to determine whether they should proceed,
64 * i.e. if this string is one line only, single-line checkers will check,
65 * multi-line checkers will ignore.
66 * If none is passed, they will use `source`.
67 * @param {boolean} [args.onlyOneChar=false] - Only check *one* character before.
68 * By default, "always-*" checks will look for the `targetWhitespace` one
69 * before and then ensure there is no whitespace two before. This option
70 * bypasses that second check.
71 * @param {boolean} [args.allowIndentation=false] - Allow arbitrary indentation
72 * between the `targetWhitespace` (almost definitely a newline) and the `index`.
73 * With this option, the checker will see if a newline *begins* the whitespace before
76 function before(args) {
77 const source = args.source;
78 const index = args.index;
80 const errTarget = args.errTarget;
81 const lineCheckStr = args.lineCheckStr;
83 args.onlyOneChar === undefined ? false : args.onlyOneChar;
84 const allowIndentation =
85 args.allowIndentation === undefined ? false : args.allowIndentation;
95 switch (expectation) {
102 case "always-single-line":
103 if (!isSingleLineString(lineCheckStr || source)) {
106 expectBefore(messages.expectedBeforeSingleLine);
108 case "never-single-line":
109 if (!isSingleLineString(lineCheckStr || source)) {
112 rejectBefore(messages.rejectedBeforeSingleLine);
114 case "always-multi-line":
115 if (isSingleLineString(lineCheckStr || source)) {
118 expectBefore(messages.expectedBeforeMultiLine);
120 case "never-multi-line":
121 if (isSingleLineString(lineCheckStr || source)) {
124 rejectBefore(messages.rejectedBeforeMultiLine);
127 throw configurationError(`Unknown expectation "${expectation}"`);
132 * Check for whitespace *after* a character.
134 * Parameters are pretty much the same as for `before()`, above, just substitute
135 * the word "after" for "before".
137 function after(args) {
138 const source = args.source;
139 const index = args.index;
140 const err = args.err;
141 const errTarget = args.errTarget;
142 const lineCheckStr = args.lineCheckStr;
144 args.onlyOneChar === undefined ? false : args.onlyOneChar;
146 activeArgs = { source, index, err, errTarget, onlyOneChar };
147 switch (expectation) {
154 case "always-single-line":
155 if (!isSingleLineString(lineCheckStr || source)) {
158 expectAfter(messages.expectedAfterSingleLine);
160 case "never-single-line":
161 if (!isSingleLineString(lineCheckStr || source)) {
164 rejectAfter(messages.rejectedAfterSingleLine);
166 case "always-multi-line":
167 if (isSingleLineString(lineCheckStr || source)) {
170 expectAfter(messages.expectedAfterMultiLine);
172 case "never-multi-line":
173 if (isSingleLineString(lineCheckStr || source)) {
176 rejectAfter(messages.rejectedAfterMultiLine);
179 throw configurationError(`Unknown expectation "${expectation}"`);
183 function beforeAllowingIndentation(obj) {
184 before(Object.assign({}, obj, { allowIndentation: true }));
187 function expectBefore() {
189 arguments.length > 0 && arguments[0] !== undefined
191 : messages.expectedBefore;
193 if (activeArgs.allowIndentation) {
194 expectBeforeAllowingIndentation(messageFunc);
198 const _activeArgs = activeArgs;
199 const source = _activeArgs.source,
200 index = _activeArgs.index;
202 const oneCharBefore = source[index - 1];
203 const twoCharsBefore = source[index - 2];
205 if (!isValue(oneCharBefore)) {
209 if (targetWhitespace === "space" && oneCharBefore === " ") {
210 if (activeArgs.onlyOneChar || !isWhitespace(twoCharsBefore)) {
216 messageFunc(activeArgs.errTarget ? activeArgs.errTarget : source[index])
220 function expectBeforeAllowingIndentation() {
222 arguments.length > 0 && arguments[0] !== undefined
224 : messages.expectedBefore;
225 const _activeArgs2 = activeArgs;
226 const source = _activeArgs2.source,
227 index = _activeArgs2.index,
228 err = _activeArgs2.err;
230 const expectedChar = (function() {
231 if (targetWhitespace === "newline") {
236 while (source[i] !== expectedChar) {
237 if (source[i] === "\t" || source[i] === " ") {
242 messageFunc(activeArgs.errTarget ? activeArgs.errTarget : source[index])
248 function rejectBefore() {
250 arguments.length > 0 && arguments[0] !== undefined
252 : messages.rejectedBefore;
253 const _activeArgs3 = activeArgs;
254 const source = _activeArgs3.source,
255 index = _activeArgs3.index;
257 const oneCharBefore = source[index - 1];
259 if (isValue(oneCharBefore) && isWhitespace(oneCharBefore)) {
261 messageFunc(activeArgs.errTarget ? activeArgs.errTarget : source[index])
266 function afterOneOnly(obj) {
267 after(Object.assign({}, obj, { onlyOneChar: true }));
270 function expectAfter() {
272 arguments.length > 0 && arguments[0] !== undefined
274 : messages.expectedAfter;
275 const _activeArgs4 = activeArgs;
276 const source = _activeArgs4.source,
277 index = _activeArgs4.index;
279 const oneCharAfter = source[index + 1];
280 const twoCharsAfter = source[index + 2];
282 if (!isValue(oneCharAfter)) {
286 if (targetWhitespace === "newline") {
287 // If index is followed by a Windows CR-LF ...
288 if (oneCharAfter === "\r" && twoCharsAfter === "\n") {
289 if (activeArgs.onlyOneChar || !isWhitespace(source[index + 3])) {
294 // If index is followed by a Unix LF ...
295 if (oneCharAfter === "\n") {
296 if (activeArgs.onlyOneChar || !isWhitespace(twoCharsAfter)) {
302 if (targetWhitespace === "space" && oneCharAfter === " ") {
303 if (activeArgs.onlyOneChar || !isWhitespace(twoCharsAfter)) {
309 messageFunc(activeArgs.errTarget ? activeArgs.errTarget : source[index])
313 function rejectAfter() {
315 arguments.length > 0 && arguments[0] !== undefined
317 : messages.rejectedAfter;
318 const _activeArgs5 = activeArgs;
319 const source = _activeArgs5.source,
320 index = _activeArgs5.index;
322 const oneCharAfter = source[index + 1];
324 if (isValue(oneCharAfter) && isWhitespace(oneCharAfter)) {
326 messageFunc(activeArgs.errTarget ? activeArgs.errTarget : source[index])
333 beforeAllowingIndentation,
339 function isValue(x) {
340 return x !== undefined && x !== null;