summaryrefslogtreecommitdiff
path: root/node_modules/sucrase/dist/util/getClassInfo.js
diff options
context:
space:
mode:
authorPhilipp Tanlak <philipp.tanlak@gmail.com>2025-11-24 20:54:57 +0100
committerPhilipp Tanlak <philipp.tanlak@gmail.com>2025-11-24 20:57:48 +0100
commitb1e2c8fd5cb5dfa46bc440a12eafaf56cd844b1c (patch)
tree49d360fd6cbc6a2754efe93524ac47ff0fbe0f7d /node_modules/sucrase/dist/util/getClassInfo.js
Docs
Diffstat (limited to 'node_modules/sucrase/dist/util/getClassInfo.js')
-rw-r--r--node_modules/sucrase/dist/util/getClassInfo.js352
1 files changed, 352 insertions, 0 deletions
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<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();
+ }
+}