Compare commits

..

1 commit

Author SHA1 Message Date
Christian Clason
53a8a63a43 fix(wasm): add iswpunct to stdlib 2026-01-15 16:52:20 +01:00
26 changed files with 1516 additions and 1357 deletions

View file

@ -1,25 +0,0 @@
module.exports = async ({ github, context, core }) => {
if (context.eventName !== 'pull_request') return;
const prNumber = context.payload.pull_request.number;
const owner = context.repo.owner;
const repo = context.repo.repo;
const { data: files } = await github.rest.pulls.listFiles({
owner,
repo,
pull_number: prNumber
});
const changedFiles = files.map(file => file.filename);
const wasmStdLibSrc = 'crates/language/wasm/';
const dirChanged = changedFiles.some(file => file.startsWith(wasmStdLibSrc));
if (!dirChanged) return;
const wasmStdLibHeader = 'lib/src/wasm/wasm-stdlib.h';
const requiredChanged = changedFiles.includes(wasmStdLibHeader);
if (!requiredChanged) core.setFailed(`Changes detected in ${wasmStdLibSrc} but ${wasmStdLibHeader} was not modified.`);
};

View file

@ -44,6 +44,3 @@ jobs:
build:
uses: ./.github/workflows/build.yml
check-wasm-stdlib:
uses: ./.github/workflows/wasm_stdlib.yml

View file

@ -1,19 +0,0 @@
name: Check Wasm Stdlib build
on:
workflow_call:
jobs:
check:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v6
- name: Check directory changes
uses: actions/github-script@v8
with:
script: |
const scriptPath = `${process.env.GITHUB_WORKSPACE}/.github/scripts/wasm_stdlib.js`;
const script = require(scriptPath);
return script({ github, context, core });

8
Cargo.lock generated
View file

@ -187,9 +187,9 @@ checksum = "b35204fbdc0b3f4446b89fc1ac2cf84a8a68971995d0bf2e925ec7cd960f9cb3"
[[package]]
name = "cc"
version = "1.2.53"
version = "1.2.52"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "755d2fce177175ffca841e9a06afdb2c4ab0f593d53b4dee48147dfaade85932"
checksum = "cd4932aefd12402b36c60956a4fe0035421f544799057659ff86f923657aada3"
dependencies = [
"find-msvc-tools",
"shlex",
@ -664,9 +664,9 @@ dependencies = [
[[package]]
name = "find-msvc-tools"
version = "0.1.8"
version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8591b0bcc8a98a64310a2fae1bb3e9b8564dd10e381e6e28010fde8e8e8568db"
checksum = "f449e6c6c08c865631d4890cfacf252b3d396c9bcc83adb6623cdb02a8336c41"
[[package]]
name = "fnv"

View file

@ -106,7 +106,7 @@ ansi_colours = "1.2.3"
anstyle = "1.0.13"
anyhow = "1.0.100"
bstr = "1.12.0"
cc = "1.2.53"
cc = "1.2.52"
clap = { version = "4.5.54", features = [
"cargo",
"derive",

View file

@ -953,7 +953,7 @@ fn render_node_range(
fn cst_render_node(
opts: &ParseFileOptions,
cursor: &TreeCursor,
cursor: &mut TreeCursor,
source_code: &[u8],
out: &mut impl Write,
total_width: usize,

View file

@ -19,8 +19,7 @@
--light-scrollbar-track: #f1f1f1;
--light-scrollbar-thumb: #c1c1c1;
--light-scrollbar-thumb-hover: #a8a8a8;
--light-tree-row-bg: #e3f2fd;
--dark-bg: #1d1f21;
--dark-border: #2d2d2d;
--dark-text: #c5c8c6;
@ -29,7 +28,6 @@
--dark-scrollbar-track: #25282c;
--dark-scrollbar-thumb: #4a4d51;
--dark-scrollbar-thumb-hover: #5a5d61;
--dark-tree-row-bg: #373737;
--primary-color: #0550ae;
--primary-color-alpha: rgba(5, 80, 174, 0.1);
@ -44,7 +42,6 @@
--text-color: var(--dark-text);
--panel-bg: var(--dark-panel-bg);
--code-bg: var(--dark-code-bg);
--tree-row-bg: var(--dark-tree-row-bg);
}
[data-theme="light"] {
@ -53,7 +50,6 @@
--text-color: var(--light-text);
--panel-bg: white;
--code-bg: white;
--tree-row-bg: var(--light-tree-row-bg);
}
/* Base Styles */
@ -279,7 +275,7 @@
}
#output-container a.highlighted {
background-color: #cae2ff;
background-color: #d9d9d9;
color: red;
border-radius: 3px;
text-decoration: underline;
@ -350,7 +346,7 @@
}
& #output-container a.highlighted {
background-color: #656669;
background-color: #373b41;
color: red;
}
@ -377,9 +373,6 @@
color: var(--dark-text);
}
}
.tree-row:has(.highlighted) {
background-color: var(--tree-row-bg);
}
</style>
</head>

