Skip to content

Commit

Permalink
Merge pull request #11 from valtzu/node-type-constants
Browse files Browse the repository at this point in the history
Use node type constants
  • Loading branch information
valtzu authored Jan 11, 2025
2 parents 638c20d + 57d753a commit e479595
Show file tree
Hide file tree
Showing 9 changed files with 145 additions and 77 deletions.
3 changes: 1 addition & 2 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
/node_modules
package-lock.json
/package-lock.json
/dist
/src/*.js
/test/*.js
/src/*.d.ts
50 changes: 35 additions & 15 deletions src/complete.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,26 @@ import { EditorState } from "@codemirror/state";
import { SyntaxNode } from "@lezer/common";
import { getExpressionLanguageConfig, keywords, resolveTypes } from "./utils";
import { syntaxTree } from "@codemirror/language";
import {
Arguments,
BinaryExpression,
Expression,
Function,
MethodAccess,
OperatorKeyword,
PropertyAccess,
TernaryExpression,
UnaryExpression,
Variable,
String,
BlockComment,
ArrayAccess,
Call,
Application,
Property,
Method,
Array,
} from "./syntax.grammar.terms";

const autocompleteFunction = (x: ELFunction): Completion => ({
label: `${x.name}(${x.args?.map(x => x.name)?.join(',') || ''})`,
Expand Down Expand Up @@ -56,11 +76,11 @@ function completeIdentifier(state: EditorState, config: ExpressionLanguageConfig
}

function completeMember(state: EditorState, config: ExpressionLanguageConfig, tree: SyntaxNode, from: number, to: number, explicit: boolean): CompletionResult | null {
if (tree.parent?.name != 'ObjectAccess' || !tree.parent.firstChild) {
if (!(tree.parent?.type.is(PropertyAccess) || tree.parent?.type.is(MethodAccess)) || !tree.parent?.firstChild) {
return null;
}

const types = resolveTypes(state, tree.parent.firstChild.node, config, false);
const types = resolveTypes(state, tree.parent.firstChild.node, config);
if (!types?.size) {
return null;
}
Expand Down Expand Up @@ -89,31 +109,31 @@ export function expressionLanguageCompletion(context: CompletionContext): Comple
const prevNode = tree.resolveInner(pos, lastChar === ')' ? 0 : -1);
const config = getExpressionLanguageConfig(state);

const isIdentifier = (node: SyntaxNode | undefined) => ['Variable', 'Function'].includes(node?.name ?? '');
const isMember = (node: SyntaxNode | undefined) => ['Property', 'Method'].includes(node?.name ?? '');
const isIdentifier = (node: SyntaxNode | undefined) => node?.type.is(Variable) || node?.type.is(Function);
const isMember = (node: SyntaxNode | undefined) => node?.type.is(Property) || node?.type.is(Method);

if (prevNode.name == 'String' || prevNode.name == 'BlockComment') {
if (prevNode.type.is(String) || prevNode.type.is(BlockComment)) {
return null;
}

if (prevNode.parent?.name == 'ObjectAccess' && ['ObjectAccess', 'ArrayAccess', 'Variable', 'Call', 'Application'].includes(prevNode.parent.firstChild?.name || '')) {
if ((prevNode.parent?.type.is(PropertyAccess) || prevNode.parent?.type.is(MethodAccess)) && [PropertyAccess, MethodAccess, ArrayAccess, Variable, Call, Application].includes(prevNode.parent.firstChild?.type.id)) {
return completeMember(state, config, prevNode, isIdentifier(prevNode) || isMember(prevNode) ? prevNode.from : pos, pos, explicit);
}

if (
['Expression', 'UnaryExpression', 'BinaryExpression', 'TernaryExpression'].includes(prevNode.name) && prevNode.lastChild && !prevNode.lastChild?.type.isError
|| ['Arguments', 'Array'].includes(prevNode.name) && prevNode.lastChild && !prevNode.lastChild?.type.isError
|| ['Expression', 'UnaryExpression', 'BinaryExpression', 'TernaryExpression'].includes(prevNode.parent?.name ?? '') && prevNode.type.isError
|| ['Variable', 'Function'].includes(prevNode.parent?.name ?? '') && prevNode.type.isError
[Expression, UnaryExpression, BinaryExpression, TernaryExpression].includes(prevNode.type.id) && prevNode.lastChild && !prevNode.lastChild?.type.isError
|| [Arguments, Array].includes(prevNode.type.id) && prevNode.lastChild && !prevNode.lastChild?.type.isError
|| [Expression, UnaryExpression, BinaryExpression, TernaryExpression].includes(prevNode.parent?.type.id) && prevNode.type.isError
|| [Variable, Function].includes(prevNode.parent?.type.id) && prevNode.type.isError
) {
return completeOperatorKeyword(state, config, prevNode, !['Expression', 'UnaryExpression', 'BinaryExpression', 'TernaryExpression', 'Arguments'].includes(prevNode.name) ? prevNode.from : pos, pos, explicit);
return completeOperatorKeyword(state, config, prevNode, ![Expression, UnaryExpression, BinaryExpression, TernaryExpression, Arguments].includes(prevNode.type.id) ? prevNode.from : pos, pos, explicit);
}

if (
!/[0-9]/.test(lastChar) && !['OperatorKeyword'].includes(prevNode.name ?? '') && (
['Expression', 'UnaryExpression', 'BinaryExpression', 'TernaryExpression'].includes(prevNode.name) && prevNode.lastChild?.type.isError
|| ['Expression', 'UnaryExpression', 'BinaryExpression', 'TernaryExpression', 'Arguments'].includes(prevNode.parent?.name ?? '') && !prevNode.type.isError
|| ['Arguments', 'Array'].includes(prevNode.name ?? '')
!/[0-9]/.test(lastChar) && !prevNode.type.is(OperatorKeyword) && (
[Expression, UnaryExpression, BinaryExpression, TernaryExpression].includes(prevNode.type.id) && prevNode.lastChild?.type.isError
|| [Expression, UnaryExpression, BinaryExpression, TernaryExpression, Arguments].includes(prevNode.parent?.type.id ?? -1) && !prevNode.type.isError
|| prevNode.type.is(Arguments) || prevNode.type.is(Array)
)
) {
return completeIdentifier(state, config, prevNode, isIdentifier(prevNode) ? prevNode.from : pos, pos);
Expand Down
27 changes: 14 additions & 13 deletions src/linter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { Diagnostic, linter } from "@codemirror/lint";
import { syntaxTree } from "@codemirror/language";
import { getExpressionLanguageConfig, resolveFunctionDefinition, resolveIdentifier, resolveTypes } from "./utils";
import { ELScalar } from "./types";
import { Arguments, Method, Property, Variable, Function, BlockComment } from "./syntax.grammar.terms";

/**
* @internal
Expand All @@ -12,11 +13,11 @@ export const expressionLanguageLinterSource = (state: EditorState) => {
let diagnostics: Diagnostic[] = [];

syntaxTree(state).cursor().iterate(node => {
const { from, to, name } = node;
const { from, to, type: { id } } = node;

let identifier: string | undefined;
switch (name) {
case '⚠':
switch (id) {
case 0:
if (state.doc.length === 0 || from === 0) {
// Don't show error on empty doc (even though it is an error)
return;
Expand All @@ -31,14 +32,14 @@ export const expressionLanguageLinterSource = (state: EditorState) => {
}

return;
case 'Arguments':
case Arguments:
const args = resolveFunctionDefinition(node.node.prevSibling, state, config)?.args;
if (!args) {
return;
}

for (let n = node.node.firstChild, i = 0; n != null; n = n.nextSibling) {
if (n.name === 'BlockComment') {
if (n.type.is(BlockComment)) {
continue;
}

Expand All @@ -47,7 +48,7 @@ export const expressionLanguageLinterSource = (state: EditorState) => {
continue;
}

const typesUsed = Array.from(resolveTypes(state, n, config, true));
const typesUsed = Array.from(resolveTypes(state, n, config));
const typesExpected = args[i].type;

if (typesExpected && !typesExpected.includes(ELScalar.Any) && !typesUsed.some(x => typesExpected.includes(x))) {
Expand All @@ -57,22 +58,22 @@ export const expressionLanguageLinterSource = (state: EditorState) => {
}

break;
case 'Property':
case 'Method':
case Property:
case Method:
const leftArgument = node.node.parent?.firstChild?.node;
const types = Array.from(resolveTypes(state, leftArgument, config, true));
const types = Array.from(resolveTypes(state, leftArgument, config));
identifier = state.sliceDoc(from, to);

if (!types.find(type => resolveIdentifier(name, identifier, config.types?.[type]))) {
if (!types.find(type => resolveIdentifier(id, identifier, config.types?.[type]))) {
diagnostics.push({ from, to, severity: 'error', message: `${node.name} "${identifier}" not found in ${types.join('|')}` });
}

break;

case 'Variable':
case 'Function':
case Variable:
case Function:
identifier = state.sliceDoc(from, node.node.firstChild ? node.node.firstChild.from - 1 : to);
if (!resolveIdentifier(name, identifier, config)) {
if (!resolveIdentifier(id, identifier, config)) {
diagnostics.push({ from, to, severity: 'error', message: `${node.node.name} "${identifier}" not found` });
}

Expand Down
19 changes: 13 additions & 6 deletions src/syntax.grammar
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
@top Expression { expression }

expression {
ObjectAccess<Property> |
PropertyAccess |
ArrayAccess |
Call |
Number |
Expand All @@ -34,9 +34,10 @@ expression {
TernaryExpression |
BinaryExpression |
UnaryExpression |
Application { "(" expression ")" }
Application
}

Application { "(" expression ")" }
Variable { word }
Function { word }
Property { word }
Expand Down Expand Up @@ -66,17 +67,23 @@ BinaryExpression {
expression !right (OperatorKeyword | Operator) expression
}

ObjectAccess<member> {
expression !left ( MemberOf | NullSafeMemberOf ) member
PropertyAccess {
expression !left ( MemberOf | NullSafeMemberOf ) Property
}

MethodAccess {
expression !left ( MemberOf | NullSafeMemberOf ) Method
}

call<callable> {
callable Arguments { "(" list ")" }
callable Arguments
}

Arguments { "(" list ")" }

Call {
call<Function> |
call<ObjectAccess<Method>>
call<MethodAccess>
}

TernaryExpression {
Expand Down
26 changes: 26 additions & 0 deletions src/syntax.grammar.terms.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
export declare const Application: number;
export declare const Arguments: number;
export declare const Array: number;
export declare const ArrayAccess: number;
export declare const BinaryExpression: number;
export declare const BlockComment: number;
export declare const Boolean: number;
export declare const Call: number;
export declare const Expression: number;
export declare const Function: number;
export declare const MemberOf: number;
export declare const Method: number;
export declare const MethodAccess: number;
export declare const Null: number;
export declare const NullSafeMemberOf: number;
export declare const Number: number;
export declare const Object: number;
export declare const Operator: number;
export declare const OperatorKeyword: number;
export declare const Property: number;
export declare const PropertyAccess: number;
export declare const String: number;
export declare const TernaryExpression: number;
export declare const UnaryExpression: number;
export declare const UnaryOperator: number;
export declare const Variable: number;
13 changes: 7 additions & 6 deletions src/tooltip.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { SyntaxNode } from "@lezer/common";
import { EditorState, StateField } from "@codemirror/state";
import { EditorView, hoverTooltip, showTooltip, Tooltip } from "@codemirror/view";
import { hoverTooltip, showTooltip, Tooltip } from "@codemirror/view";
import { syntaxTree } from "@codemirror/language";
import { createInfoElement, getExpressionLanguageConfig, keywords, resolveFunctionDefinition, resolveTypes } from "./utils";
import { Arguments, Function, Method, MethodAccess, OperatorKeyword, Property, PropertyAccess, Variable } from "./syntax.grammar.terms";

function getNodeOrdinal(node: SyntaxNode) {
let ordinal = -1;
Expand All @@ -16,7 +17,7 @@ function getNodeOrdinal(node: SyntaxNode) {
function resolveArguments(node: SyntaxNode) {
let c: SyntaxNode | null = node;

while (c && c.name !== 'Arguments') {
while (c && !c.type.is(Arguments)) {
c = c.parent;
}

Expand Down Expand Up @@ -81,7 +82,7 @@ export const keywordTooltip = hoverTooltip((view, pos, side) => {
const config = getExpressionLanguageConfig(view.state);
const tree: SyntaxNode = syntaxTree(view.state).resolveInner(pos, side);

if (tree.name === 'OperatorKeyword') {
if (tree.type.is(OperatorKeyword)) {
const name = view.state.sliceDoc(tree.from, tree.to);
const info = keywords.find(x => x.name === name)?.info;
if (info) {
Expand All @@ -94,15 +95,15 @@ export const keywordTooltip = hoverTooltip((view, pos, side) => {
}
}

if (!['Function', 'Variable', 'Method', 'Property'].includes(tree.name)) {
if (![Function, Variable, Method, Property].includes(tree.type.id)) {
return null;
}

const skipEmpty = (x: any) => x;
let info: string;
if (tree.parent?.firstChild && tree.parent?.name === 'ObjectAccess' && tree.prevSibling) {
if (tree.parent?.firstChild && (tree.parent?.type.is(PropertyAccess) || tree.parent?.type.is(MethodAccess)) && tree.prevSibling) {
const node = tree.parent.firstChild;
const types = Array.from(resolveTypes(view.state, node, config, true));
const types = Array.from(resolveTypes(view.state, node, config));
const name = view.state.sliceDoc(tree.from, tree.to);
info = [
...types.map(type => config.types?.[type]?.identifiers?.find(x => x.name === name)?.info).filter(skipEmpty),
Expand Down
Loading

0 comments on commit e479595

Please sign in to comment.