main
js 125 lines 3.86 KB
Raw
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 '@jridgewell/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 }