Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ and adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

### Fixes

- TypeScript classes with members named `abstract` or `accessor`, and auto-accessors with `static`, `override` or `abstract` modifiers, now index correctly; re-index TypeScript projects after upgrading.
- A TSX file with a broken JSX attribute such as `<a {{b:>c:d(e f)` no longer stalls indexing for about 90 seconds and then drops out of the index; it is indexed like any other file with a syntax error.
- In TypeScript, a template string with a `\u` or `\x` that has no hex digits after it, like LaTeX in ``String.raw`\usepackage{x}` ``, now parses. Before, the file had a parse error there and the template's tag, such as `String.raw`, was not recorded as a call. Re-index TypeScript projects after upgrading.
- TypeScript variance annotations like `interface Stream<out A, in E>` or `<in out T>` now parse. Before, each one was a parse error, and in a large file enough of them could make the whole file fail to parse and show up with almost no symbols. Re-index TypeScript projects after upgrading.
Expand Down
34 changes: 34 additions & 0 deletions __tests__/fixtures/ts-syntax/abstract-member-name.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// A member named `abstract` (upstream #377), next to `abstract` as a modifier.
export interface I {
abstract?: boolean;
abstract2: number;
abstract(): void;
}

export type T = { abstract: boolean; a: 1 };

export class C {
abstract = 1;
static abstract = 2;
abstract!: number;
abstract<T>(x: T) { return x; }
}

export class D {
abstract
x: number = 0;
}

export abstract class A {
abstract x: number;
abstract m?(): void;
protected abstract override n(): void;
abstract readonly r: number;
readonly abstract s: number;
abstract abstract: number;
abstract
y = 1;
}

export const abstract = { abstract: 1 };
export function f(abstract: number) { return abstract + 1; }
37 changes: 37 additions & 0 deletions __tests__/fixtures/ts-syntax/abstract-member-name.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// tsx twin of abstract-member-name.ts.
// A member named `abstract` (upstream #377), next to `abstract` as a modifier.
export interface I {
abstract?: boolean;
abstract2: number;
abstract(): void;
}

export type T = { abstract: boolean; a: 1 };

export class C {
abstract = 1;
static abstract = 2;
abstract!: number;
abstract<T>(x: T) { return x; }
}

export class D {
abstract
x: number = 0;
}

export abstract class A {
abstract x: number;
abstract m?(): void;
protected abstract override n(): void;
abstract readonly r: number;
readonly abstract s: number;
abstract abstract: number;
abstract
y = 1;
}

export const abstract = { abstract: 1 };
export function f(abstract: number) { return abstract + 1; }

