UNPKG

sucrase

Version:

Super-fast alternative to Babel for when you can target modern JS runtimes

125 lines (124 loc) 5.15 kB
/** * Implementation of babel-plugin-transform-react-display-name, which adds a * display name to usages of React.createClass and createReactClass. * * This implementation has the following limitations compared with the * - It does not handle `export default React.createClass`, using the filename, * since Sucrase currently does not know the name of the current file. */ export default class ReactDisplayNameTransformer { constructor(rootTransformer, tokens, identifierReplacer) { this.rootTransformer = rootTransformer; this.tokens = tokens; this.identifierReplacer = identifierReplacer; } preprocess() { } process() { const startIndex = this.tokens.currentIndex(); if (this.tokens.matchesName('createReactClass')) { const newName = this.identifierReplacer.getIdentifierReplacement('createReactClass'); if (newName) { this.tokens.replaceToken(`(0, ${newName})`); } else { this.tokens.copyToken(); } this.tryProcessCreateClassCall(startIndex); return true; } if (this.tokens.matches(['name', '.', 'name']) && this.tokens.matchesName('React') && this.tokens.matchesNameAtIndex(this.tokens.currentIndex() + 2, 'createClass')) { const newName = this.identifierReplacer.getIdentifierReplacement('React'); if (newName) { this.tokens.replaceToken(newName); this.tokens.copyToken(); this.tokens.copyToken(); } else { this.tokens.copyToken(); this.tokens.copyToken(); this.tokens.copyToken(); } this.tryProcessCreateClassCall(startIndex); return true; } return false; } /** * This is called with the token position at the open-paren. */ tryProcessCreateClassCall(startIndex) { const displayName = this.findDisplayName(startIndex); if (!displayName) { return; } if (this.classNeedsDisplayName()) { this.tokens.copyExpectedToken('('); this.tokens.copyExpectedToken('{'); this.tokens.appendCode(`displayName: '${displayName}',`); this.rootTransformer.processBalancedCode(); this.tokens.copyExpectedToken('}'); this.tokens.copyExpectedToken(')'); } } findDisplayName(startIndex) { if (this.tokens.matchesAtIndex(startIndex - 2, ['name', '=']) && !this.tokens.matchesAtIndex(startIndex - 3, ['.'])) { // This is an assignment (or declaration) with an identifier LHS, so use // that identifier name. return this.tokens.tokens[startIndex - 2].value; } if (this.tokens.matchesAtIndex(startIndex - 2, ['name', ':']) && this.tokens.tokens[startIndex - 2].contextName === 'object' && (this.tokens.tokens[startIndex - 3].type.label === ',' || this.tokens.tokens[startIndex - 3].type.label === '{')) { // This is an object literal value. return this.tokens.tokens[startIndex - 2].value; } return null; } /** * We only want to add a display name when this is a function call containing * one argument, which is an object literal without `displayName` as an * existing key. */ classNeedsDisplayName() { let index = this.tokens.currentIndex(); if (!this.tokens.matches(['(', '{'])) { return false; } // Currently augmentTokenContext starts the block at one after the {, so we // expect any displayName key to be in that context. We need to ignore other // other contexts to avoid matching nested displayName keys. const objectStartIndex = index + 2; for (; index < this.tokens.tokens.length; index++) { const token = this.tokens.tokens[index]; if (token.type.label === '}' && token.contextName === 'object' && token.contextStartIndex === objectStartIndex) { index++; break; } if (this.tokens.matchesNameAtIndex(index, 'displayName') && this.tokens.matchesAtIndex(index + 1, [':']) && (this.tokens.matchesAtIndex(index - 1, [',']) || this.tokens.matchesAtIndex(index - 1, ['{'])) && token.contextName === 'object' && token.contextStartIndex === objectStartIndex) { // We found a displayName key, so bail out. return false; } } // If we got this far, we know we have createClass with an object with no // display name, so we want to proceed as long as that was the only argument. return this.tokens.matchesAtIndex(index, [')']) || this.tokens.matchesAtIndex(index, [',', ')']); } getPrefixCode() { return ''; } getSuffixCode() { return ''; } }