feat(web): add missing API functions

Co-authored-by: Will Lillis <will.lillis24@gmail.com>
This commit is contained in:
Amaan Qureshi 2025-01-05 22:06:33 -05:00
parent dcdd6ce2d2
commit 45fa028201
11 changed files with 436 additions and 35 deletions

View file

@ -7,6 +7,7 @@ function languageURL(name) {
module.exports = Parser.init().then(async () => ({
Parser,
languageURL,
C: await Parser.Language.load(languageURL('c')),
EmbeddedTemplate: await Parser.Language.load(languageURL('embedded-template')),
HTML: await Parser.Language.load(languageURL('html')),
JavaScript: await Parser.Language.load(languageURL('javascript')),

View file

@ -4,6 +4,13 @@ let JavaScript;
describe('Language', () => {
before(async () => ({JavaScript, Rust} = await require('./helper')));
describe('.name, .version', () => {
it('returns the name and version of the language', () => {
assert.equal('javascript', JavaScript.name);
assert.equal(15, JavaScript.version);
});
});
describe('.fieldIdForName, .fieldNameForId', () => {
it('converts between the string and integer representations of fields', () => {
const nameId = JavaScript.fieldIdForName('name');

View file

@ -1,5 +1,5 @@
const {assert} = require('chai');
let Parser; let JavaScript; let JSON; let EmbeddedTemplate; let Python;
let Parser; let C; let JavaScript; let JSON; let EmbeddedTemplate; let Python;
const JSON_EXAMPLE = `
@ -35,7 +35,7 @@ describe('Node', () => {
let parser; let tree;
before(async () =>
({Parser, EmbeddedTemplate, JavaScript, JSON, Python} = await require('./helper')),
({Parser, C, EmbeddedTemplate, JavaScript, JSON, Python} = await require('./helper')),
);
beforeEach(() => {
@ -620,17 +620,57 @@ describe('Node', () => {
describe('.fieldNameForChild(index)', () => {
it('returns the field of a child or null', () => {
tree = parser.parse('let a = 5');
parser.setLanguage(C);
tree = parser.parse('int w = x + /* y is special! */ y;');
const noField = tree.rootNode.fieldNameForChild(0);
const name = tree.rootNode.firstChild.children[1].fieldNameForChild(0);
const value = tree.rootNode.firstChild.children[1].fieldNameForChild(2);
const overflow = tree.rootNode.firstChild.children[1].fieldNameForChild(3);
const translationUnitNode = tree.rootNode;
const declarationNode = translationUnitNode.firstChild;
const binaryExpressionNode = declarationNode
.childForFieldName('declarator')
.childForFieldName('value');
assert.equal(noField, null);
assert.equal(name, 'name');
assert.equal(value, 'value');
assert.equal(overflow, null);
// -------------------
// left: (identifier) 0
// operator: "+" _ <--- (not a named child)
// (comment) 1 <--- (is an extra)
// right: (identifier) 2
// -------------------
assert.equal(binaryExpressionNode.fieldNameForChild(0), 'left');
assert.equal(binaryExpressionNode.fieldNameForChild(1), 'operator');
// The comment should not have a field name, as it's just an extra
assert.equal(binaryExpressionNode.fieldNameForChild(2), null);
assert.equal(binaryExpressionNode.fieldNameForChild(3), 'right');
// Negative test - Not a valid child index
assert.equal(binaryExpressionNode.fieldNameForChild(4), null);
});
});
describe('.fieldNameForNamedChild(index)', () => {
it('returns the field of a named child or null', () => {
parser.setLanguage(C);
tree = parser.parse('int w = x + /* y is special! */ y;');
const translationUnitNode = tree.rootNode;
const declarationNode = translationUnitNode.firstNamedChild;
const binaryExpressionNode = declarationNode
.childForFieldName('declarator')
.childForFieldName('value');
// -------------------
// left: (identifier) 0
// operator: "+" _ <--- (not a named child)
// (comment) 1 <--- (is an extra)
// right: (identifier) 2
// -------------------
assert.equal(binaryExpressionNode.fieldNameForNamedChild(0), 'left');
// The comment should not have a field name, as it's just an extra
assert.equal(binaryExpressionNode.fieldNameForNamedChild(1), null);
// The operator is not a named child, so the named child at index 2 is the right child
assert.equal(binaryExpressionNode.fieldNameForNamedChild(2), 'right');
// Negative test - Not a valid child index
assert.equal(binaryExpressionNode.fieldNameForNamedChild(3), null);
});
});
});

View file

@ -1,11 +1,11 @@
const {assert} = require('chai');
let Parser; let JavaScript; let HTML; let languageURL;
let Parser; let JavaScript; let HTML; let languageURL; let JSON;
describe('Parser', () => {
let parser;
before(async () =>
({Parser, JavaScript, HTML, languageURL} = await require('./helper')),
({Parser, JavaScript, HTML, JSON, languageURL} = await require('./helper')),
);
beforeEach(() => {
@ -388,5 +388,26 @@ describe('Parser', () => {
'(program (expression_statement (call_expression function: (identifier) arguments: (arguments))) (expression_statement (identifier)))',
);
});
it('parses with a timeout', () => {
parser.setLanguage(JSON);
const startTime = performance.now();
assert.throws(() => {
parser.parse(
(offset, _) => offset === 0 ? '[' : ',0',
null,
{
progressCallback: (_) => {
if (performance.now() - startTime > 1) {
return true;
}
return false;
},
},
);
},
);
}).timeout(5000);
});
});

View file

@ -455,13 +455,112 @@ describe('Query', () => {
describe('Set a timeout', () =>
it('returns less than the expected matches', () => {
tree = parser.parse('function foo() while (true) { } }\n'.repeat(1000));
query = JavaScript.query('(function_declaration name: (identifier) @function)');
const matches = query.matches(tree.rootNode, { timeoutMicros: 1000 });
query = JavaScript.query(
'(function_declaration name: (identifier) @function)',
);
const matches = query.matches(tree.rootNode, {timeoutMicros: 1000});
assert.isBelow(matches.length, 1000);
const matches2 = query.matches(tree.rootNode, { timeoutMicros: 0 });
const matches2 = query.matches(tree.rootNode, {timeoutMicros: 0});
assert.equal(matches2.length, 1000);
})
);
}));
describe('Start and end indices for patterns', () => {
it('Returns the start and end indices for a pattern', () => {
const patterns1 = `
"+" @operator
"-" @operator
"*" @operator
"=" @operator
"=>" @operator
`.trim();
const patterns2 = `
(identifier) @a
(string) @b
`.trim();
const patterns3 = `
((identifier) @b (#match? @b i))
(function_declaration name: (identifier) @c)
(method_definition name: (property_identifier) @d)
`.trim();
const source = patterns1 + patterns2 + patterns3;
const query = JavaScript.query(source);
assert.equal(query.startIndexForPattern(0), 0);
assert.equal(query.endIndexForPattern(0), '"+" @operator\n'.length);
assert.equal(query.startIndexForPattern(5), patterns1.length);
assert.equal(
query.endIndexForPattern(5),
patterns1.length + '(identifier) @a\n'.length,
);
assert.equal(
query.startIndexForPattern(7),
patterns1.length + patterns2.length,
);
assert.equal(
query.endIndexForPattern(7),
patterns1.length +
patterns2.length +
'((identifier) @b (#match? @b i))\n'.length,
);
});
});
describe('Disable pattern', () => {
it('Disables patterns in the query', () => {
const query = JavaScript.query(`
(function_declaration name: (identifier) @name)
(function_declaration body: (statement_block) @body)
(class_declaration name: (identifier) @name)
(class_declaration body: (class_body) @body)
`);
// disable the patterns that match names
query.disablePattern(0);
query.disablePattern(2);
const source = 'class A { constructor() {} } function b() { return 1; }';
tree = parser.parse(source);
const matches = query.matches(tree.rootNode);
assert.deepEqual(formatMatches(matches), [
{
pattern: 3,
captures: [{name: 'body', text: '{ constructor() {} }'}],
},
{pattern: 1, captures: [{name: 'body', text: '{ return 1; }'}]},
]);
});
});
describe('Executes with a timeout', () => {
it('Returns less than the expected matches', () => {
tree = parser.parse('function foo() while (true) { } }\n'.repeat(1000));
query = JavaScript.query(
'(function_declaration) @function',
);
const startTime = performance.now();
const matches = query.matches(
tree.rootNode,
{
progressCallback: (_) => {
if (performance.now() - startTime > 1) {
return true;
}
return false;
},
},
);
assert.isBelow(matches.length, 1000);
const matches2 = query.matches(tree.rootNode);
assert.equal(matches2.length, 1000);
});
});
});
function formatMatches(matches) {

View file

@ -363,7 +363,7 @@ describe('Tree', () => {
);
const tree2 = tree.copy();
([input, edit] = spliceInput(input, 3, 0, '123'));
[input, edit] = spliceInput(input, 3, 0, '123');
assert.equal(input, 'abc123 + cde');
tree.edit(edit);