View file

@ -225,7 +225,7 @@ impl Pattern {
}
// Find every matching combination of child patterns and child nodes.
let mut finished_matches = Vec::<Match<'_, 'tree>>::new();
let mut finished_matches = Vec::<Match>::new();
if cursor.goto_first_child() {
let mut match_states = vec![(0, mat)];
loop {

View file

@ -215,11 +215,11 @@ fn try_resolve_path(path: &Path) -> rquickjs::Result<PathBuf> {
}
#[allow(clippy::needless_pass_by_value)]
fn require_from_module<'js>(
ctx: Ctx<'js>,
fn require_from_module<'a>(
ctx: Ctx<'a>,
module_path: String,
from_module: &str,
) -> rquickjs::Result<Value<'js>> {
) -> rquickjs::Result<Value<'a>> {
let current_module = PathBuf::from(from_module);
let current_dir = if current_module.is_file() {
current_module.parent().unwrap_or(Path::new("."))
@ -234,13 +234,13 @@ fn require_from_module<'js>(
load_module_from_content(&ctx, &resolved_path, &contents)
}
fn load_module_from_content<'js>(
ctx: &Ctx<'js>,
fn load_module_from_content<'a>(
ctx: &Ctx<'a>,
path: &Path,
contents: &str,
) -> rquickjs::Result<Value<'js>> {
) -> rquickjs::Result<Value<'a>> {
if path.extension().is_some_and(|ext| ext == "json") {
return ctx.eval::<Value<'js>, _>(format!("JSON.parse({contents:?})"));
return ctx.eval::<Value, _>(format!("JSON.parse({contents:?})"));
}
let exports = Object::new(ctx.clone())?;
@ -256,7 +256,7 @@ fn load_module_from_content<'js>(
let module_path = filename.clone();
let require = Function::new(
ctx.clone(),
move |ctx_inner: Ctx<'js>, target_path: String| -> rquickjs::Result<Value<'js>> {
move |ctx_inner: Ctx<'a>, target_path: String| -> rquickjs::Result<Value<'a>> {
require_from_module(ctx_inner, target_path, &module_path)
},
)?;
@ -264,8 +264,8 @@ fn load_module_from_content<'js>(
let wrapper =
format!("(function(exports, require, module, __filename, __dirname) {{ {contents} }})");
let module_func = ctx.eval::<Function<'js>, _>(wrapper)?;
module_func.call::<_, Value<'js>>((exports, require, module_obj.clone(), filename, dirname))?;
let module_func = ctx.eval::<Function, _>(wrapper)?;
module_func.call::<_, Value>((exports, require, module_obj.clone(), filename, dirname))?;
module_obj.get("exports")
}

View file

