Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,5 @@ src/language/generated/
syntaxes/
.idea
.DS_Store
cml-ls/
cml-ls/
docs/*.png
20 changes: 20 additions & 0 deletions docs/class_semantic_tokens.puml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
@startuml
abstract class AbstractSemanticTokenProvider

class ContextMapperDslSemanticTokenProvider {
# highlightElement(AstNode, SemanticTokenAcceptor)
}

class SemanticTokenProviderRegistry {
+ get(AstNode): ContextMapperSemanticTokenProvider<AstNode>
}

interface ContextMapperSemanticTokenProvider {
+ supports(AstNode): boolean
+ highlight(T, SemanticTokenAcceptor)
}

AbstractSemanticTokenProvider <|-- ContextMapperDslSemanticTokenProvider
ContextMapperDslSemanticTokenProvider --> SemanticTokenProviderRegistry
SemanticTokenProviderRegistry o-- "0..*" ContextMapperSemanticTokenProvider
@enduml
27 changes: 27 additions & 0 deletions docs/class_semantic_validation.puml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
@startuml
abstract class ValidationRegistry {
+ register(ValidationChecks<T>, ThisParameterType)
}

class ContextMapperDslValidationRegistry {
}

class ContextMapperDslValidator {
+ checkContextMappingModel(ContextMappingModel, ValidationAcceptor)
+ checkValue(Value, ValidationAcceptor)
}

class ContextMapperValidationProviderRegistry {
+ get(AstNode): ContextMapperValidationProvider<AstNode>
}

interface ContextMapperValidationProvider {
+ supports(AstNode): boolean
+ validate(T, ValidationAcceptor)
}

ValidationRegistry <|-- ContextMapperDslValidationRegistry
ContextMapperDslValidationRegistry --> ContextMapperDslValidator
ContextMapperDslValidator --> ContextMapperValidationProviderRegistry
ContextMapperValidationProviderRegistry o-- "0..*" ContextMapperValidationProvider
@enduml
14 changes: 10 additions & 4 deletions src/language/ContextMapperDslModule.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,11 @@
type PartialLangiumServices
} from 'langium/lsp'
import { ContextMapperDslGeneratedModule, ContextMapperDslGeneratedSharedModule } from './generated/module.js'
import { ContextMapperDslValidator, registerValidationChecks } from './ContextMapperDslValidator.js'
import { ContextMapperDslValidator } from './validation/ContextMapperDslValidator.js'
import { ContextMapperDslSemanticTokenProvider } from './semantictokens/ContextMapperDslSemanticTokenProvider.js'
import { SemanticTokenProviderRegistry } from './semantictokens/SemanticTokenProviderRegistry.js'
import { ContextMapperDslValidationRegistry } from './validation/ContextMapperDslValidationRegistry.js'
import { ContextMapperValidationProviderRegistry } from './validation/ContextMapperValidationProviderRegistry.js'

/**
* Declaration of custom services - add your own service classes here.
Expand All @@ -34,12 +37,16 @@

type ModuleType = PartialLangiumServices & ContextMapperDslAddedServices;

const semanticTokenProviderRegistry = new SemanticTokenProviderRegistry()
const validationProviderRegistry = new ContextMapperValidationProviderRegistry()

export const ContextMapperDslModule: Module<ContextMapperDslServices, ModuleType> = {
validation: {
ContextMapperDslValidator: () => new ContextMapperDslValidator()
ContextMapperDslValidator: () => new ContextMapperDslValidator(validationProviderRegistry),
ValidationRegistry: (services) => new ContextMapperDslValidationRegistry(services)
},
lsp: {
SemanticTokenProvider: (services) => new ContextMapperDslSemanticTokenProvider(services)
SemanticTokenProvider: (services) => new ContextMapperDslSemanticTokenProvider(services, semanticTokenProviderRegistry)
}
}

Expand Down Expand Up @@ -72,11 +79,10 @@
ContextMapperDslModule
)
shared.ServiceRegistry.register(ContextMapperDsl)
registerValidationChecks(ContextMapperDsl.validation.ValidationRegistry, ContextMapperDsl.validation.ContextMapperDslValidator)
if (!context.connection) {
// We don't run inside a language server
// Therefore, initialize the configuration provider instantly
shared.workspace.ConfigurationProvider.initialized({})

Check notice on line 85 in src/language/ContextMapperDslModule.ts

View workflow job for this annotation

GitHub Actions / Qodana for JS

Result of method call returning a promise is ignored

Promise returned from initialized is ignored
}
return { shared, ContextMapperDsl }
}
28 changes: 0 additions & 28 deletions src/language/ContextMapperDslValidator.ts

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,90 +1,21 @@
import { AstNode } from 'langium'
import { AbstractSemanticTokenProvider, SemanticTokenAcceptor } from 'langium/lsp'
import {
ContextMappingModel,
isContextMappingModel
} from '../generated/ast.js'
import { ContextMapSemanticTokenProvider } from './contextMap/ContextMapSemanticTokenProvider.js'
import { SemanticTokenTypes } from 'vscode-languageserver-types'
import { BoundedContextSemanticTokenProvider } from './boundedContext/BoundedContextSemanticTokenProvider.js'
import { DomainSemanticTokenProvider } from './domain/DomainSemanticTokenProvider.js'
import { AggregateSemanticTokenProvider } from './boundedContext/AggregateSemanticTokenProvider.js'
import { RequirementsSemanticTokenProvider } from './requirements/RequirementsSemanticTokenProvider.js'
import { ValueSemanticTokenProvider } from './vdad/ValueSemanticTokenProvider.js'
import { ContextMapperSemanticTokenProvider } from './ContextMapperSemanticTokenProvider.js'
import { SculptorModuleSemanticTokenProvider } from './boundedContext/SculptorModuleSemanticTokenProvider.js'
import { RelationshipSemanticTokenProvider } from './contextMap/RelationshipSemanticTokenProvider.js'
import { FeatureSemanticTokenProvider } from './requirements/FeatureSemanticTokenProvider.js'
import { StoryValuationSemanticTokenProvider } from './requirements/StoryValuationSemanticTokenProvider.js'
import { AbstractStakeholderSemanticTokenProvider } from './vdad/AbstractStakeholderSemanticTokenProvider.js'
import { ActionSemanticTokenProvider } from './vdad/ActionSemanticTokenProvider.js'
import { ConsequenceSemanticTokenProvider } from './vdad/ConsequenceSemanticTokenProvider.js'
import { StakeholderSemanticTokenProvider } from './vdad/StakeholderSemanticTokenProvider.js'
import { ValueClusterSemanticTokenProvider } from './vdad/ValueClusterSemanticTokenProvider.js'
import { ValueElicitationSemanticTokenProvider } from './vdad/ValueElicitationSemanticTokenProvider.js'
import { ValueEpicSemanticTokenProvider } from './vdad/ValueEpicSemanticTokenProvider.js'
import { ValueNarrativeSemanticTokenProvider } from './vdad/ValueNarrativeSemanticTokenProvider.js'
import { ValueRegisterSemanticTokenProvider } from './vdad/ValueRegisterSemanticTokenProvider.js'
import { ValueWeightingSemanticTokenProvider } from './vdad/ValueWeightingSemanticTokenProvider.js'
import { AbstractSemanticTokenProvider, LangiumServices, SemanticTokenAcceptor } from 'langium/lsp'
import { SemanticTokenProviderRegistry } from './SemanticTokenProviderRegistry.js'

export class ContextMapperDslSemanticTokenProvider extends AbstractSemanticTokenProvider {
private readonly semanticTokenProviders: ContextMapperSemanticTokenProvider<AstNode>[] = [
new AggregateSemanticTokenProvider(),
new BoundedContextSemanticTokenProvider(),
new SculptorModuleSemanticTokenProvider(),
new ContextMapSemanticTokenProvider(),
new RelationshipSemanticTokenProvider(),
new DomainSemanticTokenProvider(),
new FeatureSemanticTokenProvider(),
new RequirementsSemanticTokenProvider(),
new StoryValuationSemanticTokenProvider(),
new AbstractStakeholderSemanticTokenProvider(),
new ActionSemanticTokenProvider(),
new ConsequenceSemanticTokenProvider(),
new StakeholderSemanticTokenProvider(),
new ValueClusterSemanticTokenProvider(),
new ValueElicitationSemanticTokenProvider(),
new ValueEpicSemanticTokenProvider(),
new ValueNarrativeSemanticTokenProvider(),
new ValueRegisterSemanticTokenProvider(),
new ValueSemanticTokenProvider(),
new ValueWeightingSemanticTokenProvider()
]
private readonly semanticTokenProviderRegistry: SemanticTokenProviderRegistry

protected override highlightElement (node: AstNode, acceptor: SemanticTokenAcceptor) {
if (isContextMappingModel(node)) {
if (node.$cstNode) {
this.highlightComments(/\/\*[\s\S]*?\*\//g, node, acceptor)
this.highlightComments(/\/\/[^\n\r]*/g, node, acceptor)
}
} else {
for (const provider of this.semanticTokenProviders) {
if (provider.supports(node)) {
provider.highlight(node, acceptor)
return
}
}

console.error('Uncaught node type', node.$type)
}
constructor (services: LangiumServices, registry: SemanticTokenProviderRegistry) {
super(services)
this.semanticTokenProviderRegistry = registry
}

