getClassInfo.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352
  1. "use strict";Object.defineProperty(exports, "__esModule", {value: true});
  2. var _keywords = require('../parser/tokenizer/keywords');
  3. var _types = require('../parser/tokenizer/types');
  4. /**
  5. * Get information about the class fields for this class, given a token processor pointing to the
  6. * open-brace at the start of the class.
  7. */
  8. function getClassInfo(
  9. rootTransformer,
  10. tokens,
  11. nameManager,
  12. disableESTransforms,
  13. ) {
  14. const snapshot = tokens.snapshot();
  15. const headerInfo = processClassHeader(tokens);
  16. let constructorInitializerStatements = [];
  17. const instanceInitializerNames = [];
  18. const staticInitializerNames = [];
  19. let constructorInsertPos = null;
  20. const fields = [];
  21. const rangesToRemove = [];
  22. const classContextId = tokens.currentToken().contextId;
  23. if (classContextId == null) {
  24. throw new Error("Expected non-null class context ID on class open-brace.");
  25. }
  26. tokens.nextToken();
  27. while (!tokens.matchesContextIdAndLabel(_types.TokenType.braceR, classContextId)) {
  28. if (tokens.matchesContextual(_keywords.ContextualKeyword._constructor) && !tokens.currentToken().isType) {
  29. ({constructorInitializerStatements, constructorInsertPos} = processConstructor(tokens));
  30. } else if (tokens.matches1(_types.TokenType.semi)) {
  31. if (!disableESTransforms) {
  32. rangesToRemove.push({start: tokens.currentIndex(), end: tokens.currentIndex() + 1});
  33. }
  34. tokens.nextToken();
  35. } else if (tokens.currentToken().isType) {
  36. tokens.nextToken();
  37. } else {
  38. // Either a method or a field. Skip to the identifier part.
  39. const statementStartIndex = tokens.currentIndex();
  40. let isStatic = false;
  41. let isESPrivate = false;
  42. let isDeclareOrAbstract = false;
  43. while (isAccessModifier(tokens.currentToken())) {
  44. if (tokens.matches1(_types.TokenType._static)) {
  45. isStatic = true;
  46. }
  47. if (tokens.matches1(_types.TokenType.hash)) {
  48. isESPrivate = true;
  49. }
  50. if (tokens.matches1(_types.TokenType._declare) || tokens.matches1(_types.TokenType._abstract)) {
  51. isDeclareOrAbstract = true;
  52. }
  53. tokens.nextToken();
  54. }
  55. if (isStatic && tokens.matches1(_types.TokenType.braceL)) {
  56. // This is a static block, so don't process it in any special way.
  57. skipToNextClassElement(tokens, classContextId);
  58. continue;
  59. }
  60. if (isESPrivate) {
  61. // Sucrase doesn't attempt to transpile private fields; just leave them as-is.
  62. skipToNextClassElement(tokens, classContextId);
  63. continue;
  64. }
  65. if (
  66. tokens.matchesContextual(_keywords.ContextualKeyword._constructor) &&
  67. !tokens.currentToken().isType
  68. ) {
  69. ({constructorInitializerStatements, constructorInsertPos} = processConstructor(tokens));
  70. continue;
  71. }
  72. const nameStartIndex = tokens.currentIndex();
  73. skipFieldName(tokens);
  74. if (tokens.matches1(_types.TokenType.lessThan) || tokens.matches1(_types.TokenType.parenL)) {
  75. // This is a method, so nothing to process.
  76. skipToNextClassElement(tokens, classContextId);
  77. continue;
  78. }
  79. // There might be a type annotation that we need to skip.
  80. while (tokens.currentToken().isType) {
  81. tokens.nextToken();
  82. }
  83. if (tokens.matches1(_types.TokenType.eq)) {
  84. const equalsIndex = tokens.currentIndex();
  85. // This is an initializer, so we need to wrap in an initializer method.
  86. const valueEnd = tokens.currentToken().rhsEndIndex;
  87. if (valueEnd == null) {
  88. throw new Error("Expected rhsEndIndex on class field assignment.");
  89. }
  90. tokens.nextToken();
  91. while (tokens.currentIndex() < valueEnd) {
  92. rootTransformer.processToken();
  93. }
  94. let initializerName;
  95. if (isStatic) {
  96. initializerName = nameManager.claimFreeName("__initStatic");
  97. staticInitializerNames.push(initializerName);
  98. } else {
  99. initializerName = nameManager.claimFreeName("__init");
  100. instanceInitializerNames.push(initializerName);
  101. }
  102. // Fields start at the name, so `static x = 1;` has a field range of `x = 1;`.
  103. fields.push({
  104. initializerName,
  105. equalsIndex,
  106. start: nameStartIndex,
  107. end: tokens.currentIndex(),
  108. });
  109. } else if (!disableESTransforms || isDeclareOrAbstract) {
  110. // This is a regular field declaration, like `x;`. With the class transform enabled, we just
  111. // remove the line so that no output is produced. With the class transform disabled, we
  112. // usually want to preserve the declaration (but still strip types), but if the `declare`
  113. // or `abstract` keyword is specified, we should remove the line to avoid initializing the
  114. // value to undefined.
  115. rangesToRemove.push({start: statementStartIndex, end: tokens.currentIndex()});
  116. }
  117. }
  118. }
  119. tokens.restoreToSnapshot(snapshot);
  120. if (disableESTransforms) {
  121. // With ES transforms disabled, we don't want to transform regular class
  122. // field declarations, and we don't need to do any additional tricks to
  123. // reference the constructor for static init, but we still need to transform
  124. // TypeScript field initializers defined as constructor parameters and we
  125. // still need to remove `declare` fields. For now, we run the same code
  126. // path but omit any field information, as if the class had no field
  127. // declarations. In the future, when we fully drop the class fields
  128. // transform, we can simplify this code significantly.
  129. return {
  130. headerInfo,
  131. constructorInitializerStatements,
  132. instanceInitializerNames: [],
  133. staticInitializerNames: [],
  134. constructorInsertPos,
  135. fields: [],
  136. rangesToRemove,
  137. };
  138. } else {
  139. return {
  140. headerInfo,
  141. constructorInitializerStatements,
  142. instanceInitializerNames,
  143. staticInitializerNames,
  144. constructorInsertPos,
  145. fields,
  146. rangesToRemove,
  147. };
  148. }
  149. } exports.default = getClassInfo;
  150. /**
  151. * Move the token processor to the next method/field in the class.
  152. *
  153. * To do that, we seek forward to the next start of a class name (either an open
  154. * bracket or an identifier, or the closing curly brace), then seek backward to
  155. * include any access modifiers.
  156. */
  157. function skipToNextClassElement(tokens, classContextId) {
  158. tokens.nextToken();
  159. while (tokens.currentToken().contextId !== classContextId) {
  160. tokens.nextToken();
  161. }
  162. while (isAccessModifier(tokens.tokenAtRelativeIndex(-1))) {
  163. tokens.previousToken();
  164. }
  165. }
  166. function processClassHeader(tokens) {
  167. const classToken = tokens.currentToken();
  168. const contextId = classToken.contextId;
  169. if (contextId == null) {
  170. throw new Error("Expected context ID on class token.");
  171. }
  172. const isExpression = classToken.isExpression;
  173. if (isExpression == null) {
  174. throw new Error("Expected isExpression on class token.");
  175. }
  176. let className = null;
  177. let hasSuperclass = false;
  178. tokens.nextToken();
  179. if (tokens.matches1(_types.TokenType.name)) {
  180. className = tokens.identifierName();
  181. }
  182. while (!tokens.matchesContextIdAndLabel(_types.TokenType.braceL, contextId)) {
  183. // If this has a superclass, there will always be an `extends` token. If it doesn't have a
  184. // superclass, only type parameters and `implements` clauses can show up here, all of which
  185. // consist only of type tokens. A declaration like `class A<B extends C> {` should *not* count
  186. // as having a superclass.
  187. if (tokens.matches1(_types.TokenType._extends) && !tokens.currentToken().isType) {
  188. hasSuperclass = true;
  189. }
  190. tokens.nextToken();
  191. }
  192. return {isExpression, className, hasSuperclass};
  193. }
  194. /**
  195. * Extract useful information out of a constructor, starting at the "constructor" name.
  196. */
  197. function processConstructor(tokens)
  198. {
  199. const constructorInitializerStatements = [];
  200. tokens.nextToken();
  201. const constructorContextId = tokens.currentToken().contextId;
  202. if (constructorContextId == null) {
  203. throw new Error("Expected context ID on open-paren starting constructor params.");
  204. }
  205. // Advance through parameters looking for access modifiers.
  206. while (!tokens.matchesContextIdAndLabel(_types.TokenType.parenR, constructorContextId)) {
  207. if (tokens.currentToken().contextId === constructorContextId) {
  208. // Current token is an open paren or comma just before a param, so check
  209. // that param for access modifiers.
  210. tokens.nextToken();
  211. if (isAccessModifier(tokens.currentToken())) {
  212. tokens.nextToken();
  213. while (isAccessModifier(tokens.currentToken())) {
  214. tokens.nextToken();
  215. }
  216. const token = tokens.currentToken();
  217. if (token.type !== _types.TokenType.name) {
  218. throw new Error("Expected identifier after access modifiers in constructor arg.");
  219. }
  220. const name = tokens.identifierNameForToken(token);
  221. constructorInitializerStatements.push(`this.${name} = ${name}`);
  222. }
  223. } else {
  224. tokens.nextToken();
  225. }
  226. }
  227. // )
  228. tokens.nextToken();
  229. // Constructor type annotations are invalid, but skip them anyway since
  230. // they're easy to skip.
  231. while (tokens.currentToken().isType) {
  232. tokens.nextToken();
  233. }
  234. let constructorInsertPos = tokens.currentIndex();
  235. // Advance through body looking for a super call.
  236. let foundSuperCall = false;
  237. while (!tokens.matchesContextIdAndLabel(_types.TokenType.braceR, constructorContextId)) {
  238. if (!foundSuperCall && tokens.matches2(_types.TokenType._super, _types.TokenType.parenL)) {
  239. tokens.nextToken();
  240. const superCallContextId = tokens.currentToken().contextId;
  241. if (superCallContextId == null) {
  242. throw new Error("Expected a context ID on the super call");
  243. }
  244. while (!tokens.matchesContextIdAndLabel(_types.TokenType.parenR, superCallContextId)) {
  245. tokens.nextToken();
  246. }
  247. constructorInsertPos = tokens.currentIndex();
  248. foundSuperCall = true;
  249. }
  250. tokens.nextToken();
  251. }
  252. // }
  253. tokens.nextToken();
  254. return {constructorInitializerStatements, constructorInsertPos};
  255. }
  256. /**
  257. * Determine if this is any token that can go before the name in a method/field.
  258. */
  259. function isAccessModifier(token) {
  260. return [
  261. _types.TokenType._async,
  262. _types.TokenType._get,
  263. _types.TokenType._set,
  264. _types.TokenType.plus,
  265. _types.TokenType.minus,
  266. _types.TokenType._readonly,
  267. _types.TokenType._static,
  268. _types.TokenType._public,
  269. _types.TokenType._private,
  270. _types.TokenType._protected,
  271. _types.TokenType._override,
  272. _types.TokenType._abstract,
  273. _types.TokenType.star,
  274. _types.TokenType._declare,
  275. _types.TokenType.hash,
  276. ].includes(token.type);
  277. }
  278. /**
  279. * The next token or set of tokens is either an identifier or an expression in square brackets, for
  280. * a method or field name.
  281. */
  282. function skipFieldName(tokens) {
  283. if (tokens.matches1(_types.TokenType.bracketL)) {
  284. const startToken = tokens.currentToken();
  285. const classContextId = startToken.contextId;
  286. if (classContextId == null) {
  287. throw new Error("Expected class context ID on computed name open bracket.");
  288. }
  289. while (!tokens.matchesContextIdAndLabel(_types.TokenType.bracketR, classContextId)) {
  290. tokens.nextToken();
  291. }
  292. tokens.nextToken();
  293. } else {
  294. tokens.nextToken();
  295. }
  296. }