1 | /* eslint-env mocha */
|
---|
2 | import assert from 'assert';
|
---|
3 | import entries from 'object.entries';
|
---|
4 | import fromEntries from 'object.fromentries';
|
---|
5 | import { getOpeningElement, setParserName, fallbackToBabylon } from '../helper';
|
---|
6 | import getProp from '../../src/getProp';
|
---|
7 |
|
---|
8 | const literal = {
|
---|
9 | source: '<div {...{ id: "foo" }} />',
|
---|
10 | target: '<div id="foo" />',
|
---|
11 | offset: { keyOffset: -6, valueOffset: -7 },
|
---|
12 | };
|
---|
13 |
|
---|
14 | const expression1 = {
|
---|
15 | source: '<div {...{ id }} />',
|
---|
16 | target: '<div id={id} />',
|
---|
17 | offset: { keyOffset: -6, valueOffset: -2 },
|
---|
18 | };
|
---|
19 |
|
---|
20 | const expression2 = {
|
---|
21 | source: '<div {...{ id: `foo${bar}baz` }} />', // eslint-disable-line no-template-curly-in-string
|
---|
22 | target: '<div id={`foo${bar}baz`} />', // eslint-disable-line no-template-curly-in-string
|
---|
23 | offset: { keyOffset: -6, valueOffset: -6 },
|
---|
24 | };
|
---|
25 |
|
---|
26 | describe('getProp', () => {
|
---|
27 | it('should create the correct AST for literal with flow parser', () => {
|
---|
28 | actualTest('flow', literal);
|
---|
29 | });
|
---|
30 | it('should create the correct AST for literal with babel parser', () => {
|
---|
31 | actualTest('babel', literal);
|
---|
32 | });
|
---|
33 | it('should create the correct AST for expression with flow parser (1)', () => {
|
---|
34 | actualTest('flow', expression1);
|
---|
35 | });
|
---|
36 | it('should create the correct AST for expression with babel parser (1)', () => {
|
---|
37 | actualTest('babel', expression1);
|
---|
38 | });
|
---|
39 | it('should create the correct AST for expression with flow parser (2)', () => {
|
---|
40 | actualTest('flow', expression2);
|
---|
41 | });
|
---|
42 | it('should create the correct AST for expression with babel parser (2)', () => {
|
---|
43 | actualTest('babel', expression2);
|
---|
44 | });
|
---|
45 | });
|
---|
46 |
|
---|
47 | function actualTest(parserName, test) {
|
---|
48 | setParserName(parserName);
|
---|
49 | const { source, target, offset } = test;
|
---|
50 | const sourceProps = stripConstructors(getOpeningElement(source).attributes);
|
---|
51 | const targetProps = stripConstructors(getOpeningElement(target).attributes);
|
---|
52 | const prop = 'id';
|
---|
53 | const sourceResult = getProp(sourceProps, prop);
|
---|
54 | const targetResult = getProp(targetProps, prop);
|
---|
55 |
|
---|
56 | if (fallbackToBabylon && parserName === 'babel' && test === literal) {
|
---|
57 | // Babylon (node < 6) adds an `extra: null` prop to a literal if it is parsed from a
|
---|
58 | // JSXAttribute, other literals don't get this.
|
---|
59 | sourceResult.value.extra = null;
|
---|
60 | }
|
---|
61 |
|
---|
62 | assert.deepStrictEqual(
|
---|
63 | adjustLocations(sourceResult, offset),
|
---|
64 | adjustRange(targetResult),
|
---|
65 | );
|
---|
66 | }
|
---|
67 |
|
---|
68 | function adjustRange({ name, value: { expression, ...value }, ...node }) {
|
---|
69 | return {
|
---|
70 | ...adjustNodeRange(node),
|
---|
71 | name: adjustNodeRange(name),
|
---|
72 | value: {
|
---|
73 | ...adjustNodeRange(value),
|
---|
74 | ...(expression ? { expression: adjustNodeRangeRecursively(expression) } : {}),
|
---|
75 | },
|
---|
76 | };
|
---|
77 | }
|
---|
78 |
|
---|
79 | function adjustNodeRange(node) {
|
---|
80 | if (!node.loc) {
|
---|
81 | return node;
|
---|
82 | }
|
---|
83 |
|
---|
84 | const [start, end] = node.range || [node.start, node.end];
|
---|
85 | return {
|
---|
86 | ...node,
|
---|
87 | end: undefined,
|
---|
88 | range: [start, end],
|
---|
89 | start: undefined,
|
---|
90 | };
|
---|
91 | }
|
---|
92 |
|
---|
93 | function adjustNodeRangeRecursively(node) {
|
---|
94 | if (Array.isArray(node)) {
|
---|
95 | return node.map(adjustNodeRangeRecursively);
|
---|
96 | }
|
---|
97 |
|
---|
98 | if (node && typeof node === 'object') {
|
---|
99 | return adjustNodeRange(mapValues(node, adjustNodeRangeRecursively));
|
---|
100 | }
|
---|
101 |
|
---|
102 | return node;
|
---|
103 | }
|
---|
104 |
|
---|
105 | function stripConstructors(value) {
|
---|
106 | return JSON.parse(JSON.stringify(value));
|
---|
107 | }
|
---|
108 |
|
---|
109 | function adjustLocations(node, { keyOffset, valueOffset }) {
|
---|
110 | const hasExpression = !!node.value.expression;
|
---|
111 | return {
|
---|
112 | ...adjustNodeLocations(node, {
|
---|
113 | startOffset: keyOffset,
|
---|
114 | endOffset: valueOffset + (hasExpression ? 1 : 0),
|
---|
115 | }),
|
---|
116 | name: adjustNodeLocations(node.name, { startOffset: keyOffset, endOffset: keyOffset }),
|
---|
117 | value: {
|
---|
118 | ...adjustNodeLocations(node.value, {
|
---|
119 | startOffset: valueOffset - (hasExpression ? 1 : 0),
|
---|
120 | endOffset: valueOffset + (hasExpression ? 1 : 0),
|
---|
121 | }),
|
---|
122 | ...(hasExpression
|
---|
123 | ? {
|
---|
124 | expression: adjustLocationsRecursively(
|
---|
125 | node.value.expression,
|
---|
126 | { startOffset: valueOffset, endOffset: valueOffset },
|
---|
127 | ),
|
---|
128 | }
|
---|
129 | : {}
|
---|
130 | ),
|
---|
131 | },
|
---|
132 | };
|
---|
133 | }
|
---|
134 |
|
---|
135 | function adjustNodeLocations(node, { startOffset, endOffset }) {
|
---|
136 | if (!node.loc) {
|
---|
137 | return node;
|
---|
138 | }
|
---|
139 |
|
---|
140 | const [start, end] = node.range || [];
|
---|
141 | return {
|
---|
142 | ...node,
|
---|
143 | end: undefined,
|
---|
144 | loc: {
|
---|
145 | ...node.loc,
|
---|
146 | start: {
|
---|
147 | ...node.loc.start,
|
---|
148 | column: node.loc.start.column + startOffset,
|
---|
149 | },
|
---|
150 | end: {
|
---|
151 | ...node.loc.end,
|
---|
152 | column: node.loc.end.column + endOffset,
|
---|
153 | },
|
---|
154 | },
|
---|
155 | range: [start + startOffset, end + endOffset],
|
---|
156 | start: undefined,
|
---|
157 | };
|
---|
158 | }
|
---|
159 |
|
---|
160 | function adjustLocationsRecursively(node, { startOffset, endOffset }) {
|
---|
161 | if (Array.isArray(node)) {
|
---|
162 | return node.map((x) => adjustLocationsRecursively(x, { startOffset, endOffset }));
|
---|
163 | }
|
---|
164 | if (node && typeof node === 'object') {
|
---|
165 | return adjustNodeLocations(
|
---|
166 | mapValues(node, (x) => adjustLocationsRecursively(x, { startOffset, endOffset })),
|
---|
167 | { startOffset, endOffset },
|
---|
168 | );
|
---|
169 | }
|
---|
170 |
|
---|
171 | return node;
|
---|
172 | }
|
---|
173 |
|
---|
174 | function mapValues(o, f) {
|
---|
175 | return fromEntries(entries(o).map(([k, v]) => [k, f(v)]));
|
---|
176 | }
|
---|