Skip to content

Commit debef62

Browse files
ijlee2lukasnysdknutsen
authored
Created lexers for gjs and gts (used by Ember projects) (#2165)
* chore: Temporarily simplified /demos * chore: Temporarily added realistic examples for handlebars, javascript, and typescript * chore: Scaffolded lexers. Added demos for gjs and gts. * feature: Located <template> tags * wip, feature: Delegated templates to Handlebars * Escape closing </template> tag and add specs (#1) * fix: update regex to correctly delegate handlebars in gjs and gts lexers * chore: update demo to include javascript after the closing template tag * chore: update demo to include typescript after the closing template tag * test: add guessing test for gjs and gts lexer * refactor: Removed unused code * chore: Tested a second example * Revert "chore: Tested a second example" This reverts commit a9953db. * Revert "chore: Temporarily added realistic examples for handlebars, javascript, and typescript" This reverts commit 25eddf1. * Revert "chore: Temporarily simplified /demos" This reverts commit 7822430. * refactor: Added specs, demos, and visual samples for gjs and gts * bugfix: Temporarily removed a component argument from demos, because an existing bug in the handlebars lexer causes tests to fail * Add specs from #2163 (#3) * chore: Copied specs from #2163 * refactor: Extracted specs for lexing --------- Co-authored-by: Dan Knutsen <dknutsen@gmail.com> --------- Co-authored-by: Lukas Nys <nyslukas@gmail.com> Co-authored-by: Dan Knutsen <dknutsen@gmail.com>
1 parent d205f2e commit debef62

File tree

8 files changed

+615
-0
lines changed

8 files changed

+615
-0
lines changed

lib/rouge/demos/gjs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import { LinkTo } from '@ember/routing';
2+
import { local } from 'embroider-css-modules';
3+
4+
import styles from './navigation-menu.css';
5+
6+
const NavigationMenu = <template>
7+
<nav aria-label={{@name}} data-test-nav={{@name}}>
8+
<ul class={{styles.list}}>
9+
{{#each @menuItems as |menuItem|}}
10+
<li>
11+
<LinkTo
12+
class={{local styles "link"}}
13+
data-test-link={{menuItem.label}}
14+
>
15+
{{menuItem.label}}
16+
</LinkTo>
17+
</li>
18+
{{/each}}
19+
</ul>
20+
</nav>
21+
</template>;
22+
23+
export default NavigationMenu;

lib/rouge/demos/gts

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import type { TOC } from '@ember/component/template-only';
2+
import { LinkTo } from '@ember/routing';
3+
import { local } from 'embroider-css-modules';
4+
5+
import styles from './navigation-menu.css';
6+
7+
type MenuItem = {
8+
label: string;
9+
route: string;
10+
};
11+
12+
interface NavigationMenuSignature {
13+
Args: {
14+
menuItems: MenuItem[];
15+
name?: string;
16+
};
17+
}
18+
19+
const NavigationMenu = <template>
20+
<nav aria-label={{@name}} data-test-nav={{@name}}>
21+
<ul class={{styles.list}}>
22+
{{#each @menuItems as |menuItem|}}
23+
<li>
24+
<LinkTo
25+
class={{local styles "link"}}
26+
data-test-link={{menuItem.label}}
27+
>
28+
{{menuItem.label}}
29+
</LinkTo>
30+
</li>
31+
{{/each}}
32+
</ul>
33+
</nav>
34+
</template> satisfies TOC<NavigationMenuSignature>;
35+
36+
export default NavigationMenu;

lib/rouge/lexers/gjs.rb

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
# -*- coding: utf-8 -*- #
2+
# frozen_string_literal: true
3+
4+
module Rouge
5+
module Lexers
6+
load_lexer 'javascript.rb'
7+
8+
class Gjs < Javascript
9+
title "Template Tag (gjs)"
10+
desc "Ember.js, JavaScript with <template> tags"
11+
tag "gjs"
12+
filenames "*.gjs"
13+
mimetypes "text/x-gjs", "application/x-gjs"
14+
15+
def initialize(*)
16+
super
17+
@handlebars = Handlebars.new(options)
18+
end
19+
20+
prepend :root do
21+
rule %r/(<)(template)(>)/ do
22+
groups Name::Tag, Keyword, Name::Tag
23+
push :template
24+
end
25+
end
26+
27+
state :template do
28+
rule %r((</)(template)(>)) do
29+
groups Name::Tag, Keyword, Name::Tag
30+
pop!
31+
end
32+
33+
rule %r/.+?(?=<\/template>)/m do
34+
delegate @handlebars
35+
end
36+
end
37+
end
38+
end
39+
end

lib/rouge/lexers/gts.rb

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
# -*- coding: utf-8 -*- #
2+
# frozen_string_literal: true
3+
4+
module Rouge
5+
module Lexers
6+
load_lexer 'typescript.rb'
7+
8+
class Gts < Typescript
9+
title "Template Tag (gts)"
10+
desc "Ember.js, TypeScript with <template> tags"
11+
tag "gts"
12+
filenames "*.gts"
13+
mimetypes "text/x-gts", "application/x-gts"
14+
15+
def initialize(*)
16+
super
17+
@handlebars = Handlebars.new(options)
18+
end
19+
20+
prepend :root do
21+
rule %r/(<)(template)(>)/ do
22+
groups Name::Tag, Keyword, Name::Tag
23+
push :template
24+
end
25+
end
26+
27+
state :template do
28+
rule %r((</)(template)(>)) do
29+
groups Name::Tag, Keyword, Name::Tag
30+
pop!
31+
end
32+
33+
rule %r/.+?(?=<\/template>)/m do
34+
delegate @handlebars
35+
end
36+
end
37+
end
38+
end
39+
end

spec/lexers/gjs_spec.rb

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
# -*- coding: utf-8 -*- #
2+
# frozen_string_literal: true
3+
4+
describe Rouge::Lexers::Gjs do
5+
let(:subject) { Rouge::Lexers::Gjs.new }
6+
7+
describe 'guessing' do
8+
include Support::Guessing
9+
10+
it 'guesses by filename' do
11+
assert_guess :filename => 'app/components/ui/form.gjs'
12+
assert_guess :filename => 'app/templates/form.gjs'
13+
assert_guess :filename => 'tests/integration/components/ui/form-test.gjs'
14+
end
15+
16+
it 'guesses by mimetype' do
17+
assert_guess :mimetype => 'text/x-gjs'
18+
assert_guess :mimetype => 'application/x-gjs'
19+
end
20+
end
21+
22+
describe 'lexing' do
23+
include Support::Lexing
24+
25+
it 'lexes the opening <template> tag' do
26+
assert_tokens_equal '<template>',
27+
["Name.Tag", "<"], ["Keyword", "template"], ["Name.Tag", ">"]
28+
end
29+
30+
it 'lexes the closing </template> tag' do
31+
assert_tokens_equal '<template></template>',
32+
["Name.Tag", "<"], ["Keyword", "template"], ["Name.Tag", "></"], ["Keyword", "template"], ["Name.Tag", ">"]
33+
assert_tokens_equal '<template> </template>',
34+
["Name.Tag", "<"], ["Keyword", "template"], ["Name.Tag", ">"], ["Text", " "], ["Name.Tag", "</"], ["Keyword", "template"], ["Name.Tag", ">"]
35+
end
36+
37+
it 'lexes a Glimmer component' do
38+
code = <<~FILE
39+
import Component from '@glimmer/component';
40+
import { t } from 'ember-intl';
41+
42+
export default class Hello extends Component {
43+
<template>
44+
<div data-test-message>
45+
{{t "hello.message" name=@name}}
46+
</div>
47+
</template>
48+
}
49+
FILE
50+
51+
assert_no_errors code
52+
end
53+
54+
it 'lexes a template-only component (1)' do
55+
code = <<~FILE
56+
import { t } from 'ember-intl';
57+
58+
const Hello = <template>
59+
<div data-test-message>
60+
{{t "hello.message" name=@name}}
61+
</div>
62+
</template>;
63+
64+
export default Hello;
65+
FILE
66+
67+
assert_no_errors code
68+
end
69+
70+
it 'lexes a template-only component (2)' do
71+
code = <<~FILE
72+
import { t } from 'ember-intl';
73+
74+
<template>
75+
<div data-test-message>
76+
{{t "hello.message" name=@name}}
77+
</div>
78+
</template>;
79+
FILE
80+
81+
assert_no_errors code
82+
end
83+
84+
it 'lexes a rendering test' do
85+
code = <<~FILE
86+
import { render } from '@ember/test-helpers';
87+
import { setupIntl } from 'ember-intl/test-support';
88+
import { setupRenderingTest } from 'ember-qunit';
89+
import Hello from 'my-app/components/hello';
90+
import { module, test } from 'qunit';
91+
92+
module('Integration | Component | hello', function (hooks) {
93+
setupRenderingTest(hooks);
94+
setupIntl(hooks, 'en-us');
95+
96+
test('it renders', async function (assert) {
97+
await render(
98+
<template>
99+
<Hello @name="Zoey" />
100+
</template>
101+
);
102+
103+
assert.dom('[data-test-message]').hasText('Hello, Zoey!');
104+
});
105+
});
106+
FILE
107+
108+
# Currently unable to assert with `assert_no_errors` because
109+
# the `handlebars` lexer doesn't support arguments like `@name`
110+
assert_tokens_includes code,
111+
["Name.Tag", "<"], ["Keyword", "template"], ["Name.Tag", ">"]
112+
end
113+
end
114+
end

spec/lexers/gts_spec.rb

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
# -*- coding: utf-8 -*- #
2+
# frozen_string_literal: true
3+
4+
describe Rouge::Lexers::Gts do
5+
let(:subject) { Rouge::Lexers::Gts.new }
6+
7+
describe 'guessing' do
8+
include Support::Guessing
9+
10+
it 'guesses by filename' do
11+
assert_guess :filename => 'app/components/ui/form.gts'
12+
assert_guess :filename => 'app/templates/form.gts'
13+
assert_guess :filename => 'tests/integration/components/ui/form-test.gts'
14+
end
15+
16+
it 'guesses by mimetype' do
17+
assert_guess :mimetype => 'text/x-gts'
18+
assert_guess :mimetype => 'application/x-gts'
19+
end
20+
end
21+
22+
describe 'lexing' do
23+
include Support::Lexing
24+
25+
it 'lexes the opening <template> tag' do
26+
assert_tokens_equal '<template>',
27+
["Name.Tag", "<"], ["Keyword", "template"], ["Name.Tag", ">"]
28+
end
29+
30+
it 'lexes the closing </template> tag' do
31+
assert_tokens_equal '<template></template>',
32+
["Name.Tag", "<"], ["Keyword", "template"], ["Name.Tag", "></"], ["Keyword", "template"], ["Name.Tag", ">"]
33+
assert_tokens_equal '<template> </template>',
34+
["Name.Tag", "<"], ["Keyword", "template"], ["Name.Tag", ">"], ["Text", " "], ["Name.Tag", "</"], ["Keyword", "template"], ["Name.Tag", ">"]
35+
end
36+
37+
it 'lexes a Glimmer component' do
38+
code = <<~FILE
39+
import Component from '@glimmer/component';
40+
import { t } from 'ember-intl';
41+
42+
interface HelloSignature {
43+
Args: {
44+
name: string;
45+
};
46+
}
47+
48+
export default class Hello extends Component<HelloSignature> {
49+
<template>
50+
<div data-test-message>
51+
{{t "hello.message" name=@name}}
52+
</div>
53+
</template>
54+
}
55+
FILE
56+
57+
assert_no_errors code
58+
end
59+
60+
it 'lexes a template-only component (1)' do
61+
code = <<~FILE
62+
import type { TOC } from '@ember/component/template-only';
63+
import { t } from 'ember-intl';
64+
65+
interface HelloSignature {
66+
Args: {
67+
name: string;
68+
};
69+
}
70+
71+
const Hello: TOC<HelloSignature> = <template>
72+
<div data-test-message>
73+
{{t "hello.message" name=@name}}
74+
</div>
75+
</template>;
76+
77+
export default Hello;
78+
FILE
79+
80+
assert_no_errors code
81+
end
82+
83+
it 'lexes a template-only component (2)' do
84+
code = <<~FILE
85+
import type { TOC } from '@ember/component/template-only';
86+
import { t } from 'ember-intl';
87+
88+
interface HelloSignature {
89+
Args: {
90+
name: string;
91+
};
92+
}
93+
94+
<template>
95+
<div data-test-message>
96+
{{t "hello.message" name=@name}}
97+
</div>
98+
</template> satisfies TOC<HelloSignature>;
99+
FILE
100+
101+
assert_no_errors code
102+
end
103+
104+
it 'lexes a rendering test' do
105+
code = <<~FILE
106+
import { render } from '@ember/test-helpers';
107+
import { setupIntl } from 'ember-intl/test-support';
108+
import { setupRenderingTest } from 'ember-qunit';
109+
import Hello from 'my-app/components/hello';
110+
import { module, test } from 'qunit';
111+
112+
module('Integration | Component | hello', function (hooks) {
113+
setupRenderingTest(hooks);
114+
setupIntl(hooks, 'en-us');
115+
116+
test('it renders', async function (assert) {
117+
await render(
118+
<template>
119+
<Hello @name="Zoey" />
120+
</template>
121+
);
122+
123+
assert.dom('[data-test-message]').hasText('Hello, Zoey!');
124+
});
125+
});
126+
FILE
127+
128+
# Currently unable to assert with `assert_no_errors` because
129+
# the `handlebars` lexer doesn't support arguments like `@name`
130+
assert_tokens_includes code,
131+
["Name.Tag", "<"], ["Keyword", "template"], ["Name.Tag", ">"]
132+
end
133+
end
134+
end

0 commit comments

Comments
 (0)