Add autocomplete support for quoted names, and remove single quotes syntax [#34]

This commit is contained in:
David Evans 2018-01-20 12:10:41 +00:00
parent 394dcb0e42
commit ece615e2a0
9 changed files with 202 additions and 103 deletions

View File

@ -22,7 +22,7 @@ Goblin -> Bowie: What babe?
Bowie -> Goblin: The babe with the power Bowie -> Goblin: The babe with the power
Goblin -> Bowie: What power? Goblin -> Bowie: What power?
note right of Bowie, Goblin: Most people get muddled here! note right of Bowie, Goblin: Most people get muddled here!
Bowie -> Goblin: 'The power of voodoo' Bowie -> Goblin: "The power of voodoo"
Goblin -> Bowie: "Who-do?" Goblin -> Bowie: "Who-do?"
Bowie -> Goblin: You do! Bowie -> Goblin: You do!
Goblin -> Bowie: Do what? Goblin -> Bowie: Do what?
@ -85,7 +85,7 @@ note over Foo, Bar: "Foo and Bar
on multiple lines" on multiple lines"
note between Foo, Bar: Link note between Foo, Bar: Link
text right: 'Comments\nOver here!' text right: "Comments\nOver here!"
state over Foo: Foo is ponderous state over Foo: Foo is ponderous
``` ```
@ -133,19 +133,19 @@ A <- ]: Profit!
<img src="screenshots/MultilineText.png" alt="Multiline Text preview" width="200" align="right" /> <img src="screenshots/MultilineText.png" alt="Multiline Text preview" width="200" align="right" />
``` ```
title 'My Multiline title "My Multiline
Title' Title"
note over Foo: 'Also possible\nwith escapes' note over Foo: "Also possible\nwith escapes"
Foo -> Bar: 'Lines of text\non this arrow' Foo -> Bar: "Lines of text\non this arrow"
if 'Even multiline\ninside conditions like this' if "Even multiline\ninside conditions like this"
Foo -> 'Multiline\nagent' Foo -> "Multiline\nagent"
end end
state over Foo: 'Newlines here, state over Foo: "Newlines here,
too!' too!"
``` ```
### Themes ### Themes
@ -206,13 +206,13 @@ A <- B: than writing the whole name
<img src="screenshots/Markdown.png" alt="Markdown preview" width="200" align="right" /> <img src="screenshots/Markdown.png" alt="Markdown preview" width="200" align="right" />
``` ```
define 'Name with define "Name with
**bold** and _italic_' as A **bold** and _italic_" as A
define 'Also `code` define "Also `code`
and ~strikeout~' as B and ~strikeout~" as B
A -> B: '_**basic markdown A -> B: "_**basic markdown
is supported!**_' is supported!**_"
``` ```
### Alternative Agent Ordering ### Alternative Agent Ordering
@ -270,17 +270,16 @@ Comments begin with a `#` and end at the next newline:
Meta data can be provided with particular keywords: Meta data can be provided with particular keywords:
``` ```
title 'My title here' title "My title here"
``` ```
Quoting strings is usually optional, for example these are the same: Quoting strings is usually optional, for example these are the same:
``` ```
title 'My title here'
title "My title here" title "My title here"
title My title here title My title here
title "My title" here title "My title" here
title "My" 'title' "here" title "My" "title" "here"
``` ```
Each non-metadata line represents a step in the sequence, in order. Each non-metadata line represents a step in the sequence, in order.
@ -293,7 +292,7 @@ Foo Bar -> Zig Zag: Do a thing
# With quotes, this is the same as: # With quotes, this is the same as:
'Foo Bar' -> 'Zig Zag': 'Do a thing' "Foo Bar" -> "Zig Zag": "Do a thing"
``` ```
Blocks surround steps, and can nest: Blocks surround steps, and can nest:

View File

