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
17 changes: 0 additions & 17 deletions .eslintrc.json

This file was deleted.

2 changes: 1 addition & 1 deletion .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ jobs:
uses: JetBrains/qodana-action@v2024.3
with:
pr-mode: false
args: --linter,jetbrains/qodana-js:2024.3
args: --baseline,qodana.serif.json,--fail-threshold,2
env:
QODANA_TOKEN: ${{ secrets.QODANA_TOKEN_379728607 }}
QODANA_ENDPOINT: 'https://qodana.cloud'
Expand Down
Binary file modified .yarn/install-state.gz
Binary file not shown.
31 changes: 31 additions & 0 deletions eslint.config.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import eslint from '@eslint/js'
import typescriptEslint from 'typescript-eslint';

export default typescriptEslint.config({
ignores: [
'esbuild.mjs',
'eslint.config.mjs',
'**/generated/**'
],
},
eslint.configs.recommended,
typescriptEslint.configs.recommendedTypeChecked,
typescriptEslint.configs.stylisticTypeChecked,
{
languageOptions: {
parserOptions: {
project: ['./tsconfig.json']
}
},
rules: {
'@typescript-eslint/no-unused-vars': [
'error',
{
'argsIgnorePattern': '^_',
'varsIgnorePattern': '^_',
'caughtErrorsIgnorePattern': '^_'
}
]
}
}
);
27 changes: 12 additions & 15 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,26 +32,23 @@
"clean": "rm -rf src/language/generated && rm -rf cml-ls/ && rm -rf dist/"
},
"dependencies": {
"langium": "~3.4.0",
"langium": "~3.5.0",
"vscode-languageserver": "~9.0.1",
"vscode-languageserver-types": "~3.17.5"
},
"devDependencies": {
"@types/node": "^18.0.0",
"@typescript-eslint/eslint-plugin": "~7.3.1",
"@typescript-eslint/parser": "~7.3.1",
"@eslint/js": "^9.26.0",
"@types/node": "^22.15.17",
"@vercel/ncc": "^0.38.3",
"@vitest/coverage-v8": "^3.1.1",
"concurrently": "~8.2.1",
"esbuild": "~0.20.2",
"eslint": "~8.57.0",
"eslint-config-standard": "^17.1.0",
"eslint-plugin-import": "^2.31.0",
"eslint-plugin-n": "^17.17.0",
"eslint-plugin-promise": "^7.2.1",
"langium-cli": "~3.4.0",
"typescript": "~5.1.6",
"vitest": "^3.1.1"
"@vitest/coverage-v8": "^3.1.3",
"concurrently": "~9.1.2",
"esbuild": "~0.25.4",
"eslint": "~9.26.0",
"jiti": "^2.4.2",
"langium-cli": "~3.5.0",
"typescript": "~5.8.3",
"typescript-eslint": "^8.32.0",
"vitest": "^3.1.3"
},
"packageManager": "yarn@4.8.1"
}
10 changes: 6 additions & 4 deletions src/language/ContextMapperDslModule.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,15 +21,16 @@ import { KeywordHoverRegistry } from './hover/KeywordHoverRegistry.js'
import { ContextMapperDslNodeKindProvider } from './shared/ContextMapperDslNodeKindProvider.js'
import { ContextMapperDslFormatter } from './formatting/ContextMapperDslFormatter.js'
import { ContextMapperFormatterRegistry } from './formatting/ContextMapperFormatterRegistry.js'
import { ContextMapperDslCompletionProvider } from './completion/ContextMapperDslCompletionProvider.js'

/**
* Declaration of custom services - add your own service classes here.
*/
export type ContextMapperDslAddedServices = {
export interface ContextMapperDslAddedServices {
validation: {
ContextMapperDslValidator: ContextMapperDslValidator
}
};
}

