Skip to content

Commit f8a5814

Browse files
committed
Require literals to be marked ignoreCase if RegExps are
1 parent 47c216d commit f8a5814

File tree

2 files changed

+89
-13
lines changed

2 files changed

+89
-13
lines changed

moo.js

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,7 @@
118118
value: null,
119119
type: null,
120120
shouldThrow: false,
121+
ignoreCase: null,
121122
}
122123

123124
// Avoid Object.assign(), so we support IE9+
@@ -210,8 +211,12 @@
210211
groups.push(options)
211212

212213
// Check unicode and ignoreCase flags are used everywhere or nowhere
214+
var hasLiteralsWithCase = false
213215
for (var j = 0; j < match.length; j++) {
214216
var obj = match[j]
217+
if (typeof obj === "string" && obj.toLowerCase() !== obj.toUpperCase()) {
218+
hasLiteralsWithCase = true
219+
}
215220
if (!isRegExp(obj)) {
216221
continue
217222
}
@@ -227,6 +232,25 @@
227232
} else if (ignoreCaseFlag !== obj.ignoreCase) {
228233
throw new Error("If one rule is /i then all must be")
229234
}
235+
236+
// RegExp flags must match the rule's ignoreCase option, if set
237+
if (options.ignoreCase !== null && obj.ignoreCase !== options.ignoreCase) {
238+
throw new Error("ignoreCase option must match RegExp flags (in token '" + options.defaultType + "')")
239+
}
240+
}
241+
242+
if (hasLiteralsWithCase) {
243+
var ignoreCase = !!options.ignoreCase
244+
if (ignoreCaseFlag === null) {
245+
ignoreCaseFlag = ignoreCase
246+
} else if (ignoreCaseFlag !== ignoreCase) {
247+
if (ignoreCaseFlag) {
248+
throw new Error("Literal must be marked with {ignoreCase: true} (in token '" + options.defaultType + "')")
249+
} else {
250+
// TODO transform literals to ignore case, even if it's not set globally
251+
throw new Error("If one rule sets ignoreCase then all must (in token '" + options.defaultType + "')")
252+
}
253+
}
230254
}
231255

232256
// convert to RegExp

test/test.js

