353 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			JavaScript
		
	
	
		
			Executable File
		
	
	
	
	
			
		
		
	
	
			353 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			JavaScript
		
	
	
		
			Executable File
		
	
	
	
	
| "use strict";Object.defineProperty(exports, "__esModule", {value: true});
 | |
| 
 | |
| var _keywords = require('../parser/tokenizer/keywords');
 | |
| var _types = require('../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.
 | |
|  */
 | |
|  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(_types.TokenType.braceR, classContextId)) {
 | |
|     if (tokens.matchesContextual(_keywords.ContextualKeyword._constructor) && !tokens.currentToken().isType) {
 | |
|       ({constructorInitializerStatements, constructorInsertPos} = processConstructor(tokens));
 | |
|     } else if (tokens.matches1(_types.TokenType.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(_types.TokenType._static)) {
 | |
|           isStatic = true;
 | |
|         }
 | |
|         if (tokens.matches1(_types.TokenType.hash)) {
 | |
|           isESPrivate = true;
 | |
|         }
 | |
|         if (tokens.matches1(_types.TokenType._declare) || tokens.matches1(_types.TokenType._abstract)) {
 | |
|           isDeclareOrAbstract = true;
 | |
|         }
 | |
|         tokens.nextToken();
 | |
|       }
 | |
|       if (isStatic && tokens.matches1(_types.TokenType.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(_keywords.ContextualKeyword._constructor) &&
 | |
|         !tokens.currentToken().isType
 | |
|       ) {
 | |
|         ({constructorInitializerStatements, constructorInsertPos} = processConstructor(tokens));
 | |
|         continue;
 | |
|       }
 | |
| 
 | |
|       const nameStartIndex = tokens.currentIndex();
 | |
|       skipFieldName(tokens);
 | |
|       if (tokens.matches1(_types.TokenType.lessThan) || tokens.matches1(_types.TokenType.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(_types.TokenType.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,
 | |
|     };
 | |
|   }
 | |
| } exports.default = getClassInfo;
 | |
| 
 | |
| /**
 | |
|  * 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(_types.TokenType.name)) {
 | |
|     className = tokens.identifierName();
 | |
|   }
 | |
|   while (!tokens.matchesContextIdAndLabel(_types.TokenType.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(_types.TokenType._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(_types.TokenType.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 !== _types.TokenType.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(_types.TokenType.braceR, constructorContextId)) {
 | |
|     if (!foundSuperCall && tokens.matches2(_types.TokenType._super, _types.TokenType.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(_types.TokenType.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 [
 | |
|     _types.TokenType._async,
 | |
|     _types.TokenType._get,
 | |
|     _types.TokenType._set,
 | |
|     _types.TokenType.plus,
 | |
|     _types.TokenType.minus,
 | |
|     _types.TokenType._readonly,
 | |
|     _types.TokenType._static,
 | |
|     _types.TokenType._public,
 | |
|     _types.TokenType._private,
 | |
|     _types.TokenType._protected,
 | |
|     _types.TokenType._override,
 | |
|     _types.TokenType._abstract,
 | |
|     _types.TokenType.star,
 | |
|     _types.TokenType._declare,
 | |
|     _types.TokenType.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(_types.TokenType.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(_types.TokenType.bracketR, classContextId)) {
 | |
|       tokens.nextToken();
 | |
|     }
 | |
|     tokens.nextToken();
 | |
|   } else {
 | |
|     tokens.nextToken();
 | |
|   }
 | |
| }
 |