@ -189,7 +189,7 @@ struct HighlightIterLayer<'a> {
depth: usize,
}
pub struct _QueryCaptures<'query, 'tree, T: TextProvider<I>, I: AsRef<[u8]>> {
pub struct _QueryCaptures<'query, 'tree: 'query, T: TextProvider<I>, I: AsRef<[u8]>> {
ptr: *mut ffi::TSQueryCursor,
query: &'query Query,
text_provider: T,
@ -225,7 +225,7 @@ impl<'tree> _QueryMatch<'_, 'tree> {
}
}
impl<'query, 'tree, T: TextProvider<I>, I: AsRef<[u8]>> Iterator
impl<'query, 'tree: 'query, T: TextProvider<I>, I: AsRef<[u8]>> Iterator
for _QueryCaptures<'query, 'tree, T, I>
{
type Item = (QueryMatch<'query, 'tree>, usize);
@ -594,7 +594,6 @@ impl<'a> HighlightIterLayer<'a> {
}
}
// SAFETY:
// The `captures` iterator borrows the `Tree` and the `QueryCursor`, which
// prevents them from being moved. But both of these values are really just
// pointers, so it's actually ok to move them.

View file

@ -165,4 +165,44 @@ static inline bool iswspace(wint_t wch) {
}
}
static inline bool iswpunct(wint_t wch) {
switch (wch) {
case L'!':
case L'"':
case L'#':
case L'$':
case L'%':
case L'&':
case L'\'':
case L'(':
case L')':
case L'*':
case L'+':
case L',':
case L'-':
case L'.':
case L'/':
case L':':
case L';':
case L'<':
case L'=':
case L'>':
case L'?':
case L'@':
case L'[':
case L'\\':
case L']':
case L'^':
case L'_':
case L'`':
case L'{':
case L'|':
case L'}':
case L'~':
return true;
default:
return false;
}
}
#endif // TREE_SITTER_WASM_WCTYPE_H_

View file