export const el = <a abstract={abstract.abstract}>{f(1)}</a>;
23 changes: 23 additions & 0 deletions __tests__/fixtures/ts-syntax/accessor-member-name.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// `accessor` is a contextual keyword, so it remains a member name (upstream #369).
class AccessorMethods {
accessor() { return 1; }
static accessor() { return 2; }
get accessor() { return 3; }
}
class AccessorFields {
accessor;
accessor = 1;
accessor: number = 1;
accessor?: number;
accessor!: number;
readonly accessor = 1;
accessor accessor = 1;
}
interface AccessorInterface { accessor?: boolean; accessor(): void; }
type AccessorType = { accessor: number; accessor(): void; };
class AccessorLineBreak {
accessor
value = 1;
accessor
method() {}
}
23 changes: 23 additions & 0 deletions __tests__/fixtures/ts-syntax/accessor-member-name.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// `accessor` is a contextual keyword, so it remains a member name (upstream #369).
class AccessorMethods {
accessor() { return 1; }
static accessor() { return 2; }
get accessor() { return 3; }
}
class AccessorFields {
accessor;
accessor = 1;
accessor: number = 1;
accessor?: number;
accessor!: number;
readonly accessor = 1;
accessor accessor = 1;
}
interface AccessorInterface { accessor?: boolean; accessor(): void; }
type AccessorType = { accessor: number; accessor(): void; };
class AccessorLineBreak {
accessor
value = 1;
accessor
method() {}
}
15 changes: 15 additions & 0 deletions __tests__/fixtures/ts-syntax/accessor-modifiers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// Auto-accessors can follow other field modifiers (upstream #369).
class AccessorBase { accessor value = 1; }
class AccessorDerived extends AccessorBase {
static accessor shared = 1;
public static accessor publicShared: number = 2;
private static accessor privateShared = 3;
protected static accessor protectedShared = 4;
static accessor #hidden = 5;
override accessor value = 6;
public override accessor accessor = 7;
}
abstract class AbstractAccessors extends AccessorBase {
abstract accessor item: number;
protected abstract accessor protectedItem: number;
}
15 changes: 15 additions & 0 deletions __tests__/fixtures/ts-syntax/accessor-modifiers.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// Auto-accessors can follow other field modifiers (upstream #369).
class AccessorBase { accessor value = 1; }
class AccessorDerived extends AccessorBase {
static accessor shared = 1;
public static accessor publicShared: number = 2;
private static accessor privateShared = 3;
protected static accessor protectedShared = 4;
static accessor #hidden = 5;
override accessor value = 6;
public override accessor accessor = 7;
}
abstract class AbstractAccessors extends AccessorBase {
abstract accessor item: number;
protected abstract accessor protectedItem: number;
}
14 changes: 14 additions & 0 deletions __tests__/fixtures/ts-syntax/accessor-type-name.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// A contextual accessor keyword must retain its type-identifier interpretation.
type accessor = number;
type Parenthesized = (accessor);
let value: (accessor);
f<accessor>();
f<accessor>(0);
f<accessor>``;
class Derived extends Base<accessor> {}
type Qualified = accessor.Value;
f<accessor.Value>(0);
f<typeof accessor>();
f<typeof accessor>``;
const shorthand = { accessor };
const { accessor: renamed } = shorthand;
14 changes: 14 additions & 0 deletions __tests__/fixtures/ts-syntax/accessor-type-name.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// A contextual accessor keyword must retain its type-identifier interpretation.
type accessor = number;
type Parenthesized = (accessor);
let value: (accessor);
f<accessor>();
f<accessor>(0);
f<accessor>``;
class Derived extends Base<accessor> {}
type Qualified = accessor.Value;
f<accessor.Value>(0);
f<typeof accessor>();
f<typeof accessor>``;
const shorthand = { accessor };
const { accessor: renamed } = shorthand;
2 changes: 0 additions & 2 deletions __tests__/fixtures/ts-syntax/gap-static-accessor.ts

This file was deleted.

2 changes: 2 additions & 0 deletions __tests__/fixtures/ts-syntax/static-accessor.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
// Static auto-accessors (TS 4.9, upstream #369).
export class Config { static accessor shared = 1; }
76 changes: 76 additions & 0 deletions __tests__/typescript-grammar-fork-shapes.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,12 @@
* codegraph derives from it, so a later grammar move cannot regress them silently.
*/
import { describe, it, expect, beforeAll } from 'vitest';
import * as fs from 'node:fs';
import * as path from 'node:path';
import type { Node } from 'web-tree-sitter';
import { extractFromSource } from '../src/extraction';
import { initGrammars, loadGrammarsForLanguages, getParser } from '../src/extraction/grammars';
import { getKernel, decodeExtractBuffers } from '../src/extraction/kernel';

beforeAll(async () => {
await initGrammars();
Expand Down Expand Up @@ -148,3 +151,76 @@ describe('bare \\x / \\u escapes in templates (fork #15)', () => {
expect(parse(language, "const s = '\\x';").hasError).toBe(true);
});
});

