Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add svelte lexer #1979

Merged
merged 8 commits into from
Sep 22, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/Languages.md
Expand Up @@ -190,6 +190,7 @@
- Stan (`stan`)
- Stata (`stata`)
- SuperCollider (`supercollider`)
- Svelte (`svelte`)
- Swift (`swift`)
- Systemd (`systemd`)
- Syzlang (`syzlang`)
Expand Down
29 changes: 29 additions & 0 deletions lib/rouge/demos/svelte
@@ -0,0 +1,29 @@
<!-- This file is made up from several examples on https://svelte.dev/examples and is not expected to function. -->
<script lang="ts">
import Image from './Image.svelte';

let src: string = '/tutorial/image.gif';
let count: number = 1;
$: doubled = count * 2; // the `$:` is special in svelte
</script>

<Image {src} bind:alt="{name.capitalize()} dancing" user={name.toUpperCase(false, 42, {key: 'value'})}
tooltip="I'm helping" false text=asdf on:message={handleMessage} />

{#await loadSrc(src)}
loading...
{:then data}
{#each cats as { name }, i}
<li>{name}</li>
{/each}

<!-- Keyed Each Block -->
{#each cats as cat (cat.id)}
<li>{cat.name}</li>
{/each}
{:catch err}
{@debug err}
{#await solveErr(err, {x: 'asdf'}) then reason}{@html reason}{/await}
{/await}

<style>p {font-family: 'Comic Sans MS', cursive;}</style>
91 changes: 91 additions & 0 deletions lib/rouge/lexers/svelte.rb
@@ -0,0 +1,91 @@
# -*- coding: utf-8 -*- #
# frozen_string_literal: true

module Rouge
module Lexers
load_lexer 'html.rb'

class Svelte < HTML
desc 'Svelte single-file components (https://svelte.dev/)'
tag 'svelte'
filenames '*.svelte'
mimetypes 'text/x-svelte', 'application/x-svelte'

def initialize(*)
super
# todo add support for typescript script blocks
@js = Javascript.new(options)
end

# Shorthand syntax for passing attributes - ex, `{src}` instead of `src={src}`
prepend :tag do
rule %r/(\{)\s*([a-zA-Z0-9_]+)\s*(})/m do
groups Str::Interpol, Name::Variable, Str::Interpol
pop!
end
end

prepend :attr do
# Duplicate template_start mixin here with a pop!
# Because otherwise we'll never exit the attr state
rule %r/\{/ do
token Str::Interpol
pop!
push :template
end
end

# handle templates within attribute single/double quotes
prepend :dq do
mixin :template_start
end

prepend :sq do
mixin :template_start
end

prepend :root do
# detect curly braces within HTML text (outside of tags/attributes)
rule %r/([^<&{]*)(\{)(\s*)/ do
groups Text, Str::Interpol, Text
push :template
end
end

state :template_start do
Rudedog9d marked this conversation as resolved.
Show resolved Hide resolved
# open template
rule %r/\s*\{\s*/, Str::Interpol, :template
end

state :template do
# template end
rule %r/}/, Str::Interpol, :pop!

# Allow JS lexer to handle matched curly braces within template
rule(/(?<=^|[^\\])\{.*?(?<=^|[^\\])\}/) do
delegate @js
end

# keywords
rule %r/@(debug|html)\b/, Keyword
rule %r/(#await)(.*)(then|catch)(\s+)(\w+)/ do |m|
token Keyword, m[1]
delegate @js, m[2]
token Keyword, m[3]
token Text, m[4]
delegate @js, m[5]
end
rule %r/([#\/])(await|each|if|key)\b/, Keyword
rule %r/(:else)(\s+)(if)?\b/ do
groups Keyword, Text, Keyword
end
rule %r/:?(catch|then)\b/, Keyword

# allow JS parser to handle anything that's not a curly brace
rule %r/[^{}]+/ do
delegate @js
end
end
end
end
end
18 changes: 18 additions & 0 deletions spec/lexers/svelte_spec.rb
@@ -0,0 +1,18 @@
# frozen_string_literal: true

describe Rouge::Lexers::Svelte do
let(:subject) { Rouge::Lexers::Svelte.new }

describe 'guessing' do
include Support::Guessing

it 'guesses by filename' do
assert_guess :filename => 'foo.svelte'
end

it 'guesses by mimetype' do
assert_guess :mimetype => 'text/x-svelte'
assert_guess :mimetype => 'application/x-svelte'
end
end
end
78 changes: 78 additions & 0 deletions spec/visual/samples/svelte
@@ -0,0 +1,78 @@
<!-- This file is made up from several examples on https://svelte.dev/examples and is not expected to function. -->
<script>
let name = 'rick';
let cats = [
{ name: 'Keyboard Cat' },
{ name: 'Maru' },
{ name: 'Henri The Existential Cat' }
];

function doStuff() {
console.log('standard javascript is cool');
return false;
};
</script>

<script lang="ts">
import Nested from './Nested.svelte';

let src = '/tutorial/image.gif';
let count: number = 1;

// the `$:` is special in svelte
$: doubled = count * 2;

async function doMoreStuff(): boolean {
console.log('but typescript has more stuff!');
return false;
};
</script>

<h1 false tooltip="welcome" text=hi>Page Title</h1>

<img alt="{name} dancing" {src} />
<Nested user={name.toUpperCase(false, 42, {key: 'value'})} tooltip="I'm helping" false text=asdf on:message={handleMessage} />

<input bind:value={name} placeholder="enter your name" />
<h1>Hey {name}, check out our { cats.length } cats! Hello {name}!</h1>

Hey { name}, check out our { cats.length } cats!

{#if x > 10}
<p>{x} is greater than 10</p>
{:else if 5 > x}
<p>{x} is less than 5</p>
{:else}
<p>{x} is between 5 and 10</p>
{/if}

<ul>
{#each cats as { name }, i}
<li>{name}</li>
{/each}

<!-- Keyed Each Block -->
{#each cats as cat (cat.id)}
<li>{cat.name}</li>
{/each}
</ul>

{#await promise}
loading...
{:then data}
{@html data}
{:catch err}
{@debug err}
{/await}

{#await Fn('asdf', 42, {x: 'asdf'}) then data}
loading...
{/await}

<style>
p {
color: purple;
font-family: 'Comic Sans MS', cursive;
font-size: 2em;
}
</style>