1. /**
    
  2.  * Copyright (c) Meta Platforms, Inc. and affiliates.
    
  3.  *
    
  4.  * This source code is licensed under the MIT license found in the
    
  5.  * LICENSE file in the root directory of this source tree.
    
  6.  */
    
  7. 'use strict';
    
  8. 
    
  9. const fs = require('fs');
    
  10. const {evalStringAndTemplateConcat} = require('../shared/evalToString');
    
  11. const invertObject = require('./invertObject');
    
  12. const helperModuleImports = require('@babel/helper-module-imports');
    
  13. 
    
  14. const errorMap = invertObject(
    
  15.   JSON.parse(fs.readFileSync(__dirname + '/codes.json', 'utf-8'))
    
  16. );
    
  17. 
    
  18. const SEEN_SYMBOL = Symbol('transform-error-messages.seen');
    
  19. 
    
  20. module.exports = function (babel) {
    
  21.   const t = babel.types;
    
  22. 
    
  23.   function ErrorCallExpression(path, file) {
    
  24.     // Turns this code:
    
  25.     //
    
  26.     // new Error(`A ${adj} message that contains ${noun}`);
    
  27.     //
    
  28.     // or this code (no constructor):
    
  29.     //
    
  30.     // Error(`A ${adj} message that contains ${noun}`);
    
  31.     //
    
  32.     // into this:
    
  33.     //
    
  34.     // Error(formatProdErrorMessage(ERR_CODE, adj, noun));
    
  35.     const node = path.node;
    
  36.     if (node[SEEN_SYMBOL]) {
    
  37.       return;
    
  38.     }
    
  39.     node[SEEN_SYMBOL] = true;
    
  40. 
    
  41.     const errorMsgNode = node.arguments[0];
    
  42.     if (errorMsgNode === undefined) {
    
  43.       return;
    
  44.     }
    
  45. 
    
  46.     const errorMsgExpressions = [];
    
  47.     const errorMsgLiteral = evalStringAndTemplateConcat(
    
  48.       errorMsgNode,
    
  49.       errorMsgExpressions
    
  50.     );
    
  51. 
    
  52.     let prodErrorId = errorMap[errorMsgLiteral];
    
  53.     if (prodErrorId === undefined) {
    
  54.       // There is no error code for this message. Add an inline comment
    
  55.       // that flags this as an unminified error. This allows the build
    
  56.       // to proceed, while also allowing a post-build linter to detect it.
    
  57.       //
    
  58.       // Outputs:
    
  59.       //   /* FIXME (minify-errors-in-prod): Unminified error message in production build! */
    
  60.       //   /* <expected-error-format>"A % message that contains %"</expected-error-format> */
    
  61.       //   if (!condition) {
    
  62.       //     throw Error(`A ${adj} message that contains ${noun}`);
    
  63.       //   }
    
  64. 
    
  65.       let leadingComments = [];
    
  66. 
    
  67.       const statementParent = path.getStatementParent();
    
  68.       let nextPath = path;
    
  69.       while (true) {
    
  70.         let nextNode = nextPath.node;
    
  71.         if (nextNode.leadingComments) {
    
  72.           leadingComments.push(...nextNode.leadingComments);
    
  73.         }
    
  74.         if (nextPath === statementParent) {
    
  75.           break;
    
  76.         }
    
  77.         nextPath = nextPath.parentPath;
    
  78.       }
    
  79. 
    
  80.       if (leadingComments !== undefined) {
    
  81.         for (let i = 0; i < leadingComments.length; i++) {
    
  82.           // TODO: Since this only detects one of many ways to disable a lint
    
  83.           // rule, we should instead search for a custom directive (like
    
  84.           // no-minify-errors) instead of ESLint. Will need to update our lint
    
  85.           // rule to recognize the same directive.
    
  86.           const commentText = leadingComments[i].value;
    
  87.           if (
    
  88.             commentText.includes(
    
  89.               'eslint-disable-next-line react-internal/prod-error-codes'
    
  90.             )
    
  91.           ) {
    
  92.             return;
    
  93.           }
    
  94.         }
    
  95.       }
    
  96. 
    
  97.       statementParent.addComment(
    
  98.         'leading',
    
  99.         `! <expected-error-format>"${errorMsgLiteral}"</expected-error-format>`
    
  100.       );
    
  101.       statementParent.addComment(
    
  102.         'leading',
    
  103.         '! FIXME (minify-errors-in-prod): Unminified error message in production build!'
    
  104.       );
    
  105.       return;
    
  106.     }
    
  107.     prodErrorId = parseInt(prodErrorId, 10);
    
  108. 
    
  109.     // Import formatProdErrorMessage
    
  110.     const formatProdErrorMessageIdentifier = helperModuleImports.addDefault(
    
  111.       path,
    
  112.       'shared/formatProdErrorMessage',
    
  113.       {nameHint: 'formatProdErrorMessage'}
    
  114.     );
    
  115. 
    
  116.     // Outputs:
    
  117.     //   formatProdErrorMessage(ERR_CODE, adj, noun);
    
  118.     const prodMessage = t.callExpression(formatProdErrorMessageIdentifier, [
    
  119.       t.numericLiteral(prodErrorId),
    
  120.       ...errorMsgExpressions,
    
  121.     ]);
    
  122. 
    
  123.     // Outputs:
    
  124.     // Error(formatProdErrorMessage(ERR_CODE, adj, noun));
    
  125.     const newErrorCall = t.callExpression(t.identifier('Error'), [prodMessage]);
    
  126.     newErrorCall[SEEN_SYMBOL] = true;
    
  127.     path.replaceWith(newErrorCall);
    
  128.   }
    
  129. 
    
  130.   return {
    
  131.     visitor: {
    
  132.       NewExpression(path, file) {
    
  133.         if (path.get('callee').isIdentifier({name: 'Error'})) {
    
  134.           ErrorCallExpression(path, file);
    
  135.         }
    
  136.       },
    
  137. 
    
  138.       CallExpression(path, file) {
    
  139.         if (path.get('callee').isIdentifier({name: 'Error'})) {
    
  140.           ErrorCallExpression(path, file);
    
  141.           return;
    
  142.         }
    
  143.       },
    
  144.     },
    
  145.   };
    
  146. };