diff --git a/packages/language/package.json b/packages/language/package.json index e515552dd..05f23687b 100644 --- a/packages/language/package.json +++ b/packages/language/package.json @@ -23,8 +23,8 @@ "watch": "tsdown --watch", "lint": "eslint src --ext ts", "test": "vitest run", - "langium:generate": "langium generate", - "langium:generate:production": "langium generate --mode=production", + "langium:generate": "langium generate && tsx scripts/patch-generated-tm.ts", + "langium:generate:production": "langium generate --mode=production && tsx scripts/patch-generated-tm.ts", "pack": "pnpm pack" }, "exports": { diff --git a/packages/language/scripts/patch-generated-tm.ts b/packages/language/scripts/patch-generated-tm.ts new file mode 100644 index 000000000..25e9a33a1 --- /dev/null +++ b/packages/language/scripts/patch-generated-tm.ts @@ -0,0 +1,40 @@ +/** + * Post-processes syntaxes/zmodel.tmLanguage.json after `langium generate` to correct + * highlighting for some keywords. + */ + +import tm from '../syntaxes/zmodel.tmLanguage.json'; +import { writeFileSync } from 'fs'; +import { join } from 'path'; + +const tmPath = join(import.meta.dirname, '../syntaxes/zmodel.tmLanguage.json'); +const control = tm.patterns.find((pattern) => pattern.name === 'keyword.control.zmodel-v3'); + +if (!control?.match) { + throw new Error('Could not find control pattern/match'); +} + +const keywordsToRemove = new Set(['this', 'null', 'true', 'false', 'in']); +const matchPattern = /\((.+)\)/g; +const [, keywordsMatch] = matchPattern.exec(control.match) as RegExpExecArray; +const keywordsArray = keywordsMatch.split('|').filter((keyword) => !keywordsToRemove.has(keyword)); + +control.match = control.match.replace(keywordsMatch, keywordsArray.join('|')); + +tm.patterns.push({ + name: 'constant.language', + match: '\\b(true|false|null)\\b', +}); + +tm.patterns.push({ + name: 'variable.language.this', + match: '\\b(this)\\b', +}); + +tm.patterns.push({ + name: 'keyword.operator.expression.in', + match: '\\b(in)\\b', +}); + +writeFileSync(tmPath, JSON.stringify(tm, null, 2)); +console.log('Patched syntaxes/zmodel.tmLanguage.json'); diff --git a/packages/language/src/zmodel-semantic.ts b/packages/language/src/zmodel-semantic.ts index 7910c78af..6baadcc93 100644 --- a/packages/language/src/zmodel-semantic.ts +++ b/packages/language/src/zmodel-semantic.ts @@ -21,7 +21,18 @@ import { isPluginField, isReferenceExpr, isTypeDef, + isProcedure, + isFunctionParamType, + isAttributeParamType, + isUnaryExpr, + isBinaryExpr, + isNumberLiteral, + isCollectionPredicateBinding, + isFunctionParam, + isAttributeParam, + isProcedureParam, type AstNode, + isDataFieldParam, } from './ast'; export class ZModelSemanticTokenProvider extends AbstractSemanticTokenProvider { @@ -44,25 +55,19 @@ export class ZModelSemanticTokenProvider extends AbstractSemanticTokenProvider { property: 'baseModel', type: SemanticTokenTypes.type, }); - } else if (isDataSource(node) || isGeneratorDecl(node) || isPlugin(node) || isEnum(node) || isTypeDef(node)) { + } else if (isDataSource(node) || isGeneratorDecl(node) || isPlugin(node) || isTypeDef(node)) { acceptor({ node, property: 'name', type: SemanticTokenTypes.type, }); - } else if ( - isDataField(node) || - isConfigField(node) || - isAttributeArg(node) || - isPluginField(node) || - isEnumField(node) - ) { + } else if (isDataField(node) || isConfigField(node) || isPluginField(node)) { acceptor({ node, property: 'name', - type: SemanticTokenTypes.variable, + type: SemanticTokenTypes.property, }); - } else if (isDataFieldType(node)) { + } else if (isDataFieldType(node) || isFunctionParamType(node) || isAttributeParamType(node)) { if (node.type) { acceptor({ node, @@ -80,7 +85,21 @@ export class ZModelSemanticTokenProvider extends AbstractSemanticTokenProvider { acceptor({ node, property: 'decl', - type: SemanticTokenTypes.function, + type: SemanticTokenTypes.decorator, + }); + + if (node.decl.$refText === '@regex' && node.args[0]) { + acceptor({ + node: node.args[0], + property: 'value', + type: SemanticTokenTypes.regexp, + }); + } + } else if (isAttribute(node)) { + acceptor({ + node, + property: 'name', + type: SemanticTokenTypes.decorator, }); } else if (isInvocationExpr(node)) { acceptor({ @@ -88,24 +107,96 @@ export class ZModelSemanticTokenProvider extends AbstractSemanticTokenProvider { property: 'function', type: SemanticTokenTypes.function, }); - } else if (isFunctionDecl(node) || isAttribute(node)) { + } else if (isFunctionDecl(node) || isProcedure(node)) { acceptor({ node, property: 'name', type: SemanticTokenTypes.function, }); + + if ('mutation' in node && node.mutation) { + acceptor({ + node, + property: 'mutation', + type: SemanticTokenTypes.modifier, + }); + } } else if (isReferenceExpr(node)) { - acceptor({ - node, - property: 'target', - type: SemanticTokenTypes.variable, - }); + if (isEnumField(node.target.ref)) { + acceptor({ + node, + property: 'target', + type: SemanticTokenTypes.enumMember, + }); + } else if (isFunctionParam(node.target.ref)) { + acceptor({ + node, + property: 'target', + type: SemanticTokenTypes.parameter, + }); + } else if (isCollectionPredicateBinding(node.target.ref)) { + acceptor({ + node, + property: 'target', + type: SemanticTokenTypes.variable, + }); + } else { + acceptor({ + node, + property: 'target', + type: SemanticTokenTypes.property, + }); + } } else if (isMemberAccessExpr(node)) { acceptor({ node, property: 'member', type: SemanticTokenTypes.property, }); + } else if (isNumberLiteral(node)) { + acceptor({ + node, + property: 'value', + type: SemanticTokenTypes.number, + }); + } else if (isUnaryExpr(node) || isBinaryExpr(node)) { + if (node.operator !== 'in') { + acceptor({ + node, + property: 'operator', + type: SemanticTokenTypes.operator, + }); + } + } else if (isEnumField(node)) { + acceptor({ + node, + property: 'name', + type: SemanticTokenTypes.enumMember, + }); + } else if (isEnum(node)) { + acceptor({ + node, + property: 'name', + type: SemanticTokenTypes.enum, + }); + } else if ( + isFunctionParam(node) || + isAttributeArg(node) || + isAttributeParam(node) || + isProcedureParam(node) || + isDataFieldParam(node) + ) { + acceptor({ + node, + property: 'name', + type: SemanticTokenTypes.parameter, + }); + } else if (isCollectionPredicateBinding(node)) { + acceptor({ + node, + property: 'name', + type: SemanticTokenTypes.variable, + }); } } }