@tsrx/react 0.0.1 → 0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/index.js +63 -6
- package/src/transform.js +83 -4
- package/types/index.d.ts +7 -3
package/package.json
CHANGED
package/src/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
/** @import * as AST from 'estree' */
|
|
2
|
+
/** @import { CodeMapping, ParseOptions } from '@tsrx/core/types' */
|
|
2
3
|
|
|
3
4
|
import { createVolarMappingsResult, parseModule } from '@tsrx/core';
|
|
4
5
|
import { transform } from './transform.js';
|
|
@@ -7,10 +8,11 @@ import { transform } from './transform.js';
|
|
|
7
8
|
* Parse tsrx-react source code to an ESTree AST.
|
|
8
9
|
* @param {string} source
|
|
9
10
|
* @param {string} [filename]
|
|
11
|
+
* @param {ParseOptions} [options]
|
|
10
12
|
* @returns {AST.Program}
|
|
11
13
|
*/
|
|
12
|
-
export function parse(source, filename) {
|
|
13
|
-
return parseModule(source, filename);
|
|
14
|
+
export function parse(source, filename, options) {
|
|
15
|
+
return parseModule(source, filename, options);
|
|
14
16
|
}
|
|
15
17
|
|
|
16
18
|
/**
|
|
@@ -32,13 +34,13 @@ export function compile(source, filename) {
|
|
|
32
34
|
*
|
|
33
35
|
* @param {string} source
|
|
34
36
|
* @param {string} [filename]
|
|
37
|
+
* @param {ParseOptions} [options]
|
|
35
38
|
* @returns {import('@tsrx/core/types').VolarMappingsResult}
|
|
36
39
|
*/
|
|
37
|
-
export function compile_to_volar_mappings(source, filename) {
|
|
38
|
-
const ast = parseModule(source, filename);
|
|
40
|
+
export function compile_to_volar_mappings(source, filename, options) {
|
|
41
|
+
const ast = parseModule(source, filename, options);
|
|
39
42
|
const transformed = transform(ast, source, filename);
|
|
40
|
-
|
|
41
|
-
return createVolarMappingsResult({
|
|
43
|
+
const result = createVolarMappingsResult({
|
|
42
44
|
ast: transformed.ast,
|
|
43
45
|
ast_from_source: ast,
|
|
44
46
|
source,
|
|
@@ -46,4 +48,59 @@ export function compile_to_volar_mappings(source, filename) {
|
|
|
46
48
|
source_map: transformed.map,
|
|
47
49
|
errors: [],
|
|
48
50
|
});
|
|
51
|
+
|
|
52
|
+
return {
|
|
53
|
+
...result,
|
|
54
|
+
mappings: dedupe_mappings(result.mappings),
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Remove byte-for-byte duplicate mappings. React helper extraction can emit
|
|
60
|
+
* identical mapping entries for the same source and generated span, which
|
|
61
|
+
* causes Volar to merge duplicate hover/navigation results.
|
|
62
|
+
*
|
|
63
|
+
* @param {CodeMapping[]} mappings
|
|
64
|
+
* @returns {CodeMapping[]}
|
|
65
|
+
*/
|
|
66
|
+
function dedupe_mappings(mappings) {
|
|
67
|
+
const deduped = [];
|
|
68
|
+
const seen = new Set();
|
|
69
|
+
|
|
70
|
+
for (const mapping of mappings) {
|
|
71
|
+
const key = JSON.stringify(serialize_mapping_value(mapping));
|
|
72
|
+
|
|
73
|
+
if (seen.has(key)) {
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
seen.add(key);
|
|
78
|
+
deduped.push(mapping);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return deduped;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* @param {unknown} value
|
|
86
|
+
* @returns {unknown}
|
|
87
|
+
*/
|
|
88
|
+
function serialize_mapping_value(value) {
|
|
89
|
+
if (typeof value === 'function') {
|
|
90
|
+
return value.toString();
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (Array.isArray(value)) {
|
|
94
|
+
return value.map(serialize_mapping_value);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (value && typeof value === 'object') {
|
|
98
|
+
return Object.fromEntries(
|
|
99
|
+
Object.entries(value)
|
|
100
|
+
.sort(([left], [right]) => left.localeCompare(right))
|
|
101
|
+
.map(([key, nested_value]) => [key, serialize_mapping_value(nested_value)]),
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
return value;
|
|
49
106
|
}
|
package/src/transform.js
CHANGED
|
@@ -77,7 +77,9 @@ export function transform(ast, source, filename) {
|
|
|
77
77
|
|
|
78
78
|
Text(node, { next }) {
|
|
79
79
|
const inner = /** @type {any} */ (next() ?? node);
|
|
80
|
-
return /** @type {any} */ (
|
|
80
|
+
return /** @type {any} */ (
|
|
81
|
+
to_jsx_expression_container(to_text_expression(inner.expression, inner), inner)
|
|
82
|
+
);
|
|
81
83
|
},
|
|
82
84
|
|
|
83
85
|
TSRXExpression(node, { next }) {
|
|
@@ -133,12 +135,18 @@ function component_to_function_declaration(component, transform_context) {
|
|
|
133
135
|
metadata: {
|
|
134
136
|
path: [],
|
|
135
137
|
is_component: true,
|
|
136
|
-
is_method: true,
|
|
137
138
|
},
|
|
138
139
|
});
|
|
139
140
|
|
|
140
141
|
fn.metadata.generated_helpers = helper_state.helpers;
|
|
141
142
|
|
|
143
|
+
if (fn.id) {
|
|
144
|
+
fn.id.metadata = /** @type {AST.Identifier['metadata']} */ ({
|
|
145
|
+
...fn.id.metadata,
|
|
146
|
+
is_component: true,
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
|
|
142
150
|
setLocation(fn, /** @type {any} */ (component), true);
|
|
143
151
|
return fn;
|
|
144
152
|
}
|
|
@@ -460,10 +468,16 @@ function create_helper_function_declaration(
|
|
|
460
468
|
metadata: {
|
|
461
469
|
path: [],
|
|
462
470
|
is_component: true,
|
|
463
|
-
is_method: true,
|
|
464
471
|
},
|
|
465
472
|
});
|
|
466
473
|
|
|
474
|
+
if (fn.id) {
|
|
475
|
+
fn.id.metadata = /** @type {AST.Identifier['metadata']} */ ({
|
|
476
|
+
...fn.id.metadata,
|
|
477
|
+
is_component: true,
|
|
478
|
+
});
|
|
479
|
+
}
|
|
480
|
+
|
|
467
481
|
return set_loc(fn, source_node);
|
|
468
482
|
}
|
|
469
483
|
|
|
@@ -799,6 +813,22 @@ function is_style_element(node) {
|
|
|
799
813
|
);
|
|
800
814
|
}
|
|
801
815
|
|
|
816
|
+
/**
|
|
817
|
+
* @param {any} node
|
|
818
|
+
* @returns {boolean}
|
|
819
|
+
*/
|
|
820
|
+
function is_composite_element(node) {
|
|
821
|
+
if (!node || node.type !== 'Element' || !node.id) {
|
|
822
|
+
return false;
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
if (node.id.type === 'Identifier') {
|
|
826
|
+
return /^[A-Z]/.test(node.id.name);
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
return node.id.type === 'MemberExpression';
|
|
830
|
+
}
|
|
831
|
+
|
|
802
832
|
/**
|
|
803
833
|
* Recursively walk Element nodes within a component body and add the hash
|
|
804
834
|
* class name so scope-qualified selectors (e.g. `.foo.hash`) match.
|
|
@@ -819,7 +849,7 @@ function annotate_with_hash(node, hash) {
|
|
|
819
849
|
}
|
|
820
850
|
|
|
821
851
|
if (node.type === 'Element') {
|
|
822
|
-
if (!is_style_element(node)) {
|
|
852
|
+
if (!is_style_element(node) && !is_composite_element(node)) {
|
|
823
853
|
add_hash_class(node, hash);
|
|
824
854
|
}
|
|
825
855
|
if (Array.isArray(node.children)) {
|
|
@@ -1274,6 +1304,7 @@ function to_jsx_child(node, transform_context) {
|
|
|
1274
1304
|
case 'Element':
|
|
1275
1305
|
return to_jsx_element(node, transform_context);
|
|
1276
1306
|
case 'Text':
|
|
1307
|
+
return to_jsx_expression_container(to_text_expression(node.expression, node), node);
|
|
1277
1308
|
case 'TSRXExpression':
|
|
1278
1309
|
return to_jsx_expression_container(node.expression, node);
|
|
1279
1310
|
case 'IfStatement':
|
|
@@ -1826,6 +1857,54 @@ function to_jsx_expression_container(expression, source_node = expression) {
|
|
|
1826
1857
|
});
|
|
1827
1858
|
}
|
|
1828
1859
|
|
|
1860
|
+
/**
|
|
1861
|
+
* Ripple's `{text expr}` always renders text, even for booleans and objects.
|
|
1862
|
+
* React's normal `{expr}` child semantics would drop booleans and render
|
|
1863
|
+
* elements as elements, so we coerce to a text value explicitly.
|
|
1864
|
+
* @param {AST.Expression} expression
|
|
1865
|
+
* @param {any} [source_node]
|
|
1866
|
+
* @returns {AST.Expression}
|
|
1867
|
+
*/
|
|
1868
|
+
function to_text_expression(expression, source_node = expression) {
|
|
1869
|
+
return set_loc(
|
|
1870
|
+
/** @type {AST.Expression} */ ({
|
|
1871
|
+
type: 'ConditionalExpression',
|
|
1872
|
+
test: {
|
|
1873
|
+
type: 'BinaryExpression',
|
|
1874
|
+
operator: '==',
|
|
1875
|
+
left: clone_expression_node(expression),
|
|
1876
|
+
right: {
|
|
1877
|
+
type: 'Literal',
|
|
1878
|
+
value: null,
|
|
1879
|
+
raw: 'null',
|
|
1880
|
+
metadata: { path: [] },
|
|
1881
|
+
},
|
|
1882
|
+
metadata: { path: [] },
|
|
1883
|
+
},
|
|
1884
|
+
consequent: {
|
|
1885
|
+
type: 'Literal',
|
|
1886
|
+
value: '',
|
|
1887
|
+
raw: "''",
|
|
1888
|
+
metadata: { path: [] },
|
|
1889
|
+
},
|
|
1890
|
+
alternate: {
|
|
1891
|
+
type: 'BinaryExpression',
|
|
1892
|
+
operator: '+',
|
|
1893
|
+
left: clone_expression_node(expression),
|
|
1894
|
+
right: {
|
|
1895
|
+
type: 'Literal',
|
|
1896
|
+
value: '',
|
|
1897
|
+
raw: "''",
|
|
1898
|
+
metadata: { path: [] },
|
|
1899
|
+
},
|
|
1900
|
+
metadata: { path: [] },
|
|
1901
|
+
},
|
|
1902
|
+
metadata: { path: [] },
|
|
1903
|
+
}),
|
|
1904
|
+
source_node,
|
|
1905
|
+
);
|
|
1906
|
+
}
|
|
1907
|
+
|
|
1829
1908
|
/**
|
|
1830
1909
|
* @param {any} attr
|
|
1831
1910
|
* @returns {ESTreeJSX.JSXAttribute | ESTreeJSX.JSXSpreadAttribute}
|
package/types/index.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { Program } from 'estree';
|
|
2
|
-
import type { VolarMappingsResult } from '@tsrx/core/types';
|
|
2
|
+
import type { ParseOptions, VolarMappingsResult } from '@tsrx/core/types';
|
|
3
3
|
|
|
4
|
-
export function parse(source: string, filename?: string): Program;
|
|
4
|
+
export function parse(source: string, filename?: string, options?: ParseOptions): Program;
|
|
5
5
|
|
|
6
6
|
export function compile(
|
|
7
7
|
source: string,
|
|
@@ -12,4 +12,8 @@ export function compile(
|
|
|
12
12
|
css: { code: string; hash: string } | null;
|
|
13
13
|
};
|
|
14
14
|
|
|
15
|
-
export function compile_to_volar_mappings(
|
|
15
|
+
export function compile_to_volar_mappings(
|
|
16
|
+
source: string,
|
|
17
|
+
filename?: string,
|
|
18
|
+
options?: ParseOptions,
|
|
19
|
+
): VolarMappingsResult;
|