Lines changed: 65 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1215,27 +1215,79 @@ describe("unicode flag", () => {
12151215
describe('ignoreCase flag', () => {
12161216

12171217
test("allows all rules to be /i", () => {
1218-
expect(() => compile({ a: /foo/i, b: /bar/i, c: "quxx" })).not.toThrow()
1219-
expect(() => compile({ a: /foo/i, b: /bar/, c: "quxx" })).toThrow("If one rule is /i then all must be")
1220-
expect(() => compile({ a: /foo/, b: /bar/i, c: "quxx" })).toThrow("If one rule is /i then all must be")
1218+
expect(() => compile({ a: /foo/i, b: /bar/i })).not.toThrow()
1219+
expect(() => compile({ a: /foo/i, b: /bar/ })).toThrow("If one rule is /i then all must be")
1220+
expect(() => compile({ a: /foo/, b: /bar/i })).toThrow("If one rule is /i then all must be")
12211221
})
12221222

12231223
test("allows all rules to be /ui", () => {
1224-
expect(() => compile({ a: /foo/ui, b: /bar/ui, c: "quxx" })).not.toThrow()
1225-
expect(() => compile({ a: /foo/u, b: /bar/i, c: "quxx" })).toThrow("If one rule is /i then all must be")
1226-
expect(() => compile({ a: /foo/i, b: /bar/u, c: "quxx" })).toThrow("If one rule is /i then all must be")
1227-
expect(() => compile({ a: /foo/ui, b: /bar/i, c: "quxx" })).toThrow("If one rule is /u then all must be")
1228-
expect(() => compile({ a: /foo/ui, b: /bar/u, c: "quxx" })).toThrow("If one rule is /i then all must be")
1229-
expect(() => compile({ a: /foo/i, b: /bar/ui, c: "quxx" })).toThrow("If one rule is /u then all must be")
1230-
expect(() => compile({ a: /foo/u, b: /bar/ui, c: "quxx" })).toThrow("If one rule is /i then all must be")
1224+
expect(() => compile({ a: /foo/ui, b: /bar/ui })).not.toThrow()
1225+
expect(() => compile({ a: /foo/u, b: /bar/i })).toThrow("If one rule is /u then all must be")
1226+
expect(() => compile({ a: /foo/i, b: /bar/u })).toThrow("If one rule is /u then all must be")
1227+
expect(() => compile({ a: /foo/ui, b: /bar/i })).toThrow("If one rule is /u then all must be")
1228+
expect(() => compile({ a: /foo/ui, b: /bar/u })).toThrow("If one rule is /i then all must be")
1229+
expect(() => compile({ a: /foo/i, b: /bar/ui })).toThrow("If one rule is /u then all must be")
1230+
expect(() => compile({ a: /foo/u, b: /bar/ui })).toThrow("If one rule is /i then all must be")
1231+
})
1232+
1233+
test("allow literals to be marked ignoreCase", () => {
1234+
expect(() => compile({
1235+
a: /foo/i,
1236+
lit: {match: "quxx", ignoreCase: true},
1237+
})).not.toThrow()
1238+
expect(() => compile([
1239+
{ type: "a", match: /foo/i },
1240+
{ type: "lit", match: "quxx", ignoreCase: true },
1241+
])).not.toThrow()
1242+
})
1243+
1244+
test("require literals to be marked ignoreCase", () => {
1245+
expect(() => compile({
1246+
a: /foo/i,
1247+
lit: "quxx" ,
1248+
})).toThrow("Literal must be marked with {ignoreCase: true} (in token 'lit')")
1249+
expect(() => compile([
1250+
{ type: "a", match: /foo/i },
1251+
{ type: "lit", match: "quxx" },
1252+
])).toThrow("Literal must be marked with {ignoreCase: true} (in token 'lit')")
1253+
})
1254+
1255+
test("ignoreCase is only required when case is relevant", () => {
1256+
expect(() => compile({
1257+
cat: {match: "cat", ignoreCase: true},
1258+
bat: {match: "BAT", ignoreCase: true},
1259+
comma: ',',
1260+
semi: ';',
1261+
lparen: '(',
1262+
rparen: ')',
1263+
lbrace: '{',
1264+
rbrace: '}',
1265+
lbracket: '[',
1266+
rbracket: ']',
1267+
and: '&&',
1268+
or: '||',
1269+
bitand: '&',
1270+
bitor: '|',
1271+
})).not.toThrow()
1272+
})
1273+
1274+
test("require ignoreCase option to be match RegExp flags", () => {
1275+
expect(() => compile({
1276+
word: { match: /[a-z]+/, ignoreCase: true },
1277+
})).toThrow("ignoreCase option must match RegExp flags")
1278+
expect(() => compile({
1279+
word: { match: ["foo", /[a-z]+/], ignoreCase: true },
1280+
})).toThrow("ignoreCase option must match RegExp flags")
1281+
expect(() => compile({
1282+
word: { match: /[a-z]+/i, ignoreCase: false },
1283+
})).toThrow("ignoreCase option must match RegExp flags")
12311284
})
12321285

12331286
test("supports ignoreCase", () => {
1234-
const lexer = compile({ a: /foo/i, b: /bar/i, c: "quxx" })
1235-
lexer.reset("FoObArQuXx")
1287+
const lexer = compile({ a: /foo/i, b: /bar/i, })
1288+
lexer.reset("FoObAr")
12361289
expect(lexer.next()).toMatchObject({value: "FoO"})
12371290
expect(lexer.next()).toMatchObject({value: "bAr"})
1238-
expect(lexer.next()).toMatchObject({value: "QuXx"})
12391291
})
12401292

12411293
})

0 commit comments

Comments
 (0)