Skip to content

Commit

Permalink
Merge branch 'hanoii:master' into master
Browse files Browse the repository at this point in the history
Close GH-37

Add support for attribute names according html5 specs.
  • Loading branch information
zackad committed Aug 1, 2024
2 parents 556adbc + 009a0e0 commit 2c2d0ef
Show file tree
Hide file tree
Showing 5 changed files with 236 additions and 7 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@
- Package has been renamed `@zackad/prettier-plugin-twig-melody` -> `@zackad/prettier-plugin-twig`
- The parser has been renamed from `melody` into `twig`

### Features
- Add support attribute names according to html5 specs

### Internals
- Remove npm script to publish
- Integrate devenv into nix flakes
Expand Down
2 changes: 1 addition & 1 deletion src/melody/melody-parser/CharStream.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ export class CharStream {

lac(offset) {
const index = this.index + offset;
return index < this.length ? this.input.charCodeAt(index) : EOF;
return index < this.length ? this.input.codePointAt(index) : EOF;
}

next() {
Expand Down
55 changes: 49 additions & 6 deletions src/melody/melody-parser/Lexer.js
Original file line number Diff line number Diff line change
Expand Up @@ -497,7 +497,7 @@ export default class Lexer {
(c === 95 ||
isAlpha(c) ||
isDigit(c) ||
(inElement && (c === 45 || c === 58)))
(inElement && isValidAttributeName(c)))
) {
input.next();
}
Expand Down Expand Up @@ -561,17 +561,15 @@ export default class Lexer {
matchAttributeValue(pos) {
const input = this.input;
const start = this.state === State.STRING_SINGLE ? "'" : '"';
let c;
if (input.la(0) === "{") {
let c = input.la(0);
const c2 = input.la(1);
if (c === "{" && (c2 === "{" || c2 === "#" || c2 === "%")) {
return this.matchExpressionToken(pos);
}
while ((c = input.la(0)) !== start && c !== EOF) {
if (c === "\\" && input.la(1) === start) {
input.next();
input.next();
} else if (c === "{") {
// interpolation start
break;
} else if (c === start) {
break;
} else {
Expand Down Expand Up @@ -663,3 +661,48 @@ function isAlpha(c) {
function isDigit(c) {
return c >= 48 && c <= 57;
}

// https://html.spec.whatwg.org/multipage/syntax.html#attributes-2
function isValidAttributeName(c) {
const is_c0_control = c >= 0 && c <= 31;
const is_control = is_c0_control || (c >= 127 && c <= 159);
const is_invalid =
c === 32 || c === 34 || c === 39 || c === 62 || c === 47 || c === 61;
const is_noncharacter =
(c >= 64976 && c <= 65007) ||
c === 65534 ||
c === 65535 ||
c === 131070 ||
c === 131071 ||
c === 196606 ||
c === 196607 ||
c === 262142 ||
c === 262143 ||
c === 327678 ||
c === 327679 ||
c === 393214 ||
c === 393215 ||
c === 458750 ||
c === 458751 ||
c === 524286 ||
c === 524287 ||
c === 589822 ||
c === 589823 ||
c === 655358 ||
c === 655359 ||
c === 720894 ||
c === 720895 ||
c === 786430 ||
c === 786431 ||
c === 851966 ||
c === 851967 ||
c === 917502 ||
c === 917503 ||
c === 983038 ||
c === 983039 ||
c === 1048574 ||
c === 1048575 ||
c === 1114106 ||
c === 1114107;
return !is_control && !is_invalid && !is_noncharacter;
}
123 changes: 123 additions & 0 deletions tests/Element/__snapshots__/jsfmt.spec.js.snap
Original file line number Diff line number Diff line change
@@ -1,5 +1,128 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html

exports[`alpinejs.twig - twig-verify > alpinejs.twig 1`] = `
<div x-data="{ open: false }">
...
</div>
<div x-bind:class="! open ? 'hidden' : ''">
...
</div>
<button x-on:click="open = ! open">
Toggle
</button>
<div>
Copyright ©
<span x-text="new Date().getFullYear()"></span>
</div>
<div x-html="(await axios.get('/some/html/partial')).data">
...
</div>
<div x-data="{ search: '' }">
<input type="text" x-model="search">
Searching for: <span x-text="search"></span>
</div>
<div x-show="open">
...
</div>
<div x-show="open" x-transition>
...
</div>
<template x-for="post in posts">
<h2 x-text="post.title"></h2>
</template>
<template x-if="open">
<div>...</div>
</template>
<div x-init="date = new Date()"></div>
<div x-effect="console.log('Count is '+count)"></div>
<input type="text" x-ref="content">
<button x-on:click="navigator.clipboard.writeText($refs.content.value)">
Copy
</button>
<div x-cloak>
...
</div>
<div x-ignore>
...
</div>
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
<div x-data="{ open: false }">
...
</div>
<div x-bind:class="! open ? 'hidden' : ''">
...
</div>
<button x-on:click="open = ! open">Toggle</button>
<div>
Copyright ©
<span x-text="new Date().getFullYear()"></span>
</div>
<div x-html="(await axios.get('/some/html/partial')).data">
...
</div>
<div x-data="{ search: '' }">
<input type="text" x-model="search" />Searching for: <span x-text="search"></span>
</div>
<div x-show="open">
...
</div>
<div x-show="open" x-transition>
...
</div>
<template x-for="post in posts">
<h2 x-text="post.title"></h2>
</template>
<template x-if="open">
<div>
...
</div>
</template>
<div x-init="date = new Date()"></div>
<div x-effect="console.log('Count is '+count)"></div>
<input type="text" x-ref="content" />
<button x-on:click="navigator.clipboard.writeText($refs.content.value)">
Copy
</button>
<div x-cloak>
...
</div>
<div x-ignore>
...
</div>
`;
exports[`attributes.twig - twig-verify > attributes.twig 1`] = `
<a href="#abcd" target="_blank" lang="en" >Link</a>
Expand Down
60 changes: 60 additions & 0 deletions tests/Element/alpinejs.twig
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
<div x-data="{ open: false }">
...
</div>

<div x-bind:class="! open ? 'hidden' : ''">
...
</div>

<button x-on:click="open = ! open">
Toggle
</button>

<div>
Copyright ©

<span x-text="new Date().getFullYear()"></span>
</div>

<div x-html="(await axios.get('/some/html/partial')).data">
...
</div>

<div x-data="{ search: '' }">
<input type="text" x-model="search">

Searching for: <span x-text="search"></span>
</div>

<div x-show="open">
...
</div>

<div x-show="open" x-transition>
...
</div>

<template x-for="post in posts">
<h2 x-text="post.title"></h2>
</template>

<template x-if="open">
<div>...</div>
</template>

<div x-init="date = new Date()"></div>

<div x-effect="console.log('Count is '+count)"></div>

<input type="text" x-ref="content">
<button x-on:click="navigator.clipboard.writeText($refs.content.value)">
Copy
</button>

<div x-cloak>
...
</div>

<div x-ignore>
...
</div>

0 comments on commit 2c2d0ef

Please sign in to comment.