diff --git a/queries/highlights.scm b/queries/highlights.scm index d0daa88..ea1db07 100644 --- a/queries/highlights.scm +++ b/queries/highlights.scm @@ -63,6 +63,10 @@ (attribute attribute: (identifier) @property) (type (identifier) @type) +(splat_type (identifier) @type) +(class_definition + superclasses: (argument_list + (subscript subscript: (list_splat (identifier) @type)))) (member_type (identifier) @type) (generic_type (identifier) @type) (default_type_parameter name: (identifier) @type) diff --git a/test/fixtures/highlights/lazyImports.py b/test/fixtures/highlights/lazyImports.py new file mode 100644 index 0000000..c632911 --- /dev/null +++ b/test/fixtures/highlights/lazyImports.py @@ -0,0 +1,4 @@ +lazy import json +lazy from json import dumps +lazy = 1 +import lazy diff --git a/test/fixtures/highlights/typeParameterDefaults.py b/test/fixtures/highlights/typeParameterDefaults.py new file mode 100644 index 0000000..a7458c0 --- /dev/null +++ b/test/fixtures/highlights/typeParameterDefaults.py @@ -0,0 +1,14 @@ +class C[T: list[dict[str, int]] = list[dict[str, int]]]: pass +def f[*Ts = *tuple[int, str], **P = [str]](): pass +type A[T: (int, str) = list[int]] = T +class D[T: list[int] | None = list[str] | None]: pass +class E[T: int | str = typing.List[int]]: pass +class F[T = typing.Dict[str, typing.List[int | str]]]: pass +class G[T = (list[int] | None), **P = [typing.List[str] | None]]: pass +class H[T = int | str | None, *Ts = *typing.Tuple[int, str]]: pass +class I[*Ts = *(typing.Tuple[int] | tuple[str])]: pass +class J[T: typing.Protocol = typing.List[int]]: pass +class K[T = tuple[*tuple[int]]]: pass +class L[T = match[int]]: pass +class M[T = lazy.List[int]]: pass +class N[T = pkg.types.Value, U = other.models.Box[Leaf]]: pass diff --git a/test/fixtures/highlights/variadicTypes.py b/test/fixtures/highlights/variadicTypes.py new file mode 100644 index 0000000..3f27aa5 --- /dev/null +++ b/test/fixtures/highlights/variadicTypes.py @@ -0,0 +1,11 @@ +class C[*Ts]: pass +def f[**P](): pass +def g[*Vs](*args: *Vs): pass +type A[*Us] = tuple[*Us] +class B(Generic[*Ts]): pass +class D(typing.Generic[*Us]): pass +Ts = 1 +P = 2 +values = [*Vs] +lookup = table[*keys] +register(Generic[*idx]) diff --git a/test/helpers/renderHighlights.ts b/test/helpers/renderHighlights.ts deleted file mode 100644 index f61d591..0000000 --- a/test/helpers/renderHighlights.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { execFileSync } from 'node:child_process'; -import fs from 'node:fs'; -import path from 'node:path'; - -export function renderHighlights(source: string): string { - const root = path.resolve(import.meta.dirname, '../..'); - fs.mkdirSync(path.join(root, '.tmp'), { recursive: true }); - const directory = fs.mkdtempSync(path.join(root, '.tmp/highlights-')); - const input = path.join(directory, 'input.py'); - try { - fs.writeFileSync(input, source); - return execFileSync( - path.join(root, 'script/tree-sitter'), - ['highlight', '-p', root, '-H', '--layout', 'fragment', '--style', 'minimal', input], - { - cwd: root, - encoding: 'utf8', - timeout: 170_000, - env: { ...process.env, TREE_SITTER_LIBDIR: path.join(root, '.tmp/tree-sitter-lib') }, - } - ); - } finally { - fs.rmSync(directory, { recursive: true, force: true }); - } -} diff --git a/test/unit/lazyImports.test.ts b/test/unit/lazyImports.test.ts index 80ed8c5..0e7287b 100644 --- a/test/unit/lazyImports.test.ts +++ b/test/unit/lazyImports.test.ts @@ -1,26 +1,25 @@ -import { execFileSync } from 'node:child_process'; -import fs from 'node:fs'; -import path from 'node:path'; -import { expect, test } from 'vitest'; +import { expect } from 'vitest'; +import { testCommand } from './run.js'; -test('highlights lazy only as an import modifier', () => { - const root = path.resolve(import.meta.dirname, '../..'); - const directory = fs.mkdtempSync(path.join(root, '.tmp/lazy-highlights-')); - const input = path.join(directory, 'imports.py'); - try { - fs.writeFileSync(input, 'lazy import json\nlazy from json import dumps\nlazy = 1\nimport lazy\n'); - const html = execFileSync( - path.join(root, 'script/tree-sitter'), - ['highlight', '-p', root, '-H', '--layout', 'fragment', '--style', 'minimal', input], - { - cwd: root, - encoding: 'utf8', - env: { ...process.env, TREE_SITTER_LIBDIR: path.join(root, '.tmp/tree-sitter-lib') }, - } - ); - const classes = [...html.matchAll(/lazy<\/span>/g)].map(([, capture]) => capture); - expect(classes).toEqual(['keyword', 'keyword', 'variable', 'variable']); - } finally { - fs.rmSync(directory, { recursive: true, force: true }); +testCommand( + 'highlights lazy only as an import modifier', + [ + 'script/tree-sitter', + 'highlight', + '-p', + '.', + '-H', + '--layout', + 'fragment', + '--style', + 'minimal', + 'test/fixtures/highlights/lazyImports.py', + ], + 170_000, + { + check: (html) => { + const classes = [...html.matchAll(/lazy<\/span>/g)].map(([, capture]) => capture); + expect(classes).toEqual(['keyword', 'keyword', 'variable', 'variable']); + }, } -}, 180_000); +); diff --git a/test/unit/typeParameterHighlights.test.ts b/test/unit/typeParameterHighlights.test.ts index 39f3a36..0884dd8 100644 --- a/test/unit/typeParameterHighlights.test.ts +++ b/test/unit/typeParameterHighlights.test.ts @@ -2,7 +2,7 @@ import fs from 'node:fs'; import path from 'node:path'; import { Language, Parser, Query } from '@willbooster/web-tree-sitter'; import { expect, test } from 'vitest'; -import { renderHighlights } from '../helpers/renderHighlights.js'; +import { testCommand } from './run.js'; test('highlights defaulted type parameter names, bounds, and direct defaults as types', async () => { await Parser.init(); @@ -34,31 +34,31 @@ test('highlights defaulted type parameter names, bounds, and direct defaults as } }); -test('renders nested default and bound type names with the type highlight', () => { - const html = renderHighlights( - [ - 'class C[T: list[dict[str, int]] = list[dict[str, int]]]: pass', - 'def f[*Ts = *tuple[int, str], **P = [str]](): pass', - 'type A[T: (int, str) = list[int]] = T', - 'class D[T: list[int] | None = list[str] | None]: pass', - 'class E[T: int | str = typing.List[int]]: pass', - 'class F[T = typing.Dict[str, typing.List[int | str]]]: pass', - 'class G[T = (list[int] | None), **P = [typing.List[str] | None]]: pass', - 'class H[T = int | str | None, *Ts = *typing.Tuple[int, str]]: pass', - 'class I[*Ts = *(typing.Tuple[int] | tuple[str])]: pass', - 'class J[T: typing.Protocol = typing.List[int]]: pass', - 'class K[T = tuple[*tuple[int]]]: pass', - 'class L[T = match[int]]: pass', - 'class M[T = lazy.List[int]]: pass', - 'class N[T = pkg.types.Value, U = other.models.Box[Leaf]]: pass', - ].join('\n') - ); - expect(html.match(/(?:int|str)<\/span>/g)).toHaveLength(30); - expect(html).not.toMatch(/(?:int|str)<\/span>/); - expect(html).toContain("Protocol"); - expect(html).toContain("List"); - for (const name of ['typing', 'lazy', 'pkg', 'types', 'Value', 'other', 'models', 'Box', 'Leaf']) { - expect(html).toContain(`${name}`); - expect(html).not.toContain(`${name}`); +testCommand( + 'renders nested default and bound type names with the type highlight', + [ + 'script/tree-sitter', + 'highlight', + '-p', + '.', + '-H', + '--layout', + 'fragment', + '--style', + 'minimal', + 'test/fixtures/highlights/typeParameterDefaults.py', + ], + 170_000, + { + check: (html) => { + expect(html.match(/(?:int|str)<\/span>/g)).toHaveLength(30); + expect(html).not.toMatch(/(?:int|str)<\/span>/); + expect(html).toContain("Protocol"); + expect(html).toContain("List"); + for (const name of ['typing', 'lazy', 'pkg', 'types', 'Value', 'other', 'models', 'Box', 'Leaf']) { + expect(html).toContain(`${name}`); + expect(html).not.toContain(`${name}`); + } + }, } -}, 180_000); +); diff --git a/test/unit/variadicTypeHighlights.test.ts b/test/unit/variadicTypeHighlights.test.ts new file mode 100644 index 0000000..76f4e13 --- /dev/null +++ b/test/unit/variadicTypeHighlights.test.ts @@ -0,0 +1,38 @@ +import { expect } from 'vitest'; +import { testCommand } from './run.js'; + +testCommand( + 'renders variadic type parameter names as types', + [ + 'script/tree-sitter', + 'highlight', + '-p', + '.', + '-H', + '--layout', + 'fragment', + '--style', + 'minimal', + 'test/fixtures/highlights/variadicTypes.py', + ], + 170_000, + { + check: (html) => { + const highlights = [...html.matchAll(/(Ts|P|Us|Vs|keys|idx)<\/span>/g)].map( + ([, kind, name]) => [name, kind] + ); + expect(highlights.slice(0, 8)).toEqual([ + ['Ts', 'type'], + ['P', 'type'], + ['Vs', 'type'], + ['Vs', 'type'], + ['Us', 'type'], + ['Us', 'type'], + ['Ts', 'type'], + ['Us', 'type'], + ]); + expect(highlights.slice(8).map(([name]) => name)).toEqual(['Ts', 'P', 'Vs', 'keys', 'idx']); + expect(highlights.slice(8).map(([, kind]) => kind)).not.toContain('type'); + }, + } +);