@rettangoli/fe 1.0.0-rc5 → 1.0.1

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rettangoli/fe",
3
- "version": "1.0.0-rc5",
3
+ "version": "1.0.1",
4
4
  "description": "Frontend framework for building reactive web components",
5
5
  "type": "module",
6
6
  "main": "./src/index.js",
@@ -1,6 +1,9 @@
1
1
  const PROP_PREFIX = ":";
2
2
 
3
3
  const UNSAFE_KEYS = new Set(["__proto__", "constructor", "prototype"]);
4
+ const ATTRIBUTE_NAME_REGEX = /^[A-Za-z_][A-Za-z0-9_.:-]*$/;
5
+
6
+ const isValidAttributeName = (value) => ATTRIBUTE_NAME_REGEX.test(value);
4
7
 
5
8
  const lodashGet = (obj, path) => {
6
9
  if (!path) return obj;
@@ -73,7 +76,7 @@ export const collectBindingNames = (attrsString = "") => {
73
76
  }
74
77
 
75
78
  const attrAssignmentRegex = /(\S+?)=(?:\"([^\"]*)\"|\'([^\']*)\'|([^\s]*))/g;
76
- const booleanAttrRegex = /\b(\S+?)(?=\s|$)/g;
79
+ const booleanAttrRegex = /(\S+?)(?=\s|$)/g;
77
80
  const processedAttrs = new Set();
78
81
  const bindingNames = [];
79
82
  let match;
@@ -97,14 +100,20 @@ export const collectBindingNames = (attrsString = "") => {
97
100
 
98
101
  let boolMatch;
99
102
  while ((boolMatch = booleanAttrRegex.exec(remainingAttrsString)) !== null) {
100
- const attrName = boolMatch[1];
101
- if (attrName.startsWith(".")) {
103
+ const rawToken = boolMatch[1];
104
+ if (rawToken.startsWith(".")) {
105
+ continue;
106
+ }
107
+ const attrName = rawToken.startsWith("?")
108
+ ? rawToken.substring(1)
109
+ : rawToken;
110
+ if (!attrName || !isValidAttributeName(attrName)) {
102
111
  continue;
103
112
  }
104
113
  if (
105
- !processedAttrs.has(attrName)
106
- && !attrName.startsWith(PROP_PREFIX)
107
- && !attrName.includes("=")
114
+ !processedAttrs.has(rawToken)
115
+ && !rawToken.startsWith(PROP_PREFIX)
116
+ && !rawToken.includes("=")
108
117
  ) {
109
118
  bindingNames.push(attrName);
110
119
  }
@@ -146,6 +155,14 @@ export const parseNodeBindings = ({
146
155
  props[normalizedPropName] = propValue;
147
156
  };
148
157
 
158
+ const assertValidAttributeName = (attrName, sourceLabel) => {
159
+ if (!isValidAttributeName(attrName)) {
160
+ throw new Error(
161
+ `[Parser] Invalid ${sourceLabel} attribute name '${attrName}' on '${tagName}'.`,
162
+ );
163
+ }
164
+ };
165
+
149
166
  if (!attrsString) {
150
167
  return { attrs, props };
151
168
  }
@@ -180,6 +197,7 @@ export const parseNodeBindings = ({
180
197
 
181
198
  if (rawBindingName.startsWith("?")) {
182
199
  const attrName = rawBindingName.substring(1);
200
+ assertValidAttributeName(attrName, "boolean toggle");
183
201
  const attrValue = rawValue;
184
202
  assertSupportedBooleanToggleAttr(attrName);
185
203
 
@@ -203,6 +221,7 @@ export const parseNodeBindings = ({
203
221
  continue;
204
222
  }
205
223
 
224
+ assertValidAttributeName(rawBindingName, "binding");
206
225
  attrs[rawBindingName] = rawValue;
207
226
  if (isWebComponent && rawBindingName !== "id") {
208
227
  setComponentProp(rawBindingName, rawValue, "attribute-form");
@@ -221,21 +240,42 @@ export const parseNodeBindings = ({
221
240
  remainingAttrsString = remainingAttrsString.replace(processedMatch, " ");
222
241
  });
223
242
 
224
- const booleanAttrRegex = /\b(\S+?)(?=\s|$)/g;
243
+ const booleanAttrRegex = /(\S+?)(?=\s|$)/g;
225
244
  let boolMatch;
226
245
  while ((boolMatch = booleanAttrRegex.exec(remainingAttrsString)) !== null) {
227
- const attrName = boolMatch[1];
228
- if (attrName.startsWith(".")) {
246
+ const rawToken = boolMatch[1];
247
+ if (rawToken.startsWith(".")) {
248
+ continue;
249
+ }
250
+ if (processedAttrs.has(rawToken) || rawToken.startsWith(PROP_PREFIX) || rawToken.includes("=")) {
229
251
  continue;
230
252
  }
253
+
254
+ if (rawToken.startsWith("?")) {
255
+ const toggleAttrName = rawToken.substring(1);
256
+ assertValidAttributeName(toggleAttrName, "boolean toggle");
257
+ assertSupportedBooleanToggleAttr(toggleAttrName);
258
+ attrs[toggleAttrName] = "";
259
+ if (isWebComponent && toggleAttrName !== "id") {
260
+ setComponentProp(toggleAttrName, true, "boolean attribute-form");
261
+ }
262
+ continue;
263
+ }
264
+
265
+ if (!isValidAttributeName(rawToken)) {
266
+ throw new Error(
267
+ `[Parser] Invalid attribute token '${rawToken}' on '${tagName}'.`,
268
+ );
269
+ }
270
+
231
271
  if (
232
- !processedAttrs.has(attrName)
233
- && !attrName.startsWith(PROP_PREFIX)
234
- && !attrName.includes("=")
272
+ !processedAttrs.has(rawToken)
273
+ && !rawToken.startsWith(PROP_PREFIX)
274
+ && !rawToken.includes("=")
235
275
  ) {
236
- attrs[attrName] = "";
237
- if (isWebComponent && attrName !== "id") {
238
- setComponentProp(attrName, true, "boolean attribute-form");
276
+ attrs[rawToken] = "";
277
+ if (isWebComponent && rawToken !== "id") {
278
+ setComponentProp(rawToken, true, "boolean attribute-form");
239
279
  }
240
280
  }
241
281
  }