private highlightComments (regex: RegExp, node: ContextMappingModel, acceptor: SemanticTokenAcceptor) {
if (node.$document == null) {
throw new Error('Document not found')
}
const text = node.$document.textDocument.getText()
for (const match of text.matchAll(regex)) {
if (match?.index == null) {
continue
}
const position = node.$document.textDocument.positionAt(match.index)
acceptor({
type: SemanticTokenTypes.comment,
line: position.line,
char: position.character,
length: match[0].length
})
protected override highlightElement (node: AstNode, acceptor: SemanticTokenAcceptor) {
const semanticTokenProvider = this.semanticTokenProviderRegistry.get(node)
if (semanticTokenProvider) {
semanticTokenProvider.highlight(node, acceptor)
} else {
console.error('Node type with no token provider', node.$type)
}
}
}
53 changes: 53 additions & 0 deletions src/language/semantictokens/SemanticTokenProviderRegistry.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { ContextMapperSemanticTokenProvider } from './ContextMapperSemanticTokenProvider.js'
import { AstNode } from 'langium'
import { AggregateSemanticTokenProvider } from './boundedContext/AggregateSemanticTokenProvider.js'
import { BoundedContextSemanticTokenProvider } from './boundedContext/BoundedContextSemanticTokenProvider.js'
import { SculptorModuleSemanticTokenProvider } from './boundedContext/SculptorModuleSemanticTokenProvider.js'
import { ContextMapSemanticTokenProvider } from './contextMap/ContextMapSemanticTokenProvider.js'
import { RelationshipSemanticTokenProvider } from './contextMap/RelationshipSemanticTokenProvider.js'
import { DomainSemanticTokenProvider } from './domain/DomainSemanticTokenProvider.js'
import { FeatureSemanticTokenProvider } from './requirements/FeatureSemanticTokenProvider.js'
import { RequirementsSemanticTokenProvider } from './requirements/RequirementsSemanticTokenProvider.js'
import { StoryValuationSemanticTokenProvider } from './requirements/StoryValuationSemanticTokenProvider.js'
import { AbstractStakeholderSemanticTokenProvider } from './vdad/AbstractStakeholderSemanticTokenProvider.js'
import { ActionSemanticTokenProvider } from './vdad/ActionSemanticTokenProvider.js'
import { ConsequenceSemanticTokenProvider } from './vdad/ConsequenceSemanticTokenProvider.js'
import { StakeholderSemanticTokenProvider } from './vdad/StakeholderSemanticTokenProvider.js'
import { ValueClusterSemanticTokenProvider } from './vdad/ValueClusterSemanticTokenProvider.js'
import { ValueElicitationSemanticTokenProvider } from './vdad/ValueElicitationSemanticTokenProvider.js'
import { ValueEpicSemanticTokenProvider } from './vdad/ValueEpicSemanticTokenProvider.js'
import { ValueNarrativeSemanticTokenProvider } from './vdad/ValueNarrativeSemanticTokenProvider.js'
import { ValueRegisterSemanticTokenProvider } from './vdad/ValueRegisterSemanticTokenProvider.js'
import { ValueSemanticTokenProvider } from './vdad/ValueSemanticTokenProvider.js'
import { ValueWeightingSemanticTokenProvider } from './vdad/ValueWeightingSemanticTokenProvider.js'
import { ContextMappingModelSemanticTokenProvider } from './model/ContextMappingModelSemanticTokenProvider.js'

export class SemanticTokenProviderRegistry {
private readonly semanticTokenProviders: ContextMapperSemanticTokenProvider<AstNode>[] = [
new AggregateSemanticTokenProvider(),
new BoundedContextSemanticTokenProvider(),
new SculptorModuleSemanticTokenProvider(),
new ContextMapSemanticTokenProvider(),
new RelationshipSemanticTokenProvider(),
new DomainSemanticTokenProvider(),
new FeatureSemanticTokenProvider(),
new RequirementsSemanticTokenProvider(),
new StoryValuationSemanticTokenProvider(),
new AbstractStakeholderSemanticTokenProvider(),
new ActionSemanticTokenProvider(),
new ConsequenceSemanticTokenProvider(),
new StakeholderSemanticTokenProvider(),
new ValueClusterSemanticTokenProvider(),
new ValueElicitationSemanticTokenProvider(),
new ValueEpicSemanticTokenProvider(),
new ValueNarrativeSemanticTokenProvider(),
new ValueRegisterSemanticTokenProvider(),
new ValueSemanticTokenProvider(),
new ValueWeightingSemanticTokenProvider(),
new ContextMappingModelSemanticTokenProvider()
]

get (node: AstNode): ContextMapperSemanticTokenProvider<AstNode> | undefined {
return this.semanticTokenProviders.find(provider => provider.supports(node))
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { ContextMapperSemanticTokenProvider } from '../ContextMapperSemanticTokenProvider.js'
import { ContextMappingModel, isContextMappingModel } from '../../generated/ast.js'
import { AstNode } from 'langium'
import { SemanticTokenAcceptor } from 'langium/lsp'
import { SemanticTokenTypes } from 'vscode-languageserver-types'

const ML_COMMENT_REGEX = /\/\*[\s\S]*?\*\//g
const SL_COMMENT_REGEX = /\/\/[^\n\r]*/g

export class ContextMappingModelSemanticTokenProvider implements ContextMapperSemanticTokenProvider<ContextMappingModel> {
supports (node: AstNode): node is ContextMappingModel {
return isContextMappingModel(node)
}

highlight (node: ContextMappingModel, acceptor: SemanticTokenAcceptor): void {
if (node.$cstNode) {
this.highlightComments(ML_COMMENT_REGEX, node, acceptor)
this.highlightComments(SL_COMMENT_REGEX, node, acceptor)
}
}

private highlightComments (regex: RegExp, node: ContextMappingModel, acceptor: SemanticTokenAcceptor) {
if (node.$document == null) {
throw new Error('Document not found')
}
const text = node.$document.textDocument.getText()
for (const match of text.matchAll(regex)) {
if (match?.index == null) {
continue
}
const position = node.$document.textDocument.positionAt(match.index)
acceptor({
type: SemanticTokenTypes.comment,
line: position.line,
char: position.character,
length: match[0].length
})
}
}
}
15 changes: 15 additions & 0 deletions src/language/validation/ContextMapperDslValidationRegistry.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { type ValidationChecks, ValidationRegistry } from 'langium'
import { ContextMapperDslServices } from '../ContextMapperDslModule.js'
import type { ContextMapperDslAstType } from '../generated/ast.js'

export class ContextMapperDslValidationRegistry extends ValidationRegistry {
constructor (services: ContextMapperDslServices) {
super(services)
const validator = services.validation.ContextMapperDslValidator
const checks: ValidationChecks<ContextMapperDslAstType> = {
ContextMappingModel: validator.checkContextMappingModel,
Value: validator.checkValue
}
super.register(checks, validator)
}
}
19 changes: 19 additions & 0 deletions src/language/validation/ContextMapperDslValidator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { ValidationAcceptor } from 'langium'
import type { ContextMappingModel, Value } from '../generated/ast.js'
import { ContextMapperValidationProviderRegistry } from './ContextMapperValidationProviderRegistry.js'

export class ContextMapperDslValidator {
private readonly _registry: ContextMapperValidationProviderRegistry

constructor (contextMapperValidationProviderRegistry: ContextMapperValidationProviderRegistry) {
this._registry = contextMapperValidationProviderRegistry
}

checkContextMappingModel (model: ContextMappingModel, acceptor: ValidationAcceptor) {
this._registry.get(model)?.validate(model, acceptor)
}

checkValue (value: Value, acceptor: ValidationAcceptor) {
this._registry.get(value)?.validate(value, acceptor)
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { AstNode, ValidationAcceptor } from 'langium'

export interface ContextMapperValidator<T extends AstNode> {
export interface ContextMapperValidationProvider<T extends AstNode> {
supports(node: AstNode): node is T
validate (node: T, acceptor: ValidationAcceptor): void
}
15 changes: 15 additions & 0 deletions src/language/validation/ContextMapperValidationProviderRegistry.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { AstNode } from 'langium'
import { ContextMapperValidationProvider } from './ContextMapperValidationProvider.js'
import { ValueValidationProvider } from './ValueValidationProvider.js'
import { ContextMappingModelValidationProvider } from './ContextMappingModelValidationProvider.js'

export class ContextMapperValidationProviderRegistry {
private readonly _providers: ContextMapperValidationProvider<AstNode>[] = [
new ValueValidationProvider(),
new ContextMappingModelValidationProvider()
]

get (node: AstNode): ContextMapperValidationProvider<AstNode> | undefined {
return this._providers.find(p => p.supports(node))
}
}
Loading
Loading