/** * Validation message constants for DomainLang. * * Centralizes all validation messages to ensure consistency * and facilitate internationalization in the future. * * Messages follow VS Code conventions: * - Clear problem statement * - Brief DDD context explaining why it matters * - Inline example showing the fix * - Clickable documentation link via CodeDescription */ import type { CodeDescription } from 'vscode-languageserver-types'; // ============================================================================ // Issue Codes for Code Actions // ============================================================================ /** * Diagnostic codes used to identify validation issues. * Code actions match on these codes to provide quick fixes. * * Naming convention: CATEGORY_SPECIFIC_ISSUE */ export const IssueCodes = { // Import & Dependency Issues ImportMissingUri: 'import-missing-uri', ImportRequiresManifest: 'import-requires-manifest', ImportNotInManifest: 'import-not-in-manifest', ImportNotInstalled: 'import-not-installed', ImportConflictingSourcePath: 'import-conflicting-source-path', ImportMissingSourceOrPath: 'import-missing-source-or-path', ImportMissingRef: 'import-missing-ref', ImportAbsolutePath: 'import-absolute-path', ImportEscapesWorkspace: 'import-escapes-workspace', ImportUnresolved: 'import-unresolved', ImportCycleDetected: 'import-cycle-detected', // Domain Issues DomainNoVision: 'domain-no-vision', DomainCircularHierarchy: 'domain-circular-hierarchy', // Bounded Context Issues BoundedContextNoDescription: 'bounded-context-no-description', BoundedContextNoDomain: 'bounded-context-no-domain', BoundedContextClassificationConflict: 'bounded-context-classification-conflict', BoundedContextTeamConflict: 'bounded-context-team-conflict', // Integration Pattern Issues AclOnWrongSide: 'acl-on-wrong-side', ConformistOnWrongSide: 'conformist-on-wrong-side', OhsOnWrongSide: 'ohs-on-wrong-side', SupplierOnWrongSide: 'supplier-on-wrong-side', CustomerOnWrongSide: 'customer-on-wrong-side', SupplierOnBidirectional: 'supplier-on-bidirectional', CustomerOnBidirectional: 'customer-on-bidirectional', SelfSymmetricRelationship: 'self-symmetric-relationship', TooManyPatterns: 'too-many-patterns', // Context/Domain Map Issues ContextMapNoContexts: 'context-map-no-contexts', ContextMapNoRelationships: 'context-map-no-relationships', ContextMapDuplicateRelationship: 'context-map-duplicate-relationship', DomainMapNoDomains: 'domain-map-no-domains', // Reference Issues UnresolvedReference: 'unresolved-reference', // Metadata Issues MetadataMissingName: 'metadata-missing-name', // General Issues DuplicateElement: 'duplicate-element' } as const; export type IssueCode = typeof IssueCodes[keyof typeof IssueCodes]; // ============================================================================ // Documentation Link Utilities // ============================================================================ const REPO_BASE = 'https://github.com/DomainLang/DomainLang/blob/main'; const DOCS_BASE = `${REPO_BASE}/dsl/domain-lang/docs`; /** * Builds a documentation URL for error messages. * @param docPath - Relative path from docs/ folder * @param anchor - Optional section anchor (without #) */ const buildDocLink = (docPath: string, anchor?: string): string => { const anchorPart = anchor ? `#${anchor}` : ''; return `${DOCS_BASE}/${docPath}${anchorPart}`; }; /** * Creates a CodeDescription for clickable documentation links in VS Code. * @param docPath - Relative path from docs/ folder * @param anchor - Optional section anchor (without #) */ export const buildCodeDescription = (docPath: string, anchor?: string): CodeDescription => ({ href: buildDocLink(docPath, anchor) }); // ============================================================================ // Enhanced Validation Messages // ============================================================================ export const ValidationMessages = { /** * Warning message when a domain lacks a vision statement. * @param name - The name of the domain */ DOMAIN_NO_VISION: (name: string) => `Domain '${name}' is missing a vision statement.`, /** * Error message when a circular domain hierarchy is detected. * @param cycle - Array of domain names forming the cycle */ DOMAIN_CIRCULAR_HIERARCHY: (cycle: string[]) => `Circular domain hierarchy detected: ${cycle.join(' → ')}.`, /** * Warning message when a bounded context lacks a description. * @param name - The name of the bounded context */ BOUNDED_CONTEXT_NO_DESCRIPTION: (name: string) => `Bounded Context '${name}' is missing a description.`, /** * Warning message when a bounded context lacks a domain reference. * @param name - The name of the bounded context */ BOUNDED_CONTEXT_NO_DOMAIN: (name: string) => `Bounded Context '${name}' must belong to a domain. Use 'for DomainName'.`, /** * Warning when classification is specified both inline and in a block. * Inline value takes precedence. * @param bcName - The name of the bounded context * @param inlineClassification - The inline classification name (from 'as') * @param blockClassification - The block classification name (from 'classification:') */ BOUNDED_CONTEXT_CLASSIFICATION_CONFLICT: (_bcName: string, inlineClassification?: string, blockClassification?: string) => { const inlinePart = inlineClassification ? ` ('as ${inlineClassification}')` : ''; const blockPart = blockClassification ? ` ('classification: ${blockClassification}')` : ''; return `Classification specified both inline${inlinePart} and in block${blockPart}. Inline value takes precedence.`; }, /** * Warning when team is specified both inline and in a block. * Inline value takes precedence. * @param bcName - The name of the bounded context * @param inlineTeam - The inline team name (from 'by') * @param blockTeam - The block team name (from 'team:') */ BOUNDED_CONTEXT_TEAM_CONFLICT: (_bcName: string, inlineTeam?: string, blockTeam?: string) => { const inlinePart = inlineTeam ? ` ('by ${inlineTeam}')` : ''; const blockPart = blockTeam ? ` ('team: ${blockTeam}')` : ''; return `Team specified both inline${inlinePart} and in block${blockPart}. Inline value takes precedence.`; }, /** * Error message when an element is defined multiple times. * @param fqn - The fully qualified name of the duplicate element */ DUPLICATE_ELEMENT: (fqn: string) => `Duplicate element: '${fqn}' is already defined.`, // ======================================================================== // Integration Pattern & Relationship Validation // ======================================================================== /** * Warning when Anti-Corruption Layer is on the wrong side of relationship. * ACL should protect the consuming context (downstream). */ ACL_ON_WRONG_SIDE: (context: string, side: 'left' | 'right') => `Anti-Corruption Layer (ACL) on '${context}' should be on downstream (consuming) side, not ${side} side.`, /** * Warning when Conformist pattern is on the wrong side. * Conformist accepts upstream model without translation. */ CONFORMIST_ON_WRONG_SIDE: (context: string, side: 'left' | 'right') => `Conformist (CF) on '${context}' should be on downstream (consuming) side, not ${side} side.`, /** * Warning when Open Host Service is on the wrong side of relationship. * OHS should be on the upstream (providing) side. */ OHS_ON_WRONG_SIDE: (context: string, side: 'left' | 'right') => `Open Host Service (OHS) on '${context}' should be on upstream (providing) side, not ${side} side.`, /** * Error when Supplier is on the wrong side of relationship. * Supplier must always be upstream. */ SUPPLIER_ON_WRONG_SIDE: (context: string, _side: 'left' | 'right') => `Supplier (S) on '${context}' must be on the upstream side. Supplier is always the provider in a Customer/Supplier relationship.`, /** * Error when Customer is on the wrong side of relationship. * Customer must always be downstream. */ CUSTOMER_ON_WRONG_SIDE: (context: string, _side: 'left' | 'right') => `Customer (C) on '${context}' must be on the downstream side. Customer is always the consumer in a Customer/Supplier relationship.`, /** * Error when Supplier is used on a bidirectional relationship. * Customer/Supplier is inherently directional. */ SUPPLIER_ON_BIDIRECTIONAL: () => 'Supplier [S] cannot be used on a bidirectional (<->) relationship — Customer/Supplier is inherently directional.', /** * Error when Customer is used on a bidirectional relationship. * Customer/Supplier is inherently directional. */ CUSTOMER_ON_BIDIRECTIONAL: () => 'Customer [C] cannot be used on a bidirectional (<->) relationship — Customer/Supplier is inherently directional.', /** * Warning when a symmetric relationship references the same context on both sides. */ SELF_SYMMETRIC_RELATIONSHIP: (context: string) => `Symmetric relationship with self: '${context}' has a symmetric relationship with itself. This is likely unintended.`, /** * Info message when relationship has too many integration patterns. * Suggests possible syntax confusion. */ TOO_MANY_PATTERNS: (count: number, side: 'left' | 'right') => `Too many integration patterns (${count}) on ${side} side. Typically use 1-2 patterns per side.`, // ======================================================================== // Import & Dependency Validation (PRS-010 Phase 8) // ======================================================================== /** * Error when import statement has no URI. */ IMPORT_MISSING_URI: () => `Import statement must have a URI. Use: import "package" or import "./local-path"`, /** * Error when external dependency requires model.yaml but none exists. * @param specifier - The import specifier (e.g., "core", "domainlang/patterns") */ IMPORT_REQUIRES_MANIFEST: (specifier: string) => `External dependency '${specifier}' requires model.yaml.\n` + `Hint: Create model.yaml and add the dependency:\n` + ` dependencies:\n` + ` ${specifier}:\n` + ` ref: v1.0.0`, /** * Error when import specifier not found in manifest dependencies. * @param alias - The dependency alias/specifier */ IMPORT_NOT_IN_MANIFEST: (alias: string) => `Import '${alias}' not found in model.yaml dependencies.\n` + `Hint: Run 'dlang add ${alias} @' to add it, or manually add to model.yaml:\n` + ` dependencies:\n` + ` ${alias}:\n` + ` ref: v1.0.0`, /** * Error when dependency not installed (no lock file entry). * @param alias - The dependency alias */ IMPORT_NOT_INSTALLED: (alias: string) => `Dependency '${alias}' not installed.\n` + `Hint: Run 'dlang install' to fetch dependencies and generate model.lock.`, /** * Error when dependency has conflicting source and path definitions. * @param alias - The dependency alias */ IMPORT_CONFLICTING_SOURCE_PATH: (alias: string) => `Dependency '${alias}' cannot define both 'source' and 'path' in model.yaml.\n` + `Hint: Use 'source' for git-based packages or 'path' for local workspace packages.`, /** * Error when dependency is missing both source and path. * @param alias - The dependency alias */ IMPORT_MISSING_SOURCE_OR_PATH: (alias: string) => `Dependency '${alias}' must define either 'source' or 'path' in model.yaml.\n` + `Hint: Add 'source: owner/repo' for git packages, or 'path: ./local/path' for local packages.`, /** * Error when git dependency is missing ref (tag, branch, or commit). * @param alias - The dependency alias */ IMPORT_MISSING_REF: (alias: string) => `Dependency '${alias}' must specify a git ref in model.yaml.\n` + `Hint: Add a git ref: 'ref: v1.0.0' (tag), 'ref: main' (branch), or a commit SHA.`, /** * Error when local path uses absolute path. * @param alias - The dependency alias * @param absolutePath - The absolute path that was specified */ IMPORT_ABSOLUTE_PATH: (alias: string, absolutePath: string) => `Local path dependency '${alias}' cannot use absolute path '${absolutePath}'.\n` + `Hint: Use a relative path from the workspace root, e.g., 'path: ./packages/shared'.`, /** * Error when local path escapes workspace boundary. * @param alias - The dependency alias */ IMPORT_ESCAPES_WORKSPACE: (alias: string) => `Local path dependency '${alias}' escapes workspace boundary.\n` + `Hint: Local dependencies must be within the workspace. Consider moving the dependency or using a git-based source.`, /** * Error when import path cannot be resolved to a file. * @param uri - The import URI that couldn't be resolved */ IMPORT_UNRESOLVED: (uri: string) => `Cannot resolve import '${uri}'.\n` + `Hint: Check that the file exists and the path is correct.`, /** * Error when an import creates a cycle in the dependency graph. * @param cycleDisplay - Human-readable cycle path (e.g., "a.dlang → b.dlang → a.dlang") */ IMPORT_CYCLE_DETECTED: (cycleDisplay: string) => `Import cycle detected: ${cycleDisplay}.\n` + `Hint: Break the cycle by extracting shared definitions into a separate file.`, // ======================================================================== // Context Map & Domain Map Validation // ======================================================================== /** * Warning when context map contains no bounded contexts. * @param name - The context map name */ CONTEXT_MAP_NO_CONTEXTS: (name: string) => `Context Map '${name}' contains no bounded contexts.\n` + `Hint: Use 'contains ContextA, ContextB' to specify which contexts are in the map.`, /** * Info when context map has multiple contexts but no relationships. * @param name - The context map name * @param count - Number of contexts */ CONTEXT_MAP_NO_RELATIONSHIPS: (name: string, count: number) => `Context Map '${name}' contains ${count} contexts but no documented relationships.\n` + `Hint: Add relationships to show how contexts integrate (e.g., 'A [OHS] -> [CF] B').`, /** * Warning when a context map contains duplicate relationships. * @param leftContext - Name of the left context * @param rightContext - Name of the right context */ CONTEXT_MAP_DUPLICATE_RELATIONSHIP: (leftContext: string, rightContext: string) => `Duplicate relationship between '${leftContext}' and '${rightContext}' in context map.`, /** * Warning when domain map contains no domains. * @param name - The domain map name */ DOMAIN_MAP_NO_DOMAINS: (name: string) => `Domain Map '${name}' contains no domains.\n` + `Hint: Use 'contains DomainA, DomainB' to specify which domains are in the map.`, // ======================================================================== // Reference Resolution Validation // ======================================================================== /** * Error when a reference cannot be resolved (for MultiReferences). * @param type - The type being referenced (e.g., 'BoundedContext') * @param name - The unresolved name */ UNRESOLVED_REFERENCE: (type: string, name: string) => `Could not resolve reference to ${type} named '${name}'.`, // ======================================================================== // Metadata Validation // ======================================================================== /** * Error when metadata is missing a name. */ METADATA_MISSING_NAME: () => `Metadata must have a name.\n` + `Hint: Define metadata with: Metadata MyMetadata { ... }` } as const;