@ -606,6 +606,15 @@ define('sequence/CodeMirrorMode',['core/ArrayUtilities'], (array) => {
const CM_ERROR = {type: 'error line-error', then: {'': 0}}; const CM_ERROR = {type: 'error line-error', then: {'': 0}};
function suggestionsEqual(a, b) {
return (
(a.v === b.v) &&
(a.prefix === b.prefix) &&
(a.suffix === b.suffix) &&
(a.q === b.q)
);
}
const makeCommands = ((() => { const makeCommands = ((() => {
// The order of commands inside "then" blocks directly influences the // The order of commands inside "then" blocks directly influences the
// order they are displayed to the user in autocomplete menus. // order they are displayed to the user in autocomplete menus.
@ -882,9 +891,9 @@ define('sequence/CodeMirrorMode',['core/ArrayUtilities'], (array) => {
function cmCappedToken(token, current) { function cmCappedToken(token, current) {
if(Object.keys(current.then).length > 0) { if(Object.keys(current.then).length > 0) {
return token + ' '; return {v: token, suffix: ' ', q: false};
} else { } else {
return token + '\n'; return {v: token, suffix: '\n', q: false};
} }
} }
@ -907,9 +916,9 @@ define('sequence/CodeMirrorMode',['core/ArrayUtilities'], (array) => {
} else if(current.suggest === true) { } else if(current.suggest === true) {
return [cmCappedToken(token, current)]; return [cmCappedToken(token, current)];
} else if(Array.isArray(current.suggest)) { } else if(Array.isArray(current.suggest)) {
return current.suggest; return current.suggest.map((v) => ({v, q: false}));
} else if(current.suggest) { } else if(current.suggest) {
return [current.suggest]; return [{v: current.suggest, q: false}];
} else { } else {
return null; return null;
} }
@ -925,7 +934,8 @@ define('sequence/CodeMirrorMode',['core/ArrayUtilities'], (array) => {
} }
array.mergeSets( array.mergeSets(
comp, comp,
cmGetSuggestions(state, token, current, next) cmGetSuggestions(state, token, current, next),
suggestionsEqual
); );
}); });
return comp; return comp;
@ -939,7 +949,8 @@ define('sequence/CodeMirrorMode',['core/ArrayUtilities'], (array) => {
} }
array.mergeSets( array.mergeSets(
state['known' + locals.type], state['known' + locals.type],
[locals.value + ' '] [{v: locals.value, suffix: ' ', q: true}],
suggestionsEqual
); );
locals.type = ''; locals.type = '';
locals.value = ''; locals.value = '';
@ -1153,14 +1164,6 @@ define('sequence/Tokeniser',['./CodeMirrorMode'], (CMMode) => {
escapeWith: unescape, escapeWith: unescape,
baseToken: {q: true}, baseToken: {q: true},
}, },
{
start: /'/y,
end: /'/y,
escape: /\\(.)/y,
escapeWith:
unescape,
baseToken: {q: true},
},
{start: /(?=[^ \t\r\n:+\-~*!<>,])/y, end: /(?=[ \t\r\n:+\-~*!<>,])|$/y}, {start: /(?=[^ \t\r\n:+\-~*!<>,])/y, end: /(?=[ \t\r\n:+\-~*!<>,])|$/y},
{ {
start: /(?=[\-~<])/y, start: /(?=[\-~<])/y,
@ -5643,6 +5646,17 @@ define('sequence/CodeMirrorHints',['core/ArrayUtilities'], (array) => {
const TRIMMER = /^([ \t]*)(.*)$/; const TRIMMER = /^([ \t]*)(.*)$/;
const SQUASH_START = /^[ \t\r\n:,]/; const SQUASH_START = /^[ \t\r\n:,]/;
const SQUASH_END = /[ \t\r\n]$/; const SQUASH_END = /[ \t\r\n]$/;
const REQUIRED_QUOTED = /[\r\n:,"]/;
const QUOTE_ESCAPE = /["\\]/g;
function suggestionsEqual(a, b) {
return (
(a.v === b.v) &&
(a.prefix === b.prefix) &&
(a.suffix === b.suffix) &&
(a.q === b.q)
);
}
function makeRanges(cm, line, chFrom, chTo) { function makeRanges(cm, line, chFrom, chTo) {
const ln = cm.getLine(line); const ln = cm.getLine(line);
@ -5661,13 +5675,27 @@ define('sequence/CodeMirrorHints',['core/ArrayUtilities'], (array) => {
return ranges; return ranges;
} }
function makeHintItem(text, ranges) { function wrapQuote(entry, quote) {
if(!quote && entry.q && REQUIRED_QUOTED.test(entry.v)) {
quote = '"';
}
let inner = entry.v;
if(quote) {
inner = quote + inner.replace(QUOTE_ESCAPE, '\\$&') + quote;
}
return (entry.prefix || '') + inner + (entry.suffix || '');
}
function makeHintItem(entry, ranges, quote) {
const quoted = wrapQuote(entry, quote);
return { return {
text: text, text: quoted,
displayText: (text === '\n') ? '<END>' : text.trim(), displayText: (quoted === '\n') ? '<END>' : quoted.trim(),
className: (text === '\n') ? 'pick-virtual' : null, className: (quoted === '\n') ? 'pick-virtual' : null,
from: SQUASH_START.test(text) ? ranges.squashFrom : ranges.wordFrom, from: SQUASH_START.test(quoted) ?
to: SQUASH_END.test(text) ? ranges.squashTo : ranges.wordTo, ranges.squashFrom : ranges.wordFrom,
to: SQUASH_END.test(quoted) ?
ranges.squashTo : ranges.wordTo,
}; };
} }
@ -5676,14 +5704,14 @@ define('sequence/CodeMirrorHints',['core/ArrayUtilities'], (array) => {
if(!identified) { if(!identified) {
return []; return [];
} }
return identified.map((item) => (prefix + item + suffix)); return identified.map((item) => ({v: item, prefix, suffix, q: true}));
} }
function populateGlobals(suggestions, globals = {}) { function populateGlobals(suggestions, globals = {}) {
for(let i = 0; i < suggestions.length;) { for(let i = 0; i < suggestions.length;) {
if(typeof suggestions[i] === 'object') { if(suggestions[i].global) {
const identified = getGlobals(suggestions[i], globals); const identified = getGlobals(suggestions[i], globals);
array.mergeSets(suggestions, identified); array.mergeSets(suggestions, identified, suggestionsEqual);
suggestions.splice(i, 1); suggestions.splice(i, 1);
} else { } else {
++ i; ++ i;
@ -5691,16 +5719,29 @@ define('sequence/CodeMirrorHints',['core/ArrayUtilities'], (array) => {
} }
} }
function getHints(cm, options) { function getPartial(cur, token) {
const cur = cm.getCursor();
const token = cm.getTokenAt(cur);
let partial = token.string; let partial = token.string;
if(token.end > cur.ch) { if(token.end > cur.ch) {
partial = partial.substr(0, cur.ch - token.start); partial = partial.substr(0, cur.ch - token.start);
} }
const parts = TRIMMER.exec(partial); const parts = TRIMMER.exec(partial);
partial = parts[2]; partial = parts[2];
const from = token.start + parts[1].length; let quote = '';
if(partial[0] === '"') {
quote = partial[0];
partial = partial.substr(1);
}
return {
partial,
quote,
from: token.start + parts[1].length,
};
}
function getHints(cm, options) {
const cur = cm.getCursor();
const token = cm.getTokenAt(cur);
const {partial, from, quote} = getPartial(cur, token);
const continuation = (cur.ch > 0 && token.state.line.length > 0); const continuation = (cur.ch > 0 && token.state.line.length > 0);
let comp = (continuation ? let comp = (continuation ?
@ -5716,18 +5757,22 @@ define('sequence/CodeMirrorHints',['core/ArrayUtilities'], (array) => {
const ranges = makeRanges(cm, cur.line, from, token.end); const ranges = makeRanges(cm, cur.line, from, token.end);
let selfValid = false; let selfValid = false;
const list = (comp const list = (comp
.filter((opt) => opt.startsWith(partial)) .filter(({v, q}) => (q || !quote) && v.startsWith(partial))
.map((opt) => { .map((o) => {
if(opt === partial + ' ' && !options.completeSingle) { if(o.v === partial + ' ' && !options.completeSingle) {
selfValid = true; selfValid = true;
return null; return null;
} }
return makeHintItem(opt, ranges); return makeHintItem(o, ranges, quote);
}) })
.filter((opt) => (opt !== null)) .filter((opt) => (opt !== null))
); );
if(selfValid && list.length > 0) { if(selfValid && list.length > 0) {
list.unshift(makeHintItem(partial + ' ', ranges)); list.unshift(makeHintItem(
{v: partial, suffix: ' ', q: false},
ranges,
quote
));
} }
return { return {

File diff suppressed because one or more lines are too long

View File

@ -196,7 +196,7 @@ note over Foo, Bar: "Foo and Bar
on multiple lines" on multiple lines"
note between Foo, Bar: Link note between Foo, Bar: Link
text right: 'Comments\nOver here!' text right: "Comments\nOver here!"
state over Foo: Foo is ponderous state over Foo: Foo is ponderous
</pre> </pre>
@ -235,19 +235,19 @@ A <- ]: Profit!
<h3 id="MultilineText">Multiline Text</h3> <h3 id="MultilineText">Multiline Text</h3>
<pre class="example" data-lang="sequence"> <pre class="example" data-lang="sequence">
title 'My Multiline title "My Multiline
Title' Title"
note over Foo: 'Also possible\nwith escapes' note over Foo: "Also possible\nwith escapes"
Foo -> Bar: 'Lines of text\non this arrow' Foo -> Bar: "Lines of text\non this arrow"
if 'Even multiline\ninside conditions like this' if "Even multiline\ninside conditions like this"
Foo -> 'Multiline\nagent' Foo -> "Multiline\nagent"
end end
state over Foo: 'Newlines here, state over Foo: "Newlines here,
too!' too!"
</pre> </pre>
<h3 id="Themes">Themes</h3> <h3 id="Themes">Themes</h3>
@ -305,13 +305,13 @@ A <- B: than writing the whole name
<h3 id="Markdown">Markdown</h3> <h3 id="Markdown">Markdown</h3>
<pre class="example" data-lang="sequence"> <pre class="example" data-lang="sequence">
define 'Name with define "Name with
**bold** and _italic_' as A **bold** and _italic_" as A
define 'Also `code` define "Also `code`
and ~strikeout~' as B and ~strikeout~" as B
A -> B: '_**basic markdown A -> B: "_**basic markdown
is supported!**_' is supported!**_"
</pre> </pre>
<h3 id="AlternativeAgentOrdering">Alternative Agent Ordering</h3> <h3 id="AlternativeAgentOrdering">Alternative Agent Ordering</h3>

View File

@ -21,7 +21,7 @@
'Bowie -> Goblin: The babe with the power\n' + 'Bowie -> Goblin: The babe with the power\n' +
'Goblin -> Bowie: What power?\n' + 'Goblin -> Bowie: What power?\n' +
'note right of Bowie, Goblin: Most people get muddled here!\n' + 'note right of Bowie, Goblin: Most people get muddled here!\n' +
'Bowie -> Goblin: \'The power of voodoo\'\n' + 'Bowie -> Goblin: "The power of voodoo"\n' +
'Goblin -> Bowie: "Who-do?"\n' + 'Goblin -> Bowie: "Who-do?"\n' +
'Bowie -> Goblin: You do!\n' + 'Bowie -> Goblin: You do!\n' +
'Goblin -> Bowie: Do what?\n' + 'Goblin -> Bowie: Do what?\n' +

View File

@ -4,6 +4,17 @@ define(['core/ArrayUtilities'], (array) => {
const TRIMMER = /^([ \t]*)(.*)$/; const TRIMMER = /^([ \t]*)(.*)$/;
const SQUASH_START = /^[ \t\r\n:,]/; const SQUASH_START = /^[ \t\r\n:,]/;
const SQUASH_END = /[ \t\r\n]$/; const SQUASH_END = /[ \t\r\n]$/;
const REQUIRED_QUOTED = /[\r\n:,"]/;
const QUOTE_ESCAPE = /["\\]/g;
function suggestionsEqual(a, b) {
return (
(a.v === b.v) &&
(a.prefix === b.prefix) &&
(a.suffix === b.suffix) &&
(a.q === b.q)
);
}
function makeRanges(cm, line, chFrom, chTo) { function makeRanges(cm, line, chFrom, chTo) {
const ln = cm.getLine(line); const ln = cm.getLine(line);
@ -22,13 +33,27 @@ define(['core/ArrayUtilities'], (array) => {
return ranges; return ranges;
} }
function makeHintItem(text, ranges) { function wrapQuote(entry, quote) {
if(!quote && entry.q && REQUIRED_QUOTED.test(entry.v)) {
quote = '"';
}
let inner = entry.v;
if(quote) {
inner = quote + inner.replace(QUOTE_ESCAPE, '\\$&') + quote;
}
return (entry.prefix || '') + inner + (entry.suffix || '');
}
function makeHintItem(entry, ranges, quote) {
const quoted = wrapQuote(entry, quote);
return { return {
text: text, text: quoted,
displayText: (text === '\n') ? '<END>' : text.trim(), displayText: (quoted === '\n') ? '<END>' : quoted.trim(),
className: (text === '\n') ? 'pick-virtual' : null, className: (quoted === '\n') ? 'pick-virtual' : null,
from: SQUASH_START.test(text) ? ranges.squashFrom : ranges.wordFrom, from: SQUASH_START.test(quoted) ?
to: SQUASH_END.test(text) ? ranges.squashTo : ranges.wordTo, ranges.squashFrom : ranges.wordFrom,
to: SQUASH_END.test(quoted) ?
ranges.squashTo : ranges.wordTo,
}; };
} }
@ -37,14 +62,14 @@ define(['core/ArrayUtilities'], (array) => {
if(!identified) { if(!identified) {
return []; return [];
} }
return identified.map((item) => (prefix + item + suffix)); return identified.map((item) => ({v: item, prefix, suffix, q: true}));
} }
function populateGlobals(suggestions, globals = {}) { function populateGlobals(suggestions, globals = {}) {
for(let i = 0; i < suggestions.length;) { for(let i = 0; i < suggestions.length;) {
if(typeof suggestions[i] === 'object') { if(suggestions[i].global) {
const identified = getGlobals(suggestions[i], globals); const identified = getGlobals(suggestions[i], globals);
array.mergeSets(suggestions, identified); array.mergeSets(suggestions, identified, suggestionsEqual);
suggestions.splice(i, 1); suggestions.splice(i, 1);
} else { } else {
++ i; ++ i;
@ -52,16 +77,29 @@ define(['core/ArrayUtilities'], (array) => {
} }
} }
function getHints(cm, options) { function getPartial(cur, token) {
const cur = cm.getCursor();
const token = cm.getTokenAt(cur);
let partial = token.string; let partial = token.string;
if(token.end > cur.ch) { if(token.end > cur.ch) {
partial = partial.substr(0, cur.ch - token.start); partial = partial.substr(0, cur.ch - token.start);
} }
const parts = TRIMMER.exec(partial); const parts = TRIMMER.exec(partial);
partial = parts[2]; partial = parts[2];
const from = token.start + parts[1].length; let quote = '';
if(partial[0] === '"') {
quote = partial[0];
partial = partial.substr(1);
}
return {
partial,
quote,
from: token.start + parts[1].length,
};
}
function getHints(cm, options) {
const cur = cm.getCursor();
const token = cm.getTokenAt(cur);
const {partial, from, quote} = getPartial(cur, token);
const continuation = (cur.ch > 0 && token.state.line.length > 0); const continuation = (cur.ch > 0 && token.state.line.length > 0);
let comp = (continuation ? let comp = (continuation ?
@ -77,18 +115,22 @@ define(['core/ArrayUtilities'], (array) => {
const ranges = makeRanges(cm, cur.line, from, token.end); const ranges = makeRanges(cm, cur.line, from, token.end);
let selfValid = false; let selfValid = false;
const list = (comp const list = (comp
.filter((opt) => opt.startsWith(partial)) .filter(({v, q}) => (q || !quote) && v.startsWith(partial))
.map((opt) => { .map((o) => {
if(opt === partial + ' ' && !options.completeSingle) { if(o.v === partial + ' ' && !options.completeSingle) {
selfValid = true; selfValid = true;
return null; return null;
} }
return makeHintItem(opt, ranges); return makeHintItem(o, ranges, quote);
}) })
.filter((opt) => (opt !== null)) .filter((opt) => (opt !== null))
); );
if(selfValid && list.length > 0) { if(selfValid && list.length > 0) {
list.unshift(makeHintItem(partial + ' ', ranges)); list.unshift(makeHintItem(
{v: partial, suffix: ' ', q: false},
ranges,
quote
));
} }
return { return {

View File

@ -3,6 +3,15 @@ define(['core/ArrayUtilities'], (array) => {
const CM_ERROR = {type: 'error line-error', then: {'': 0}}; const CM_ERROR = {type: 'error line-error', then: {'': 0}};
function suggestionsEqual(a, b) {
return (
(a.v === b.v) &&
(a.prefix === b.prefix) &&
(a.suffix === b.suffix) &&
(a.q === b.q)
);
}
const makeCommands = ((() => { const makeCommands = ((() => {
// The order of commands inside "then" blocks directly influences the // The order of commands inside "then" blocks directly influences the
// order they are displayed to the user in autocomplete menus. // order they are displayed to the user in autocomplete menus.
@ -279,9 +288,9 @@ define(['core/ArrayUtilities'], (array) => {
function cmCappedToken(token, current) { function cmCappedToken(token, current) {
if(Object.keys(current.then).length > 0) { if(Object.keys(current.then).length > 0) {
return token + ' '; return {v: token, suffix: ' ', q: false};
} else { } else {
return token + '\n'; return {v: token, suffix: '\n', q: false};
} }
} }
@ -304,9 +313,9 @@ define(['core/ArrayUtilities'], (array) => {
} else if(current.suggest === true) { } else if(current.suggest === true) {
return [cmCappedToken(token, current)]; return [cmCappedToken(token, current)];
} else if(Array.isArray(current.suggest)) { } else if(Array.isArray(current.suggest)) {
return current.suggest; return current.suggest.map((v) => ({v, q: false}));
} else if(current.suggest) { } else if(current.suggest) {
return [current.suggest]; return [{v: current.suggest, q: false}];
} else { } else {
return null; return null;
} }
@ -322,7 +331,8 @@ define(['core/ArrayUtilities'], (array) => {
} }
array.mergeSets( array.mergeSets(
comp, comp,
cmGetSuggestions(state, token, current, next) cmGetSuggestions(state, token, current, next),
suggestionsEqual
); );
}); });
return comp; return comp;
@ -336,7 +346,8 @@ define(['core/ArrayUtilities'], (array) => {
} }
array.mergeSets( array.mergeSets(
state['known' + locals.type], state['known' + locals.type],
[locals.value + ' '] [{v: locals.value, suffix: ' ', q: true}],
suggestionsEqual
); );
locals.type = ''; locals.type = '';
locals.value = ''; locals.value = '';

View File

@ -23,14 +23,6 @@ define(['./CodeMirrorMode'], (CMMode) => {
escapeWith: unescape, escapeWith: unescape,
baseToken: {q: true}, baseToken: {q: true},
}, },
{
start: /'/y,
end: /'/y,
escape: /\\(.)/y,
escapeWith:
unescape,
baseToken: {q: true},
},
{start: /(?=[^ \t\r\n:+\-~*!<>,])/y, end: /(?=[ \t\r\n:+\-~*!<>,])|$/y}, {start: /(?=[^ \t\r\n:+\-~*!<>,])/y, end: /(?=[ \t\r\n:+\-~*!<>,])|$/y},
{ {
start: /(?=[\-~<])/y, start: /(?=[\-~<])/y,

View File

@ -102,7 +102,7 @@ defineDescribe('Sequence Tokeniser', ['./Tokeniser'], (Tokeniser) => {
}); });
it('parses quoted strings as single tokens', () => { it('parses quoted strings as single tokens', () => {
const input = 'foo "zig zag" \'abc def\''; const input = 'foo "zig zag" "abc def"';
const tokens = tokeniser.tokenise(input); const tokens = tokeniser.tokenise(input);
expect(tokens).toEqual([ expect(tokens).toEqual([
token({s: '', v: 'foo', q: false}), token({s: '', v: 'foo', q: false}),
@ -111,6 +111,16 @@ defineDescribe('Sequence Tokeniser', ['./Tokeniser'], (Tokeniser) => {
]); ]);
}); });
it('does not consider single quotes as quotes', () => {
const input = 'foo \'zig zag\'';
const tokens = tokeniser.tokenise(input);
expect(tokens).toEqual([
token({s: '', v: 'foo', q: false}),
token({s: ' ', v: '\'zig', q: false}),
token({s: ' ', v: 'zag\'', q: false}),
]);
});
it('stores character positions around quoted strings', () => { it('stores character positions around quoted strings', () => {
const input = '"foo bar"'; const input = '"foo bar"';
const tokens = tokeniser.tokenise(input); const tokens = tokeniser.tokenise(input);
@ -130,7 +140,7 @@ defineDescribe('Sequence Tokeniser', ['./Tokeniser'], (Tokeniser) => {
}); });
it('ignores quotes within comments', () => { it('ignores quotes within comments', () => {
const input = 'foo # bar "\'baz\nzig'; const input = 'foo # bar "baz\nzig';
const tokens = tokeniser.tokenise(input); const tokens = tokeniser.tokenise(input);
expect(tokens).toEqual([ expect(tokens).toEqual([
token({s: '', v: 'foo'}), token({s: '', v: 'foo'}),