Merge branch 'master' into implement-smart-pipeline-in-parser
* master: (222 commits) Set correct methods name Use toPropertyKey in the "decorate" helper Allow function types in type params within arrow return types (#8954) Fix message when plugin of a wrong type is passed (#8950) rename colliding let bindings with for loop init (#8937) edge incomplete support for arrow destructuring (babel #8349) (#8926) fix single-arg async arrows when retainLines=true (#8868) [flow] Explicit inexact objects with `...` (#8884) Update preset-env data (#8898) Treat break inside block inside loop (#8914) fixed "source map" formatting in comment (#8878) [skip ci] fix typo in contributing guidelines (#8901) [skip ci] fix: Expression x === 'y' && '' should not evaluate to undefined. (#8880) fixed an extra word Fixes #8865 (#8866) v7.1.4 v7.1.3 Bump Babel deps (#8770) flow-bin@0.82.0 (#8832) Insertafter jsx fix (#8833) ... # Conflicts: # packages/babel-parser/src/tokenizer/index.js # packages/babel-parser/test/fixtures/experimental/class-private-properties/failure-numeric-literal/options.json # packages/babel-parser/test/fixtures/experimental/pipeline-operator/invalid-proposal/options.json
This commit is contained in:
@@ -5,8 +5,10 @@ import type { PluginList } from "./plugin-utils";
|
||||
// A second optional argument can be given to further configure
|
||||
// the parser process. These options are recognized:
|
||||
|
||||
export type SourceType = "script" | "module" | "unambiguous";
|
||||
|
||||
export type Options = {
|
||||
sourceType: "script" | "module",
|
||||
sourceType: SourceType,
|
||||
sourceFilename?: string,
|
||||
startLine: number,
|
||||
allowAwaitOutsideFunction: boolean,
|
||||
|
||||
@@ -122,6 +122,15 @@ export default class CommentsParser extends BaseParser {
|
||||
lastComment.end <= node.end
|
||||
) {
|
||||
if (this.state.commentPreviousNode) {
|
||||
for (j = 0; j < this.state.leadingComments.length; j++) {
|
||||
if (
|
||||
this.state.leadingComments[j].end <
|
||||
this.state.commentPreviousNode.end
|
||||
) {
|
||||
this.state.leadingComments.splice(j, 1);
|
||||
j--;
|
||||
}
|
||||
}
|
||||
if (this.state.leadingComments.length > 0) {
|
||||
lastArg.trailingComments = this.state.leadingComments;
|
||||
this.state.leadingComments = [];
|
||||
|
||||
@@ -609,22 +609,41 @@ export default class ExpressionParser extends LValParser {
|
||||
}
|
||||
return node;
|
||||
} else if (this.match(tt.backQuote)) {
|
||||
const node = this.startNodeAt(startPos, startLoc);
|
||||
node.tag = base;
|
||||
node.quasi = this.parseTemplate(true);
|
||||
if (state.optionalChainMember) {
|
||||
this.raise(
|
||||
startPos,
|
||||
"Tagged Template Literals are not allowed in optionalChain",
|
||||
);
|
||||
}
|
||||
return this.finishNode(node, "TaggedTemplateExpression");
|
||||
return this.parseTaggedTemplateExpression(
|
||||
startPos,
|
||||
startLoc,
|
||||
base,
|
||||
state,
|
||||
);
|
||||
} else {
|
||||
state.stop = true;
|
||||
return base;
|
||||
}
|
||||
}
|
||||
|
||||
parseTaggedTemplateExpression(
|
||||
startPos: number,
|
||||
startLoc: Position,
|
||||
base: N.Expression,
|
||||
state: N.ParseSubscriptState,
|
||||
typeArguments?: ?N.TsTypeParameterInstantiation,
|
||||
): N.TaggedTemplateExpression {
|
||||
const node: N.TaggedTemplateExpression = this.startNodeAt(
|
||||
startPos,
|
||||
startLoc,
|
||||
);
|
||||
node.tag = base;
|
||||
node.quasi = this.parseTemplate(true);
|
||||
if (typeArguments) node.typeParameters = typeArguments;
|
||||
if (state.optionalChainMember) {
|
||||
this.raise(
|
||||
startPos,
|
||||
"Tagged Template Literals are not allowed in optionalChain",
|
||||
);
|
||||
}
|
||||
return this.finishNode(node, "TaggedTemplateExpression");
|
||||
}
|
||||
|
||||
atPossibleAsync(base: N.Expression): boolean {
|
||||
return (
|
||||
!this.state.containsEsc &&
|
||||
@@ -821,7 +840,12 @@ export default class ExpressionParser extends LValParser {
|
||||
) {
|
||||
this.next();
|
||||
return this.parseFunction(node, false, false, true);
|
||||
} else if (canBeArrow && id.name === "async" && this.match(tt.name)) {
|
||||
} else if (
|
||||
canBeArrow &&
|
||||
!this.canInsertSemicolon() &&
|
||||
id.name === "async" &&
|
||||
this.match(tt.name)
|
||||
) {
|
||||
const oldYield = this.state.yieldInPossibleArrowParameters;
|
||||
this.state.yieldInPossibleArrowParameters = null;
|
||||
const params = [this.parseIdentifier()];
|
||||
@@ -977,7 +1001,19 @@ export default class ExpressionParser extends LValParser {
|
||||
if (isPrivate) {
|
||||
this.expectOnePlugin(["classPrivateProperties", "classPrivateMethods"]);
|
||||
const node = this.startNode();
|
||||
const columnHashEnd = this.state.end;
|
||||
this.next();
|
||||
const columnIdentifierStart = this.state.start;
|
||||
|
||||
const spacesBetweenHashAndIdentifier =
|
||||
columnIdentifierStart - columnHashEnd;
|
||||
if (spacesBetweenHashAndIdentifier != 0) {
|
||||
this.raise(
|
||||
columnIdentifierStart,
|
||||
"Unexpected space between # and identifier",
|
||||
);
|
||||
}
|
||||
|
||||
node.id = this.parseIdentifier(true);
|
||||
return this.finishNode(node, "PrivateName");
|
||||
} else {
|
||||
@@ -1037,7 +1073,7 @@ export default class ExpressionParser extends LValParser {
|
||||
} else if (!this.hasPlugin("importMeta")) {
|
||||
this.raise(
|
||||
id.start,
|
||||
`Dynamic imports require a parameter: import('a.js').then`,
|
||||
`Dynamic imports require a parameter: import('a.js')`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// @flow
|
||||
|
||||
import type { Options } from "../options";
|
||||
import type { File } from "../types";
|
||||
import type { File, JSXOpeningElement } from "../types";
|
||||
import type { PluginList } from "../plugin-utils";
|
||||
import { getOptions } from "../options";
|
||||
import StatementParser from "./statement";
|
||||
@@ -11,6 +11,11 @@ export type PluginsMap = {
|
||||
};
|
||||
|
||||
export default class Parser extends StatementParser {
|
||||
// Forward-declaration so typescript plugin can override jsx plugin
|
||||
+jsxParseOpeningElementAfterName: (
|
||||
node: JSXOpeningElement,
|
||||
) => JSXOpeningElement;
|
||||
|
||||
constructor(options: ?Options, input: string) {
|
||||
options = getOptions(options);
|
||||
super(options, input);
|
||||
|
||||
@@ -3,7 +3,8 @@
|
||||
import * as N from "../types";
|
||||
import { types as tt, type TokenType } from "../tokenizer/types";
|
||||
import ExpressionParser from "./expression";
|
||||
import { lineBreak } from "../util/whitespace";
|
||||
import { isIdentifierChar } from "../util/identifier";
|
||||
import { lineBreak, skipWhiteSpace } from "../util/whitespace";
|
||||
|
||||
// Reused empty array added for node fields that are always empty.
|
||||
|
||||
@@ -304,15 +305,7 @@ export default class StatementParser extends ExpressionParser {
|
||||
}
|
||||
}
|
||||
|
||||
if (this.eat(tt.parenL)) {
|
||||
const node = this.startNodeAt(startPos, startLoc);
|
||||
node.callee = expr;
|
||||
node.arguments = this.parseCallExpressionArguments(tt.parenR, false);
|
||||
this.toReferencedList(node.arguments);
|
||||
expr = this.finishNode(node, "CallExpression");
|
||||
}
|
||||
|
||||
node.expression = expr;
|
||||
node.expression = this.parseMaybeDecoratorArguments(expr);
|
||||
this.state.decoratorStack.pop();
|
||||
} else {
|
||||
node.expression = this.parseMaybeAssign();
|
||||
@@ -320,6 +313,18 @@ export default class StatementParser extends ExpressionParser {
|
||||
return this.finishNode(node, "Decorator");
|
||||
}
|
||||
|
||||
parseMaybeDecoratorArguments(expr: N.Expression): N.Expression {
|
||||
if (this.eat(tt.parenL)) {
|
||||
const node = this.startNodeAtNode(expr);
|
||||
node.callee = expr;
|
||||
node.arguments = this.parseCallExpressionArguments(tt.parenR, false);
|
||||
this.toReferencedList(node.arguments);
|
||||
return this.finishNode(node, "CallExpression");
|
||||
}
|
||||
|
||||
return expr;
|
||||
}
|
||||
|
||||
parseBreakContinueStatement(
|
||||
node: N.BreakStatement | N.ContinueStatement,
|
||||
keyword: string,
|
||||
@@ -1497,18 +1502,37 @@ export default class StatementParser extends ExpressionParser {
|
||||
return this.finishNode(node, "ExportNamedDeclaration");
|
||||
}
|
||||
|
||||
isAsyncFunction() {
|
||||
if (!this.isContextual("async")) return false;
|
||||
|
||||
const { input, pos } = this.state;
|
||||
|
||||
skipWhiteSpace.lastIndex = pos;
|
||||
const skip = skipWhiteSpace.exec(input);
|
||||
|
||||
if (!skip || !skip.length) return false;
|
||||
|
||||
const next = pos + skip[0].length;
|
||||
|
||||
return (
|
||||
!lineBreak.test(input.slice(pos, next)) &&
|
||||
input.slice(next, next + 8) === "function" &&
|
||||
(next + 8 === input.length || !isIdentifierChar(input.charAt(next + 8)))
|
||||
);
|
||||
}
|
||||
|
||||
parseExportDefaultExpression(): N.Expression | N.Declaration {
|
||||
const expr = this.startNode();
|
||||
if (this.eat(tt._function)) {
|
||||
return this.parseFunction(expr, true, false, false, true);
|
||||
} else if (
|
||||
this.isContextual("async") &&
|
||||
this.lookahead().type === tt._function
|
||||
) {
|
||||
// async function declaration
|
||||
this.eatContextual("async");
|
||||
this.eat(tt._function);
|
||||
return this.parseFunction(expr, true, false, true, true);
|
||||
|
||||
const isAsync = this.isAsyncFunction();
|
||||
|
||||
if (this.eat(tt._function) || isAsync) {
|
||||
if (isAsync) {
|
||||
this.eatContextual("async");
|
||||
this.expect(tt._function);
|
||||
}
|
||||
|
||||
return this.parseFunction(expr, true, false, isAsync, true);
|
||||
} else if (this.match(tt._class)) {
|
||||
return this.parseClass(expr, true, true);
|
||||
} else if (this.match(tt.at)) {
|
||||
@@ -1641,7 +1665,7 @@ export default class StatementParser extends ExpressionParser {
|
||||
this.state.type.keyword === "let" ||
|
||||
this.state.type.keyword === "function" ||
|
||||
this.state.type.keyword === "class" ||
|
||||
this.isContextual("async")
|
||||
this.isAsyncFunction()
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -41,13 +41,28 @@ export function getPluginOption(
|
||||
const PIPELINE_PROPOSALS = ["minimal", "smart"];
|
||||
|
||||
export function validatePlugins(plugins: PluginList) {
|
||||
if (
|
||||
hasPlugin(plugins, "decorators") &&
|
||||
hasPlugin(plugins, "decorators-legacy")
|
||||
) {
|
||||
throw new Error(
|
||||
"Cannot use the decorators and decorators-legacy plugin together",
|
||||
if (hasPlugin(plugins, "decorators")) {
|
||||
if (hasPlugin(plugins, "decorators-legacy")) {
|
||||
throw new Error(
|
||||
"Cannot use the decorators and decorators-legacy plugin together",
|
||||
);
|
||||
}
|
||||
|
||||
const decoratorsBeforeExport = getPluginOption(
|
||||
plugins,
|
||||
"decorators",
|
||||
"decoratorsBeforeExport",
|
||||
);
|
||||
if (decoratorsBeforeExport == null) {
|
||||
throw new Error(
|
||||
"The 'decorators' plugin requires a 'decoratorsBeforeExport' option," +
|
||||
" whose value must be a boolean. If you are migrating from" +
|
||||
" Babylon/Babel 6 or want to use the old decorators proposal, you" +
|
||||
" should use the 'decorators-legacy' plugin instead of 'decorators'.",
|
||||
);
|
||||
} else if (typeof decoratorsBeforeExport !== "boolean") {
|
||||
throw new Error("'decoratorsBeforeExport' must be a boolean.");
|
||||
}
|
||||
}
|
||||
|
||||
if (hasPlugin(plugins, "flow") && hasPlugin(plugins, "typescript")) {
|
||||
|
||||
@@ -463,7 +463,13 @@ export default (superClass: Class<Parser>): Class<Parser> =>
|
||||
} while (this.eat(tt.comma));
|
||||
}
|
||||
|
||||
node.body = this.flowParseObjectType(true, false, false, isClass);
|
||||
node.body = this.flowParseObjectType({
|
||||
allowStatic: isClass,
|
||||
allowExact: false,
|
||||
allowSpread: false,
|
||||
allowProto: isClass,
|
||||
allowInexact: false,
|
||||
});
|
||||
}
|
||||
|
||||
flowParseInterfaceExtends(): N.FlowInterfaceExtends {
|
||||
@@ -632,12 +638,15 @@ export default (superClass: Class<Parser>): Class<Parser> =>
|
||||
this.state.inType = true;
|
||||
|
||||
this.expectRelational("<");
|
||||
const oldNoAnonFunctionType = this.state.noAnonFunctionType;
|
||||
this.state.noAnonFunctionType = false;
|
||||
while (!this.isRelational(">")) {
|
||||
node.params.push(this.flowParseType());
|
||||
if (!this.isRelational(">")) {
|
||||
this.expect(tt.comma);
|
||||
}
|
||||
}
|
||||
this.state.noAnonFunctionType = oldNoAnonFunctionType;
|
||||
this.expectRelational(">");
|
||||
|
||||
this.state.inType = oldInType;
|
||||
@@ -656,7 +665,13 @@ export default (superClass: Class<Parser>): Class<Parser> =>
|
||||
} while (this.eat(tt.comma));
|
||||
}
|
||||
|
||||
node.body = this.flowParseObjectType(true, false, false, false);
|
||||
node.body = this.flowParseObjectType({
|
||||
allowStatic: false,
|
||||
allowExact: false,
|
||||
allowSpread: false,
|
||||
allowProto: false,
|
||||
allowInexact: false,
|
||||
});
|
||||
|
||||
return this.finishNode(node, "InterfaceTypeAnnotation");
|
||||
}
|
||||
@@ -754,12 +769,19 @@ export default (superClass: Class<Parser>): Class<Parser> =>
|
||||
return this.finishNode(node, "ObjectTypeCallProperty");
|
||||
}
|
||||
|
||||
flowParseObjectType(
|
||||
flowParseObjectType({
|
||||
allowStatic,
|
||||
allowExact,
|
||||
allowSpread,
|
||||
allowProto,
|
||||
allowInexact,
|
||||
}: {
|
||||
allowStatic: boolean,
|
||||
allowExact: boolean,
|
||||
allowSpread: boolean,
|
||||
allowProto: boolean,
|
||||
): N.FlowObjectTypeAnnotation {
|
||||
allowInexact: boolean,
|
||||
}): N.FlowObjectTypeAnnotation {
|
||||
const oldInType = this.state.inType;
|
||||
this.state.inType = true;
|
||||
|
||||
@@ -772,6 +794,7 @@ export default (superClass: Class<Parser>): Class<Parser> =>
|
||||
|
||||
let endDelim;
|
||||
let exact;
|
||||
let inexact = false;
|
||||
if (allowExact && this.match(tt.braceBarL)) {
|
||||
this.expect(tt.braceBarL);
|
||||
endDelim = tt.braceBarR;
|
||||
@@ -852,16 +875,21 @@ export default (superClass: Class<Parser>): Class<Parser> =>
|
||||
}
|
||||
}
|
||||
|
||||
nodeStart.properties.push(
|
||||
this.flowParseObjectTypeProperty(
|
||||
node,
|
||||
isStatic,
|
||||
protoStart,
|
||||
variance,
|
||||
kind,
|
||||
allowSpread,
|
||||
),
|
||||
const propOrInexact = this.flowParseObjectTypeProperty(
|
||||
node,
|
||||
isStatic,
|
||||
protoStart,
|
||||
variance,
|
||||
kind,
|
||||
allowSpread,
|
||||
allowInexact,
|
||||
);
|
||||
|
||||
if (propOrInexact === null) {
|
||||
inexact = true;
|
||||
} else {
|
||||
nodeStart.properties.push(propOrInexact);
|
||||
}
|
||||
}
|
||||
|
||||
this.flowObjectTypeSemicolon();
|
||||
@@ -869,6 +897,15 @@ export default (superClass: Class<Parser>): Class<Parser> =>
|
||||
|
||||
this.expect(endDelim);
|
||||
|
||||
/* The inexact flag should only be added on ObjectTypeAnnotations that
|
||||
* are not the body of an interface, declare interface, or declare class.
|
||||
* Since spreads are only allowed in objec types, checking that is
|
||||
* sufficient here.
|
||||
*/
|
||||
if (allowSpread) {
|
||||
nodeStart.inexact = inexact;
|
||||
}
|
||||
|
||||
const out = this.finishNode(nodeStart, "ObjectTypeAnnotation");
|
||||
|
||||
this.state.inType = oldInType;
|
||||
@@ -883,7 +920,8 @@ export default (superClass: Class<Parser>): Class<Parser> =>
|
||||
variance: ?N.FlowVariance,
|
||||
kind: string,
|
||||
allowSpread: boolean,
|
||||
): N.FlowObjectTypeProperty | N.FlowObjectTypeSpreadProperty {
|
||||
allowInexact: boolean,
|
||||
): (N.FlowObjectTypeProperty | N.FlowObjectTypeSpreadProperty) | null {
|
||||
if (this.match(tt.ellipsis)) {
|
||||
if (!allowSpread) {
|
||||
this.unexpected(
|
||||
@@ -901,8 +939,30 @@ export default (superClass: Class<Parser>): Class<Parser> =>
|
||||
);
|
||||
}
|
||||
this.expect(tt.ellipsis);
|
||||
node.argument = this.flowParseType();
|
||||
const isInexactToken = this.eat(tt.comma) || this.eat(tt.semi);
|
||||
|
||||
if (this.match(tt.braceR)) {
|
||||
if (allowInexact) return null;
|
||||
this.unexpected(
|
||||
null,
|
||||
"Explicit inexact syntax is only allowed inside inexact objects",
|
||||
);
|
||||
}
|
||||
|
||||
if (this.match(tt.braceBarR)) {
|
||||
this.unexpected(
|
||||
null,
|
||||
"Explicit inexact syntax cannot appear inside an explicit exact object type",
|
||||
);
|
||||
}
|
||||
|
||||
if (isInexactToken) {
|
||||
this.unexpected(
|
||||
null,
|
||||
"Explicit inexact syntax must appear at the end of an inexact object",
|
||||
);
|
||||
}
|
||||
node.argument = this.flowParseType();
|
||||
return this.finishNode(node, "ObjectTypeSpreadProperty");
|
||||
} else {
|
||||
node.key = this.flowParseObjectPropertyKey();
|
||||
@@ -1146,10 +1206,22 @@ export default (superClass: Class<Parser>): Class<Parser> =>
|
||||
);
|
||||
|
||||
case tt.braceL:
|
||||
return this.flowParseObjectType(false, false, true, false);
|
||||
return this.flowParseObjectType({
|
||||
allowStatic: false,
|
||||
allowExact: false,
|
||||
allowSpread: true,
|
||||
allowProto: false,
|
||||
allowInexact: true,
|
||||
});
|
||||
|
||||
case tt.braceBarL:
|
||||
return this.flowParseObjectType(false, true, true, false);
|
||||
return this.flowParseObjectType({
|
||||
allowStatic: false,
|
||||
allowExact: true,
|
||||
allowSpread: true,
|
||||
allowProto: false,
|
||||
allowInexact: false,
|
||||
});
|
||||
|
||||
case tt.bracketL:
|
||||
return this.flowParseTupleType();
|
||||
@@ -1917,6 +1989,15 @@ export default (superClass: Class<Parser>): Class<Parser> =>
|
||||
return super.parseClassProperty(node);
|
||||
}
|
||||
|
||||
parseClassPrivateProperty(
|
||||
node: N.ClassPrivateProperty,
|
||||
): N.ClassPrivateProperty {
|
||||
if (this.match(tt.colon)) {
|
||||
node.typeAnnotation = this.flowParseTypeAnnotation();
|
||||
}
|
||||
return super.parseClassPrivateProperty(node);
|
||||
}
|
||||
|
||||
// determine whether or not we're currently in the position where a class method would appear
|
||||
isClassMethod(): boolean {
|
||||
return this.isRelational("<") || super.isClassMethod();
|
||||
|
||||
@@ -358,11 +358,18 @@ export default (superClass: Class<Parser>): Class<Parser> =>
|
||||
this.expect(tt.jsxTagEnd);
|
||||
return this.finishNode(node, "JSXOpeningFragment");
|
||||
}
|
||||
node.attributes = [];
|
||||
node.name = this.jsxParseElementName();
|
||||
return this.jsxParseOpeningElementAfterName(node);
|
||||
}
|
||||
|
||||
jsxParseOpeningElementAfterName(
|
||||
node: N.JSXOpeningElement,
|
||||
): N.JSXOpeningElement {
|
||||
const attributes: N.JSXAttribute[] = [];
|
||||
while (!this.match(tt.slash) && !this.match(tt.jsxTagEnd)) {
|
||||
node.attributes.push(this.jsxParseAttribute());
|
||||
attributes.push(this.jsxParseAttribute());
|
||||
}
|
||||
node.attributes = attributes;
|
||||
node.selfClosing = this.eat(tt.slash);
|
||||
this.expect(tt.jsxTagEnd);
|
||||
return this.finishNode(node, "JSXOpeningElement");
|
||||
|
||||
@@ -57,6 +57,8 @@ function keywordTypeFromName(
|
||||
return "TSSymbolKeyword";
|
||||
case "undefined":
|
||||
return "TSUndefinedKeyword";
|
||||
case "unknown":
|
||||
return "TSUnknownKeyword";
|
||||
default:
|
||||
return undefined;
|
||||
}
|
||||
@@ -501,13 +503,32 @@ export default (superClass: Class<Parser>): Class<Parser> =>
|
||||
const node: N.TsTupleType = this.startNode();
|
||||
node.elementTypes = this.tsParseBracketedList(
|
||||
"TupleElementTypes",
|
||||
this.tsParseType.bind(this),
|
||||
this.tsParseTupleElementType.bind(this),
|
||||
/* bracket */ true,
|
||||
/* skipFirstToken */ false,
|
||||
);
|
||||
return this.finishNode(node, "TSTupleType");
|
||||
}
|
||||
|
||||
tsParseTupleElementType(): N.TsType {
|
||||
// parses `...TsType[]`
|
||||
if (this.match(tt.ellipsis)) {
|
||||
const restNode: N.TsRestType = this.startNode();
|
||||
this.next(); // skips ellipsis
|
||||
restNode.typeAnnotation = this.tsParseType();
|
||||
return this.finishNode(restNode, "TSRestType");
|
||||
}
|
||||
|
||||
const type = this.tsParseType();
|
||||
// parses `TsType?`
|
||||
if (this.eat(tt.question)) {
|
||||
const optionalTypeNode: N.TsOptionalType = this.startNodeAtNode(type);
|
||||
optionalTypeNode.typeAnnotation = type;
|
||||
return this.finishNode(optionalTypeNode, "TSOptionalType");
|
||||
}
|
||||
return type;
|
||||
}
|
||||
|
||||
tsParseParenthesizedType(): N.TsParenthesizedType {
|
||||
const node = this.startNode();
|
||||
this.expect(tt.parenL);
|
||||
@@ -836,14 +857,6 @@ export default (superClass: Class<Parser>): Class<Parser> =>
|
||||
return this.finishNode(node, "TSTypeAssertion");
|
||||
}
|
||||
|
||||
tsTryParseTypeArgumentsInExpression(): ?N.TsTypeParameterInstantiation {
|
||||
return this.tsTryParseAndCatch(() => {
|
||||
const res = this.tsParseTypeArguments();
|
||||
this.expect(tt.parenL);
|
||||
return res;
|
||||
});
|
||||
}
|
||||
|
||||
tsParseHeritageClause(): $ReadOnlyArray<N.TsExpressionWithTypeArguments> {
|
||||
return this.tsParseDelimitedList(
|
||||
"HeritageClauseElement",
|
||||
@@ -887,6 +900,16 @@ export default (superClass: Class<Parser>): Class<Parser> =>
|
||||
return this.finishNode(node, "TSTypeAliasDeclaration");
|
||||
}
|
||||
|
||||
tsInNoContext<T>(cb: () => T): T {
|
||||
const oldContext = this.state.context;
|
||||
this.state.context = [oldContext[0]];
|
||||
try {
|
||||
return cb();
|
||||
} finally {
|
||||
this.state.context = oldContext;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs `cb` in a type context.
|
||||
* This should be called one token *before* the first type token,
|
||||
@@ -1241,13 +1264,19 @@ export default (superClass: Class<Parser>): Class<Parser> =>
|
||||
|
||||
tsParseTypeArguments(): N.TsTypeParameterInstantiation {
|
||||
const node = this.startNode();
|
||||
node.params = this.tsInType(() => {
|
||||
this.expectRelational("<");
|
||||
return this.tsParseDelimitedList(
|
||||
"TypeParametersOrArguments",
|
||||
this.tsParseType.bind(this),
|
||||
);
|
||||
});
|
||||
node.params = this.tsInType(() =>
|
||||
// Temporarily remove a JSX parsing context, which makes us scan different tokens.
|
||||
this.tsInNoContext(() => {
|
||||
this.expectRelational("<");
|
||||
return this.tsParseDelimitedList(
|
||||
"TypeParametersOrArguments",
|
||||
this.tsParseType.bind(this),
|
||||
);
|
||||
}),
|
||||
);
|
||||
// This reads the next token after the `>` too, so do this in the enclosing context.
|
||||
// But be sure not to parse a regex in the jsx expression `<C<number> />`, so set exprAllowed = false
|
||||
this.state.exprAllowed = false;
|
||||
this.expectRelational(">");
|
||||
return this.finishNode(node, "TSTypeParameterInstantiation");
|
||||
}
|
||||
@@ -1358,34 +1387,53 @@ export default (superClass: Class<Parser>): Class<Parser> =>
|
||||
return this.finishNode(nonNullExpression, "TSNonNullExpression");
|
||||
}
|
||||
|
||||
if (!noCalls && this.isRelational("<")) {
|
||||
if (this.atPossibleAsync(base)) {
|
||||
// Almost certainly this is a generic async function `async <T>() => ...
|
||||
// But it might be a call with a type argument `async<T>();`
|
||||
const asyncArrowFn = this.tsTryParseGenericAsyncArrowFunction(
|
||||
startPos,
|
||||
startLoc,
|
||||
);
|
||||
if (asyncArrowFn) {
|
||||
return asyncArrowFn;
|
||||
if (this.isRelational("<")) {
|
||||
// tsTryParseAndCatch is expensive, so avoid if not necessary.
|
||||
// There are number of things we are going to "maybe" parse, like type arguments on
|
||||
// tagged template expressions. If any of them fail, walk it back and continue.
|
||||
const result = this.tsTryParseAndCatch(() => {
|
||||
if (!noCalls && this.atPossibleAsync(base)) {
|
||||
// Almost certainly this is a generic async function `async <T>() => ...
|
||||
// But it might be a call with a type argument `async<T>();`
|
||||
const asyncArrowFn = this.tsTryParseGenericAsyncArrowFunction(
|
||||
startPos,
|
||||
startLoc,
|
||||
);
|
||||
if (asyncArrowFn) {
|
||||
return asyncArrowFn;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const node: N.CallExpression = this.startNodeAt(startPos, startLoc);
|
||||
node.callee = base;
|
||||
const node: N.CallExpression = this.startNodeAt(startPos, startLoc);
|
||||
node.callee = base;
|
||||
|
||||
// May be passing type arguments. But may just be the `<` operator.
|
||||
const typeArguments = this.tsTryParseTypeArgumentsInExpression(); // Also eats the "("
|
||||
if (typeArguments) {
|
||||
// possibleAsync always false here, because we would have handled it above.
|
||||
// $FlowIgnore (won't be any undefined arguments)
|
||||
node.arguments = this.parseCallExpressionArguments(
|
||||
tt.parenR,
|
||||
/* possibleAsync */ false,
|
||||
);
|
||||
node.typeParameters = typeArguments;
|
||||
return this.finishCallExpression(node);
|
||||
}
|
||||
const typeArguments = this.tsParseTypeArguments();
|
||||
|
||||
if (typeArguments) {
|
||||
if (!noCalls && this.eat(tt.parenL)) {
|
||||
// possibleAsync always false here, because we would have handled it above.
|
||||
// $FlowIgnore (won't be any undefined arguments)
|
||||
node.arguments = this.parseCallExpressionArguments(
|
||||
tt.parenR,
|
||||
/* possibleAsync */ false,
|
||||
);
|
||||
node.typeParameters = typeArguments;
|
||||
return this.finishCallExpression(node);
|
||||
} else if (this.match(tt.backQuote)) {
|
||||
return this.parseTaggedTemplateExpression(
|
||||
startPos,
|
||||
startLoc,
|
||||
base,
|
||||
state,
|
||||
typeArguments,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
this.unexpected();
|
||||
});
|
||||
|
||||
if (result) return result;
|
||||
}
|
||||
|
||||
return super.parseSubscript(base, startPos, startLoc, noCalls, state);
|
||||
@@ -1509,6 +1557,19 @@ export default (superClass: Class<Parser>): Class<Parser> =>
|
||||
cls.abstract = true;
|
||||
return cls;
|
||||
}
|
||||
|
||||
// export default interface allowed in:
|
||||
// https://github.com/Microsoft/TypeScript/pull/16040
|
||||
if (this.state.value === "interface") {
|
||||
const result = this.tsParseDeclaration(
|
||||
this.startNode(),
|
||||
this.state.value,
|
||||
true,
|
||||
);
|
||||
|
||||
if (result) return result;
|
||||
}
|
||||
|
||||
return super.parseExportDefaultExpression();
|
||||
}
|
||||
|
||||
@@ -1764,9 +1825,8 @@ export default (superClass: Class<Parser>): Class<Parser> =>
|
||||
}
|
||||
|
||||
parseObjPropValue(prop: N.ObjectMember, ...args): void {
|
||||
if (this.isRelational("<")) {
|
||||
throw new Error("TODO");
|
||||
}
|
||||
const typeParameters = this.tsTryParseTypeParameters();
|
||||
if (typeParameters) prop.typeParameters = typeParameters;
|
||||
|
||||
super.parseObjPropValue(prop, ...args);
|
||||
}
|
||||
@@ -2006,6 +2066,22 @@ export default (superClass: Class<Parser>): Class<Parser> =>
|
||||
}
|
||||
}
|
||||
|
||||
parseMaybeDecoratorArguments(expr: N.Expression): N.Expression {
|
||||
if (this.isRelational("<")) {
|
||||
const typeArguments = this.tsParseTypeArguments();
|
||||
|
||||
if (this.match(tt.parenL)) {
|
||||
const call = super.parseMaybeDecoratorArguments(expr);
|
||||
call.typeParameters = typeArguments;
|
||||
return call;
|
||||
}
|
||||
|
||||
this.unexpected(this.state.start, tt.parenL);
|
||||
}
|
||||
|
||||
return super.parseMaybeDecoratorArguments(expr);
|
||||
}
|
||||
|
||||
// === === === === === === === === === === === === === === === ===
|
||||
// Note: All below methods are duplicates of something in flow.js.
|
||||
// Not sure what the best way to combine these is.
|
||||
@@ -2102,4 +2178,14 @@ export default (superClass: Class<Parser>): Class<Parser> =>
|
||||
// Avoid unnecessary lookahead in checking for abstract class unless needed!
|
||||
return super.canHaveLeadingDecorator() || this.isAbstractClass();
|
||||
}
|
||||
|
||||
jsxParseOpeningElementAfterName(
|
||||
node: N.JSXOpeningElement,
|
||||
): N.JSXOpeningElement {
|
||||
const typeArguments = this.tsTryParseAndCatch(() =>
|
||||
this.tsParseTypeArguments(),
|
||||
);
|
||||
if (typeArguments) node.typeParameters = typeArguments;
|
||||
return super.jsxParseOpeningElementAfterName(node);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -106,7 +106,7 @@ tt.incDec.updateContext = function() {
|
||||
// tokExprAllowed stays unchanged
|
||||
};
|
||||
|
||||
tt._function.updateContext = function(prevType) {
|
||||
tt._function.updateContext = tt._class.updateContext = function(prevType) {
|
||||
if (this.state.exprAllowed && !this.braceIsBlock(prevType)) {
|
||||
this.state.context.push(types.functionExpression);
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ import {
|
||||
lineBreak,
|
||||
lineBreakG,
|
||||
isNewLine,
|
||||
nonASCIIwhitespace,
|
||||
isWhitespace,
|
||||
} from "../util/whitespace";
|
||||
import State from "./state";
|
||||
|
||||
@@ -110,18 +110,6 @@ export class Token {
|
||||
|
||||
// ## Tokenizer
|
||||
|
||||
function codePointToString(code: number): string {
|
||||
// UTF-16 Decoding
|
||||
if (code <= 0xffff) {
|
||||
return String.fromCharCode(code);
|
||||
} else {
|
||||
return String.fromCharCode(
|
||||
((code - 0x10000) >> 10) + 0xd800,
|
||||
((code - 0x10000) & 1023) + 0xdc00,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default class Tokenizer extends LocationParser {
|
||||
// Forward-declarations
|
||||
// parser/util.js
|
||||
@@ -226,7 +214,7 @@ export default class Tokenizer extends LocationParser {
|
||||
if (curContext.override) {
|
||||
curContext.override(this);
|
||||
} else {
|
||||
this.readToken(this.fullCharCodeAtPos());
|
||||
this.readToken(this.input.codePointAt(this.state.pos));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -240,14 +228,6 @@ export default class Tokenizer extends LocationParser {
|
||||
}
|
||||
}
|
||||
|
||||
fullCharCodeAtPos(): number {
|
||||
const code = this.input.charCodeAt(this.state.pos);
|
||||
if (code <= 0xd7ff || code >= 0xe000) return code;
|
||||
|
||||
const next = this.input.charCodeAt(this.state.pos + 1);
|
||||
return (code << 10) + next - 0x35fdc00;
|
||||
}
|
||||
|
||||
pushComment(
|
||||
block: boolean,
|
||||
text: string,
|
||||
@@ -331,11 +311,6 @@ export default class Tokenizer extends LocationParser {
|
||||
loop: while (this.state.pos < this.input.length) {
|
||||
const ch = this.input.charCodeAt(this.state.pos);
|
||||
switch (ch) {
|
||||
case charCodes.space:
|
||||
case charCodes.nonBreakingSpace:
|
||||
++this.state.pos;
|
||||
break;
|
||||
|
||||
case charCodes.carriageReturn:
|
||||
if (
|
||||
this.input.charCodeAt(this.state.pos + 1) === charCodes.lineFeed
|
||||
@@ -367,11 +342,7 @@ export default class Tokenizer extends LocationParser {
|
||||
break;
|
||||
|
||||
default:
|
||||
if (
|
||||
(ch > charCodes.backSpace && ch < charCodes.shiftOut) ||
|
||||
(ch >= charCodes.oghamSpaceMark &&
|
||||
nonASCIIwhitespace.test(String.fromCharCode(ch)))
|
||||
) {
|
||||
if (isWhitespace(ch)) {
|
||||
++this.state.pos;
|
||||
} else {
|
||||
break loop;
|
||||
@@ -455,7 +426,7 @@ export default class Tokenizer extends LocationParser {
|
||||
|
||||
readToken_slash(): void {
|
||||
// '/'
|
||||
if (this.state.exprAllowed) {
|
||||
if (this.state.exprAllowed && !this.state.inType) {
|
||||
++this.state.pos;
|
||||
this.readRegexp();
|
||||
return;
|
||||
@@ -845,7 +816,7 @@ export default class Tokenizer extends LocationParser {
|
||||
|
||||
this.raise(
|
||||
this.state.pos,
|
||||
`Unexpected character '${codePointToString(code)}'`,
|
||||
`Unexpected character '${String.fromCodePoint(code)}'`,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -887,7 +858,7 @@ export default class Tokenizer extends LocationParser {
|
||||
|
||||
while (this.state.pos < this.input.length) {
|
||||
const char = this.input[this.state.pos];
|
||||
const charCode = this.fullCharCodeAtPos();
|
||||
const charCode = this.input.codePointAt(this.state.pos);
|
||||
|
||||
if (VALID_REGEX_FLAGS.indexOf(char) > -1) {
|
||||
if (mods.indexOf(char) > -1) {
|
||||
@@ -999,7 +970,7 @@ export default class Tokenizer extends LocationParser {
|
||||
}
|
||||
}
|
||||
|
||||
if (isIdentifierStart(this.fullCharCodeAtPos())) {
|
||||
if (isIdentifierStart(this.input.codePointAt(this.state.pos))) {
|
||||
this.raise(this.state.pos, "Identifier directly after number");
|
||||
}
|
||||
|
||||
@@ -1055,7 +1026,7 @@ export default class Tokenizer extends LocationParser {
|
||||
}
|
||||
}
|
||||
|
||||
if (isIdentifierStart(this.fullCharCodeAtPos())) {
|
||||
if (isIdentifierStart(this.input.codePointAt(this.state.pos))) {
|
||||
this.raise(this.state.pos, "Identifier directly after number");
|
||||
}
|
||||
|
||||
@@ -1132,6 +1103,7 @@ export default class Tokenizer extends LocationParser {
|
||||
(ch === charCodes.lineSeparator || ch === charCodes.paragraphSeparator)
|
||||
) {
|
||||
++this.state.pos;
|
||||
++this.state.curLine;
|
||||
} else if (isNewLine(ch)) {
|
||||
this.raise(this.state.start, "Unterminated string constant");
|
||||
} else {
|
||||
@@ -1224,7 +1196,7 @@ export default class Tokenizer extends LocationParser {
|
||||
}
|
||||
case charCodes.lowercaseU: {
|
||||
const code = this.readCodePoint(throwOnInvalid);
|
||||
return code === null ? null : codePointToString(code);
|
||||
return code === null ? null : String.fromCodePoint(code);
|
||||
}
|
||||
case charCodes.lowercaseT:
|
||||
return "\t";
|
||||
@@ -1302,7 +1274,7 @@ export default class Tokenizer extends LocationParser {
|
||||
first = true,
|
||||
chunkStart = this.state.pos;
|
||||
while (this.state.pos < this.input.length) {
|
||||
const ch = this.fullCharCodeAtPos();
|
||||
const ch = this.input.codePointAt(this.state.pos);
|
||||
if (isIdentifierChar(ch)) {
|
||||
this.state.pos += ch <= 0xffff ? 1 : 2;
|
||||
} else if (this.state.isIterator && ch === charCodes.atSign) {
|
||||
@@ -1328,7 +1300,7 @@ export default class Tokenizer extends LocationParser {
|
||||
}
|
||||
|
||||
// $FlowFixMe
|
||||
word += codePointToString(esc);
|
||||
word += String.fromCodePoint(esc);
|
||||
chunkStart = this.state.pos;
|
||||
} else {
|
||||
break;
|
||||
|
||||
@@ -66,18 +66,12 @@ export class TokenType {
|
||||
}
|
||||
}
|
||||
|
||||
class KeywordTokenType extends TokenType {
|
||||
constructor(name: string, options: TokenOptions = {}) {
|
||||
options.keyword = name;
|
||||
|
||||
super(name, options);
|
||||
}
|
||||
function KeywordTokenType(keyword: string, options: TokenOptions = {}) {
|
||||
return new TokenType(keyword, { ...options, keyword });
|
||||
}
|
||||
|
||||
export class BinopTokenType extends TokenType {
|
||||
constructor(name: string, prec: number) {
|
||||
super(name, { beforeExpr, binop: prec });
|
||||
}
|
||||
function BinopTokenType(name: string, binop: number) {
|
||||
return new TokenType(name, { beforeExpr, binop });
|
||||
}
|
||||
|
||||
export const types: { [name: string]: TokenType } = {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
// @flow
|
||||
|
||||
import type { SourceType } from "./options";
|
||||
import type { Token } from "./tokenizer";
|
||||
import type { SourceLocation } from "./util/location";
|
||||
|
||||
@@ -135,7 +136,7 @@ export type File = NodeBase & {
|
||||
|
||||
export type Program = NodeBase & {
|
||||
type: "Program",
|
||||
sourceType: "script" | "module",
|
||||
sourceType: SourceType,
|
||||
body: Array<Statement | ModuleDeclaration>, // TODO: $ReadOnlyArray
|
||||
directives: $ReadOnlyArray<Directive>, // TODO: Not in spec
|
||||
interpreter: InterpreterDirective | null,
|
||||
@@ -397,7 +398,7 @@ export type ObjectMemberBase = NodeBase & {
|
||||
decorators: $ReadOnlyArray<Decorator>,
|
||||
kind?: "get" | "set" | "method",
|
||||
method: boolean, // TODO: Not in spec
|
||||
|
||||
typeParameters?: ?TypeParameterInstantiationBase, // TODO: Not in spec
|
||||
variance?: ?FlowVariance, // TODO: Not in spec
|
||||
};
|
||||
|
||||
@@ -599,10 +600,11 @@ export type TemplateLiteral = NodeBase & {
|
||||
expressions: $ReadOnlyArray<Expression>,
|
||||
};
|
||||
|
||||
export type TaggedTmplateExpression = NodeBase & {
|
||||
export type TaggedTemplateExpression = NodeBase & {
|
||||
type: "TaggedTemplateExpression",
|
||||
tag: Expression,
|
||||
quasi: TemplateLiteral,
|
||||
typeParameters?: ?TypeParameterInstantiationBase, // TODO: Not in spec
|
||||
};
|
||||
|
||||
export type TemplateElement = NodeBase & {
|
||||
@@ -742,6 +744,7 @@ export type ClassPrivateProperty = NodeBase & {
|
||||
value: ?Expression, // TODO: Not in spec that this is nullable.
|
||||
static: boolean,
|
||||
computed: false,
|
||||
typeAnnotation?: ?TypeAnnotation, // TODO: Not in spec
|
||||
};
|
||||
|
||||
export type OptClassDeclaration = ClassBase &
|
||||
@@ -846,7 +849,13 @@ export type JSXEmptyExpression = Node;
|
||||
export type JSXSpreadChild = Node;
|
||||
export type JSXExpressionContainer = Node;
|
||||
export type JSXAttribute = Node;
|
||||
export type JSXOpeningElement = Node;
|
||||
export type JSXOpeningElement = NodeBase & {
|
||||
type: "JSXOpeningElement",
|
||||
name: JSXNamespacedName | JSXMemberExpression,
|
||||
typeParameters?: ?TypeParameterInstantiationBase, // TODO: Not in spec
|
||||
attributes: $ReadOnlyArray<JSXAttribute>,
|
||||
selfClosing: boolean,
|
||||
};
|
||||
export type JSXClosingElement = Node;
|
||||
export type JSXElement = Node;
|
||||
export type JSXOpeningFragment = Node;
|
||||
@@ -1121,6 +1130,8 @@ export type TsType =
|
||||
| TsTypeLiteral
|
||||
| TsArrayType
|
||||
| TsTupleType
|
||||
| TsOptionalType
|
||||
| TsRestType
|
||||
| TsUnionOrIntersectionType
|
||||
| TsConditionalType
|
||||
| TsInferType
|
||||
@@ -1136,6 +1147,7 @@ export type TsTypeBase = NodeBase;
|
||||
|
||||
export type TsKeywordTypeType =
|
||||
| "TSAnyKeyword"
|
||||
| "TSUnknownKeyword"
|
||||
| "TSNumberKeyword"
|
||||
| "TSObjectKeyword"
|
||||
| "TSBooleanKeyword"
|
||||
@@ -1200,6 +1212,16 @@ export type TsTupleType = TsTypeBase & {
|
||||
elementTypes: $ReadOnlyArray<TsType>,
|
||||
};
|
||||
|
||||
export type TsOptionalType = TsTypeBase & {
|
||||
type: "TSOptionalType",
|
||||
typeAnnotation: TsType,
|
||||
};
|
||||
|
||||
export type TsRestType = TsTypeBase & {
|
||||
type: "TSRestType",
|
||||
typeAnnotation: TsType,
|
||||
};
|
||||
|
||||
export type TsUnionOrIntersectionType = TsUnionType | TsIntersectionType;
|
||||
|
||||
export type TsUnionOrIntersectionTypeBase = TsTypeBase & {
|
||||
|
||||
@@ -39,16 +39,14 @@ export class SourceLocation {
|
||||
// into.
|
||||
|
||||
export function getLineInfo(input: string, offset: number): Position {
|
||||
for (let line = 1, cur = 0; ; ) {
|
||||
lineBreakG.lastIndex = cur;
|
||||
const match = lineBreakG.exec(input);
|
||||
if (match && match.index < offset) {
|
||||
++line;
|
||||
cur = match.index + match[0].length;
|
||||
} else {
|
||||
return new Position(line, offset - cur);
|
||||
}
|
||||
let line = 1;
|
||||
let lineStart = 0;
|
||||
let match;
|
||||
lineBreakG.lastIndex = 0;
|
||||
while ((match = lineBreakG.exec(input)) && match.index < offset) {
|
||||
line++;
|
||||
lineStart = lineBreakG.lastIndex;
|
||||
}
|
||||
// istanbul ignore next
|
||||
throw new Error("Unreachable");
|
||||
|
||||
return new Position(line, offset - lineStart);
|
||||
}
|
||||
|
||||
@@ -1,13 +1,55 @@
|
||||
// @flow
|
||||
|
||||
import * as charCodes from "charcodes";
|
||||
|
||||
// Matches a whole line break (where CRLF is considered a single
|
||||
// line break). Used to count lines.
|
||||
|
||||
export const lineBreak = /\r\n?|\n|\u2028|\u2029/;
|
||||
export const lineBreakG = new RegExp(lineBreak.source, "g");
|
||||
|
||||
// https://tc39.github.io/ecma262/#sec-line-terminators
|
||||
export function isNewLine(code: number): boolean {
|
||||
return code === 10 || code === 13 || code === 0x2028 || code === 0x2029;
|
||||
switch (code) {
|
||||
case charCodes.lineFeed:
|
||||
case charCodes.carriageReturn:
|
||||
case charCodes.lineSeparator:
|
||||
case charCodes.paragraphSeparator:
|
||||
return true;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export const nonASCIIwhitespace = /[\u1680\u180e\u2000-\u200a\u202f\u205f\u3000\ufeff]/;
|
||||
export const skipWhiteSpace = /(?:\s|\/\/.*|\/\*[^]*?\*\/)*/g;
|
||||
|
||||
// https://tc39.github.io/ecma262/#sec-white-space
|
||||
export function isWhitespace(code: number): boolean {
|
||||
switch (code) {
|
||||
case 0x0009: // CHARACTER TABULATION
|
||||
case 0x000b: // LINE TABULATION
|
||||
case 0x000c: // FORM FEED
|
||||
case charCodes.space:
|
||||
case charCodes.nonBreakingSpace:
|
||||
case charCodes.oghamSpaceMark:
|
||||
case 0x2000: // EN QUAD
|
||||
case 0x2001: // EM QUAD
|
||||
case 0x2002: // EN SPACE
|
||||
case 0x2003: // EM SPACE
|
||||
case 0x2004: // THREE-PER-EM SPACE
|
||||
case 0x2005: // FOUR-PER-EM SPACE
|
||||
case 0x2006: // SIX-PER-EM SPACE
|
||||
case 0x2007: // FIGURE SPACE
|
||||
case 0x2008: // PUNCTUATION SPACE
|
||||
case 0x2009: // THIN SPACE
|
||||
case 0x200a: // HAIR SPACE
|
||||
case 0x202f: // NARROW NO-BREAK SPACE
|
||||
case 0x205f: // MEDIUM MATHEMATICAL SPACE
|
||||
case 0x3000: // IDEOGRAPHIC SPACE
|
||||
case 0xfeff: // ZERO WIDTH NO-BREAK SPACE
|
||||
return true;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user