Skip to content
This repository has been archived by the owner on Oct 15, 2024. It is now read-only.

Commit

Permalink
feat: use zod to validate qmllint result
Browse files Browse the repository at this point in the history
  • Loading branch information
seanwu1105 committed Jan 26, 2023
1 parent 9a8db10 commit dad6c5e
Show file tree
Hide file tree
Showing 6 changed files with 71 additions and 59 deletions.
16 changes: 15 additions & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@
"prepare": "husky install"
},
"dependencies": {
"vscode-uri": "^3.0.3"
"vscode-uri": "^3.0.3",
"zod": "^3.20.2"
},
"devDependencies": {
"@commitlint/cli": "^17.0.3",
Expand Down
8 changes: 5 additions & 3 deletions python/tests/assets/qml/schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@
"message": {
"type": "string"
},
"suggestion": {
"suggestions": {
"type": "array",
"items": {
"type": "object",
Expand Down Expand Up @@ -95,12 +95,14 @@
"success": {
"type": "boolean"
}
}
},
"required": ["filename", "warnings", "success"]
}
},
"revision": {
"type": "integer",
"const": 3
}
}
},
"required": ["files", "revision"]
}
2 changes: 1 addition & 1 deletion src/qmllint/converters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ export function toDiagnostic(qmlLintWarning: QmlLintWarning): Diagnostic {
: 1,
),
),
qmlLintWarning.suggestions.length === 0
qmlLintWarning.suggestions?.length === 0
? qmlLintWarning.message
: `${qmlLintWarning.message} (${JSON.stringify(
qmlLintWarning.suggestions,
Expand Down
99 changes: 46 additions & 53 deletions src/qmllint/lint.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { z } from 'zod'
import type { CommandArgs, ExecError, StdErrError } from '../run'
import { run } from '../run'
import type { ErrorResult, SuccessResult } from '../types'
import { notNil } from '../utils'

export async function lint({
qmlLintCommand,
Expand Down Expand Up @@ -66,10 +66,14 @@ function parseQmlLintRunReturnValue(
return { kind: 'ParseError', message: JSON.stringify(error) }
}

if (!isQmlLintResult(json))
const parseQmlLintResult = qmlLintResultSchema.safeParse(json)

if (!parseQmlLintResult.success)
return {
kind: 'ParseError',
message: `Not a valid QML Lint result: ${value}`,
message: `Not a valid QML Lint result. \n${parseQmlLintResult.error.issues
.map(i => `${i.path}: ${i.message}`)
.join('\n')}`,
}
return { kind: 'Success', value: json }
}
Expand All @@ -78,53 +82,42 @@ type ParseQmlLintRunReturnValueResult =
| SuccessResult<QmlLintResult>
| ErrorResult<'Parse'>

export type QmlLintResult = {
readonly files: readonly QmlLintFileResult[]
}

type QmlLintFileResult = {
readonly filename: string
readonly warnings: readonly QmlLintWarning[]
}

export type QmlLintWarning = {
readonly column?: number
readonly length?: number
readonly line?: number
readonly message: string
readonly suggestions: readonly unknown[]
readonly type: typeof QmlLintWarningType[number]
}

const QmlLintWarningType = ['info', 'warning', 'critical'] as const

function isQmlLintResult(value: any): value is QmlLintResult {
if (typeof value !== 'object') return false
if (!Array.isArray(value['files'])) return false
if (value.files.some((file: any) => !isQmlLintFileResult(file))) return false
return true
}

function isQmlLintFileResult(value: any): value is QmlLintFileResult {
if (typeof value !== 'object') return false
if (typeof value['filename'] !== 'string') return false
if (!Array.isArray(value['warnings'])) return false
if (value.warnings.some((warning: any) => !isQmlLintWarning(warning)))
return false
return true
}

function isQmlLintWarning(value: any): value is QmlLintWarning {
if (typeof value !== 'object') return false

if (typeof value['message'] !== 'string') return false
if (!Array.isArray(value['suggestions'])) return false
if (!QmlLintWarningType.includes(value['type'])) return false

if (notNil(value['column']) && typeof value['column'] !== 'number')
return false
if (notNil(value['length']) && typeof value['length'] !== 'number')
return false
if (notNil(value['line']) && typeof value['line'] !== 'number') return false
return true
}
// Converted from python/tests/assets/qml/schema.json
const qmlLintWarningSchema = z.object({
type: z.enum(['debug', 'warning', 'critical', 'fatal', 'info', 'unknown']),
id: z.string().optional(),
line: z.number().int().optional(),
column: z.number().int().optional(),
charOffset: z.number().int().optional(),
length: z.number().int().optional(),
message: z.string(),
suggestions: z
.array(
z.object({
message: z.string(),
line: z.number().int(),
column: z.number().int(),
charOffset: z.number().int(),
length: z.number().int(),
replacement: z.string(),
isHint: z.boolean(),
filename: z.string().optional(),
}),
)
.optional(),
})

const qmlLintResultSchema = z.object({
files: z.array(
z.object({
filename: z.string(),
warnings: z.array(qmlLintWarningSchema),
success: z.boolean(),
}),
),
revision: z.number(), // Should not be too strict to be 3 only
})

export type QmlLintResult = z.infer<typeof qmlLintResultSchema>

export type QmlLintWarning = z.infer<typeof qmlLintWarningSchema>
2 changes: 2 additions & 0 deletions src/test/suite/qmllint/lint.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,10 @@ suite('qmllint/lint', () => {
{
filename: 'myFile.qml',
warnings: [{ message: 'my message', suggestions: [], type: 'warning' }],
success: false,
},
],
revision: 3,
}

let result: LintResult
Expand Down

0 comments on commit dad6c5e

Please sign in to comment.