refactor(compiler): refactor code matching helpers for compliance spec (#22835)

PR Close #22835
This commit is contained in:
Victor Berchet 2018-03-16 12:06:45 -07:00 committed by Matias Niemelä
parent 49396ca2ae
commit 129bb1800b
2 changed files with 32 additions and 38 deletions

View File

@ -114,6 +114,7 @@ export function getParseErrors(error: Error): ParseError[] {
return (error as any)[ERROR_PARSE_ERRORS] || []; return (error as any)[ERROR_PARSE_ERRORS] || [];
} }
// Escape characters that have a special meaning in Regular Expressions
export function escapeRegExp(s: string): string { export function escapeRegExp(s: string): string {
return s.replace(/([.*+?^=!:${}()|[\]\/\\])/g, '\\$1'); return s.replace(/([.*+?^=!:${}()|[\]\/\\])/g, '\\$1');
} }

View File

@ -20,70 +20,64 @@ import {compilePipe} from '../../src/render3/r3_pipe_compiler';
import {OutputMode} from '../../src/render3/r3_types'; import {OutputMode} from '../../src/render3/r3_types';
import {compileComponent, compileDirective} from '../../src/render3/r3_view_compiler'; import {compileComponent, compileDirective} from '../../src/render3/r3_view_compiler';
import {BindingParser} from '../../src/template_parser/binding_parser'; import {BindingParser} from '../../src/template_parser/binding_parser';
import {OutputContext} from '../../src/util'; import {OutputContext, escapeRegExp} from '../../src/util';
import {MockAotCompilerHost, MockCompilerHost, MockData, MockDirectory, arrayToMockDir, expectNoDiagnostics, settings, toMockFileArray} from '../aot/test_util'; import {MockAotCompilerHost, MockCompilerHost, MockData, MockDirectory, arrayToMockDir, expectNoDiagnostics, settings, toMockFileArray} from '../aot/test_util';
const IDENTIFIER = /[A-Za-z_$ɵ][A-Za-z0-9_$]*/; const IDENTIFIER = /[A-Za-z_$ɵ][A-Za-z0-9_$]*/;
const OPERATOR = const OPERATOR =
/!|%|\*|\/|\^|\&|\&\&\|\||\|\||\(|\)|\{|\}|\[|\]|:|;|\.|<|<=|>|>=|=|==|===|!=|!==|=>|\+|\+\+|-|--|@|,|\.|\.\.\./; /!|%|\*|\/|\^|&{1,2}|\|{1,2}|\(|\)|\{|\}|\[|\]|:|;|<=?|>=?|={1,3}|!={1,2}|=>|\+{1,2}|-{1,2}|@|,|\.|\.\.\./;
const STRING = /\'[^'\n]*\'|"[^'\n]*"|`[^`]*`/; const STRING = /'[^']*'|"[^"]*"|`[\s\S]*?`/;
const NUMBER = /[0-9]+/; const NUMBER = /\d+/;
const ELLIPSIS = '…'; const ELLIPSIS = '…';
const TOKEN = new RegExp( const TOKEN = new RegExp(
`^((${IDENTIFIER.source})|(${OPERATOR.source})|(${STRING.source})|${NUMBER.source}|${ELLIPSIS})`); `\\s*((${IDENTIFIER.source})|(${OPERATOR.source})|(${STRING.source})|${NUMBER.source}|${ELLIPSIS})`,
const WHITESPACE = /^\s+/; 'y');
type Piece = string | RegExp; type Piece = string | RegExp;
const IDENT = /[A-Za-z$_][A-Za-z0-9$_]*/;
const SKIP = /(?:.|\n|\r)*/; const SKIP = /(?:.|\n|\r)*/;
const MATCHING_IDENT = /^\$.*\$$/; const MATCHING_IDENT = /^\$.*\$$/;
// Transform the expected output to set of tokens
function tokenize(text: string): Piece[] { function tokenize(text: string): Piece[] {
function matches(exp: RegExp): string|false { TOKEN.lastIndex = 0;
const m = text.match(exp);
if (!m) return false;
text = text.substr(m[0].length);
return m[0];
}
function next(): string {
const result = matches(TOKEN);
if (!result) {
throw Error(`Invalid test, no token found for '${text.substr(0, 30)}...'`);
}
matches(WHITESPACE);
return result;
}
let match: RegExpMatchArray|null;
const pieces: Piece[] = []; const pieces: Piece[] = [];
matches(WHITESPACE);
while (text) { while ((match = TOKEN.exec(text)) !== null) {
const token = next(); const token = match[1];
if (token === 'IDENT') { if (token === 'IDENT') {
pieces.push(IDENT); pieces.push(IDENTIFIER);
} else if (token === ELLIPSIS) { } else if (token === ELLIPSIS) {
pieces.push(SKIP); pieces.push(SKIP);
} else { } else {
pieces.push(token); pieces.push(token);
} }
} }
if (TOKEN.lastIndex !== 0) {
const from = TOKEN.lastIndex;
const to = from + 30;
throw Error(`Invalid test, no token found for '${text.substr(from, to)}...'`)
}
return pieces; return pieces;
} }
const contextWidth = 100; export function expectEmit(source: string, expected: string, description: string) {
export function expectEmit(source: string, emitted: string, description: string) { const pieces = tokenize(expected);
const pieces = tokenize(emitted); const expr = r(pieces);
const expr = r(...pieces);
if (!expr.test(source)) { if (!expr.test(source)) {
let last: number = 0; let last: number = 0;
for (let i = 1; i <= pieces.length; i++) { for (let i = 1; i < pieces.length; i++) {
const t = r(...pieces.slice(0, i)); const t = r(pieces.slice(0, i));
const m = source.match(t); const m = source.match(t);
const expected = pieces[i - 1] == IDENT ? '<IDENT>' : pieces[i - 1]; const expectedPiece = pieces[i - 1] == IDENTIFIER ? '<IDENT>' : pieces[i - 1];
if (!m) { if (!m) {
const contextPieceWidth = contextWidth / 2;
fail( fail(
`${description}: Expected to find ${expected} '${source.substr(0,last)}[<---HERE expected "${expected}"]${source.substr(last)}'`); `${description}: Expected to find ${expectedPiece} '${source.substr(0,last)}[<---HERE expected "${expectedPiece}"]${source.substr(last)}'`);
return; return;
} else { } else {
last = (m.index || 0) + m[0].length; last = (m.index || 0) + m[0].length;
@ -95,8 +89,7 @@ export function expectEmit(source: string, emitted: string, description: string)
} }
const IDENT_LIKE = /^[a-z][A-Z]/; const IDENT_LIKE = /^[a-z][A-Z]/;
const SPECIAL_RE_CHAR = /\/|\(|\)|\||\*|\+|\[|\]|\{|\}|\$/g; function r(pieces: (string | RegExp)[]): RegExp {
function r(...pieces: (string | RegExp)[]): RegExp {
const results: string[] = []; const results: string[] = [];
let first = true; let first = true;
let group = 0; let group = 0;
@ -110,14 +103,14 @@ function r(...pieces: (string | RegExp)[]): RegExp {
if (MATCHING_IDENT.test(piece)) { if (MATCHING_IDENT.test(piece)) {
const matchGroup = groups.get(piece); const matchGroup = groups.get(piece);
if (!matchGroup) { if (!matchGroup) {
results.push('(' + IDENT.source + ')'); results.push('(' + IDENTIFIER.source + ')');
const newGroup = ++group; const newGroup = ++group;
groups.set(piece, newGroup); groups.set(piece, newGroup);
} else { } else {
results.push(`\\${matchGroup}`); results.push(`\\${matchGroup}`);
} }
} else { } else {
results.push(piece.replace(SPECIAL_RE_CHAR, s => '\\' + s)); results.push(escapeRegExp(piece));
} }
} else { } else {
results.push('(?:' + piece.source + ')'); results.push('(?:' + piece.source + ')');