From b1e2c8fd5cb5dfa46bc440a12eafaf56cd844b1c Mon Sep 17 00:00:00 2001 From: Philipp Tanlak Date: Mon, 24 Nov 2025 20:54:57 +0100 Subject: Docs --- node_modules/sucrase/dist/util/getClassInfo.js | 352 +++++++++++++++++++++++++ 1 file changed, 352 insertions(+) create mode 100644 node_modules/sucrase/dist/util/getClassInfo.js (limited to 'node_modules/sucrase/dist/util/getClassInfo.js') diff --git a/node_modules/sucrase/dist/util/getClassInfo.js b/node_modules/sucrase/dist/util/getClassInfo.js new file mode 100644 index 0000000..510a771 --- /dev/null +++ b/node_modules/sucrase/dist/util/getClassInfo.js @@ -0,0 +1,352 @@ +"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 {` 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(); + } +} -- cgit v1.2.3