describe('contextual abstract and accessor members (fork #12 and #13)', () => {
const members = 'export class Names { abstract = 1; accessor() { return helper(); } }';
const modifiers = [
'class Base { accessor value = 0; }',
'class Derived extends Base {',
' public static accessor shared = 1;',
' static accessor #hidden = 2;',
' override accessor value = 3;',
' @dec accessor decorated = 4;',
'}',
'abstract class Abstract { abstract accessor value: number; }',
].join('\n');

it.each(LANGS)('%s: keyword names retain member nodes and calls', (language) => {
const root = parse(language, members);
expect(root.hasError).toBe(false);
const body = root.descendantsOfType('class_body')[0]!;
expect(body.namedChildren.map((n) => [n.type, n.childForFieldName('name')?.text])).toEqual([
['public_field_definition', 'abstract'],
['method_definition', 'accessor'],
]);
const result = extractFromSource('src/names.' + (language === 'tsx' ? 'tsx' : 'ts'), members, language);
expect(result.nodes.filter((n) => n.kind === 'method').map((n) => n.name)).toContain('accessor');
expect(calls(language, members)).toContain('helper');
expect(parse(language, 'interface I { abstract(): void; accessor?: boolean; }').hasError).toBe(false);
});

it.each(LANGS)('%s: auto-accessors accept field modifiers and private names', (language) => {
const root = parse(language, modifiers);
expect(root.hasError).toBe(false);
expect(root.descendantsOfType('public_field_definition').map((n) => n.childForFieldName('name')?.text))
.toEqual(['value', 'shared', '#hidden', 'value', 'decorated', 'value']);
});

it.each(LANGS)('%s: bare names end at a newline; a signature continues the method', (language) => {
const root = parse(language, 'class C { abstract\n one = 1; accessor\n two = 2; accessor\n () {} }');
expect(root.hasError).toBe(false);
expect(root.descendantsOfType('class_body')[0]!.namedChildren
.map((n) => [n.type, n.childForFieldName('name')?.text])).toEqual([
['public_field_definition', 'abstract'],
['public_field_definition', 'one'],
['public_field_definition', 'accessor'],
['public_field_definition', 'two'],
['method_definition', 'accessor'],
]);
});

it.each(LANGS)('%s: accessor type arguments remain calls and shorthand remains a name', (language) => {
const src = 'type T = (accessor); type Q = accessor.Value; f<accessor>(0); f<typeof accessor>``; const o = { accessor };';
const root = parse(language, src);
expect(root.hasError).toBe(false);
expect(root.descendantsOfType('binary_expression')).toHaveLength(0);
expect(root.descendantsOfType('call_expression').map((n) => n.childForFieldName('type_arguments')?.type))
.toEqual(['type_arguments', 'type_arguments']);
expect(root.descendantsOfType('parenthesized_type')).toHaveLength(1);
expect(root.descendantsOfType('shorthand_property_identifier').map((n) => n.text)).toEqual(['accessor']);
expect(calls(language, src)).toEqual(['f', 'f']);
});

const kernelBuilt = fs.existsSync(path.join(__dirname, '..', 'codegraph-kernel', 'prebuilds',
`${process.platform}-${process.arch}`, 'codegraph-kernel.node'));
it.skipIf(!kernelBuilt).each(LANGS)('%s: native grammar handles these members without deferring', (language) => {
const kernel = getKernel();
expect(kernel).not.toBeNull();
const file = 'src/native.' + (language === 'tsx' ? 'tsx' : 'ts');
const result = decodeExtractBuffers(kernel!.extractFile(file, members + '\n' + modifiers, language), file, language);
expect(result.errors).toEqual([]);
expect(result.nodes.filter((n) => n.kind === 'method').map((n) => n.name)).toContain('accessor');
expect(result.unresolvedReferences.filter((r) => r.referenceKind === 'calls').map((r) => r.referenceName))
.toContain('helper');
});
});
4 changes: 2 additions & 2 deletions codegraph-kernel/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -126,8 +126,8 @@ fn main() {
// unedited. See docs/grammars/tree-sitter-typescript.md for what the base
// and the fork each fix.
// common/scanner.h 23fd656d7e82f80bf66e0b9b8ef96aa6102ef5bfd9f32c561a4caabe08b9fd34
// typescript/src/parser.c 2a0e260b20b549794d633422ad63b4783252b3bd497d06b531cc2a21405670ac
// tsx/src/parser.c 1634073b56b61987f8435b80d4a335166e6c05b55c143f301415b52b86fce142
// typescript/src/parser.c 2e19c6950b932128349565f8788f3672ab4242191aad2db359e3416e5c89d2cc
// tsx/src/parser.c f545ccfa32ef06243cc7ffff04223cd85ac045f8350d2e0064c6aef2eef8df53
// Flags crib the crate's bindings/rust/build.rs (c11). Each parser.c is
// ~9 MB.
for dialect in ["typescript", "tsx"] {
Expand Down
Loading
Loading