[d565449] | 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 | }
|
---|