coralite 0.14.0 → 0.14.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/lib/parse.js +18 -28
- package/lib/tags.js +36 -3
- package/package.json +1 -1
- package/.continue/rules/javascript.md +0 -5
package/lib/parse.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Parser } from 'htmlparser2'
|
|
2
|
-
import {
|
|
2
|
+
import { isValidCustomElementName, VALID_TAGS } from './tags.js'
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* @import {
|
|
@@ -15,9 +15,6 @@ import { invalidCustomTags, validTags } from './tags.js'
|
|
|
15
15
|
* ParseHTMLResult} from '#types'
|
|
16
16
|
*/
|
|
17
17
|
|
|
18
|
-
const customElementTagRegExp = /^[^-].*[-._a-z0-9\u00B7\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u037D\u037F-\u1FFF\u200C-\u200D\u203F-\u2040\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD\u{10000}-\u{EFFFF}]*$/ui
|
|
19
|
-
const customElementTagTokenRegExp = /^[^-].*[-._a-z0-9\u00B7\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u037D\u037F-\u1FFF\u200C-\u200D\u203F-\u2040\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD\u{10000}-\u{EFFFF}\{\}]*$/ui
|
|
20
|
-
|
|
21
18
|
/**
|
|
22
19
|
* Parse HTML content and return a CoraliteDocument object representing the parsed document structure
|
|
23
20
|
*
|
|
@@ -180,13 +177,13 @@ export function parseModule (string, { ignoreByAttribute }) {
|
|
|
180
177
|
const customElements = []
|
|
181
178
|
/** @type {Object.<string, Object.<string,CoraliteModuleSlotElement>>} */
|
|
182
179
|
const slotElements = {}
|
|
183
|
-
let isTemplate = false
|
|
184
|
-
|
|
185
180
|
/** @type {CoraliteDocumentValues} */
|
|
186
181
|
const documentValues = {
|
|
187
182
|
attributes: [],
|
|
188
183
|
textNodes: []
|
|
189
184
|
}
|
|
185
|
+
let isScript = false
|
|
186
|
+
let isTemplate = false
|
|
190
187
|
let templateId = ''
|
|
191
188
|
|
|
192
189
|
const parser = new Parser({
|
|
@@ -206,7 +203,9 @@ export function parseModule (string, { ignoreByAttribute }) {
|
|
|
206
203
|
// push element to stack as it may have children
|
|
207
204
|
stack.push(element)
|
|
208
205
|
|
|
209
|
-
if (element.name === '
|
|
206
|
+
if (element.name === 'script') {
|
|
207
|
+
isScript = true
|
|
208
|
+
} else if (element.name === 'template') {
|
|
210
209
|
// enter template tag
|
|
211
210
|
isTemplate = true
|
|
212
211
|
|
|
@@ -214,19 +213,7 @@ export function parseModule (string, { ignoreByAttribute }) {
|
|
|
214
213
|
throw new Error('Template requires an "id"')
|
|
215
214
|
}
|
|
216
215
|
|
|
217
|
-
|
|
218
|
-
// check if template id contains a token
|
|
219
|
-
for (let i = 0; i < documentValues.attributes.length; i++) {
|
|
220
|
-
const token = documentValues.attributes[i]
|
|
221
|
-
|
|
222
|
-
if (token.name === 'id') {
|
|
223
|
-
idHasToken = true
|
|
224
|
-
break
|
|
225
|
-
}
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
if (!customElementTagRegExp.test(attributes.id)
|
|
229
|
-
|| (idHasToken && !customElementTagTokenRegExp.test(attributes.id))) {
|
|
216
|
+
if (!isValidCustomElementName(attributes.id)) {
|
|
230
217
|
throw new Error('Invalid template id: "' + originalName + '" it must match following the pattern https://html.spec.whatwg.org/multipage/custom-elements.html#valid-custom-element-name')
|
|
231
218
|
}
|
|
232
219
|
|
|
@@ -275,7 +262,7 @@ export function parseModule (string, { ignoreByAttribute }) {
|
|
|
275
262
|
const parent = stack[stack.length - 1]
|
|
276
263
|
const textNode = createTextNode(text, parent)
|
|
277
264
|
|
|
278
|
-
if (text.trim()) {
|
|
265
|
+
if (isTemplate && !isScript && text.trim()) {
|
|
279
266
|
const tokens = getTokensFromString(text)
|
|
280
267
|
|
|
281
268
|
// store tokens
|
|
@@ -298,6 +285,8 @@ export function parseModule (string, { ignoreByAttribute }) {
|
|
|
298
285
|
if (name === 'template') {
|
|
299
286
|
// exit template tag
|
|
300
287
|
isTemplate = false
|
|
288
|
+
} else if (name === 'script') {
|
|
289
|
+
isScript = false
|
|
301
290
|
}
|
|
302
291
|
|
|
303
292
|
// remove current element from stack as we're done with its children
|
|
@@ -435,12 +424,9 @@ export function createElement ({
|
|
|
435
424
|
}
|
|
436
425
|
}
|
|
437
426
|
|
|
438
|
-
if (!
|
|
439
|
-
if
|
|
440
|
-
|
|
441
|
-
}
|
|
442
|
-
|
|
443
|
-
if (customElementTagRegExp.test(sanitisedName)) {
|
|
427
|
+
if (!VALID_TAGS[sanitisedName]) {
|
|
428
|
+
// check if the tag name matches the regex for valid custom elements
|
|
429
|
+
if (isValidCustomElementName(sanitisedName)) {
|
|
444
430
|
// store custom elements
|
|
445
431
|
element.slots = []
|
|
446
432
|
} else {
|
|
@@ -501,7 +487,11 @@ function findAttributesToIgnore (ignoreByAttribute, attributes) {
|
|
|
501
487
|
* @returns {CoraliteToken[]}
|
|
502
488
|
*/
|
|
503
489
|
function getTokensFromString (string) {
|
|
504
|
-
|
|
490
|
+
if (string.length > 100) {
|
|
491
|
+
console.warn(`Token "${string}" is too long`)
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
const matches = string.matchAll(/\{\{[^}]{0,100}?\}\}/g)
|
|
505
495
|
const result = []
|
|
506
496
|
|
|
507
497
|
for (const match of matches) {
|
package/lib/tags.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export const
|
|
1
|
+
export const VALID_TAGS = {
|
|
2
2
|
a: true,
|
|
3
3
|
abbr: true,
|
|
4
4
|
acronym: true, // deprecated
|
|
@@ -193,8 +193,7 @@ export const validTags = {
|
|
|
193
193
|
view: true
|
|
194
194
|
}
|
|
195
195
|
|
|
196
|
-
|
|
197
|
-
export const invalidCustomTags = {
|
|
196
|
+
export const RESERVED_ELEMENT_NAMES = {
|
|
198
197
|
'annotation-xml': true,
|
|
199
198
|
'color-profile': true,
|
|
200
199
|
'font-face': true,
|
|
@@ -204,3 +203,37 @@ export const invalidCustomTags = {
|
|
|
204
203
|
'font-face-name': true,
|
|
205
204
|
'missing-glyph': true
|
|
206
205
|
}
|
|
206
|
+
|
|
207
|
+
const CUSTOM_ELEMENT_TAG = /^[a-z](?:[-.\w\u00B7\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u037D\u037F-\u1FFF\u200C-\u200D\u203F-\u2040\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD])*?-(?:[-.\w\u00B7\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u037D\u037F-\u1FFF\u200C-\u200D\u203F-\u2040\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD])*$/;
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Validates a custom element name
|
|
211
|
+
* @param {string} name - The custom element name to validate
|
|
212
|
+
* @param {number} [maxLength=100] - Max length of the tag name
|
|
213
|
+
* @returns {boolean} - True if valid, false otherwise
|
|
214
|
+
* @throws {Error} - If the element name is reserved
|
|
215
|
+
*/
|
|
216
|
+
export function isValidCustomElementName(name, maxLength = 100) {
|
|
217
|
+
// Check if string is empty or not a string
|
|
218
|
+
if (!name || typeof name !== 'string') {
|
|
219
|
+
return false;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// Check against reserved names first (case-insensitive)
|
|
223
|
+
if (RESERVED_ELEMENT_NAMES[name.toLowerCase()]) {
|
|
224
|
+
throw new Error('Element name is reserved: "'+ name +'"')
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// Length check to prevent ReDoS
|
|
228
|
+
if (name.length > maxLength) {
|
|
229
|
+
return false;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// check for obviously invalid patterns that could cause backtracking
|
|
233
|
+
if (name.includes('--') || name.startsWith('-') || name.endsWith('-')) {
|
|
234
|
+
return false;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// Test against the regex
|
|
238
|
+
return CUSTOM_ELEMENT_TAG.test(name);
|
|
239
|
+
}
|
package/package.json
CHANGED