First commit.

Signed-off-by: Chen Xiao <abigwc@gmail.com>
This commit is contained in:
Chen Xiao
2026-05-08 14:43:16 +08:00
commit 0b64e2de94
10989 changed files with 2253791 additions and 0 deletions
@@ -0,0 +1,21 @@
/******************************************************************************
* Copyright 2022 TypeFox GmbH
* This program and the accompanying materials are made available under the
* terms of the MIT License, which is available in the project root.
******************************************************************************/
import type { ValidationAcceptor } from '../../validation/validation-registry.js';
import type { LangiumGrammarServices } from '../langium-grammar-module.js';
import * as ast from '../../languages/generated/ast.js';
export declare function registerTypeValidationChecks(services: LangiumGrammarServices): void;
export declare class LangiumGrammarTypesValidator {
checkAttributeDefaultValue(grammarInterface: ast.Interface, accept: ValidationAcceptor): void;
checkCyclicType(type: ast.Type, accept: ValidationAcceptor): void;
checkCyclicInterface(type: ast.Interface, accept: ValidationAcceptor): void;
checkDeclaredTypesConsistency(grammar: ast.Grammar, accept: ValidationAcceptor): void;
checkDeclaredAndInferredTypesConsistency(grammar: ast.Grammar, accept: ValidationAcceptor): void;
checkActionIsNotUnionType(action: ast.Action, accept: ValidationAcceptor): void;
checkInfixRuleExplicitReturnType(infixRule: ast.InfixRule, accept: ValidationAcceptor): void;
private isValidOperatorPropertyType;
private isValidLeftRightPropertyType;
}
//# sourceMappingURL=types-validator.d.ts.map
@@ -0,0 +1 @@
{"version":3,"file":"types-validator.d.ts","sourceRoot":"","sources":["../../../src/grammar/validation/types-validator.ts"],"names":[],"mappings":"AAAA;;;;gFAIgF;AAGhF,OAAO,KAAK,EAAkB,kBAAkB,EAAoB,MAAM,yCAAyC,CAAC;AACpH,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,8BAA8B,CAAC;AAG3E,OAAO,KAAK,GAAG,MAAM,kCAAkC,CAAC;AAQxD,wBAAgB,4BAA4B,CAAC,QAAQ,EAAE,sBAAsB,GAAG,IAAI,CAuBnF;AAED,qBAAa,4BAA4B;IAErC,0BAA0B,CAAC,gBAAgB,EAAE,GAAG,CAAC,SAAS,EAAE,MAAM,EAAE,kBAAkB,GAAG,IAAI;IAiB7F,eAAe,CAAC,IAAI,EAAE,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,kBAAkB,GAAG,IAAI;IAMjE,oBAAoB,CAAC,IAAI,EAAE,GAAG,CAAC,SAAS,EAAE,MAAM,EAAE,kBAAkB,GAAG,IAAI;IAM3E,6BAA6B,CAAC,OAAO,EAAE,GAAG,CAAC,OAAO,EAAE,MAAM,EAAE,kBAAkB,GAAG,IAAI;IAarF,wCAAwC,CAAC,OAAO,EAAE,GAAG,CAAC,OAAO,EAAE,MAAM,EAAE,kBAAkB,GAAG,IAAI;IAchG,yBAAyB,CAAC,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,kBAAkB,GAAG,IAAI;IAM/E,gCAAgC,CAAC,SAAS,EAAE,GAAG,CAAC,SAAS,EAAE,MAAM,EAAE,kBAAkB,GAAG,IAAI;IAgE5F,OAAO,CAAC,2BAA2B;IAOnC,OAAO,CAAC,4BAA4B;CAQvC"}
+487
View File
@@ -0,0 +1,487 @@
/******************************************************************************
* Copyright 2022 TypeFox GmbH
* This program and the accompanying materials are made available under the
* terms of the MIT License, which is available in the project root.
******************************************************************************/
import * as ast from '../../languages/generated/ast.js';
import { MultiMap } from '../../utils/collections.js';
import { extractAssignments } from '../internal-grammar-util.js';
import { getExplicitRuleType } from '../../utils/grammar-utils.js';
import { flattenPropertyUnion, InterfaceType, isArrayType, isInterfaceType, isMandatoryPropertyType, isPrimitiveType, isPropertyUnion, isReferenceType, isStringType, isTypeAssignable, isUnionType, isValueType, propertyTypeToString } from '../type-system/type-collector/types.js';
import { getTypeOption, isDeclared, isInferred, isInferredAndDeclared } from '../workspace/documents.js';
import { getDocument } from '../../utils/ast-utils.js';
export function registerTypeValidationChecks(services) {
const registry = services.validation.ValidationRegistry;
const typesValidator = services.validation.LangiumGrammarTypesValidator;
const checks = {
Action: [
typesValidator.checkActionIsNotUnionType,
],
Grammar: [
typesValidator.checkDeclaredTypesConsistency,
typesValidator.checkDeclaredAndInferredTypesConsistency
],
InfixRule: [
typesValidator.checkInfixRuleExplicitReturnType,
],
Interface: [
typesValidator.checkAttributeDefaultValue,
typesValidator.checkCyclicInterface
],
Type: [
typesValidator.checkCyclicType
]
};
registry.register(checks, typesValidator);
}
export class LangiumGrammarTypesValidator {
checkAttributeDefaultValue(grammarInterface, accept) {
const validationResources = getDocument(grammarInterface)?.validationResources;
if (!validationResources) {
return;
}
const matchedProperties = matchInterfaceAttributes(validationResources, grammarInterface);
for (const [grammarProperty, property] of matchedProperties) {
const defaultType = getDefaultValueType(grammarProperty.defaultValue);
if (defaultType && !isTypeAssignable(defaultType, property.type)) {
accept('error', `Cannot assign default value of type '${propertyTypeToString(defaultType, 'DeclaredType')}' to type '${propertyTypeToString(property.type, 'DeclaredType')}'.`, {
node: grammarProperty,
property: 'defaultValue'
});
}
}
}
checkCyclicType(type, accept) {
if (isCyclicType(type, new Set())) {
accept('error', `Type alias '${type.name}' circularly references itself.`, { node: type, property: 'name' });
}
}
checkCyclicInterface(type, accept) {
if (isCyclicType(type, new Set())) {
accept('error', `Type '${type.name}' recursively references itself as a base type.`, { node: type, property: 'name' });
}
}
checkDeclaredTypesConsistency(grammar, accept) {
const validationResources = grammar.$document?.validationResources;
if (validationResources) {
for (const typeInfo of validationResources.typeToValidationInfo.values()) {
if (isDeclared(typeInfo) && isInterfaceType(typeInfo.declared) && ast.isInterface(typeInfo.declaredNode)) {
const declInterface = typeInfo;
validateInterfaceSuperTypes(declInterface, accept);
validateSuperTypesConsistency(declInterface, accept);
}
}
}
}
checkDeclaredAndInferredTypesConsistency(grammar, accept) {
const validationResources = grammar.$document?.validationResources;
if (validationResources) {
for (const typeInfo of validationResources.typeToValidationInfo.values()) {
if (isInferred(typeInfo) && typeInfo.inferred instanceof InterfaceType) {
validateInferredInterface(typeInfo.inferred, accept);
}
if (isInferredAndDeclared(typeInfo)) {
validateDeclaredAndInferredConsistency(typeInfo, validationResources, accept);
}
}
}
}
checkActionIsNotUnionType(action, accept) {
if (ast.isType(action.type)) {
accept('error', 'Actions cannot create union types.', { node: action, property: 'type' });
}
}
checkInfixRuleExplicitReturnType(infixRule, accept) {
if (!infixRule.returnType?.ref) {
return;
}
const validationResources = getDocument(infixRule)?.validationResources;
if (!validationResources) {
return;
}
const returnTypeName = infixRule.returnType.ref.name;
const returnTypeProperties = validationResources.typeToSuperProperties.get(returnTypeName);
if (!returnTypeProperties) {
return;
}
const propertyMap = new Map(returnTypeProperties.map(prop => [prop.name, prop]));
// Check for 'operator' property
const operatorProp = propertyMap.get('operator');
if (!operatorProp) {
accept('error', `Infix rule '${infixRule.name}' with explicit return type '${returnTypeName}' must have an 'operator' property.`, {
node: infixRule,
property: 'returnType'
});
}
else {
// Validate operator property type
const operatorKeywords = infixRule.operators.precedences.flatMap(prec => prec.operators.map(op => op.value));
const isValidOperatorType = this.isValidOperatorPropertyType(operatorProp.type, operatorKeywords);
if (!isValidOperatorType) {
const expectedType = operatorKeywords.length > 1
? `one of: ${operatorKeywords.map(op => `'${op}'`).join(', ')}`
: `'${operatorKeywords[0]}'`;
accept('error', `Property 'operator' must be of type 'string' or ${expectedType}.`, {
node: infixRule,
property: 'returnType'
});
}
}
// Check for 'left' and 'right' properties
const callRule = infixRule.call.rule.ref;
if (!callRule) {
return;
}
const callRuleType = getExplicitRuleType(callRule) ?? callRule.name;
for (const propName of ['left', 'right']) {
const prop = propertyMap.get(propName);
if (!prop) {
accept('error', `Infix rule '${infixRule.name}' with explicit return type '${returnTypeName}' must have a '${propName}' property.`, {
node: infixRule,
property: 'returnType'
});
}
else {
// Validate that the property type matches or is a supertype of the called rule
const isValidType = this.isValidLeftRightPropertyType(prop.type, callRuleType, validationResources);
if (!isValidType) {
accept('error', `Property '${propName}' must be of type '${callRuleType}' or a supertype of it.`, {
node: infixRule,
property: 'returnType'
});
}
}
}
}
isValidOperatorPropertyType(propertyType, operatorKeywords) {
const union = {
types: operatorKeywords.map(keyword => ({ string: keyword }))
};
return isTypeAssignable(union, propertyType);
}
isValidLeftRightPropertyType(propertyType, callRuleTypeName, validationResources) {
const callRuleTypeInfo = validationResources.typeToValidationInfo.get(callRuleTypeName);
if (callRuleTypeInfo) {
const callRuleType = getTypeOption(callRuleTypeInfo);
return isTypeAssignable({ value: callRuleType }, propertyType);
}
return false;
}
}
function matchInterfaceAttributes(resources, grammarInterface) {
const elements = [];
const interfaceType = resources.typeToValidationInfo.get(grammarInterface.name);
if (interfaceType && isDeclared(interfaceType) && isInterfaceType(interfaceType.declared)) {
for (const grammarProperty of grammarInterface.attributes.filter(prop => prop.defaultValue)) {
const property = interfaceType.declared.properties.find(e => e.name === grammarProperty.name);
if (property) {
elements.push([grammarProperty, property]);
}
}
}
return elements;
}
function getDefaultValueType(defaultValue) {
if (ast.isBooleanLiteral(defaultValue)) {
return { primitive: 'boolean' };
}
else if (ast.isNumberLiteral(defaultValue)) {
return { primitive: 'number' };
}
else if (ast.isStringLiteral(defaultValue)) {
return { string: defaultValue.value };
}
else if (ast.isArrayLiteral(defaultValue)) {
return { elementType: generateElementType(defaultValue) };
}
else {
return undefined;
}
}
function generateElementType(arrayLiteral) {
if (arrayLiteral.elements.length === 0) {
return undefined;
}
const foundTypes = [];
for (const element of arrayLiteral.elements) {
const elementType = getDefaultValueType(element);
if (!elementType) {
continue;
}
if (isPrimitiveType(elementType)) {
if (!(foundTypes.some(e => isPrimitiveType(e) && e.primitive === elementType.primitive))) {
foundTypes.push(elementType);
}
}
else if (isStringType(elementType)) {
if (!(foundTypes.some(e => isStringType(e) && e.string === elementType.string))) {
foundTypes.push(elementType);
}
}
else {
foundTypes.push(elementType);
}
}
if (foundTypes.length === 0) {
return undefined;
}
else if (foundTypes.length === 1) {
return foundTypes[0];
}
else {
return {
types: foundTypes
};
}
}
function isCyclicType(type, visited) {
if (visited.has(type)) {
return true;
}
visited.add(type);
if (ast.isType(type)) {
return isCyclicType(type.type, visited);
}
else if (ast.isInterface(type)) {
return type.superTypes.some(t => t.ref && isCyclicType(t.ref, new Set(visited)));
}
else if (ast.isSimpleType(type)) {
if (type.typeRef?.ref) {
return isCyclicType(type.typeRef.ref, visited);
}
}
else if (ast.isReferenceType(type)) {
return isCyclicType(type.referenceType, visited);
}
else if (ast.isArrayType(type)) {
return isCyclicType(type.elementType, visited);
}
else if (ast.isUnionType(type)) {
return type.types.some(t => isCyclicType(t, new Set(visited)));
}
return false;
}
function validateInferredInterface(inferredInterface, accept) {
inferredInterface.properties.forEach(prop => {
const flattened = flattenPropertyUnion(prop.type);
if (flattened.length > 1) {
const typeKind = (type) => isReferenceType(type) ? (type.isMulti ? 'multi-ref' : 'ref') : 'other';
const firstKind = typeKind(flattened[0]);
if (flattened.slice(1).some(type => typeKind(type) !== firstKind)) {
const targetNode = prop.astNodes.values().next()?.value;
if (targetNode) {
accept('error', `Mixing a cross-reference with other types is not supported. Consider splitting property "${prop.name}" into two or more different properties.`, { node: targetNode });
}
}
}
const referenceTypes = collectReferenceTypes(prop.type);
for (const refType of referenceTypes) {
if (refType.isMulti && refType.isSingle) {
const targetNode = prop.astNodes.values().next()?.value;
if (targetNode) {
accept('error', `Multi references and normal references cannot be mixed. Consider splitting property "${prop.name}" into two or more different properties.`, { node: targetNode });
}
}
}
});
}
function collectReferenceTypes(type) {
const result = new Set();
if (isReferenceType(type)) {
result.add(type);
}
else if (isArrayType(type) && type.elementType) {
const elementTypes = collectReferenceTypes(type.elementType);
elementTypes.forEach(e => result.add(e));
}
else if (isPropertyUnion(type)) {
type.types.forEach(t => {
const subTypes = collectReferenceTypes(t);
subTypes.forEach(e => result.add(e));
});
}
return result;
}
function validateInterfaceSuperTypes({ declared, declaredNode }, accept) {
Array.from(declared.superTypes).forEach((superType, i) => {
if (superType) {
if (isUnionType(superType)) {
accept('error', 'Interfaces cannot extend union types.', { node: declaredNode, property: 'superTypes', index: i });
}
if (!superType.declared) {
accept('error', 'Extending an inferred type is discouraged.', { node: declaredNode, property: 'superTypes', index: i });
}
}
});
}
function validateSuperTypesConsistency({ declared, declaredNode }, accept) {
const nameToProp = declared.properties.reduce((acc, e) => acc.add(e.name, e), new MultiMap());
for (const [name, props] of nameToProp.entriesGroupedByKey()) {
if (props.length > 1) {
for (const prop of props) {
accept('error', `Cannot have two properties with the same name '${name}'.`, {
node: Array.from(prop.astNodes)[0],
property: 'name'
});
}
}
}
const allSuperTypes = Array.from(declared.superTypes);
for (let i = 0; i < allSuperTypes.length; i++) {
for (let j = i + 1; j < allSuperTypes.length; j++) {
const outerType = allSuperTypes[i];
const innerType = allSuperTypes[j];
const outerProps = isInterfaceType(outerType) ? outerType.superProperties : [];
const innerProps = isInterfaceType(innerType) ? innerType.superProperties : [];
const nonIdentical = getNonIdenticalProps(outerProps, innerProps);
if (nonIdentical.length > 0) {
accept('error', `Cannot simultaneously inherit from '${outerType}' and '${innerType}'. Their ${nonIdentical.map(e => "'" + e + "'").join(', ')} properties are not identical.`, {
node: declaredNode,
property: 'name'
});
}
}
}
const allSuperProps = new Set();
for (const superType of allSuperTypes) {
const props = isInterfaceType(superType) ? superType.superProperties : [];
for (const prop of props) {
allSuperProps.add(prop.name);
}
}
for (const ownProp of declared.properties) {
if (allSuperProps.has(ownProp.name)) {
const propNode = declaredNode.attributes.find(e => e.name === ownProp.name);
if (propNode) {
accept('error', `Cannot redeclare property '${ownProp.name}'. It is already inherited from another interface.`, {
node: propNode,
property: 'name'
});
}
}
}
}
function getNonIdenticalProps(a, b) {
const nonIdentical = [];
for (const outerProp of a) {
const innerProp = b.find(e => e.name === outerProp.name);
if (innerProp && !arePropTypesIdentical(outerProp, innerProp)) {
nonIdentical.push(outerProp.name);
}
}
return nonIdentical;
}
function arePropTypesIdentical(a, b) {
return isTypeAssignable(a.type, b.type) && isTypeAssignable(b.type, a.type);
}
///////////////////////////////////////////////////////////////////////////////
function validateDeclaredAndInferredConsistency(typeInfo, resources, accept) {
const { inferred, declared, declaredNode, inferredNodes } = typeInfo;
const typeName = declared.name;
const applyErrorToRulesAndActions = (msgPostfix) => (errorMsg) => inferredNodes.forEach(node => accept('error', `${errorMsg}${msgPostfix ? ` ${msgPostfix}` : ''}.`, (node?.inferredType) ?
{ node: node?.inferredType, property: 'name' } :
{ node, property: ast.isAction(node) ? 'type' : 'name' }));
const applyErrorToProperties = (nodes, errorMessage) => nodes.forEach(node => accept('error', errorMessage, { node, property: ast.isAssignment(node) || ast.isAction(node) ? 'feature' : 'name' }));
// todo add actions
// currently we don't track which assignments belong to which actions and can't apply this error
const applyMissingPropErrorToRules = (missingProp) => {
inferredNodes.forEach(node => {
if (ast.isParserRule(node)) {
const assignments = extractAssignments(node.definition);
if (assignments.find(e => e.feature === missingProp) === undefined) {
accept('error', `Property '${missingProp}' is missing in a rule '${node.name}', but is required in type '${typeName}'.`, {
node,
property: 'parameters'
});
}
}
});
};
if (isUnionType(inferred) && isUnionType(declared)) {
validateAlternativesConsistency(inferred.type, declared.type, applyErrorToRulesAndActions(`in a rule that returns type '${typeName}'`));
}
else if (isInterfaceType(inferred) && isInterfaceType(declared)) {
validatePropertiesConsistency(inferred, declared, resources, applyErrorToRulesAndActions(`in a rule that returns type '${typeName}'`), applyErrorToProperties, applyMissingPropErrorToRules);
}
else {
const errorMessage = `Inferred and declared versions of type '${typeName}' both have to be interfaces or unions.`;
applyErrorToRulesAndActions()(errorMessage);
accept('error', errorMessage, { node: declaredNode, property: 'name' });
}
}
function validateAlternativesConsistency(inferred, declared, applyErrorToInferredTypes) {
if (!isTypeAssignable(inferred, declared)) {
applyErrorToInferredTypes(`Cannot assign type '${propertyTypeToString(inferred, 'DeclaredType')}' to '${propertyTypeToString(declared, 'DeclaredType')}'`);
}
}
function isOptionalProperty(prop) {
// mandatory properties will always be created so there are no issues if they are missing
return prop.optional || isMandatoryPropertyType(prop.type);
}
function validatePropertiesConsistency(inferred, declared, resources, applyErrorToType, applyErrorToProperties, applyMissingPropErrorToRules) {
const ownInferredProps = new Set(inferred.properties.map(e => e.name));
// This field also contains properties of sub types
const allInferredProps = new Map(inferred.allProperties.map(e => [e.name, e]));
// This field only contains properties of itself or super types
const declaredProps = new Map(declared.superProperties.map(e => [e.name, e]));
// The inferred props may not have full hierarchy information so try finding
// a corresponding declared type
const matchingProp = (type) => {
if (isPropertyUnion(type))
return {
types: type.types.map(t => matchingProp(t))
};
if (isReferenceType(type))
return {
referenceType: matchingProp(type.referenceType),
isMulti: type.isMulti,
isSingle: type.isSingle
};
if (isArrayType(type))
return {
elementType: type.elementType && matchingProp(type.elementType)
};
if (isValueType(type)) {
const resource = resources.typeToValidationInfo.get(type.value.name);
if (!resource)
return type;
return { value: 'declared' in resource ? resource.declared : resource.inferred };
}
return type;
};
// detects extra properties & validates matched ones on consistency by the 'optional' property
for (const [name, foundProp] of allInferredProps.entries()) {
const expectedProp = declaredProps.get(name);
if (expectedProp) {
const foundTypeAsStr = propertyTypeToString(foundProp.type, 'DeclaredType');
const expectedTypeAsStr = propertyTypeToString(expectedProp.type, 'DeclaredType');
const typeAlternativesErrors = isTypeAssignable(matchingProp(foundProp.type), expectedProp.type);
if (!typeAlternativesErrors && expectedTypeAsStr !== 'unknown') {
const errorMsgPrefix = `The assigned type '${foundTypeAsStr}' is not compatible with the declared property '${name}' of type '${expectedTypeAsStr}'.`;
applyErrorToProperties(foundProp.astNodes, errorMsgPrefix);
}
if (foundProp.optional && !isOptionalProperty(expectedProp)) {
applyMissingPropErrorToRules(name);
}
}
else if (ownInferredProps.has(name)) {
// Only apply the superfluous property error on properties which are actually declared on the current type
applyErrorToProperties(foundProp.astNodes, `A property '${name}' is not expected.`);
}
}
// Detect any missing properties
const missingProps = new Set();
for (const [name, expectedProperties] of declaredProps.entries()) {
const foundProperty = allInferredProps.get(name);
if (!foundProperty && !isOptionalProperty(expectedProperties) && expectedProperties.defaultValue === undefined) {
missingProps.add(name);
}
}
if (missingProps.size > 0) {
const prefix = missingProps.size > 1 ? 'Properties' : 'A property';
const postfix = missingProps.size > 1 ? 'are expected' : 'is expected';
const props = Array.from(missingProps).map(e => `'${e}'`).sort().join(', ');
applyErrorToType(`${prefix} ${props} ${postfix}.`);
}
}
//# sourceMappingURL=types-validator.js.map
File diff suppressed because one or more lines are too long
@@ -0,0 +1,17 @@
/******************************************************************************
* Copyright 2022 TypeFox GmbH
* This program and the accompanying materials are made available under the
* terms of the MIT License, which is available in the project root.
******************************************************************************/
import type { Grammar } from '../../languages/generated/ast.js';
import type { ValidationResources } from '../workspace/documents.js';
import type { LangiumGrammarServices } from '../langium-grammar-module.js';
export declare class LangiumGrammarValidationResourcesCollector {
private readonly services;
constructor(services: LangiumGrammarServices);
collectValidationResources(grammar: Grammar): ValidationResources;
private collectValidationInfo;
private collectSuperProperties;
private addSuperProperties;
}
//# sourceMappingURL=validation-resources-collector.d.ts.map
@@ -0,0 +1 @@
{"version":3,"file":"validation-resources-collector.d.ts","sourceRoot":"","sources":["../../../src/grammar/validation/validation-resources-collector.ts"],"names":[],"mappings":"AAAA;;;;gFAIgF;AAGhF,OAAO,KAAK,EAA2B,OAAO,EAA+B,MAAM,kCAAkC,CAAC;AAEtH,OAAO,KAAK,EAAwB,mBAAmB,EAAE,MAAM,2BAA2B,CAAC;AAC3F,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,8BAA8B,CAAC;AAS3E,qBAAa,0CAA0C;IACnD,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAsB;gBAEnC,QAAQ,EAAE,sBAAsB;IAI5C,0BAA0B,CAAC,OAAO,EAAE,OAAO,GAAG,mBAAmB;IAajE,OAAO,CAAC,qBAAqB;IA8B7B,OAAO,CAAC,sBAAsB;IAU9B,OAAO,CAAC,kBAAkB;CAc7B"}
@@ -0,0 +1,93 @@
/******************************************************************************
* Copyright 2022 TypeFox GmbH
* This program and the accompanying materials are made available under the
* terms of the MIT License, which is available in the project root.
******************************************************************************/
import { MultiMap } from '../../utils/collections.js';
import { stream } from '../../utils/stream.js';
import { isAction, isAlternatives, isGroup, isUnorderedGroup } from '../../languages/generated/ast.js';
import { mergeInterfaces, mergeTypesAndInterfaces } from '../type-system/types-util.js';
import { collectValidationAst } from '../type-system/ast-collector.js';
import { getActionType, getRuleTypeName } from '../../utils/grammar-utils.js';
export class LangiumGrammarValidationResourcesCollector {
constructor(services) {
this.services = services;
}
collectValidationResources(grammar) {
try {
const typeResources = collectValidationAst(grammar, this.services);
return {
typeToValidationInfo: this.collectValidationInfo(typeResources),
typeToSuperProperties: this.collectSuperProperties(typeResources),
};
}
catch (err) {
console.error('Error collecting validation resources', err);
return { typeToValidationInfo: new Map(), typeToSuperProperties: new Map() };
}
}
collectValidationInfo({ astResources, inferred, declared }) {
const res = new Map();
const typeNameToRulesActions = collectNameToRulesActions(astResources);
for (const type of mergeTypesAndInterfaces(inferred)) {
res.set(type.name, { inferred: type, inferredNodes: typeNameToRulesActions.get(type.name) });
}
const typeNametoInterfacesUnions = stream(astResources.interfaces)
.concat(astResources.types)
.reduce((acc, type) => acc.set(type.name, type), new Map());
for (const type of mergeTypesAndInterfaces(declared)) {
const node = typeNametoInterfacesUnions.get(type.name);
if (node) {
const inferred = res.get(type.name);
res.set(type.name, { ...inferred ?? {}, declared: type, declaredNode: node });
}
}
return res;
}
collectSuperProperties({ inferred, declared }) {
const typeToSuperProperties = new Map();
const interfaces = mergeInterfaces(inferred, declared);
const interfaceMap = new Map(interfaces.map(e => [e.name, e]));
for (const type of mergeInterfaces(inferred, declared)) {
typeToSuperProperties.set(type.name, this.addSuperProperties(type, interfaceMap, new Set()));
}
return typeToSuperProperties;
}
addSuperProperties(interfaceType, map, visited) {
if (visited.has(interfaceType.name)) {
return [];
}
visited.add(interfaceType.name);
const properties = [...interfaceType.properties];
for (const superType of interfaceType.superTypes) {
const value = map.get(superType.name);
if (value) {
properties.push(...this.addSuperProperties(value, map, visited));
}
}
return properties;
}
}
function collectNameToRulesActions({ parserRules, datatypeRules }) {
const acc = new MultiMap();
// collect rules
stream(parserRules)
.concat(datatypeRules)
.forEach(rule => acc.add(getRuleTypeName(rule), rule));
// collect actions
function collectActions(element) {
if (isAction(element)) {
const name = getActionType(element);
if (name) {
acc.add(name, element);
}
}
if (isAlternatives(element) || isGroup(element) || isUnorderedGroup(element)) {
element.elements.forEach(e => collectActions(e));
}
}
parserRules
.forEach(rule => collectActions(rule.definition));
return acc;
}
//# sourceMappingURL=validation-resources-collector.js.map
@@ -0,0 +1 @@
{"version":3,"file":"validation-resources-collector.js","sourceRoot":"","sources":["../../../src/grammar/validation/validation-resources-collector.ts"],"names":[],"mappings":"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"}
+113
View File
@@ -0,0 +1,113 @@
/******************************************************************************
* Copyright 2021 TypeFox GmbH
* This program and the accompanying materials are made available under the
* terms of the MIT License, which is available in the project root.
******************************************************************************/
import * as ast from '../../languages/generated/ast.js';
import type { References } from '../../references/references.js';
import type { Reference } from '../../syntax-tree.js';
import type { ValidationAcceptor } from '../../validation/validation-registry.js';
import type { AstNodeLocator } from '../../workspace/ast-node-locator.js';
import type { LangiumDocuments } from '../../workspace/documents.js';
import type { LangiumGrammarServices } from '../langium-grammar-module.js';
export interface LangiumGrammarValidationOptions {
/**
* Handling of type definitions and inference in the grammar.
*
* `normal` allows both inferred and declared types, `strict` only allows declared types.
*/
types?: 'normal' | 'strict';
}
export declare function registerValidationChecks(services: LangiumGrammarServices): void;
export declare namespace IssueCodes {
const GrammarNameUppercase = "grammar-name-uppercase";
const RuleNameUppercase = "rule-name-uppercase";
const UseRegexTokens = "use-regex-tokens";
const EntryRuleTokenSyntax = "entry-rule-token-syntax";
const CrossRefTokenSyntax = "cross-ref-token-syntax";
const ParserRuleToTypeDecl = "parser-rule-to-type-decl";
const UnnecessaryFileExtension = "unnecessary-file-extension";
const InvalidReturns = "invalid-returns";
const InvalidInfers = "invalid-infers";
const MissingInfer = "missing-infer";
const MissingReturns = "missing-returns";
const MissingCrossRefTerminal = "missing-cross-ref-terminal";
const SuperfluousInfer = "superfluous-infer";
const OptionalUnorderedGroup = "optional-unordered-group";
const ParsingRuleEmpty = "parsing-rule-empty";
}
export declare class LangiumGrammarValidator {
options: LangiumGrammarValidationOptions;
protected readonly references: References;
protected readonly nodeLocator: AstNodeLocator;
protected readonly documents: LangiumDocuments;
constructor(services: LangiumGrammarServices);
checkGrammarName(grammar: ast.Grammar, accept: ValidationAcceptor): void;
checkEntryGrammarRule(grammar: ast.Grammar, accept: ValidationAcceptor): void;
/**
* Check whether any rule defined in this grammar is a duplicate of an already defined rule or an imported rule
*/
checkUniqueRuleName(grammar: ast.Grammar, accept: ValidationAcceptor): void;
/**
* Check whether any type defined in this grammar is a duplicate of an already defined type or an imported type
*/
checkUniqueTypeName(grammar: ast.Grammar, accept: ValidationAcceptor): void;
private checkUniqueName;
checkUniqueTypeAndGrammarNames(inputGrammar: ast.Grammar, accept: ValidationAcceptor): void;
checkDuplicateImportedGrammar(grammar: ast.Grammar, accept: ValidationAcceptor): void;
/**
* Compared to the validation above, this validation only checks whether two imported grammars export the same grammar rule.
*/
checkUniqueImportedRules(grammar: ast.Grammar, accept: ValidationAcceptor): void;
private getDuplicateExportedRules;
checkGrammarTypeInfer(grammar: ast.Grammar, accept: ValidationAcceptor): void;
private getActionType;
checkHiddenTerminalRule(terminalRule: ast.TerminalRule, accept: ValidationAcceptor): void;
checkEmptyTerminalRule(terminalRule: ast.TerminalRule, accept: ValidationAcceptor): void;
checkEmptyParserRule(parserRule: ast.ParserRule, accept: ValidationAcceptor): void;
checkInvalidRegexFlags(token: ast.RegexToken, accept: ValidationAcceptor): void;
checkDirectlyUsedRegexFlags(token: ast.RegexToken, accept: ValidationAcceptor): void;
private getFlagRange;
checkUsedHiddenTerminalRule(ruleCall: ast.RuleCall | ast.TerminalRuleCall, accept: ValidationAcceptor): void;
checkUsedFragmentTerminalRule(ruleCall: ast.RuleCall, accept: ValidationAcceptor): void;
checkCrossReferenceSyntax(crossRef: ast.CrossReference, accept: ValidationAcceptor): void;
checkPackageImport(imp: ast.GrammarImport, accept: ValidationAcceptor): void;
checkInvalidCharacterRange(range: ast.CharacterRange, accept: ValidationAcceptor): void;
checkGrammarForUnusedRules(grammar: ast.Grammar, accept: ValidationAcceptor): void;
checkClashingTerminalNames(grammar: ast.Grammar, accept: ValidationAcceptor): void;
checkRuleName(rule: ast.AbstractRule, accept: ValidationAcceptor): void;
/** This validation checks, that parser rules which are called multiple times are assigned (except for fragments). */
checkMultiRuleCallsAreAssigned(call: ast.RuleCall, accept: ValidationAcceptor): void;
checkTypeReservedName(type: ast.Interface | ast.TypeAttribute | ast.Type | ast.InferredType, accept: ValidationAcceptor): void;
checkAssignmentReservedName(assignment: ast.Assignment | ast.Action, accept: ValidationAcceptor): void;
checkParserRuleReservedName(rule: ast.ParserRule, accept: ValidationAcceptor): void;
private checkReservedName;
checkKeyword(keyword: ast.Keyword, accept: ValidationAcceptor): void;
checkUnorderedGroup(unorderedGroup: ast.UnorderedGroup, accept: ValidationAcceptor): void;
checkRuleParameters(rule: ast.ParserRule, accept: ValidationAcceptor): void;
checkParserRuleDataType(rule: ast.ParserRule, accept: ValidationAcceptor): void;
checkFragmentKeywords(rule: ast.ParserRule, accept: ValidationAcceptor): void;
checkInfixRuleDataType(rule: ast.InfixRule, accept: ValidationAcceptor): void;
checkAssignmentToFragmentRule(assignment: ast.Assignment, accept: ValidationAcceptor): void;
checkAssignmentTypes(assignment: ast.Assignment, accept: ValidationAcceptor): void;
/** This validation recursively looks at all assignments (and rewriting actions) with '=' as assignment operator and checks,
* whether the operator should be '+=' instead. */
checkOperatorMultiplicitiesForMultiAssignments(rule: ast.ParserRule, accept: ValidationAcceptor): void;
private checkOperatorMultiplicitiesForMultiAssignmentsIndependent;
private checkOperatorMultiplicitiesForMultiAssignmentsNested;
checkInterfacePropertyTypes(interfaceDecl: ast.Interface, accept: ValidationAcceptor): void;
protected createMixedTypeError(propName: string): string;
checkTerminalRuleReturnType(rule: ast.TerminalRule, accept: ValidationAcceptor): void;
checkRuleCallParameters(ruleCall: ast.RuleCall, accept: ValidationAcceptor): void;
private checkNamedArguments;
checkCrossRefNameAssignment(reference: ast.CrossReference, accept: ValidationAcceptor): void;
checkCrossRefTerminalType(reference: ast.CrossReference, accept: ValidationAcceptor): void;
checkCrossRefType(reference: ast.CrossReference, accept: ValidationAcceptor): void;
checkCrossReferenceToTypeUnion(reference: ast.CrossReference, accept: ValidationAcceptor): void;
checkFragmentsInTypes(type: ast.SimpleType, accept: ValidationAcceptor): void;
checkReferenceTypeUnion(type: ast.ReferenceType, accept: ValidationAcceptor): void;
protected checkReferenceToRuleButNotType(type: Reference<ast.AbstractType>): string | undefined;
checkAssignmentWithFeatureName(assignment: ast.Assignment, accept: ValidationAcceptor): void;
checkPredicateNotSupported(node: ast.Assignment | ast.Group | ast.Keyword | ast.RuleCall, accept: ValidationAcceptor): void;
}
//# sourceMappingURL=validator.d.ts.map
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long