ripple 0.2.132 → 0.2.134
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 +2 -2
- package/src/compiler/phases/1-parse/index.js +114 -23
- package/src/compiler/phases/2-analyze/index.js +35 -1
- package/src/compiler/phases/3-transform/client/index.js +13 -2
- package/src/runtime/index-client.js +3 -3
- package/src/runtime/internal/client/blocks.js +10 -3
- package/src/runtime/internal/client/compat.js +37 -5
- package/src/runtime/internal/client/types.d.ts +10 -0
- package/tests/client/compiler/compiler.tracked-access.test.ripple +108 -0
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"description": "Ripple is an elegant TypeScript UI framework",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"author": "Dominic Gannaway",
|
|
6
|
-
"version": "0.2.
|
|
6
|
+
"version": "0.2.134",
|
|
7
7
|
"type": "module",
|
|
8
8
|
"module": "src/runtime/index-client.js",
|
|
9
9
|
"main": "src/runtime/index-client.js",
|
|
@@ -81,6 +81,6 @@
|
|
|
81
81
|
"typescript": "^5.9.2"
|
|
82
82
|
},
|
|
83
83
|
"peerDependencies": {
|
|
84
|
-
"ripple": "0.2.
|
|
84
|
+
"ripple": "0.2.134"
|
|
85
85
|
}
|
|
86
86
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
// @ts-nocheck
|
|
1
2
|
/** @import { Program } from 'estree' */
|
|
2
3
|
/** @import {
|
|
3
4
|
* CommentWithLocation,
|
|
@@ -28,6 +29,14 @@ function convert_from_jsx(node) {
|
|
|
28
29
|
return node;
|
|
29
30
|
}
|
|
30
31
|
|
|
32
|
+
function isWhitespaceTextNode(node) {
|
|
33
|
+
if (!node || node.type !== 'Text') {
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
36
|
+
const value = typeof node.value === 'string' ? node.value : typeof node.raw === 'string' ? node.raw : '';
|
|
37
|
+
return /^\s*$/.test(value);
|
|
38
|
+
}
|
|
39
|
+
|
|
31
40
|
/**
|
|
32
41
|
* Acorn parser plugin for Ripple syntax extensions
|
|
33
42
|
* @param {RipplePluginConfig} [config] - Plugin configuration
|
|
@@ -42,6 +51,37 @@ function RipplePlugin(config) {
|
|
|
42
51
|
class RippleParser extends Parser {
|
|
43
52
|
/** @type {any[]} */
|
|
44
53
|
#path = [];
|
|
54
|
+
#commentContextId = 0;
|
|
55
|
+
|
|
56
|
+
#createCommentMetadata() {
|
|
57
|
+
if (this.#path.length === 0) {
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const container = this.#path[this.#path.length - 1];
|
|
62
|
+
if (!container || container.type !== 'Element') {
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const children = Array.isArray(container.children) ? container.children : [];
|
|
67
|
+
const hasMeaningfulChildren = children.some((child) => child && !isWhitespaceTextNode(child));
|
|
68
|
+
|
|
69
|
+
if (hasMeaningfulChildren) {
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
container.metadata ??= {};
|
|
74
|
+
if (container.metadata.commentContainerId === undefined) {
|
|
75
|
+
container.metadata.commentContainerId = ++this.#commentContextId;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return {
|
|
79
|
+
containerId: container.metadata.commentContainerId,
|
|
80
|
+
containerType: container.type,
|
|
81
|
+
childIndex: children.length,
|
|
82
|
+
beforeMeaningfulChild: !hasMeaningfulChildren,
|
|
83
|
+
};
|
|
84
|
+
}
|
|
45
85
|
|
|
46
86
|
/**
|
|
47
87
|
* Helper method to get the element name from a JSX identifier or member expression
|
|
@@ -1083,6 +1123,7 @@ function RipplePlugin(config) {
|
|
|
1083
1123
|
|
|
1084
1124
|
// Call onComment if it exists
|
|
1085
1125
|
if (this.options.onComment) {
|
|
1126
|
+
const metadata = this.#createCommentMetadata();
|
|
1086
1127
|
this.options.onComment(
|
|
1087
1128
|
false,
|
|
1088
1129
|
commentText,
|
|
@@ -1090,6 +1131,7 @@ function RipplePlugin(config) {
|
|
|
1090
1131
|
commentEnd,
|
|
1091
1132
|
startLoc,
|
|
1092
1133
|
endLoc,
|
|
1134
|
+
metadata,
|
|
1093
1135
|
);
|
|
1094
1136
|
}
|
|
1095
1137
|
|
|
@@ -1120,6 +1162,7 @@ function RipplePlugin(config) {
|
|
|
1120
1162
|
|
|
1121
1163
|
// Call onComment if it exists
|
|
1122
1164
|
if (this.options.onComment) {
|
|
1165
|
+
const metadata = this.#createCommentMetadata();
|
|
1123
1166
|
this.options.onComment(
|
|
1124
1167
|
true,
|
|
1125
1168
|
commentText,
|
|
@@ -1127,6 +1170,7 @@ function RipplePlugin(config) {
|
|
|
1127
1170
|
commentEnd,
|
|
1128
1171
|
startLoc,
|
|
1129
1172
|
endLoc,
|
|
1173
|
+
metadata,
|
|
1130
1174
|
);
|
|
1131
1175
|
}
|
|
1132
1176
|
|
|
@@ -1246,7 +1290,8 @@ function RipplePlugin(config) {
|
|
|
1246
1290
|
}
|
|
1247
1291
|
|
|
1248
1292
|
element.attributes = open.attributes;
|
|
1249
|
-
element.metadata
|
|
1293
|
+
element.metadata ??= {};
|
|
1294
|
+
element.metadata.commentContainerId = ++this.#commentContextId;
|
|
1250
1295
|
|
|
1251
1296
|
if (element.selfClosing) {
|
|
1252
1297
|
this.#path.pop();
|
|
@@ -1643,7 +1688,7 @@ function RipplePlugin(config) {
|
|
|
1643
1688
|
*/
|
|
1644
1689
|
function get_comment_handlers(source, comments, index = 0) {
|
|
1645
1690
|
return {
|
|
1646
|
-
onComment: (block, value, start, end, start_loc, end_loc) => {
|
|
1691
|
+
onComment: (block, value, start, end, start_loc, end_loc, metadata) => {
|
|
1647
1692
|
if (block && /\n/.test(value)) {
|
|
1648
1693
|
let a = start;
|
|
1649
1694
|
while (a > 0 && source[a - 1] !== '\n') a -= 1;
|
|
@@ -1664,6 +1709,7 @@ function get_comment_handlers(source, comments, index = 0) {
|
|
|
1664
1709
|
start: /** @type {import('acorn').Position} */ (start_loc),
|
|
1665
1710
|
end: /** @type {import('acorn').Position} */ (end_loc),
|
|
1666
1711
|
},
|
|
1712
|
+
context: metadata ?? null,
|
|
1667
1713
|
});
|
|
1668
1714
|
},
|
|
1669
1715
|
add_comments: (ast) => {
|
|
@@ -1671,40 +1717,85 @@ function get_comment_handlers(source, comments, index = 0) {
|
|
|
1671
1717
|
|
|
1672
1718
|
comments = comments
|
|
1673
1719
|
.filter((comment) => comment.start >= index)
|
|
1674
|
-
.map(({ type, value, start, end, loc }) => ({ type, value, start, end, loc }));
|
|
1720
|
+
.map(({ type, value, start, end, loc, context }) => ({ type, value, start, end, loc, context }));
|
|
1675
1721
|
|
|
1676
1722
|
walk(ast, null, {
|
|
1677
1723
|
_(node, { next, path }) {
|
|
1678
1724
|
let comment;
|
|
1679
1725
|
|
|
1726
|
+
const metadata = /** @type {{ commentContainerId?: number, elementLeadingComments?: CommentWithLocation[] }} */ (node?.metadata);
|
|
1727
|
+
|
|
1728
|
+
if (metadata && metadata.commentContainerId !== undefined) {
|
|
1729
|
+
while (
|
|
1730
|
+
comments[0] &&
|
|
1731
|
+
comments[0].context &&
|
|
1732
|
+
comments[0].context.containerId === metadata.commentContainerId &&
|
|
1733
|
+
comments[0].context.beforeMeaningfulChild
|
|
1734
|
+
) {
|
|
1735
|
+
const elementComment = /** @type {CommentWithLocation & { context?: any }} */ (comments.shift());
|
|
1736
|
+
(metadata.elementLeadingComments ||= []).push(elementComment);
|
|
1737
|
+
}
|
|
1738
|
+
}
|
|
1739
|
+
|
|
1680
1740
|
while (comments[0] && comments[0].start < node.start) {
|
|
1681
1741
|
comment = /** @type {CommentWithLocation} */ (comments.shift());
|
|
1682
|
-
|
|
1683
|
-
|
|
1742
|
+
if (comment.loc) {
|
|
1743
|
+
const ancestorElements = path
|
|
1744
|
+
.filter((ancestor) => ancestor && ancestor.type === 'Element' && ancestor.loc)
|
|
1745
|
+
.sort((a, b) => a.loc.start.line - b.loc.start.line);
|
|
1684
1746
|
|
|
1685
|
-
|
|
1747
|
+
const targetAncestor = ancestorElements.find((ancestor) => comment.loc.start.line < ancestor.loc.start.line);
|
|
1686
1748
|
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
(node.innerComments ||= []).push(comment);
|
|
1692
|
-
return;
|
|
1749
|
+
if (targetAncestor) {
|
|
1750
|
+
targetAncestor.metadata ??= {};
|
|
1751
|
+
(targetAncestor.metadata.elementLeadingComments ||= []).push(comment);
|
|
1752
|
+
continue;
|
|
1693
1753
|
}
|
|
1694
1754
|
}
|
|
1695
|
-
|
|
1755
|
+
(node.leadingComments ||= []).push(comment);
|
|
1756
|
+
}
|
|
1757
|
+
|
|
1758
|
+
next();
|
|
1696
1759
|
|
|
1697
|
-
|
|
1760
|
+
if (comments[0]) {
|
|
1761
|
+
if (node.type === 'BlockStatement' && node.body.length === 0) {
|
|
1762
|
+
// Collect all comments that fall within this empty block
|
|
1763
|
+
while (comments[0] && comments[0].start < node.end && comments[0].end < node.end) {
|
|
1764
|
+
comment = /** @type {CommentWithLocation} */ (comments.shift());
|
|
1765
|
+
(node.innerComments ||= []).push(comment);
|
|
1766
|
+
}
|
|
1767
|
+
if (node.innerComments && node.innerComments.length > 0) {
|
|
1768
|
+
return;
|
|
1769
|
+
}
|
|
1770
|
+
}
|
|
1771
|
+
// Handle empty Element nodes the same way as empty BlockStatements
|
|
1772
|
+
if (node.type === 'Element' && (!node.children || node.children.length === 0)) {
|
|
1773
|
+
if (comments[0].start < node.end && comments[0].end < node.end) {
|
|
1774
|
+
comment = /** @type {CommentWithLocation} */ (comments.shift());
|
|
1775
|
+
(node.innerComments ||= []).push(comment);
|
|
1776
|
+
return;
|
|
1777
|
+
}
|
|
1778
|
+
}
|
|
1779
|
+
const parent = /** @type {any} */ (path.at(-1)); if (parent === undefined || node.end !== parent.end) {
|
|
1698
1780
|
const slice = source.slice(node.end, comments[0].start);
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1781
|
+
|
|
1782
|
+
// Check if this node is the last item in an array-like structure
|
|
1783
|
+
let is_last_in_array = false;
|
|
1784
|
+
let array_prop = null;
|
|
1785
|
+
|
|
1786
|
+
if (parent?.type === 'BlockStatement' || parent?.type === 'Program' || parent?.type === 'Component') {
|
|
1787
|
+
array_prop = 'body';
|
|
1788
|
+
} else if (parent?.type === 'ArrayExpression') {
|
|
1789
|
+
array_prop = 'elements';
|
|
1790
|
+
} else if (parent?.type === 'ObjectExpression') {
|
|
1791
|
+
array_prop = 'properties';
|
|
1792
|
+
}
|
|
1793
|
+
|
|
1794
|
+
if (array_prop && Array.isArray(parent[array_prop])) {
|
|
1795
|
+
is_last_in_array = parent[array_prop].indexOf(node) === parent[array_prop].length - 1;
|
|
1796
|
+
}
|
|
1797
|
+
|
|
1798
|
+
if (is_last_in_array) {
|
|
1708
1799
|
// Special case: There can be multiple trailing comments after the last node in a block,
|
|
1709
1800
|
// and they can be separated by newlines
|
|
1710
1801
|
let end = node.end;
|
|
@@ -177,7 +177,30 @@ const visitors = {
|
|
|
177
177
|
if (node.object.type === 'Identifier' && !node.object.tracked) {
|
|
178
178
|
const binding = context.state.scope.get(node.object.name);
|
|
179
179
|
|
|
180
|
-
if (binding
|
|
180
|
+
if (binding && binding.metadata?.is_tracked_object) {
|
|
181
|
+
const internalProperties = new Set(['__v', 'a', 'b', 'c', 'f']);
|
|
182
|
+
|
|
183
|
+
let propertyName = null;
|
|
184
|
+
if (node.property.type === 'Identifier' && !node.computed) {
|
|
185
|
+
propertyName = node.property.name;
|
|
186
|
+
} else if (node.property.type === 'Literal' && typeof node.property.value === 'string') {
|
|
187
|
+
propertyName = node.property.value;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
if (propertyName && internalProperties.has(propertyName)) {
|
|
191
|
+
error(
|
|
192
|
+
`Directly accessing internal property "${propertyName}" of a tracked object is not allowed. Use \`get(${node.object.name})\` or \`@${node.object.name}\` instead.`,
|
|
193
|
+
context.state.analysis.module.filename,
|
|
194
|
+
node.property
|
|
195
|
+
);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
if (
|
|
200
|
+
binding !== null &&
|
|
201
|
+
binding.initial?.type === 'CallExpression' &&
|
|
202
|
+
is_ripple_track_call(binding.initial.callee, context)
|
|
203
|
+
) {
|
|
181
204
|
error(
|
|
182
205
|
`Accessing a tracked object directly is not allowed, use the \`@\` prefix to read the value inside a tracked object - for example \`@${node.object.name}${node.property.type === 'Identifier' ? `.${node.property.name}` : ''}\``,
|
|
183
206
|
context.state.analysis.module.filename,
|
|
@@ -231,6 +254,17 @@ const visitors = {
|
|
|
231
254
|
const metadata = { tracking: false, await: false };
|
|
232
255
|
|
|
233
256
|
if (declarator.id.type === 'Identifier') {
|
|
257
|
+
const binding = state.scope.get(declarator.id.name);
|
|
258
|
+
if (binding && declarator.init && declarator.init.type === 'CallExpression') {
|
|
259
|
+
const callee = declarator.init.callee;
|
|
260
|
+
// Check if it's a call to `track` or `tracked`
|
|
261
|
+
if (
|
|
262
|
+
(callee.type === 'Identifier' && (callee.name === 'track' || callee.name === 'tracked')) ||
|
|
263
|
+
(callee.type === 'MemberExpression' && callee.property.type === 'Identifier' && (callee.property.name === 'track' || callee.property.name === 'tracked'))
|
|
264
|
+
) {
|
|
265
|
+
binding.metadata = { ...binding.metadata, is_tracked_object: true };
|
|
266
|
+
}
|
|
267
|
+
}
|
|
234
268
|
visit(declarator, state);
|
|
235
269
|
} else {
|
|
236
270
|
const paths = extract_paths(declarator.id);
|
|
@@ -556,6 +556,10 @@ const visitors = {
|
|
|
556
556
|
return b.id(node.name);
|
|
557
557
|
},
|
|
558
558
|
|
|
559
|
+
JSXExpressionContainer(node, context) {
|
|
560
|
+
return context.visit(node.expression);
|
|
561
|
+
},
|
|
562
|
+
|
|
559
563
|
JSXElement(node, context) {
|
|
560
564
|
const name = node.openingElement.name;
|
|
561
565
|
const attributes = node.openingElement.attributes;
|
|
@@ -586,7 +590,7 @@ const visitors = {
|
|
|
586
590
|
}
|
|
587
591
|
|
|
588
592
|
return b.call(
|
|
589
|
-
'
|
|
593
|
+
'__compat.jsx',
|
|
590
594
|
name.type === 'JSXIdentifier' && name.name[0].toLowerCase() === name.name[0]
|
|
591
595
|
? b.literal(name.name)
|
|
592
596
|
: context.visit(name),
|
|
@@ -2064,9 +2068,16 @@ function transform_body(body, { visit, state }) {
|
|
|
2064
2068
|
function create_tsx_with_typescript_support() {
|
|
2065
2069
|
const base_tsx = tsx();
|
|
2066
2070
|
|
|
2067
|
-
//
|
|
2071
|
+
// Add custom TypeScript node handlers that aren't in tsx
|
|
2068
2072
|
return {
|
|
2069
2073
|
...base_tsx,
|
|
2074
|
+
// Custom handler for TSParenthesizedType: (Type)
|
|
2075
|
+
TSParenthesizedType(node, context) {
|
|
2076
|
+
context.write('(');
|
|
2077
|
+
context.visit(node.typeAnnotation);
|
|
2078
|
+
context.write(')');
|
|
2079
|
+
},
|
|
2080
|
+
// Override the ArrowFunctionExpression handler to support TypeScript return types
|
|
2070
2081
|
ArrowFunctionExpression(node, context) {
|
|
2071
2082
|
if (node.async) context.write('async ');
|
|
2072
2083
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/** @import { Block } from '#client' */
|
|
1
|
+
/** @import { Block, CompatOptions } from '#client' */
|
|
2
2
|
|
|
3
3
|
import { destroy_block, root } from './internal/client/blocks.js';
|
|
4
4
|
import { handle_root_events } from './internal/client/events.js';
|
|
@@ -12,7 +12,7 @@ export { jsx, jsxs, Fragment } from '../jsx-runtime.js';
|
|
|
12
12
|
|
|
13
13
|
/**
|
|
14
14
|
* @param {(anchor: Node, props: Record<string, any>, active_block: Block | null) => void} component
|
|
15
|
-
* @param {{ props?: Record<string, any>, target: HTMLElement }} options
|
|
15
|
+
* @param {{ props?: Record<string, any>, target: HTMLElement, compat?: CompatOptions }} options
|
|
16
16
|
* @returns {() => void}
|
|
17
17
|
*/
|
|
18
18
|
export function mount(component, options) {
|
|
@@ -34,7 +34,7 @@ export function mount(component, options) {
|
|
|
34
34
|
|
|
35
35
|
const _root = root(() => {
|
|
36
36
|
component(anchor, props, active_block);
|
|
37
|
-
});
|
|
37
|
+
}, options.compat);
|
|
38
38
|
|
|
39
39
|
return () => {
|
|
40
40
|
cleanup_events();
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/** @import { Block, Derived } from '#client' */
|
|
1
|
+
/** @import { Block, Derived, CompatOptions } from '#client' */
|
|
2
2
|
|
|
3
3
|
import {
|
|
4
4
|
BLOCK_HAS_RUN,
|
|
@@ -125,10 +125,17 @@ export function ref(element, get_fn) {
|
|
|
125
125
|
|
|
126
126
|
/**
|
|
127
127
|
* @param {() => void} fn
|
|
128
|
+
* @param {CompatOptions} [compat]
|
|
128
129
|
* @returns {Block}
|
|
129
130
|
*/
|
|
130
|
-
export function root(fn) {
|
|
131
|
-
|
|
131
|
+
export function root(fn, compat) {
|
|
132
|
+
if (compat != null) {
|
|
133
|
+
for (var key in compat) {
|
|
134
|
+
var api = compat[key];
|
|
135
|
+
api.createRoot();
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
return block(ROOT_BLOCK, fn, { compat });
|
|
132
139
|
}
|
|
133
140
|
|
|
134
141
|
/**
|
|
@@ -1,8 +1,40 @@
|
|
|
1
|
+
/** @import { CompatApi } from '#client' */
|
|
2
|
+
|
|
3
|
+
import { ROOT_BLOCK } from "./constants";
|
|
4
|
+
import { active_block } from "./runtime";
|
|
5
|
+
|
|
1
6
|
/**
|
|
2
|
-
* @param {string} kind
|
|
3
|
-
* @
|
|
4
|
-
|
|
7
|
+
* @param {string} kind
|
|
8
|
+
* @returns {CompatApi | null}
|
|
9
|
+
*/
|
|
10
|
+
function get_compat_from_root(kind) {
|
|
11
|
+
var current = active_block;
|
|
12
|
+
|
|
13
|
+
while (current !== null) {
|
|
14
|
+
if ((current.f & ROOT_BLOCK) !== 0) {
|
|
15
|
+
var api = current.s.compat[kind];
|
|
16
|
+
|
|
17
|
+
if (api != null) {
|
|
18
|
+
return api;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
current = current.p;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* @param {string} kind
|
|
29
|
+
* @param {Node} node
|
|
30
|
+
* @param {() => JSX.Element[]} children_fn
|
|
5
31
|
*/
|
|
6
32
|
export function tsx_compat(kind, node, children_fn) {
|
|
7
|
-
|
|
8
|
-
|
|
33
|
+
var compat = get_compat_from_root(kind);
|
|
34
|
+
|
|
35
|
+
if (compat == null) {
|
|
36
|
+
throw new Error(`No compat API found for kind "${kind}"`);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
compat.createComponent(node, children_fn);
|
|
40
|
+
}
|
|
@@ -53,3 +53,13 @@ export type Block = {
|
|
|
53
53
|
// teardown function
|
|
54
54
|
t: (() => {}) | null;
|
|
55
55
|
};
|
|
56
|
+
|
|
57
|
+
export type CompatApi = {
|
|
58
|
+
createRoot: () => void;
|
|
59
|
+
createComponent: (node: any, children_fn: () => any) => void;
|
|
60
|
+
jsx: (type: any, props: any) => any;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export type CompatOptions = {
|
|
64
|
+
[key: string]: CompatApi;
|
|
65
|
+
}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { compile } from 'ripple/compiler';
|
|
2
|
+
import { track } from 'ripple';
|
|
3
|
+
|
|
4
|
+
describe('Compiler: Tracked Object Direct Access Checks', () => {
|
|
5
|
+
|
|
6
|
+
it('should error on direct access to __v of a tracked object', () => {
|
|
7
|
+
const code = `
|
|
8
|
+
export default component App() {
|
|
9
|
+
let count = track(0);
|
|
10
|
+
console.log(count.__v);
|
|
11
|
+
}
|
|
12
|
+
`;
|
|
13
|
+
expect(() => compile(code, 'test.ripple')).toThrow(/Directly accessing internal property "__v" of a tracked object is not allowed/);
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
it('should error on direct access to "a" (get/set config) of a tracked object', () => {
|
|
17
|
+
const code = `
|
|
18
|
+
export default component App() {
|
|
19
|
+
let myTracked = track(0);
|
|
20
|
+
console.log(myTracked.a);
|
|
21
|
+
}
|
|
22
|
+
`;
|
|
23
|
+
expect(() => compile(code, 'test.ripple')).toThrow(/Directly accessing internal property "a" of a tracked object is not allowed/);
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it('should error on direct access to "b" (block) of a tracked object', () => {
|
|
27
|
+
const code = `
|
|
28
|
+
export default component App() {
|
|
29
|
+
let myTracked = track(0);
|
|
30
|
+
console.log(myTracked.b);
|
|
31
|
+
}
|
|
32
|
+
`;
|
|
33
|
+
expect(() => compile(code, 'test.ripple')).toThrow(/Directly accessing internal property "b" of a tracked object is not allowed/);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it('should error on direct access to "c" (clock) of a tracked object', () => {
|
|
37
|
+
const code = `
|
|
38
|
+
export default component App() {
|
|
39
|
+
let myTracked = track(0);
|
|
40
|
+
console.log(myTracked.c);
|
|
41
|
+
}
|
|
42
|
+
`;
|
|
43
|
+
expect(() => compile(code, 'test.ripple')).toThrow(/Directly accessing internal property "c" of a tracked object is not allowed/);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it('should error on direct access to "f" (flags) of a tracked object', () => {
|
|
47
|
+
const code = `
|
|
48
|
+
export default component App() {
|
|
49
|
+
let myTracked = track(0);
|
|
50
|
+
console.log(myTracked.f);
|
|
51
|
+
}
|
|
52
|
+
`;
|
|
53
|
+
expect(() => compile(code, 'test.ripple')).toThrow(/Directly accessing internal property "f" of a tracked object is not allowed/);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it('should compile successfully with correct @ syntax access', () => {
|
|
57
|
+
const code = `
|
|
58
|
+
export default component App() {
|
|
59
|
+
let count = track(0);
|
|
60
|
+
console.log(@count);
|
|
61
|
+
}
|
|
62
|
+
`;
|
|
63
|
+
expect(() => compile(code, 'test.ripple')).not.toThrow();
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it('should compile successfully with correct get() function access', () => {
|
|
67
|
+
const code = `
|
|
68
|
+
import { get, track } from 'ripple';
|
|
69
|
+
export default component App() {
|
|
70
|
+
let count = track(0);
|
|
71
|
+
console.log(get(count));
|
|
72
|
+
}
|
|
73
|
+
`;
|
|
74
|
+
expect(() => compile(code, 'test.ripple')).not.toThrow();
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it('should not error on accessing __v of a non-tracked object', () => {
|
|
78
|
+
const code = `
|
|
79
|
+
export default component App() {
|
|
80
|
+
let obj = { __v: 123 };
|
|
81
|
+
console.log(obj.__v);
|
|
82
|
+
}
|
|
83
|
+
`;
|
|
84
|
+
expect(() => compile(code, 'test.ripple')).not.toThrow();
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it('should not error on accessing __v of a non-tracked object (member expression)', () => {
|
|
88
|
+
const code = `
|
|
89
|
+
export default component App() {
|
|
90
|
+
let data = { value: { __v: 456 } };
|
|
91
|
+
console.log(data.value.__v);
|
|
92
|
+
}
|
|
93
|
+
`;
|
|
94
|
+
expect(() => compile(code, 'test.ripple')).not.toThrow();
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it('should not error on accessing a property named like an internal one on a non-tracked object', () => {
|
|
98
|
+
const code = `
|
|
99
|
+
export default component App() {
|
|
100
|
+
let config = { a: 'some_value', b: 'another_value' };
|
|
101
|
+
console.log(config.a);
|
|
102
|
+
console.log(config.b);
|
|
103
|
+
}
|
|
104
|
+
`;
|
|
105
|
+
expect(() => compile(code, 'test.ripple')).not.toThrow();
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
});
|