/**
* Union of Langium default services and your custom services - use this as constructor parameter
Expand Down Expand Up @@ -57,7 +58,8 @@ export const ContextMapperDslModule: Module<ContextMapperDslServices, ModuleType
SemanticTokenProvider: (services) => new ContextMapperDslSemanticTokenProvider(services, semanticTokenProviderRegistry),
FoldingRangeProvider: (services) => new ContextMapperDslFoldingRangeProvider(services),
HoverProvider: (services) => new ContextMapperDslHoverProvider(services, new KeywordHoverRegistry()),
Formatter: () => new ContextMapperDslFormatter(new ContextMapperFormatterRegistry())
Formatter: () => new ContextMapperDslFormatter(new ContextMapperFormatterRegistry()),
CompletionProvider: (services) => new ContextMapperDslCompletionProvider(services)
},
references: {
ScopeProvider: (services) => new ContextMapperDslScopeProvider(services),
Expand Down Expand Up @@ -102,7 +104,7 @@ export function createContextMapperDslServices (context: DefaultSharedModuleCont
if (!context.connection) {
// We don't run inside a language server
// Therefore, initialize the configuration provider instantly
shared.workspace.ConfigurationProvider.initialized({})
void shared.workspace.ConfigurationProvider.initialized({})
}
return { shared, ContextMapperDsl }
}
13 changes: 13 additions & 0 deletions src/language/completion/ContextMapperDslCompletionProvider.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { CompletionContext, DefaultCompletionProvider } from 'langium/lsp'
import { GrammarAST } from 'langium'

export class ContextMapperDslCompletionProvider extends DefaultCompletionProvider {
/*
Langium hides non-alphabetical keywords by default. (see https://github.com/eclipse-langium/langium/pull/697)
This causes relationship arrows like '<->' to not be suggested in autocomplete.
Overriding this function allows all keywords to be suggested.
*/
protected override filterKeyword (_context: CompletionContext, _keyword: GrammarAST.Keyword): boolean {
return true
}
}
1 change: 1 addition & 0 deletions src/language/hover/ContextMapperDslHoverProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { KeywordHoverRegistry } from './KeywordHoverRegistry.js'