@ -765,7 +765,7 @@ impl Loader {
}
#[must_use]
pub fn get_all_language_configurations(&self) -> Vec<(&LanguageConfiguration<'static>, &Path)> {
pub fn get_all_language_configurations(&self) -> Vec<(&LanguageConfiguration, &Path)> {
self.language_configurations
.iter()
.map(|c| (c, self.languages_by_id[c.language_id].0.as_ref()))
@ -775,7 +775,7 @@ impl Loader {
pub fn language_configuration_for_scope(
&self,
scope: &str,
) -> LoaderResult<Option<(Language, &LanguageConfiguration<'static>)>> {
) -> LoaderResult<Option<(Language, &LanguageConfiguration)>> {
for configuration in &self.language_configurations {
if configuration.scope.as_ref().is_some_and(|s| s == scope) {
let language = self.language_for_id(configuration.language_id)?;
@ -788,7 +788,7 @@ impl Loader {
pub fn language_configuration_for_first_line_regex(
&self,
path: &Path,
) -> LoaderResult<Option<(Language, &LanguageConfiguration<'static>)>> {
) -> LoaderResult<Option<(Language, &LanguageConfiguration)>> {
self.language_configuration_ids_by_first_line_regex
.iter()
.try_fold(None, |_, (regex, ids)| {
@ -817,7 +817,7 @@ impl Loader {
pub fn language_configuration_for_file_name(
&self,
path: &Path,
) -> LoaderResult<Option<(Language, &LanguageConfiguration<'static>)>> {
) -> LoaderResult<Option<(Language, &LanguageConfiguration)>> {
// Find all the language configurations that match this file name
// or a suffix of the file name.
let configuration_ids = path
@ -889,7 +889,7 @@ impl Loader {
pub fn language_configuration_for_injection_string(
&self,
string: &str,
) -> LoaderResult<Option<(Language, &LanguageConfiguration<'static>)>> {
) -> LoaderResult<Option<(Language, &LanguageConfiguration)>> {
let mut best_match_length = 0;
let mut best_match_position = None;
for (i, configuration) in self.language_configurations.iter().enumerate() {
@ -1305,11 +1305,6 @@ impl Loader {
}));
}
}
} else {
warn!(
"Failed to run `nm` to verify symbols in {}",
library_path.display()
);
}
Ok(())
@ -1539,9 +1534,7 @@ impl Loader {
}
#[must_use]
pub fn get_language_configuration_in_current_path(
&self,
) -> Option<&LanguageConfiguration<'static>> {
pub fn get_language_configuration_in_current_path(&self) -> Option<&LanguageConfiguration> {
self.language_configuration_in_current_path
.map(|i| &self.language_configurations[i])
}
@ -1550,7 +1543,7 @@ impl Loader {
&mut self,
parser_path: &Path,
set_current_path_config: bool,
) -> LoaderResult<&[LanguageConfiguration<'static>]> {
) -> LoaderResult<&[LanguageConfiguration]> {
let initial_language_configuration_count = self.language_configurations.len();
match TreeSitterJSON::from_file(parser_path) {

View file

@ -313,7 +313,6 @@ impl TagsContext {
)
.ok_or(Error::Cancelled)?;
// SAFETY:
// The `matches` iterator borrows the `Tree`, which prevents it from being
// moved. But the tree is really just a pointer, so it's actually ok to
// move it.

View file

@ -317,7 +317,7 @@ pub trait Decode {
/// A stateful object for walking a syntax [`Tree`] efficiently.
#[doc(alias = "TSTreeCursor")]
pub struct TreeCursor<'tree>(ffi::TSTreeCursor, PhantomData<&'tree ()>);
pub struct TreeCursor<'cursor>(ffi::TSTreeCursor, PhantomData<&'cursor ()>);
/// A set of patterns that match nodes in a syntax tree.
#[doc(alias = "TSQuery")]
@ -392,7 +392,7 @@ pub struct QueryMatch<'cursor, 'tree> {
}
/// A sequence of [`QueryMatch`]es associated with a given [`QueryCursor`].
pub struct QueryMatches<'query, 'tree, T: TextProvider<I>, I: AsRef<[u8]>> {
pub struct QueryMatches<'query, 'tree: 'query, T: TextProvider<I>, I: AsRef<[u8]>> {
ptr: *mut ffi::TSQueryCursor,
query: &'query Query,
text_provider: T,
@ -407,7 +407,7 @@ pub struct QueryMatches<'query, 'tree, T: TextProvider<I>, I: AsRef<[u8]>> {
///
/// During iteration, each element contains a [`QueryMatch`] and index. The index can
/// be used to access the new capture inside of the [`QueryMatch::captures`]'s [`captures`].
pub struct QueryCaptures<'query, 'tree, T: TextProvider<I>, I: AsRef<[u8]>> {
pub struct QueryCaptures<'query, 'tree: 'query, T: TextProvider<I>, I: AsRef<[u8]>> {
ptr: *mut ffi::TSQueryCursor,
query: &'query Query,
text_provider: T,
@ -1581,7 +1581,7 @@ impl<'tree> Node<'tree> {
/// Get the [`Language`] that was used to parse this node's syntax tree.
#[doc(alias = "ts_node_language")]
#[must_use]
pub fn language(&self) -> LanguageRef<'tree> {
pub fn language(&self) -> LanguageRef {
LanguageRef(unsafe { ffi::ts_node_language(self.0) }, PhantomData)
}
@ -2082,11 +2082,11 @@ impl fmt::Display for Node<'_> {
}
}
impl<'tree> TreeCursor<'tree> {
impl<'cursor> TreeCursor<'cursor> {
/// Get the tree cursor's current [`Node`].
#[doc(alias = "ts_tree_cursor_current_node")]
#[must_use]
pub fn node(&self) -> Node<'tree> {
pub fn node(&self) -> Node<'cursor> {
Node(
unsafe { ffi::ts_tree_cursor_current_node(&self.0) },
PhantomData,
@ -2227,7 +2227,7 @@ impl<'tree> TreeCursor<'tree> {
/// Re-initialize this tree cursor to start at the original node that the
/// cursor was constructed with.
#[doc(alias = "ts_tree_cursor_reset")]
pub fn reset(&mut self, node: Node<'tree>) {
pub fn reset(&mut self, node: Node<'cursor>) {
unsafe { ffi::ts_tree_cursor_reset(&mut self.0, node.0) };
}
@ -3404,7 +3404,7 @@ impl QueryProperty {
/// Provide a `StreamingIterator` instead of the traditional `Iterator`, as the
/// underlying object in the C library gets updated on each iteration. Copies would
/// have their internal state overwritten, leading to Undefined Behavior
impl<'query, 'tree, T: TextProvider<I>, I: AsRef<[u8]>> StreamingIterator
impl<'query, 'tree: 'query, T: TextProvider<I>, I: AsRef<[u8]>> StreamingIterator
for QueryMatches<'query, 'tree, T, I>
{
type Item = QueryMatch<'query, 'tree>;
@ -3435,13 +3435,15 @@ impl<'query, 'tree, T: TextProvider<I>, I: AsRef<[u8]>> StreamingIterator
}
}
impl<T: TextProvider<I>, I: AsRef<[u8]>> StreamingIteratorMut for QueryMatches<'_, '_, T, I> {
impl<'query, 'tree: 'query, T: TextProvider<I>, I: AsRef<[u8]>> StreamingIteratorMut
for QueryMatches<'query, 'tree, T, I>
{
fn get_mut(&mut self) -> Option<&mut Self::Item> {
self.current_match.as_mut()
}
}
impl<'query, 'tree, T: TextProvider<I>, I: AsRef<[u8]>> StreamingIterator
impl<'query, 'tree: 'query, T: TextProvider<I>, I: AsRef<[u8]>> StreamingIterator
for QueryCaptures<'query, 'tree, T, I>
{
type Item = (QueryMatch<'query, 'tree>, usize);
@ -3478,7 +3480,9 @@ impl<'query, 'tree, T: TextProvider<I>, I: AsRef<[u8]>> StreamingIterator
}
}
impl<T: TextProvider<I>, I: AsRef<[u8]>> StreamingIteratorMut for QueryCaptures<'_, '_, T, I> {
impl<'query, 'tree: 'query, T: TextProvider<I>, I: AsRef<[u8]>> StreamingIteratorMut
for QueryCaptures<'query, 'tree, T, I>
{
fn get_mut(&mut self) -> Option<&mut Self::Item> {
self.current_match.as_mut()
}
@ -3618,8 +3622,8 @@ impl From<ffi::TSRange> for Range {
}
}
impl From<&InputEdit> for ffi::TSInputEdit {
fn from(val: &InputEdit) -> Self {
impl From<&'_ InputEdit> for ffi::TSInputEdit {
fn from(val: &'_ InputEdit) -> Self {
Self {
start_byte: val.start_byte as u32,
old_end_byte: val.old_end_byte as u32,

View file

@ -182,6 +182,7 @@ interface WasmModule {
_memmove(_0: number, _1: number, _2: number): number;
_iswalpha(_0: number): number;
_iswblank(_0: number): number;
_iswpunct(_0: number): number;
_iswdigit(_0: number): number;
_iswlower(_0: number): number;
_iswupper(_0: number): number;

View file

@ -1,8 +0,0 @@
export function newFinalizer<T>(handler: (value: T) => void): FinalizationRegistry<T> | undefined {
try {
return new FinalizationRegistry(handler);
} catch(e) {
console.error('Unsupported FinalizationRegistry:', e);
return;
}
}

View file

@ -1,10 +1,5 @@
import { C, Internal, assertInternal } from './constants';
import { Language } from './language';
import { newFinalizer } from './finalization_registry';
const finalizer = newFinalizer((address: number) => {
C._ts_lookahead_iterator_delete(address);
});
export class LookaheadIterator implements Iterable<string> {
/** @internal */
@ -18,7 +13,6 @@ export class LookaheadIterator implements Iterable<string> {
assertInternal(internal);
this[0] = address;
this.language = language;
finalizer?.register(this, address, this);
}
/** Get the current symbol of the lookahead iterator. */
@ -33,7 +27,6 @@ export class LookaheadIterator implements Iterable<string> {
/** Delete the lookahead iterator, freeing its resources. */
delete(): void {
finalizer?.unregister(this);
C._ts_lookahead_iterator_delete(this[0]);
this[0] = 0;
}

View file

@ -3,7 +3,6 @@ import { Language } from './language';
import { marshalRange, unmarshalRange } from './marshal';
import { checkModule, initializeBinding } from './bindings';
import { Tree } from './tree';
import { newFinalizer } from './finalization_registry';
/**
* Options for parsing
@ -83,11 +82,6 @@ export let LANGUAGE_VERSION: number;
*/
export let MIN_COMPATIBLE_VERSION: number;
const finalizer = newFinalizer((addresses: number[]) => {
C._ts_parser_delete(addresses[0]);
C._free(addresses[1]);
});
/**
* A stateful object that is used to produce a {@link Tree} based on some
* source code.
@ -123,7 +117,6 @@ export class Parser {
*/
constructor() {
this.initialize();
finalizer?.register(this, [this[0], this[1]], this);
}
/** @internal */
@ -138,7 +131,6 @@ export class Parser {
/** Delete the parser, freeing its resources. */
delete() {
finalizer?.unregister(this);
C._ts_parser_delete(this[0]);
C._free(this[1]);
this[0] = 0;

View file

@ -3,7 +3,6 @@ import { Node } from './node';
import { marshalNode, unmarshalCaptures } from './marshal';
import { TRANSFER_BUFFER } from './parser';
import { Language } from './language';
import { newFinalizer } from './finalization_registry';
const PREDICATE_STEP_TYPE_CAPTURE = 1;
@ -507,10 +506,6 @@ function parsePattern(
}
}
const finalizer = newFinalizer((address: number) => {
C._ts_query_delete(address);
});
export class Query {
/** @internal */
private [0] = 0; // Internal handle for Wasm
@ -692,12 +687,10 @@ export class Query {
this.assertedProperties = assertedProperties;
this.refutedProperties = refutedProperties;
this.exceededMatchLimit = false;
finalizer?.register(this, address, this);
}
/** Delete the query, freeing its resources. */
delete(): void {
finalizer?.unregister(this);
C._ts_query_delete(this[0]);
this[0] = 0;
}

View file

@ -5,7 +5,6 @@ import { TreeCursor } from './tree_cursor';
import { marshalEdit, marshalPoint, unmarshalNode, unmarshalRange } from './marshal';
import { TRANSFER_BUFFER } from './parser';
import { Edit } from './edit';
import { newFinalizer } from './finalization_registry';
/** @internal */
export function getText(tree: Tree, startIndex: number, endIndex: number, startPosition: Point): string {
@ -29,10 +28,6 @@ export function getText(tree: Tree, startIndex: number, endIndex: number, startP
return result ?? '';
}
const finalizer = newFinalizer((address: number) => {
C._ts_tree_delete(address);
});
/** A tree that represents the syntactic structure of a source code file. */
export class Tree {
/** @internal */
@ -50,7 +45,6 @@ export class Tree {
this[0] = address;
this.language = language;
this.textCallback = textCallback;
finalizer?.register(this, address, this);
}
/** Create a shallow copy of the syntax tree. This is very fast. */
@ -61,7 +55,6 @@ export class Tree {
/** Delete the syntax tree, freeing its resources. */
delete(): void {
finalizer?.unregister(this);
C._ts_tree_delete(this[0]);
this[0] = 0;
}

View file

@ -3,11 +3,6 @@ import { marshalNode, marshalPoint, marshalTreeCursor, unmarshalNode, unmarshalP
import { Node } from './node';
import { TRANSFER_BUFFER } from './parser';
import { getText, Tree } from './tree';
import { newFinalizer } from './finalization_registry';
const finalizer = newFinalizer((address: number) => {
C._ts_tree_cursor_delete_wasm(address);
});
/** A stateful object for walking a syntax {@link Tree} efficiently. */
export class TreeCursor {
@ -35,7 +30,6 @@ export class TreeCursor {
assertInternal(internal);
this.tree = tree;
unmarshalTreeCursor(this);
finalizer?.register(this, this.tree[0], this);
}
/** Creates a deep copy of the tree cursor. This allocates new memory. */
@ -48,7 +42,6 @@ export class TreeCursor {
/** Delete the tree cursor, freeing its resources. */
delete(): void {
finalizer?.unregister(this);
marshalTreeCursor(this);
C._ts_tree_cursor_delete_wasm(this.tree[0]);
this[0] = this[1] = this[2] = 0;

View file

@ -1,74 +0,0 @@
import { describe, expect, it } from 'vitest';
import { gc, event, Finalizer } from './memory';
// hijack finalization registry before import web-tree-sitter
globalThis.FinalizationRegistry = Finalizer;
describe('Memory Management', () => {
describe('call .delete()', () => {
it('test free memory manually', async () => {
const timer = setInterval(() => {
gc();
}, 100);
let done = 0;
event.on('gc', () => {
done++;
});
await (async () => {
const { JavaScript } = await (await import('./helper')).default;
const { Parser, Query } = await import('../src');
const parser = new Parser();
parser.setLanguage(JavaScript);
const tree = parser.parse('1+1')!;
const copyTree = tree.copy();
const cursor = tree.walk();
const copyCursor = cursor.copy();
const lookaheadIterator = JavaScript.lookaheadIterator(cursor.currentNode.nextParseState)!;
const query = new Query(JavaScript, '(identifier) @element');
parser.delete();
tree.delete();
copyTree.delete();
cursor.delete();
copyCursor.delete();
lookaheadIterator.delete();
query.delete();
})();
// wait for gc
await new Promise((resolve) => setTimeout(resolve, 1000));
clearInterval(timer);
// expect no gc event fired
expect(done).toBe(0);
});
});
describe('do not call .delete()', () => {
it('test free memory automatically', async () => {
const timer = setInterval(() => {
gc();
}, 100);
let done = 0;
const promise = new Promise((resolve) => {
event.on('gc', () => {
if (++done === 7) {
resolve(undefined);
clearInterval(timer);
}
console.log('free memory times: ', done);
});
});
await (async () => {
const { JavaScript } = await (await import('./helper')).default;
const { Parser, Query } = await import('../src');
const parser = new Parser(); // 1
parser.setLanguage(JavaScript);
const tree = parser.parse('1+1')!; // 2
tree.copy(); // 3
const cursor = tree.walk(); // 4
cursor.copy(); // 5
JavaScript.lookaheadIterator(cursor.currentNode.nextParseState)!; // 6
new Query(JavaScript, '(identifier) @element'); // 7
})();
await promise;
});
});
});

View file

@ -1,20 +0,0 @@
import { EventEmitter } from 'events';
import { Session } from 'inspector';
const session = new Session();
session.connect();
export function gc() {
session.post('HeapProfiler.collectGarbage');
}
export const event = new EventEmitter();
export class Finalizer<T> extends FinalizationRegistry<T> {
constructor(handler: (value: T) => void) {
super((value) => {
handler(value);
event.emit('gc');
});
}
}

View file

@ -1,25 +0,0 @@
import { describe, it } from 'vitest';
describe('FinalizationRegistry is unsupported', () => {
it('test FinalizationRegistry is unsupported', async () => {
// @ts-expect-error: test FinalizationRegistry is not supported
globalThis.FinalizationRegistry = undefined;
const { JavaScript } = await (await import('./helper')).default;
const { Parser, Query } = await import('../src');
const parser = new Parser();
parser.setLanguage(JavaScript);
const tree = parser.parse('1+1')!;
const copyTree = tree.copy();
const cursor = tree.walk();
const copyCursor = cursor.copy();
const lookaheadIterator = JavaScript.lookaheadIterator(cursor.currentNode.nextParseState)!;
const query = new Query(JavaScript, '(identifier) @element');
parser.delete();
tree.delete();
copyTree.delete();
cursor.delete();
copyCursor.delete();
lookaheadIterator.delete();
query.delete();
});
});

View file

@ -5,6 +5,7 @@
"iswblank",
"iswdigit",
"iswlower",
"iswpunct",
"iswspace",
"iswupper",
"iswxdigit",

File diff suppressed because it is too large Load diff