123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704 |
- const BodyParser = require('../lib/snippet-body-parser');
- function expectMatch (input, tree) {
- expect(BodyParser.parse(input)).toEqual(tree);
- }
- describe("Snippet Body Parser", () => {
- it("parses a snippet with no special behavior", () => {
- const bodyTree = BodyParser.parse('${} $ n $}1} ${/upcase/} \n world ${||}');
- expect(bodyTree).toEqual([
- '${} $ n $}1} ${/upcase/} \n world ${||}'
- ]);
- });
- describe('for snippets with variables', () => {
- it('parses simple variables', () => {
- expectMatch('$f_o_0', [{variable: 'f_o_0'}]);
- expectMatch('$_FOO', [{variable: '_FOO'}]);
- });
- it('parses verbose variables', () => {
- expectMatch('${foo}', [{variable: 'foo'}]);
- expectMatch('${FOO}', [{variable: 'FOO'}]);
- });
- it('parses variables with placeholders', () => {
- expectMatch(
- '${f:placeholder}',
- [{variable: 'f', content: ['placeholder']}]
- );
- expectMatch(
- '${f:foo$1 $VAR}',
- [
- {
- variable: 'f',
- content: [
- 'foo',
- {index: 1, content: []},
- ' ',
- {variable: 'VAR'}
- ]
- }
- ]
- );
- // Allows a colon as part of the placeholder value.
- expectMatch(
- '${TM_SELECTED_TEXT:foo:bar}',
- [
- {
- variable: 'TM_SELECTED_TEXT',
- content: [
- 'foo:bar'
- ]
- }
- ]
- );
- });
- it('parses simple transformations like /upcase', () => {
- const bodyTree = BodyParser.parse("lorem ipsum ${CLIPBOARD:/upcase} dolor sit amet");
- expectMatch(
- "lorem ipsum ${CLIPBOARD:/upcase} dolor sit amet",
- [
- "lorem ipsum ",
- {
- variable: 'CLIPBOARD',
- substitution: {flag: 'upcase'}
- },
- " dolor sit amet"
- ]
- );
- });
- it('parses variables with transforms', () => {
- expectMatch('${f/.*/$0/}', [
- {
- variable: 'f',
- substitution: {
- find: /.*/,
- replace: [
- {backreference: 0}
- ]
- }
- }
- ]);
- });
- });
- describe('for snippets with tabstops', () => {
- it('parses simple tabstops', () => {
- expectMatch('hello$1world$2', [
- 'hello',
- {index: 1, content: []},
- 'world',
- {index: 2, content: []}
- ]);
- });
- it('parses verbose tabstops', () => {
- expectMatch('hello${1}world${2}', [
- 'hello',
- {index: 1, content: []},
- 'world',
- {index: 2, content: []}
- ]);
- });
- it('skips escaped tabstops', () => {
- expectMatch('$1 \\$2 $3 \\\\$4 \\\\\\$5 $6', [
- {index: 1, content: []},
- ' $2 ',
- {index: 3, content: []},
- ' \\',
- {index: 4, content: []},
- ' \\$5 ',
- {index: 6, content: []}
- ]);
- });
- describe('for tabstops with placeholders', () => {
- it('parses them', () => {
- expectMatch('hello${1:placeholder}world', [
- 'hello',
- {index: 1, content: ['placeholder']},
- 'world'
- ]);
- });
- it('allows escaped back braces', () => {
- expectMatch('${1:{}}', [
- {index: 1, content: ['{']},
- '}'
- ]);
- expectMatch('${1:{\\}}', [
- {index: 1, content: ['{}']}
- ]);
- });
- });
- it('parses tabstops with transforms', () => {
- expectMatch('${1/.*/$0/}', [
- {
- index: 1,
- content: [],
- substitution: {
- find: /.*/,
- replace: [{backreference: 0}]
- }
- }
- ]);
- });
- it('parses tabstops with choices', () => {
- expectMatch('${1|on}e,t\\|wo,th\\,ree|}', [
- {index: 1, content: ['on}e'], choice: ['on}e', 't|wo', 'th,ree']}
- ]);
- });
- it('parses if-else syntax', () => {
- expectMatch(
- '$1 ${1/(?:(wat)|^.*$)$/${1:+hey}/}',
- [
- {index: 1, content: []},
- " ",
- {
- index: 1,
- content: [],
- substitution: {
- find: /(?:(wat)|^.*$)$/,
- replace: [
- {
- backreference: 1,
- iftext: "hey",
- elsetext: ""
- }
- ],
- },
- },
- ]
- );
- expectMatch(
- '$1 ${1/(?:(wat)|^.*$)$/${1:?hey:nah}/}',
- [
- {index: 1, content: []},
- " ",
- {
- index: 1,
- content: [],
- substitution: {
- find: /(?:(wat)|^.*$)$/,
- replace: [
- {
- backreference: 1,
- iftext: "hey",
- elsetext: "nah"
- }
- ],
- },
- },
- ]
- );
- // else with `:` syntax
- expectMatch(
- '$1 ${1/(?:(wat)|^.*$)$/${1:fallback}/}',
- [
- {index: 1, content: []},
- " ",
- {
- index: 1,
- content: [],
- substitution: {
- find: /(?:(wat)|^.*$)$/,
- replace: [
- {
- backreference: 1,
- iftext: "",
- elsetext: "fallback"
- }
- ],
- },
- },
- ]
- );
- // else with `:-` syntax; should be same as above
- expectMatch(
- '$1 ${1/(?:(wat)|^.*$)$/${1:-fallback}/}',
- [
- {index: 1, content: []},
- " ",
- {
- index: 1,
- content: [],
- substitution: {
- find: /(?:(wat)|^.*$)$/,
- replace: [
- {
- backreference: 1,
- iftext: "",
- elsetext: "fallback"
- }
- ],
- },
- },
- ]
- );
- });
- it('parses alternative if-else syntax', () => {
- expectMatch(
- '$1 ${1/(?:(wat)|^.*$)$/(?1:hey:)/}',
- [
- {index: 1, content: []},
- " ",
- {
- index: 1,
- content: [],
- substitution: {
- find: /(?:(wat)|^.*$)$/,
- replace: [
- {
- backreference: 1,
- iftext: ["hey"],
- elsetext: ""
- }
- ],
- },
- },
- ]
- );
- expectMatch(
- '$1 ${1/(?:(wat)|^.*$)$/(?1:\\u$1:)/}',
- [
- {index: 1, content: []},
- " ",
- {
- index: 1,
- content: [],
- substitution: {
- find: /(?:(wat)|^.*$)$/,
- replace: [
- {
- backreference: 1,
- iftext: [
- {escape: 'u'},
- {backreference: 1}
- ],
- elsetext: ""
- }
- ],
- },
- },
- ]
- );
- expectMatch(
- '$1 ${1/(?:(wat)|^.*$)$/(?1::hey)/}',
- [
- {index: 1, content: []},
- " ",
- {
- index: 1,
- content: [],
- substitution: {
- find: /(?:(wat)|^.*$)$/,
- replace: [
- {
- backreference: 1,
- iftext: "",
- elsetext: ["hey"]
- }
- ],
- },
- },
- ]
- );
- expectMatch(
- 'class ${1:${TM_FILENAME/(?:\\A|_)([A-Za-z0-9]+)(?:\\.rb)?/(?2::\\u$1)/g}} < ${2:Application}Controller\n $3\nend',
- [
- 'class ',
- {
- index: 1,
- content: [
- {
- variable: 'TM_FILENAME',
- substitution: {
- find: /(?:\A|_)([A-Za-z0-9]+)(?:\.rb)?/g,
- replace: [
- {
- backreference: 2,
- iftext: '',
- elsetext: [
- {escape: 'u'},
- {backreference: 1}
- ]
- }
- ]
- }
- }
- ]
- },
- ' < ',
- {
- index: 2,
- content: ['Application']
- },
- 'Controller\n ',
- {index: 3, content : []},
- '\nend'
- ]
- );
- });
- it('recognizes escape characters in if/else syntax', () => {
- expectMatch(
- '$1 ${1/(?:(wat)|^.*$)$/${1:?hey\\:hey:nah}/}',
- [
- {index: 1, content: []},
- " ",
- {
- index: 1,
- content: [],
- substitution: {
- find: /(?:(wat)|^.*$)$/,
- replace: [
- {
- backreference: 1,
- iftext: "hey:hey",
- elsetext: "nah"
- }
- ],
- },
- },
- ]
- );
- expectMatch(
- '$1 ${1/(?:(wat)|^.*$)$/${1:?hey:n\\}ah}/}',
- [
- {index: 1, content: []},
- " ",
- {
- index: 1,
- content: [],
- substitution: {
- find: /(?:(wat)|^.*$)$/,
- replace: [
- {
- backreference: 1,
- iftext: "hey",
- elsetext: "n}ah"
- }
- ],
- },
- },
- ]
- );
- });
- it('parses nested tabstops', () => {
- expectMatch(
- '${1:place${2:hol${3:der}}}',
- [
- {
- index: 1,
- content: [
- 'place',
- {index: 2, content: [
- 'hol',
- {index: 3, content: ['der']}
- ]}
- ]
- }
- ]
- );
- expectMatch(
- '${1:${foo:${1}}}',
- [
- {
- index: 1,
- content: [
- {
- variable: 'foo',
- content: [
- {
- index: 1,
- content: []
- }
- ]
- }
- ]
- }
- ]
- );
- });
- });
- it("breaks a snippet body into lines, with each line containing tab stops at the appropriate position", () => {
- const bodyTree = BodyParser.parse(`\
- the quick brown $1fox \${2:jumped \${3:over}
- }the \${4:lazy} dog\
- `
- );
- expect(bodyTree).toEqual([
- "the quick brown ",
- {index: 1, content: []},
- "fox ",
- {
- index: 2,
- content: [
- "jumped ",
- {index: 3, content: ["over"]},
- "\n"
- ],
- },
- "the ",
- {index: 4, content: ["lazy"]},
- " dog"
- ]);
- });
- it('handles a snippet with a transformed variable', () => {
- expectMatch(
- 'module ${1:ActiveRecord::${TM_FILENAME/(?:\\A|_)([A-Za-z0-9]+)(?:\\.rb)?/\\u$1/g}}',
- [
- 'module ',
- {
- index: 1,
- content: [
- 'ActiveRecord::',
- {
- variable: 'TM_FILENAME',
- substitution: {
- find: /(?:\A|_)([A-Za-z0-9]+)(?:\.rb)?/g,
- replace: [
- {escape: 'u'},
- {backreference: 1}
- ]
- }
- }
- ]
- }
- ]
- );
- });
- it("skips escaped tabstops", () => {
- const bodyTree = BodyParser.parse("snippet $1 escaped \\$2 \\\\$3");
- expect(bodyTree).toEqual([
- "snippet ",
- {
- index: 1,
- content: []
- },
- " escaped $2 \\",
- {
- index: 3,
- content: []
- }
- ]);
- });
- it("includes escaped right-braces", () => {
- const bodyTree = BodyParser.parse("snippet ${1:{\\}}");
- expect(bodyTree).toEqual([
- "snippet ",
- {
- index: 1,
- content: ["{}"]
- }
- ]);
- });
- it("parses a snippet with transformations", () => {
- const bodyTree = BodyParser.parse("<${1:p}>$0</${1/f/F/}>");
- expect(bodyTree).toEqual([
- '<',
- {index: 1, content: ['p']},
- '>',
- {index: 0, content: []},
- '</',
- {index: 1, content: [], substitution: {find: /f/, replace: ['F']}},
- '>'
- ]);
- });
- it("parses a snippet with transformations and a global flag", () => {
- const bodyTree = BodyParser.parse("<${1:p}>$0</${1/f/F/g}>");
- expect(bodyTree).toEqual([
- '<',
- {index: 1, content: ['p']},
- '>',
- {index: 0, content: []},
- '</',
- {index: 1, content: [], substitution: {find: /f/g, replace: ['F']}},
- '>'
- ]);
- });
- it("parses a snippet with multiple tab stops with transformations", () => {
- const bodyTree = BodyParser.parse("${1:placeholder} ${1/(.)/\\u$1/g} $1 ${2:ANOTHER} ${2/^(.*)$/\\L$1/} $2");
- expect(bodyTree).toEqual([
- {index: 1, content: ['placeholder']},
- ' ',
- {
- index: 1,
- content: [],
- substitution: {
- find: /(.)/g,
- replace: [
- {escape: 'u'},
- {backreference: 1}
- ]
- }
- },
- ' ',
- {index: 1, content: []},
- ' ',
- {index: 2, content: ['ANOTHER']},
- ' ',
- {
- index: 2,
- content: [],
- substitution: {
- find: /^(.*)$/,
- replace: [
- {escape: 'L'},
- {backreference: 1}
- ]
- }
- },
- ' ',
- {index: 2, content: []},
- ]);
- });
- it("parses a snippet with transformations and mirrors", () => {
- const bodyTree = BodyParser.parse("${1:placeholder}\n${1/(.)/\\u$1/g}\n$1");
- expect(bodyTree).toEqual([
- {index: 1, content: ['placeholder']},
- '\n',
- {
- index: 1,
- content: [],
- substitution: {
- find: /(.)/g,
- replace: [
- {escape: 'u'},
- {backreference: 1}
- ]
- }
- },
- '\n',
- {index: 1, content: []}
- ]);
- });
- it("parses a snippet with a format string and case-control flags", () => {
- const bodyTree = BodyParser.parse("<${1:p}>$0</${1/(.)(.*)/\\u$1$2/g}>");
- expect(bodyTree).toEqual([
- '<',
- {index: 1, content: ['p']},
- '>',
- {index: 0, content: []},
- '</',
- {
- index: 1,
- content: [],
- substitution: {
- find: /(.)(.*)/g,
- replace: [
- {escape: 'u'},
- {backreference: 1},
- {backreference: 2}
- ]
- }
- },
- '>'
- ]);
- });
- it("parses a snippet with an escaped forward slash in a transform", () => {
- // Annoyingly, a forward slash needs to be double-backslashed just like the
- // other escapes.
- const bodyTree = BodyParser.parse("<${1:p}>$0</${1/(.)\\/(.*)/\\u$1$2/g}>");
- expect(bodyTree).toEqual([
- '<',
- {index: 1, content: ['p']},
- '>',
- {index: 0, content: []},
- '</',
- {
- index: 1,
- content: [],
- substitution: {
- find: /(.)\/(.*)/g,
- replace: [
- {escape: 'u'},
- {backreference: 1},
- {backreference: 2}
- ]
- }
- },
- '>'
- ]);
- });
- it("parses a snippet with a placeholder that mirrors another tab stop's content", () => {
- const bodyTree = BodyParser.parse("$4console.${3:log}('${2:$1}', $1);$0");
- expect(bodyTree).toEqual([
- {index: 4, content: []},
- 'console.',
- {index: 3, content: ['log']},
- '(\'',
- {
- index: 2, content: [
- {index: 1, content: []}
- ]
- },
- '\', ',
- {index: 1, content: []},
- ');',
- {index: 0, content: []}
- ]);
- });
- it("parses a snippet with a placeholder that mixes text and tab stop references", () => {
- const bodyTree = BodyParser.parse("$4console.${3:log}('${2:uh $1}', $1);$0");
- expect(bodyTree).toEqual([
- {index: 4, content: []},
- 'console.',
- {index: 3, content: ['log']},
- '(\'',
- {
- index: 2, content: [
- 'uh ',
- {index: 1, content: []}
- ]
- },
- '\', ',
- {index: 1, content: []},
- ');',
- {index: 0, content: []}
- ]);
- });
- });
|