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.  * @flow
    
  8.  */
    
  9. 
    
  10. import {getHookNamesMappingFromAST} from './astUtils';
    
  11. import {encode, decode} from 'sourcemap-codec';
    
  12. 
    
  13. // Missing types in @babel/types
    
  14. type File = any;
    
  15. 
    
  16. export type HookMap = {
    
  17.   names: $ReadOnlyArray<string>,
    
  18.   mappings: HookMapMappings,
    
  19. };
    
  20. 
    
  21. export type EncodedHookMap = {
    
  22.   names: $ReadOnlyArray<string>,
    
  23.   mappings: string,
    
  24. };
    
  25. 
    
  26. // See generateHookMap below for more details on formatting
    
  27. export type HookMapEntry = [
    
  28.   number, // 1-indexed line number
    
  29.   number, // 0-indexed column number
    
  30.   number, // 0-indexed index into names array
    
  31.   number, // TODO: filler number to support reusing encoding from `sourcemap-codec` (see TODO below)
    
  32. ];
    
  33. export type HookMapLine = HookMapEntry[];
    
  34. export type HookMapMappings = HookMapLine[];
    
  35. 
    
  36. /**
    
  37.  * Given a parsed source code AST, returns a "Hook Map", which is a
    
  38.  * mapping which maps locations in the source code to their to their
    
  39.  * corresponding Hook name, if there is a relevant Hook name for that
    
  40.  * location (see getHookNamesMappingFromAST for details on the
    
  41.  * representation of the mapping).
    
  42.  *
    
  43.  * The format of the Hook Map follows a similar format as the `name`
    
  44.  * and `mappings` fields in the Source Map spec, where `names` is an
    
  45.  * array of strings, and `mappings` contains segments lines, columns,
    
  46.  * and indices into the `names` array.
    
  47.  *
    
  48.  * E.g.:
    
  49.  *   {
    
  50.  *     names: ["<no-hook>", "state"],
    
  51.  *     mappings: [
    
  52.  *       [ -> line 1
    
  53.  *         [1, 0, 0],  -> line, col, name index
    
  54.  *       ],
    
  55.  *       [ -> line 2
    
  56.  *         [2, 5, 1],  -> line, col, name index
    
  57.  *         [2, 15, 0],  -> line, col, name index
    
  58.  *       ],
    
  59.  *     ],
    
  60.  *   }
    
  61.  */
    
  62. export function generateHookMap(sourceAST: File): HookMap {
    
  63.   const hookNamesMapping = getHookNamesMappingFromAST(sourceAST);
    
  64.   const namesMap: Map<string, number> = new Map();
    
  65.   const names = [];
    
  66.   const mappings: Array<HookMapLine> = [];
    
  67. 
    
  68.   let currentLine: $FlowFixMe | null = null;
    
  69.   hookNamesMapping.forEach(({name, start}) => {
    
  70.     let nameIndex = namesMap.get(name);
    
  71.     if (nameIndex == null) {
    
  72.       names.push(name);
    
  73.       nameIndex = names.length - 1;
    
  74.       namesMap.set(name, nameIndex);
    
  75.     }
    
  76. 
    
  77.     // TODO: We add a -1 at the end of the entry so we can later
    
  78.     // encode/decode the mappings by reusing the encode/decode functions
    
  79.     // from the `sourcemap-codec` library. This library expects segments
    
  80.     // of specific sizes (i.e. of size 4) in order to encode them correctly.
    
  81.     // In the future, when we implement our own encoding, we will not
    
  82.     // need this restriction and can remove the -1 at the end.
    
  83.     const entry = [start.line, start.column, nameIndex, -1];
    
  84. 
    
  85.     if (currentLine !== start.line) {
    
  86.       currentLine = start.line;
    
  87.       mappings.push([entry]);
    
  88.     } else {
    
  89.       const current = mappings[mappings.length - 1];
    
  90.       current.push(entry);
    
  91.     }
    
  92.   });
    
  93. 
    
  94.   return {names, mappings};
    
  95. }
    
  96. 
    
  97. /**
    
  98.  * Returns encoded version of a Hook Map that is returned
    
  99.  * by generateHookMap.
    
  100.  *
    
  101.  * **NOTE:**
    
  102.  * TODO: To encode the `mappings` in the Hook Map, we
    
  103.  * reuse the encode function from the `sourcemap-codec`
    
  104.  * library, which means that we are restricted to only
    
  105.  * encoding segments of specific sizes.
    
  106.  * Inside generateHookMap we make sure to build segments
    
  107.  * of size 4.
    
  108.  * In the future, when we implement our own encoding, we will not
    
  109.  * need this restriction and can remove the -1 at the end.
    
  110.  */
    
  111. export function generateEncodedHookMap(sourceAST: File): EncodedHookMap {
    
  112.   const hookMap = generateHookMap(sourceAST);
    
  113.   const encoded = encode(hookMap.mappings);
    
  114.   return {
    
  115.     names: hookMap.names,
    
  116.     mappings: encoded,
    
  117.   };
    
  118. }
    
  119. 
    
  120. export function decodeHookMap(encodedHookMap: EncodedHookMap): HookMap {
    
  121.   return {
    
  122.     names: encodedHookMap.names,
    
  123.     mappings: decode(encodedHookMap.mappings),
    
  124.   };
    
  125. }