Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 | 67x 67x 2x 2x 42x 26x 26x 26x 26x 6x 20x 20x 4x 20x 20x 16x 6x 10x 12x 3x 10x 20x 20x 9x 5x 4x 2x 20x | import type { LintIssue, LintResult } from '@/layers/interfaces'
// Mirrors api/internal/handlers/project_config.go LintProjectConfig exactly:
// same rule semantics, same message strings, same emission order (keys are
// visited in sorted order like the API's sortedObjectKeys). The lint is
// wired to the project `config` field, so `parsed` is the full project
// config object ({ vikunja?: { url?, project_id? } }). Undeclared root
// keys, a non-object vikunja value, undeclared vikunja keys, and non-string
// (including null) or empty url / project_id values are errors. A config
// without vikunja yields no issues. The API's "config: must be valid JSON"
// issue cannot occur here — the YAML editor parses before linting.
const CONFIG_ROOT_KEYS = new Set(['vikunja'])
const VIKUNJA_CONFIG_KEYS = ['url', 'project_id'] as const
function isPlainObject(value: unknown): value is Record<string, unknown> {
return typeof value === 'object' && value !== null && !Array.isArray(value)
}
function error(field: string, message: string): LintIssue {
return { field, message }
}
export function projectConfigLint(parsed: unknown, _raw: string): LintResult {
const errors: LintIssue[] = []
const warnings: LintIssue[] = []
if (!isPlainObject(parsed)) {
return { errors: [error('config', 'config: must be a JSON object')], warnings }
}
for (const key of Object.keys(parsed).sort()) {
if (!CONFIG_ROOT_KEYS.has(key)) {
errors.push(error('config', `config: unknown key "${key}"`))
}
}
const vikunja = parsed['vikunja']
if (vikunja !== undefined) {
if (!isPlainObject(vikunja)) {
errors.push(error('vikunja', 'vikunja: must be an object'))
} else {
for (const key of Object.keys(vikunja).sort()) {
if (!VIKUNJA_CONFIG_KEYS.includes(key as (typeof VIKUNJA_CONFIG_KEYS)[number])) {
errors.push(error('vikunja', `vikunja: unknown key "${key}"`))
}
}
for (const key of VIKUNJA_CONFIG_KEYS) {
const value = vikunja[key]
if (value === undefined) continue
if (typeof value !== 'string') {
errors.push(error(`vikunja.${key}`, `vikunja.${key}: must be a string`))
} else if (value === '') {
errors.push(error(`vikunja.${key}`, `vikunja.${key}: must be a non-empty string`))
}
}
}
}
return { errors, warnings }
}
|