export class ContextMapperDslHoverProvider extends MultilineCommentHoverProvider {
private readonly _keywordHoverRegistry: KeywordHoverRegistry

constructor (services: LangiumServices, keywordHoverRegistry: KeywordHoverRegistry) {
super(services)
this._keywordHoverRegistry = keywordHoverRegistry
Expand Down
2 changes: 1 addition & 1 deletion src/language/hover/KeywordHoverRegistry.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
export class KeywordHoverRegistry {
private readonly _registry: Map<string, string> = new Map([
private readonly _registry = new Map<string, string>([
[
'BoundedContext',
`**Bounded Context**: A description of a boundary (typically a subsystem, or the work of a particular team) within which a particular model is defined and applicable. \
Expand Down
5 changes: 4 additions & 1 deletion src/language/main.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { startLanguageServer } from 'langium/lsp'

Check warning on line 1 in src/language/main.ts

View workflow job for this annotation

GitHub Actions / Qodana for JS

Check JavaScript and TypeScript source code coverage

File `main.ts` coverage is below the threshold 50%
import { NodeFileSystem } from 'langium/node'
import { createConnection, ProposedFeatures } from 'vscode-languageserver/node.js'
import { createContextMapperDslServices } from './ContextMapperDslModule.js'
Expand All @@ -7,7 +7,10 @@
const connection = createConnection(ProposedFeatures.all)

// Inject the shared services and language-specific services
const { shared } = createContextMapperDslServices({ connection, ...NodeFileSystem })
const { shared } = createContextMapperDslServices({
connection,
...NodeFileSystem
})

// Start the language server with the shared services
startLanguageServer(shared)
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { AstNode, AstNodeDescription, DefaultScopeComputation, LangiumDocument }
import { CancellationToken } from 'vscode-languageserver'

export class ContextMapperDslScopeComputation extends DefaultScopeComputation {

/*
For the time being imports aren't supported yet.
By default, Langium adds top-level elements to the global scope.
Expand Down
5 changes: 3 additions & 2 deletions src/language/references/ContextMapperDslScopeProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,11 @@ import {
Aggregate,
DomainPart,
isContextMappingModel,
isStakeholderGroup, StakeholderGroup
isStakeholderGroup
} from '../generated/ast.js'

export class ContextMapperDslScopeProvider extends DefaultScopeProvider {

/*
Some ContextMapper elements are not defined on the top-level of the document and should still be referencable.
Langium assumes that nested elements don't belong in the global scope.
Expand Down Expand Up @@ -43,7 +44,7 @@ export class ContextMapperDslScopeProvider extends DefaultScopeProvider {
.concat(
model.stakeholders.flatMap(s => s.stakeholders)
.filter(s => isStakeholderGroup(s))
.flatMap(sg => (sg as StakeholderGroup).stakeholders)
.flatMap(sg => sg.stakeholders)
.map(s => this.descriptions.createDescription(s, s.name))
)
return this.createScope(stakeholderDescriptions)
Expand Down
10 changes: 1 addition & 9 deletions src/language/semanticTokens/HighlightingHelper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,6 @@ export function highlightKeyword (node: AstNode, acceptor: SemanticTokenAcceptor
})
}

export function highlightOperator (node: AstNode, acceptor: SemanticTokenAcceptor, operator: string) {
acceptor({
node,
type: SemanticTokenTypes.operator,
keyword: operator
})
}

export function highlightType (node: AstNode, acceptor: SemanticTokenAcceptor, property: string, modifiers: string[] = []) {
acceptor({
node,
Expand All @@ -35,7 +27,7 @@ export function highlightType (node: AstNode, acceptor: SemanticTokenAcceptor, p
})
}

export function highlightTypeDeclaration (node: AstNode, acceptor: SemanticTokenAcceptor, keyword: string, hasName: boolean = true) {
export function highlightTypeDeclaration (node: AstNode, acceptor: SemanticTokenAcceptor, keyword: string, hasName = true) {
highlightKeyword(node, acceptor, keyword)
if (hasName) {
highlightType(node, acceptor, 'name', [SemanticTokenModifiers.declaration])
Expand Down
4 changes: 4 additions & 0 deletions src/language/semanticTokens/SemanticTokenProviderRegistry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,9 +54,13 @@ import {

export class SemanticTokenProviderRegistry {
private readonly _domainProvider = new DomainSemanticTokenProvider()

private readonly _relationshipProvider = new RelationshipSemanticTokenProvider()

private readonly _featureProvider = new FeatureSemanticTokenProvider()

private readonly _userRequirementProvider = new RequirementsSemanticTokenProvider()

private readonly _stakeholderProvider = new AbstractStakeholderSemanticTokenProvider()

private readonly semanticTokenProviders = new Map<string, ContextMapperSemanticTokenProvider<AstNode>>([
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import {
import { SemanticTokenAcceptor } from 'langium/lsp'
import { SemanticTokenModifiers, SemanticTokenTypes } from 'vscode-languageserver-types'
import { ContextMapperSemanticTokenProvider } from '../ContextMapperSemanticTokenProvider.js'
import { highlightField, highlightKeyword, highlightOperator, highlightType } from '../HighlightingHelper.js'
import { highlightField, highlightKeyword, highlightType } from '../HighlightingHelper.js'

export class RelationshipSemanticTokenProvider implements ContextMapperSemanticTokenProvider<Relationship> {
highlight (node: Relationship, acceptor: SemanticTokenAcceptor) {
Expand Down Expand Up @@ -46,13 +46,13 @@ export class RelationshipSemanticTokenProvider implements ContextMapperSemanticT

private highlightPartnership (node: Partnership, acceptor: SemanticTokenAcceptor) {
highlightKeyword(node, acceptor, 'P')
highlightOperator(node, acceptor, '<->')
highlightKeyword(node, acceptor, '<->')
highlightKeyword(node, acceptor, 'Partnership')
}

private highlightSharedKernel (node: SharedKernel, acceptor: SemanticTokenAcceptor) {
highlightKeyword(node, acceptor, 'SK')
highlightOperator(node, acceptor, '<->')
highlightKeyword(node, acceptor, '<->')
highlightKeyword(node, acceptor, 'Shared-Kernel')
}

Expand All @@ -77,8 +77,8 @@ export class RelationshipSemanticTokenProvider implements ContextMapperSemanticT
})
}

highlightOperator(node, acceptor, '->')
highlightOperator(node, acceptor, '<-')
highlightKeyword(node, acceptor, '->')
highlightKeyword(node, acceptor, '<-')

if (node.upstreamExposedAggregates.length > 0) {
highlightField(node, acceptor, ['exposedAggregates'], 'upstreamExposedAggregates', SemanticTokenTypes.type)
Expand Down
4 changes: 2 additions & 2 deletions src/language/validation/ContextMapperDslValidationRegistry.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { type ValidationChecks, ValidationRegistry } from 'langium'
import { AstNode, ValidationAcceptor, type ValidationChecks, ValidationRegistry } from 'langium'
import { ContextMapperDslServices } from '../ContextMapperDslModule.js'
import { ContextMapperDslAstType } from '../generated/ast.js'
import { ContextMapperValidationProviderRegistry } from './ContextMapperValidationProviderRegistry.js'
Expand All @@ -12,7 +12,7 @@ export class ContextMapperDslValidationRegistry extends ValidationRegistry {

// dynamically set validator for all grammar elements
const checks: ValidationChecks<ContextMapperDslAstType> = Object.fromEntries(
typesToValidate.map(type => [type, validator.validate])
typesToValidate.map(type => [type, (node: AstNode, acceptor: ValidationAcceptor) => validator.validate(node, acceptor)])
)

super.register(checks, validator)
Expand Down
2 changes: 1 addition & 1 deletion src/language/validation/ContextMapperValidationProvider.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { AstNode, ValidationAcceptor } from 'langium'

export interface ContextMapperValidationProvider<T extends AstNode> {
validate (node: T, acceptor: ValidationAcceptor): void
validate (_node: T, _acceptor: ValidationAcceptor): void
}
4 changes: 2 additions & 2 deletions src/language/validation/impl/AggregateValidationProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,9 @@ export class AggregateValidationProvider implements ContextMapperValidationProvi
enforceZeroOrOneCardinality(node, 'securityAccessGroup', acceptor)

// make sure only one of the userRequirements keywords is used
const userRequirementProperties = ['userRequirements', 'useCases', 'userStories'] as Array<keyof Aggregate>
const userRequirementProperties = ['userRequirements', 'useCases', 'userStories'] as (keyof Aggregate)[]
const setUserRequirements = userRequirementProperties.filter(p => {
const value = node[p] as Array<any>
const value = node[p] as object[]
return value.length > 0
})
if (setUserRequirements.length > 1) {
Expand Down
3 changes: 2 additions & 1 deletion src/language/validation/impl/SubDomainValidationProvider.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { ContextMapperValidationProvider } from '../ContextMapperValidationProvider.js'
import { Subdomain } from '../../generated/ast.js'
import { enforceZeroOrOneCardinality } from '../ValidationHelper.js'
import { ValidationAcceptor } from 'langium'

export class SubDomainValidationProvider implements ContextMapperValidationProvider<Subdomain> {
validate (node: Subdomain, acceptor: any): void {
validate (node: Subdomain, acceptor: ValidationAcceptor): void {
enforceZeroOrOneCardinality(node, 'type', acceptor)
enforceZeroOrOneCardinality(node, 'domainVisionStatement', acceptor)
}
Expand Down
2 changes: 1 addition & 1 deletion test/ParsingTestHelper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ export async function parseValidInput (parse: ReturnType<typeof parseHelper<Cont

expectNoParsingErrors(document)
expect(document.parseResult.lexerErrors).toHaveLength(0)
expect(document.diagnostics || []).toHaveLength(0)
expect(document.diagnostics ?? []).toHaveLength(0)

return document
}
Expand Down
25 changes: 23 additions & 2 deletions test/completion/Completion.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,13 @@ import { EmptyFileSystem, LangiumDocument } from 'langium'
import { afterEach, beforeAll, describe, expect, test } from 'vitest'
import { fail } from 'node:assert'
import { uinteger } from 'vscode-languageserver-types'
import { CompletionParams } from 'vscode-languageserver'

let services: ReturnType<typeof createContextMapperDslServices>
let completionProvider: CompletionProvider
let parse: ReturnType<typeof parseHelper<ContextMappingModel>>

beforeAll(async () => {
beforeAll(() => {
services = createContextMapperDslServices(EmptyFileSystem)
completionProvider = services.ContextMapperDsl.lsp.CompletionProvider!
parse = parseHelper<ContextMappingModel>(services.ContextMapperDsl)
Expand Down Expand Up @@ -84,9 +85,29 @@ describe('Completion tests', () => {
expect(completionList.items).toHaveLength(1)
expect(completionList.items[0].label).toEqual('type')
})

test('check completion of relationship arrows', async () => {
const docToComplete = await parse(`
ContextMap {
AnotherContext
}
BoundedContext TestContext
BoundedContext AnotherContext
`)

const params = createCompletionParams(docToComplete, 2, 23)
const completionList = await completionProvider.getCompletion(docToComplete, params)
if (completionList == null) {
fail('Expected completion provider to return completion list')
return
}

const suggestions = completionList.items.map(item => item.label)
expect(suggestions).toContain('<->')
})
})

function createCompletionParams (document: LangiumDocument<ContextMappingModel>, positionLine: uinteger, positionChar: uinteger): any {
function createCompletionParams (document: LangiumDocument<ContextMappingModel>, positionLine: uinteger, positionChar: uinteger): CompletionParams {
return {
textDocument: {
uri: document.uri.path
Expand Down
4 changes: 2 additions & 2 deletions test/documentSymbol/DocumentSymbolProvider.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,14 @@ let parse: ReturnType<typeof parseHelper<ContextMappingModel>>
let document: LangiumDocument<ContextMappingModel> | undefined
let documentSymbolProvider: DocumentSymbolProvider

beforeAll(async () => {
beforeAll(() => {
services = createContextMapperDslServices(EmptyFileSystem)
parse = parseHelper<ContextMappingModel>(services.ContextMapperDsl)
documentSymbolProvider = services.ContextMapperDsl.lsp.DocumentSymbolProvider!
})

afterEach(async () => {
document && await clearDocuments(services.shared, [document])
if (document) await clearDocuments(services.shared, [document])
})

describe('Document symbol provider tests', () => {
Expand Down
Loading