New Case Study:See how Anthropic automated 95% of dependency reviews with Socket.Learn More
Socket
Sign inDemoInstall
Socket

@lightningjs/blits

Package Overview
Dependencies
Maintainers
7
Versions
97
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@lightningjs/blits - npm Package Compare versions

Comparing version 0.4.1 to 0.4.2

2

boilerplate/package.json

@@ -34,4 +34,4 @@ {

"dependencies": {
"@lightningjs/blits": "^0.4.1"
"@lightningjs/blits": "^0.4.2"
}
}
# Changelog
# v0.4.2
_22 nov 2023_
- Improved parser and added more template validation (i.e. one single root element in a template)
- Fixed typo in documentation
# v0.4.1

@@ -4,0 +11,0 @@

@@ -28,3 +28,3 @@ # Blits - Lightning 3 App Development Framework

},
watchers: {
watch: {
alpha(value, oldValue) {

@@ -31,0 +31,0 @@ if(value > oldValue) {

{
"name": "@lightningjs/blits",
"version": "0.4.1",
"version": "0.4.2",
"description": "Blits: The Lightning 3 App Development Framework",

@@ -5,0 +5,0 @@ "bin": "bin/index.cjs",

@@ -22,3 +22,3 @@ /*

constructor(message, name, context) {
super(`TemplateParseError ${message}`)
super(`TemplateParseError: ${message}`)
this.name = name

@@ -45,5 +45,4 @@ this.context = context

template = clean(template)
parseLoop(parseEmptyTagStart)
try {
parseLoop(parseEmptyTagStart)
return format(tags)

@@ -128,2 +127,6 @@ } catch (error) {

if (match[1] === '/>') {
if (currentTag[symbols.type] === 'closing') {
// 10 is arbitrary, just to show some context by moving the cursor back a bit
throw new TemplateParseError('InvalidClosingTag', template.slice(cursor - 10))
}
currentTag[symbols.type] = 'self-closing'

@@ -180,79 +183,90 @@ currentLevel-- // because it was parsed as opening tag before

// formatter
function format(data) {
// formating and validation
/*
validation rules:
#1: Every opening tag must have a corresponding closing tag at the same level. If a closing tag is encountered without
a preceding opening tag at the same level, or if an opening tag is not followed by a corresponding closing tag at
the same level, an error should be thrown.
#2: There must be exactly one top-level element (an element at level 0). This element may either be a self-closing
element or an opening tag followed by a closing tag. If more than one top-level element is encountered, an error
should be thrown.
*/
const format = (parsedData) => {
let stack = []
let rootElementDefined = false
let output = { children: [] }
let currentParent = output
for (const item of data) {
const { type, [symbols.type]: __type, [symbols.level]: __level } = item
for (let i = 0; i < parsedData.length; i++) {
let element = parsedData[i]
// Check for unclosed tags
while (stack.length && stack[stack.length - 1][symbols.level] >= __level) {
const popped = stack.pop()
if (popped[symbols.type] === 'opening') {
throw new TemplateParseError('MismatchedClosingTag', `tag: ${popped.type || 'null'}`)
// Rule #1
if (element[symbols.level] === 0 && element[symbols.type] !== 'closing') {
if (rootElementDefined) {
throw new TemplateParseError('MultipleTopLevelTags', formatErrorContext(element))
}
rootElementDefined = true
}
// For closing tags, just pop the opening tag from stack and continue
if (__type === 'closing') {
let lastStackType = stack[stack.length - 1] ? stack[stack.length - 1].type : null
// Rule #2
if (element[symbols.type] === 'opening') {
stack.push({
[symbols.level]: element[symbols.level],
[symbols.type]: element[symbols.type],
type: element.type,
parent: currentParent, // helps getting the previous parent when closing tag is encountered
})
} else if (element[symbols.type] === 'closing') {
const isStackEmpty = stack.length === 0
let isLevelMismatch = false
let isTagMismatch = false
if (!isStackEmpty) {
isLevelMismatch = stack[stack.length - 1][symbols.level] !== element[symbols.level]
isTagMismatch = stack[stack.length - 1].type !== element.type
}
if (
stack.length === 0 ||
(type
? lastStackType && lastStackType.toLowerCase() !== type.toLowerCase()
: lastStackType !== null)
) {
throw new TemplateParseError('MismatchedClosingTag', `tag: ${type || 'null'}`)
if (isStackEmpty || isLevelMismatch || isTagMismatch) {
throw new TemplateParseError('MismatchedClosingTag', formatErrorContext(element))
}
stack.pop()
continue
// when we remove the closing element from the stack, we should set
// the current parent to the parent of the closing element
const lastTag = stack.pop()
currentParent = lastTag.parent
}
// Create a new item, copying properties but deleting __type and __level
const newItem = { ...item }
const newItem = { ...element }
delete newItem[symbols.type]
delete newItem[symbols.level]
if (__type === 'opening') {
newItem.children = []
}
// Find out where to insert this new item
let current = output.children
for (const stackItem of stack) {
if (stackItem.children) {
current = stackItem.children
// if it is an opening tag, add children[] to it and update current parent
if (element[symbols.type] === 'opening') {
// make sure the current opening tag has really a child element
if (i + 1 < parsedData.length && parsedData[i + 1][symbols.type] !== 'closing') {
newItem.children = []
}
currentParent.children.push(newItem)
currentParent = newItem
} else if (element[symbols.type] === 'self-closing') {
currentParent.children.push(newItem)
}
// Insert the item and push it to the stack
current.push(newItem)
stack.push(newItem)
// If this is a self-closing tag, immediately pop it off the stack
if (__type === 'self-closing') {
stack.pop()
}
}
// Check for any remaining unclosed tags
for (const item of stack) {
if (item.__type === 'opening') {
throw new TemplateParseError('MismatchedClosingTag', `tag: ${item.type || 'null'}`)
}
// Check if all tags are closed (so stack should be empty)[Rule #1]
if (stack.length > 0) {
const unclosedTags = stack
.map((item) => {
return formatErrorContext(item)
})
.join(', ')
throw new TemplateParseError('UnclosedTags', unclosedTags)
}
// Remove empty 'children' arrays
function removeEmptyChildren(obj, level = 0) {
if (Array.isArray(obj.children) && obj.children.length === 0 && level > 0) {
delete obj.children
}
if (obj.children) {
obj.children.forEach((child) => removeEmptyChildren(child, level + 1))
}
function formatErrorContext(element) {
return `${element.type || 'empty-tag'}[${element[symbols.type]}] at level ${
element[symbols.level]
}`
}
removeEmptyChildren(output)
return output

@@ -259,0 +273,0 @@ }

@@ -950,1 +950,100 @@ /*

})
test('Parse template with multiple top level elements and parsing should fail', (assert) => {
const template = `
<Component>
<Element
w='160' h="160" x='40' y='40' color="#fb923c"
:effects='[$shader(
"radius",
{radius: 44}
)]'
/>
</Component>
<Component>
<Element
w='120' h="120"
x='100' y="100"
:effects="[
$shader(
'radius',
{
radius: 45
}
)
]"
/>
</Component>`
const actual = parser(template)
assert.equal(actual, null, 'Parser should throw an error')
assert.end()
})
test('Parse template with unclosed tag and parsing should fail', (assert) => {
const template = `
<Component>
<Element>
</Component>
`
const actual = parser(template)
assert.equal(actual, null, 'Parser should throw an error')
assert.end()
})
test('Parse template with multiple unclosed tags and parsing should fail', (assert) => {
const template = `
<Component>
<Element>
<Button />
<Text>Lorem Ipsum</Text>
<Element>
<Button />
<Text>Lorem Ipsum</Text>
</Component>
`
const actual = parser(template)
assert.equal(actual, null, 'Parser should throw an error')
assert.end()
})
test('Parse template with an invalid closing tag and parsing should fail', (assert) => {
const template = `
<Component>
<Element>
</Element/>
</Component>
`
const actual = parser(template)
assert.equal(actual, null, 'Parser should throw an error')
assert.end()
})
test('Parse template with multiple self-closing tags at the top level and parsing should fail', (assert) => {
const template = `
<Component/>
<Element/>
`
const actual = parser(template)
assert.equal(actual, null, 'Parser should throw an error')
assert.end()
})
test('Parse template with a closing tag at the beginning and parsing should fail', (assert) => {
const template = `
</Element>
<Component>
<Element/>
</Component>
`
const actual = parser(template)
assert.equal(actual, null, 'Parser should throw an error')
assert.end()
})
SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc