mirror of
https://github.com/fluencelabs/assemblyscript
synced 2025-05-20 19:21:30 +00:00
Regexp literal support; Properly resolve statically inherited members
This commit is contained in:
parent
d8fa04f910
commit
7c8670ac35
16
src/ast.ts
16
src/ast.ts
@ -240,10 +240,11 @@ export abstract class Node {
|
||||
return expr;
|
||||
}
|
||||
|
||||
static createRegexpLiteralExpression(value: string, range: Range): RegexpLiteralExpression {
|
||||
static createRegexpLiteralExpression(pattern: string, modifiers: string, range: Range): RegexpLiteralExpression {
|
||||
var expr = new RegexpLiteralExpression();
|
||||
expr.range = range;
|
||||
expr.value = value;
|
||||
expr.pattern = pattern;
|
||||
expr.modifiers = modifiers;
|
||||
return expr;
|
||||
}
|
||||
|
||||
@ -1045,11 +1046,16 @@ export class RegexpLiteralExpression extends LiteralExpression {
|
||||
// kind = NodeKind.LITERAL
|
||||
literalKind = LiteralKind.REGEXP;
|
||||
|
||||
/** Value of the expression. */
|
||||
value: string;
|
||||
/** Regular expression pattern. */
|
||||
pattern: string;
|
||||
/** Regular expression modifiers. */
|
||||
modifiers: string;
|
||||
|
||||
serialize(sb: string[]): void {
|
||||
sb.push(this.value);
|
||||
sb.push("/");
|
||||
sb.push(this.pattern);
|
||||
sb.push("/");
|
||||
sb.push(this.modifiers);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -212,6 +212,11 @@ export function compileCall(compiler: Compiler, prototype: FunctionPrototype, ty
|
||||
|
||||
case "isNaN": // isNaN<T>(value: T) -> bool
|
||||
compiler.currentType = Type.bool;
|
||||
// if (operands.length != 1) {
|
||||
// compiler.error(DiagnosticCode.Expected_0_arguments_but_got_1, reportNode.range, "1", operands.length.toString(10));
|
||||
// return module.createUnreachable();
|
||||
// }
|
||||
// TODO: infer type argument if omitted
|
||||
if (!validateCall(compiler, typeArguments, 1, operands, 1, reportNode))
|
||||
return module.createUnreachable();
|
||||
if ((<Type[]>typeArguments)[0].isAnyInteger)
|
||||
|
@ -30,8 +30,7 @@ export class Decompiler {
|
||||
|
||||
private tempI64: I64 = new I64();
|
||||
|
||||
constructor() {
|
||||
}
|
||||
constructor() { }
|
||||
|
||||
/** Decompiles a module to an AST that can then be serialized. */
|
||||
decompile(module: Module) {
|
||||
|
@ -63,6 +63,7 @@ export enum DiagnosticCode {
|
||||
Generic_type_0_requires_1_type_argument_s = 2314,
|
||||
Type_0_is_not_generic = 2315,
|
||||
Type_0_is_not_assignable_to_type_1 = 2322,
|
||||
Index_signature_is_missing_in_type_0 = 2329,
|
||||
_this_cannot_be_referenced_in_current_location = 2332,
|
||||
_super_can_only_be_referenced_in_a_derived_class = 2335,
|
||||
Property_0_does_not_exist_on_type_1 = 2339,
|
||||
@ -147,6 +148,7 @@ export function diagnosticCodeToString(code: DiagnosticCode): string {
|
||||
case 2314: return "Generic type '{0}' requires {1} type argument(s).";
|
||||
case 2315: return "Type '{0}' is not generic.";
|
||||
case 2322: return "Type '{0}' is not assignable to type '{1}'.";
|
||||
case 2329: return "Index signature is missing in type '{0}'.";
|
||||
case 2332: return "'this' cannot be referenced in current location.";
|
||||
case 2335: return "'super' can only be referenced in a derived class.";
|
||||
case 2339: return "Property '{0}' does not exist on type '{1}'.";
|
||||
|
@ -63,6 +63,7 @@
|
||||
"Generic type '{0}' requires {1} type argument(s).": 2314,
|
||||
"Type '{0}' is not generic.": 2315,
|
||||
"Type '{0}' is not assignable to type '{1}'.": 2322,
|
||||
"Index signature is missing in type '{0}'.": 2329,
|
||||
"'this' cannot be referenced in current location.": 2332,
|
||||
"'super' can only be referenced in a derived class.": 2335,
|
||||
"Property '{0}' does not exist on type '{1}'.": 2339,
|
||||
|
@ -31,13 +31,14 @@ import {
|
||||
NodeKind,
|
||||
Source,
|
||||
TypeNode,
|
||||
// expressions
|
||||
|
||||
Expression,
|
||||
AssertionKind,
|
||||
CallExpression,
|
||||
Expression,
|
||||
IdentifierExpression,
|
||||
StringLiteralExpression,
|
||||
// statements
|
||||
|
||||
Statement,
|
||||
BlockStatement,
|
||||
BreakStatement,
|
||||
ClassDeclaration,
|
||||
@ -63,7 +64,6 @@ import {
|
||||
NamespaceDeclaration,
|
||||
Parameter,
|
||||
ReturnStatement,
|
||||
Statement,
|
||||
SwitchCase,
|
||||
SwitchStatement,
|
||||
ThrowStatement,
|
||||
@ -73,7 +73,7 @@ import {
|
||||
VariableStatement,
|
||||
VariableDeclaration,
|
||||
WhileStatement,
|
||||
// utility
|
||||
|
||||
addModifier,
|
||||
getModifier,
|
||||
hasModifier,
|
||||
@ -108,6 +108,7 @@ export class Parser extends DiagnosticEmitter {
|
||||
this.program.sources.push(source);
|
||||
|
||||
var tn = new Tokenizer(source, this.program.diagnostics);
|
||||
tn.silentDiagnostics = this.silentDiagnostics;
|
||||
source.tokenizer = tn;
|
||||
|
||||
while (!tn.skip(Token.ENDOFFILE)) {
|
||||
@ -1512,18 +1513,15 @@ export class Parser extends DiagnosticEmitter {
|
||||
return Node.createFloatLiteralExpression(tn.readFloat(), tn.range(startPos, tn.pos));
|
||||
|
||||
// RegexpLiteralExpression
|
||||
/*
|
||||
case Token.SLASH:
|
||||
var regexpLit = Node.createRegexpLiteral(tn.readRegexp(), tn.range(startPos, tn.pos));
|
||||
var regexpPattern = tn.readRegexpPattern();
|
||||
if (regexpPattern == null)
|
||||
return null;
|
||||
if (!tn.skip(Token.SLASH)) {
|
||||
this.error(DiagnosticCode._0_expected, tn.range(), "/");
|
||||
return null;
|
||||
}
|
||||
// TODO: modifiers, may be move to tokenizer
|
||||
return regexpLit;
|
||||
*/
|
||||
case Token.REGEXPLITERAL: // not yet supported
|
||||
return Node.createRegexpLiteralExpression(tn.readRegexp(), tn.range(startPos, tn.pos));
|
||||
return Node.createRegexpLiteralExpression(regexpPattern, tn.readRegexpModifiers(), tn.range(startPos, tn.pos));
|
||||
|
||||
default:
|
||||
this.error(DiagnosticCode.Expression_expected, tn.range());
|
||||
|
118
src/program.ts
118
src/program.ts
@ -146,6 +146,7 @@ export class Program extends DiagnosticEmitter {
|
||||
|
||||
var queuedExports = new Map<string,QueuedExport>();
|
||||
var queuedImports = new Array<QueuedImport>();
|
||||
var queuedDerivedClasses = new Array<ClassPrototype>();
|
||||
|
||||
// build initial lookup maps of internal names to declarations
|
||||
for (var i = 0, k = this.sources.length; i < k; ++i) {
|
||||
@ -156,7 +157,7 @@ export class Program extends DiagnosticEmitter {
|
||||
switch (statement.kind) {
|
||||
|
||||
case NodeKind.CLASS:
|
||||
this.initializeClass(<ClassDeclaration>statement);
|
||||
this.initializeClass(<ClassDeclaration>statement, queuedDerivedClasses);
|
||||
break;
|
||||
|
||||
case NodeKind.ENUM:
|
||||
@ -180,7 +181,7 @@ export class Program extends DiagnosticEmitter {
|
||||
break;
|
||||
|
||||
case NodeKind.NAMESPACE:
|
||||
this.initializeNamespace(<NamespaceDeclaration>statement);
|
||||
this.initializeNamespace(<NamespaceDeclaration>statement, queuedDerivedClasses, null);
|
||||
break;
|
||||
|
||||
case NodeKind.TYPEDECLARATION:
|
||||
@ -232,6 +233,22 @@ export class Program extends DiagnosticEmitter {
|
||||
}
|
||||
} while (currentExport);
|
||||
}
|
||||
|
||||
// resolve base prototypes of derived classes
|
||||
for (i = 0, k = queuedDerivedClasses.length; i < k; ++i) {
|
||||
var derivedDeclaration = queuedDerivedClasses[i].declaration;
|
||||
assert(derivedDeclaration != null);
|
||||
var derivedType = (<ClassDeclaration>derivedDeclaration).extendsType;
|
||||
assert(derivedType != null);
|
||||
var resolved = this.resolveIdentifier((<TypeNode>derivedType).identifier, null); // reports
|
||||
if (resolved) {
|
||||
if (resolved.element.kind != ElementKind.CLASS_PROTOTYPE) {
|
||||
this.error(DiagnosticCode.A_class_may_only_extend_another_class, (<TypeNode>derivedType).range);
|
||||
continue;
|
||||
}
|
||||
queuedDerivedClasses[i].basePrototype = <ClassPrototype>resolved.element;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Tries to resolve an import by traversing exports and queued exports. */
|
||||
@ -252,7 +269,7 @@ export class Program extends DiagnosticEmitter {
|
||||
} while (true);
|
||||
}
|
||||
|
||||
private initializeClass(declaration: ClassDeclaration, namespace: Element | null = null): void {
|
||||
private initializeClass(declaration: ClassDeclaration, queuedDerivedClasses: ClassPrototype[], namespace: Element | null = null): void {
|
||||
var internalName = declaration.internalName;
|
||||
if (this.elements.has(internalName)) {
|
||||
this.error(DiagnosticCode.Duplicate_identifier_0, declaration.name.range, internalName);
|
||||
@ -276,6 +293,10 @@ export class Program extends DiagnosticEmitter {
|
||||
} else if (declaration.implementsTypes.length)
|
||||
throw new Error("not implemented");
|
||||
|
||||
// remember classes that extend another one
|
||||
if (declaration.extendsType)
|
||||
queuedDerivedClasses.push(prototype);
|
||||
|
||||
// add as namespace member if applicable
|
||||
if (namespace) {
|
||||
if (namespace.members) {
|
||||
@ -762,7 +783,7 @@ export class Program extends DiagnosticEmitter {
|
||||
}
|
||||
}
|
||||
|
||||
private initializeNamespace(declaration: NamespaceDeclaration, parentNamespace: Element | null = null): void {
|
||||
private initializeNamespace(declaration: NamespaceDeclaration, queuedExtendingClasses: ClassPrototype[], parentNamespace: Element | null = null): void {
|
||||
var internalName = declaration.internalName;
|
||||
|
||||
var namespace = this.elements.get(internalName);
|
||||
@ -794,7 +815,7 @@ export class Program extends DiagnosticEmitter {
|
||||
switch (members[i].kind) {
|
||||
|
||||
case NodeKind.CLASS:
|
||||
this.initializeClass(<ClassDeclaration>members[i], namespace);
|
||||
this.initializeClass(<ClassDeclaration>members[i], queuedExtendingClasses, namespace);
|
||||
break;
|
||||
|
||||
case NodeKind.ENUM:
|
||||
@ -810,7 +831,7 @@ export class Program extends DiagnosticEmitter {
|
||||
break;
|
||||
|
||||
case NodeKind.NAMESPACE:
|
||||
this.initializeNamespace(<NamespaceDeclaration>members[i], namespace);
|
||||
this.initializeNamespace(<NamespaceDeclaration>members[i], queuedExtendingClasses, namespace);
|
||||
break;
|
||||
|
||||
case NodeKind.TYPEDECLARATION:
|
||||
@ -957,22 +978,26 @@ export class Program extends DiagnosticEmitter {
|
||||
}
|
||||
|
||||
/** Resolves an identifier to the element it refers to. */
|
||||
resolveIdentifier(identifier: IdentifierExpression, contextualFunction: Function): ResolvedElement | null {
|
||||
resolveIdentifier(identifier: IdentifierExpression, contextualFunction: Function | null): ResolvedElement | null {
|
||||
var name = identifier.name;
|
||||
var local = contextualFunction.locals.get(name);
|
||||
if (local)
|
||||
return (resolvedElement || (resolvedElement = new ResolvedElement())).set(local);
|
||||
|
||||
var element: Element | null;
|
||||
var namespace: Element | null;
|
||||
|
||||
// search contextual parent namespaces if applicable
|
||||
if (contextualFunction && (namespace = contextualFunction.prototype.namespace)) {
|
||||
do {
|
||||
if (element = this.elements.get(namespace.internalName + STATIC_DELIMITER + name))
|
||||
// if ((namespace.members && (element = namespace.members.get(name))) || (element = this.elements.get(namespace.internalName + STATIC_DELIMITER + name)))
|
||||
return (resolvedElement || (resolvedElement = new ResolvedElement())).set(element);
|
||||
} while (namespace = namespace.namespace);
|
||||
if (contextualFunction) {
|
||||
// check locals
|
||||
var local = contextualFunction.locals.get(name);
|
||||
if (local)
|
||||
return (resolvedElement || (resolvedElement = new ResolvedElement())).set(local);
|
||||
|
||||
// search contextual parent namespaces if applicable
|
||||
if (namespace = contextualFunction.prototype.namespace) {
|
||||
do {
|
||||
if (element = this.elements.get(namespace.internalName + STATIC_DELIMITER + name))
|
||||
// if ((namespace.members && (element = namespace.members.get(name))) || (element = this.elements.get(namespace.internalName + STATIC_DELIMITER + name)))
|
||||
return (resolvedElement || (resolvedElement = new ResolvedElement())).set(element);
|
||||
} while (namespace = namespace.namespace);
|
||||
}
|
||||
}
|
||||
|
||||
// search current file
|
||||
@ -998,6 +1023,7 @@ export class Program extends DiagnosticEmitter {
|
||||
// at this point we know exactly what the target is, so look up the element within
|
||||
var propertyName = propertyAccess.property.name;
|
||||
var targetType: Type;
|
||||
var member: Element | null;
|
||||
switch (target.kind) {
|
||||
|
||||
case ElementKind.GLOBAL:
|
||||
@ -1008,12 +1034,31 @@ export class Program extends DiagnosticEmitter {
|
||||
target = <Class>targetType.classType;
|
||||
// fall-through
|
||||
|
||||
default:
|
||||
if (target.members) {
|
||||
var member = target.members.get(propertyName);
|
||||
if (member)
|
||||
case ElementKind.CLASS_PROTOTYPE:
|
||||
case ElementKind.CLASS:
|
||||
do {
|
||||
if (target.members && (member = target.members.get(propertyName)))
|
||||
return resolvedElement.set(member).withTarget(target, targetExpression);
|
||||
}
|
||||
// check inherited static members on the base prototype while target is a class prototype
|
||||
if (target.kind == ElementKind.CLASS_PROTOTYPE) {
|
||||
if ((<ClassPrototype>target).basePrototype)
|
||||
target = <ClassPrototype>(<ClassPrototype>target).basePrototype;
|
||||
else
|
||||
break;
|
||||
// or inherited instance members on the cbase class while target is a class instance
|
||||
} else if (target.kind == ElementKind.CLASS) {
|
||||
if ((<Class>target).base)
|
||||
target = <Class>(<Class>target).base;
|
||||
else
|
||||
break;
|
||||
} else
|
||||
break;
|
||||
} while (true);
|
||||
break;
|
||||
|
||||
default: // enums or other namespace-like elements
|
||||
if (target.members && (member = target.members.get(propertyName)))
|
||||
return resolvedElement.set(member).withTarget(target, targetExpression);
|
||||
break;
|
||||
}
|
||||
this.error(DiagnosticCode.Property_0_does_not_exist_on_type_1, propertyAccess.property.range, propertyName, target.internalName);
|
||||
@ -1026,16 +1071,19 @@ export class Program extends DiagnosticEmitter {
|
||||
if (!(resolvedElement = this.resolveExpression(targetExpression, contextualFunction)))
|
||||
return null;
|
||||
var target = resolvedElement.element;
|
||||
|
||||
switch (target.kind) {
|
||||
|
||||
// TBD: should indexed access on static classes, like `Heap`, be a supported as well?
|
||||
case ElementKind.CLASS:
|
||||
var type = (<Class>target).type;
|
||||
if (type.classType) {
|
||||
// TODO: check if array etc.
|
||||
var indexedGet: FunctionPrototype | null;
|
||||
if (indexedGet = (target = type.classType).prototype.opIndexedGet)
|
||||
return resolvedElement.set(indexedGet).withTarget(target, targetExpression);
|
||||
}
|
||||
break;
|
||||
}
|
||||
this.error(DiagnosticCode.Operation_not_supported, elementAccess.range);
|
||||
this.error(DiagnosticCode.Index_signature_is_missing_in_type_0, targetExpression.range, target.internalName);
|
||||
return null;
|
||||
}
|
||||
|
||||
@ -1820,15 +1868,17 @@ export class ClassPrototype extends Element {
|
||||
instances: Map<string,Class> = new Map();
|
||||
/** Instance member prototypes. */
|
||||
instanceMembers: Map<string,Element> | null = null;
|
||||
/** Base class prototype, if applicable. */
|
||||
basePrototype: ClassPrototype | null = null; // set in Program#initialize
|
||||
|
||||
/** Overloaded indexed get method, if any. */
|
||||
opIndexedGet: FunctionPrototype | null;
|
||||
opIndexedGet: FunctionPrototype | null = null; // TODO: indexedGet and indexedSet as an accessor?
|
||||
/** Overloaded indexed set method, if any. */
|
||||
opIndexedSet: FunctionPrototype | null;
|
||||
opIndexedSet: FunctionPrototype | null = null;
|
||||
/** Overloaded concatenation method, if any. */
|
||||
opConcat: FunctionPrototype | null;
|
||||
opConcat: FunctionPrototype | null = null;
|
||||
/** Overloaded equality comparison method, if any. */
|
||||
opEquals: FunctionPrototype | null;
|
||||
opEquals: FunctionPrototype | null = null;
|
||||
|
||||
constructor(program: Program, simpleName: string, internalName: string, declaration: ClassDeclaration | null = null) {
|
||||
super(program, simpleName, internalName);
|
||||
@ -1878,16 +1928,6 @@ export class ClassPrototype extends Element {
|
||||
this.program.error(DiagnosticCode.A_class_may_only_extend_another_class, declaration.extendsType.range);
|
||||
return null;
|
||||
}
|
||||
if ((this.flags & ElementFlags.HAS_STATIC_BASE_MEMBERS) == 0) { // inherit static base members once
|
||||
this.flags |= ElementFlags.HAS_STATIC_BASE_MEMBERS;
|
||||
if (baseClass.prototype.members) {
|
||||
if (!this.members)
|
||||
this.members = new Map();
|
||||
for (var baseMember of baseClass.prototype.members.values())
|
||||
if (!baseMember.isInstance)
|
||||
this.members.set(baseMember.simpleName, baseMember);
|
||||
}
|
||||
}
|
||||
if (baseClass.prototype.isStruct != this.isStruct) {
|
||||
this.program.error(DiagnosticCode.Structs_cannot_extend_classes_and_vice_versa, Range.join(declaration.name.range, declaration.extendsType.range));
|
||||
return null;
|
||||
|
@ -4,18 +4,17 @@
|
||||
as much bookkeeping, simply skips over trivia and provides a more general
|
||||
mark/reset mechanism for the parser to utilize on ambiguous tokens.
|
||||
|
||||
next() advances the token
|
||||
peek() peeks for the next token
|
||||
skip(token) skips over a token if possible
|
||||
mark() marks at current token
|
||||
reset() resets to marked state
|
||||
range() gets the range of the current token
|
||||
next() advances the token
|
||||
peek() peeks for the next token
|
||||
skip(token) skips over a token if possible
|
||||
mark() marks at current token
|
||||
reset() resets to marked state
|
||||
range() gets the range of the current token
|
||||
|
||||
readFloat() on FLOATLITERAL
|
||||
readIdentifier() on IDENTIFIER
|
||||
readInteger() on INTEGERLITERAL
|
||||
readRegexp() on REGEXPLITERAL // TODO
|
||||
readString() on STRINGLITERAL
|
||||
readFloat() on FLOATLITERAL
|
||||
readIdentifier() on IDENTIFIER
|
||||
readInteger() on INTEGERLITERAL
|
||||
readString() on STRINGLITERAL
|
||||
|
||||
*/
|
||||
|
||||
@ -174,7 +173,6 @@ export enum Token {
|
||||
STRINGLITERAL,
|
||||
INTEGERLITERAL,
|
||||
FLOATLITERAL,
|
||||
REGEXPLITERAL,
|
||||
|
||||
// meta
|
||||
|
||||
@ -562,8 +560,6 @@ export class Tokenizer extends DiagnosticEmitter {
|
||||
return Token.SLASH_EQUALS;
|
||||
}
|
||||
}
|
||||
if (this.testRegexp())
|
||||
return Token.REGEXPLITERAL; // expects a call to readRegexp
|
||||
return Token.SLASH;
|
||||
|
||||
case CharCode._0:
|
||||
@ -905,21 +901,14 @@ export class Tokenizer extends DiagnosticEmitter {
|
||||
}
|
||||
}
|
||||
|
||||
testRegexp(): bool {
|
||||
// TODO: this'll require more context
|
||||
return false;
|
||||
}
|
||||
|
||||
readRegexp(): string {
|
||||
readRegexpPattern(): string | null {
|
||||
var text = this.source.text;
|
||||
var start = this.pos;
|
||||
var result = "";
|
||||
var escaped = false;
|
||||
while (true) {
|
||||
if (this.pos >= this.end) {
|
||||
result += text.substring(start, this.pos);
|
||||
this.error(DiagnosticCode.Unterminated_regular_expression_literal, this.range(start, this.end));
|
||||
break;
|
||||
return null;
|
||||
}
|
||||
if (text.charCodeAt(this.pos) == CharCode.BACKSLASH) {
|
||||
++this.pos;
|
||||
@ -927,19 +916,36 @@ export class Tokenizer extends DiagnosticEmitter {
|
||||
continue;
|
||||
}
|
||||
var c = text.charCodeAt(this.pos);
|
||||
if (c == CharCode.SLASH) {
|
||||
result += text.substring(start, this.pos);
|
||||
++this.pos;
|
||||
if (c == CharCode.SLASH && !escaped)
|
||||
break;
|
||||
}
|
||||
if (isLineBreak(c)) {
|
||||
result += text.substring(start, this.pos);
|
||||
this.error(DiagnosticCode.Unterminated_regular_expression_literal, this.range(start, this.pos));
|
||||
break;
|
||||
return null;
|
||||
}
|
||||
++this.pos;
|
||||
escaped = false;
|
||||
}
|
||||
return result;
|
||||
return text.substring(start, this.pos);
|
||||
}
|
||||
|
||||
readRegexpModifiers(): string {
|
||||
var text = this.source.text;
|
||||
var start = this.pos;
|
||||
/a/
|
||||
while (this.pos < this.end) {
|
||||
switch (text.charCodeAt(this.pos)) {
|
||||
|
||||
case CharCode.g:
|
||||
case CharCode.i:
|
||||
case CharCode.m:
|
||||
++this.pos;
|
||||
break;
|
||||
|
||||
default:
|
||||
return text.substring(start, this.pos);
|
||||
}
|
||||
}
|
||||
return text.substring(start, this.pos);
|
||||
}
|
||||
|
||||
testInteger(): bool {
|
||||
|
18
tests/parser/regexp.ts
Normal file
18
tests/parser/regexp.ts
Normal file
@ -0,0 +1,18 @@
|
||||
// with modifiers
|
||||
/(abc)\//ig;
|
||||
|
||||
// without modifiers
|
||||
/(abc)\//;
|
||||
|
||||
// can be assigned
|
||||
var re = /(abc)\//ig;
|
||||
|
||||
// generally behaves like an expression
|
||||
var noRe = !/(abc)\//i;
|
||||
|
||||
// inner line break is unterminated
|
||||
/a
|
||||
b/ig;
|
||||
|
||||
// just a comment
|
||||
//ig;
|
6
tests/parser/regexp.ts.fixture.ts
Normal file
6
tests/parser/regexp.ts.fixture.ts
Normal file
@ -0,0 +1,6 @@
|
||||
/(abc)\//ig;
|
||||
/(abc)\//;
|
||||
let re = /(abc)\//ig;
|
||||
let noRe = !/(abc)\//i;
|
||||
b / ig;
|
||||
// ERROR 1161: "Unterminated regular expression literal." in regexp.ts @ 75,76
|
Loading…
x
Reference in New Issue
Block a user