123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352 |
- import {ContextualKeyword} from "../parser/tokenizer/keywords";
- import {TokenType as tt} from "../parser/tokenizer/types";
- /**
- * Get information about the class fields for this class, given a token processor pointing to the
- * open-brace at the start of the class.
- */
- export default function getClassInfo(
- rootTransformer,
- tokens,
- nameManager,
- disableESTransforms,
- ) {
- const snapshot = tokens.snapshot();
- const headerInfo = processClassHeader(tokens);
- let constructorInitializerStatements = [];
- const instanceInitializerNames = [];
- const staticInitializerNames = [];
- let constructorInsertPos = null;
- const fields = [];
- const rangesToRemove = [];
- const classContextId = tokens.currentToken().contextId;
- if (classContextId == null) {
- throw new Error("Expected non-null class context ID on class open-brace.");
- }
- tokens.nextToken();
- while (!tokens.matchesContextIdAndLabel(tt.braceR, classContextId)) {
- if (tokens.matchesContextual(ContextualKeyword._constructor) && !tokens.currentToken().isType) {
- ({constructorInitializerStatements, constructorInsertPos} = processConstructor(tokens));
- } else if (tokens.matches1(tt.semi)) {
- if (!disableESTransforms) {
- rangesToRemove.push({start: tokens.currentIndex(), end: tokens.currentIndex() + 1});
- }
- tokens.nextToken();
- } else if (tokens.currentToken().isType) {
- tokens.nextToken();
- } else {
- // Either a method or a field. Skip to the identifier part.
- const statementStartIndex = tokens.currentIndex();
- let isStatic = false;
- let isESPrivate = false;
- let isDeclareOrAbstract = false;
- while (isAccessModifier(tokens.currentToken())) {
- if (tokens.matches1(tt._static)) {
- isStatic = true;
- }
- if (tokens.matches1(tt.hash)) {
- isESPrivate = true;
- }
- if (tokens.matches1(tt._declare) || tokens.matches1(tt._abstract)) {
- isDeclareOrAbstract = true;
- }
- tokens.nextToken();
- }
- if (isStatic && tokens.matches1(tt.braceL)) {
- // This is a static block, so don't process it in any special way.
- skipToNextClassElement(tokens, classContextId);
- continue;
- }
- if (isESPrivate) {
- // Sucrase doesn't attempt to transpile private fields; just leave them as-is.
- skipToNextClassElement(tokens, classContextId);
- continue;
- }
- if (
- tokens.matchesContextual(ContextualKeyword._constructor) &&
- !tokens.currentToken().isType
- ) {
- ({constructorInitializerStatements, constructorInsertPos} = processConstructor(tokens));
- continue;
- }
- const nameStartIndex = tokens.currentIndex();
- skipFieldName(tokens);
- if (tokens.matches1(tt.lessThan) || tokens.matches1(tt.parenL)) {
- // This is a method, so nothing to process.
- skipToNextClassElement(tokens, classContextId);
- continue;
- }
- // There might be a type annotation that we need to skip.
- while (tokens.currentToken().isType) {
- tokens.nextToken();
- }
- if (tokens.matches1(tt.eq)) {
- const equalsIndex = tokens.currentIndex();
- // This is an initializer, so we need to wrap in an initializer method.
- const valueEnd = tokens.currentToken().rhsEndIndex;
- if (valueEnd == null) {
- throw new Error("Expected rhsEndIndex on class field assignment.");
- }
- tokens.nextToken();
- while (tokens.currentIndex() < valueEnd) {
- rootTransformer.processToken();
- }
- let initializerName;
- if (isStatic) {
- initializerName = nameManager.claimFreeName("__initStatic");
- staticInitializerNames.push(initializerName);
- } else {
- initializerName = nameManager.claimFreeName("__init");
- instanceInitializerNames.push(initializerName);
- }
- // Fields start at the name, so `static x = 1;` has a field range of `x = 1;`.
- fields.push({
- initializerName,
- equalsIndex,
- start: nameStartIndex,
- end: tokens.currentIndex(),
- });
- } else if (!disableESTransforms || isDeclareOrAbstract) {
- // This is a regular field declaration, like `x;`. With the class transform enabled, we just
- // remove the line so that no output is produced. With the class transform disabled, we
- // usually want to preserve the declaration (but still strip types), but if the `declare`
- // or `abstract` keyword is specified, we should remove the line to avoid initializing the
- // value to undefined.
- rangesToRemove.push({start: statementStartIndex, end: tokens.currentIndex()});
- }
- }
- }
- tokens.restoreToSnapshot(snapshot);
- if (disableESTransforms) {
- // With ES transforms disabled, we don't want to transform regular class
- // field declarations, and we don't need to do any additional tricks to
- // reference the constructor for static init, but we still need to transform
- // TypeScript field initializers defined as constructor parameters and we
- // still need to remove `declare` fields. For now, we run the same code
- // path but omit any field information, as if the class had no field
- // declarations. In the future, when we fully drop the class fields
- // transform, we can simplify this code significantly.
- return {
- headerInfo,
- constructorInitializerStatements,
- instanceInitializerNames: [],
- staticInitializerNames: [],
- constructorInsertPos,
- fields: [],
- rangesToRemove,
- };
- } else {
- return {
- headerInfo,
- constructorInitializerStatements,
- instanceInitializerNames,
- staticInitializerNames,
- constructorInsertPos,
- fields,
- rangesToRemove,
- };
- }
- }
- /**
- * Move the token processor to the next method/field in the class.
- *
- * To do that, we seek forward to the next start of a class name (either an open
- * bracket or an identifier, or the closing curly brace), then seek backward to
- * include any access modifiers.
- */
- function skipToNextClassElement(tokens, classContextId) {
- tokens.nextToken();
- while (tokens.currentToken().contextId !== classContextId) {
- tokens.nextToken();
- }
- while (isAccessModifier(tokens.tokenAtRelativeIndex(-1))) {
- tokens.previousToken();
- }
- }
- function processClassHeader(tokens) {
- const classToken = tokens.currentToken();
- const contextId = classToken.contextId;
- if (contextId == null) {
- throw new Error("Expected context ID on class token.");
- }
- const isExpression = classToken.isExpression;
- if (isExpression == null) {
- throw new Error("Expected isExpression on class token.");
- }
- let className = null;
- let hasSuperclass = false;
- tokens.nextToken();
- if (tokens.matches1(tt.name)) {
- className = tokens.identifierName();
- }
- while (!tokens.matchesContextIdAndLabel(tt.braceL, contextId)) {
- // If this has a superclass, there will always be an `extends` token. If it doesn't have a
- // superclass, only type parameters and `implements` clauses can show up here, all of which
- // consist only of type tokens. A declaration like `class A<B extends C> {` should *not* count
- // as having a superclass.
- if (tokens.matches1(tt._extends) && !tokens.currentToken().isType) {
- hasSuperclass = true;
- }
- tokens.nextToken();
- }
- return {isExpression, className, hasSuperclass};
- }
- /**
- * Extract useful information out of a constructor, starting at the "constructor" name.
- */
- function processConstructor(tokens)
- {
- const constructorInitializerStatements = [];
- tokens.nextToken();
- const constructorContextId = tokens.currentToken().contextId;
- if (constructorContextId == null) {
- throw new Error("Expected context ID on open-paren starting constructor params.");
- }
- // Advance through parameters looking for access modifiers.
- while (!tokens.matchesContextIdAndLabel(tt.parenR, constructorContextId)) {
- if (tokens.currentToken().contextId === constructorContextId) {
- // Current token is an open paren or comma just before a param, so check
- // that param for access modifiers.
- tokens.nextToken();
- if (isAccessModifier(tokens.currentToken())) {
- tokens.nextToken();
- while (isAccessModifier(tokens.currentToken())) {
- tokens.nextToken();
- }
- const token = tokens.currentToken();
- if (token.type !== tt.name) {
- throw new Error("Expected identifier after access modifiers in constructor arg.");
- }
- const name = tokens.identifierNameForToken(token);
- constructorInitializerStatements.push(`this.${name} = ${name}`);
- }
- } else {
- tokens.nextToken();
- }
- }
- // )
- tokens.nextToken();
- // Constructor type annotations are invalid, but skip them anyway since
- // they're easy to skip.
- while (tokens.currentToken().isType) {
- tokens.nextToken();
- }
- let constructorInsertPos = tokens.currentIndex();
- // Advance through body looking for a super call.
- let foundSuperCall = false;
- while (!tokens.matchesContextIdAndLabel(tt.braceR, constructorContextId)) {
- if (!foundSuperCall && tokens.matches2(tt._super, tt.parenL)) {
- tokens.nextToken();
- const superCallContextId = tokens.currentToken().contextId;
- if (superCallContextId == null) {
- throw new Error("Expected a context ID on the super call");
- }
- while (!tokens.matchesContextIdAndLabel(tt.parenR, superCallContextId)) {
- tokens.nextToken();
- }
- constructorInsertPos = tokens.currentIndex();
- foundSuperCall = true;
- }
- tokens.nextToken();
- }
- // }
- tokens.nextToken();
- return {constructorInitializerStatements, constructorInsertPos};
- }
- /**
- * Determine if this is any token that can go before the name in a method/field.
- */
- function isAccessModifier(token) {
- return [
- tt._async,
- tt._get,
- tt._set,
- tt.plus,
- tt.minus,
- tt._readonly,
- tt._static,
- tt._public,
- tt._private,
- tt._protected,
- tt._override,
- tt._abstract,
- tt.star,
- tt._declare,
- tt.hash,
- ].includes(token.type);
- }
- /**
- * The next token or set of tokens is either an identifier or an expression in square brackets, for
- * a method or field name.
- */
- function skipFieldName(tokens) {
- if (tokens.matches1(tt.bracketL)) {
- const startToken = tokens.currentToken();
- const classContextId = startToken.contextId;
- if (classContextId == null) {
- throw new Error("Expected class context ID on computed name open bracket.");
- }
- while (!tokens.matchesContextIdAndLabel(tt.bracketR, classContextId)) {
- tokens.nextToken();
- }
- tokens.nextToken();
- } else {
- tokens.nextToken();
- }
- }
|