diff --git a/src/lint/dto/lint-error.dto.ts b/src/lint/dto/lint-error.dto.ts new file mode 100644 index 0000000..e5a5235 --- /dev/null +++ b/src/lint/dto/lint-error.dto.ts @@ -0,0 +1,9 @@ +export class LintErrorDTO { + message: string; + + line: number | null; + + column: number | null; + + offset: number | null; +} diff --git a/src/lint/dto/lint-result.dto.ts b/src/lint/dto/lint-result.dto.ts new file mode 100644 index 0000000..736e588 --- /dev/null +++ b/src/lint/dto/lint-result.dto.ts @@ -0,0 +1,7 @@ +import { LintErrorDTO } from './lint-error.dto'; + +export class LintResultDTO { + score: number; + + errors: LintErrorDTO[]; +} diff --git a/src/lint/lint.service.ts b/src/lint/lint.service.ts index 36d13c3..f3fb091 100644 --- a/src/lint/lint.service.ts +++ b/src/lint/lint.service.ts @@ -7,12 +7,14 @@ import child_process from 'child_process'; import fs from 'fs'; import * as uuid from 'uuid'; import YAML from 'yaml'; +import { LintErrorDTO } from './dto/lint-error.dto'; +import { LintResultDTO } from './dto/lint-result.dto'; import { ConvertToGolangCILintOutput } from './linters/golangcilint'; import { ConvertToPylintOutput } from './linters/pylint'; @Injectable() export class LintService { - lintPython3(code: string): number { + lintPython3(code: string): LintResultDTO { const result = child_process.spawnSync( 'pylint', ['--from-stdin', '-f', 'json', 'module_or_package', '--'], @@ -49,7 +51,22 @@ export class LintService { Remove one point to the score per violation */ - return 100 - pylintOutput.length; + const score = 100 - pylintOutput.length; + const errors: LintErrorDTO[] = []; + + for (const violation of pylintOutput) { + errors.push({ + message: violation.message, + line: violation.line, + column: violation.column, + offset: null, + }); + } + + return { + score, + errors, + }; } } catch (e) { throw new InternalServerErrorException(); @@ -58,7 +75,7 @@ export class LintService { throw new InternalServerErrorException(); } - lintGolang(code: string): number { + lintGolang(code: string): LintResultDTO { // Golangci-lint doesn't support stdin const path = `/tmp/codebench_${uuid.v4()}.go`; try { @@ -112,19 +129,35 @@ export class LintService { */ if (lintOuput.issues) { // Remove one point to the score per violation - return 100 - lintOuput.issues.length; + const score = 100 - lintOuput.issues.length; + const errors: LintErrorDTO[] = []; + + for (const issue of lintOuput.issues) { + errors.push({ + message: issue.text, + line: issue.pos.line, + column: issue.pos.column, + offset: null, + }); + } + return { + score, + errors, + }; } // Golangci-lint return `null` if there are no violation - return 100; } } catch (e) { throw new InternalServerErrorException(e); } - throw new InternalServerErrorException(); + return { + score: 100, + errors: [], + }; } - lintCpp(code: string): number { + lintCpp(code: string): LintResultDTO { // clang-tidy doesn't support stdin const path = `/tmp/codebench_${uuid.v4()}.cpp`; const outputPath = `${path}.yaml`; @@ -152,27 +185,46 @@ export class LintService { if (fs.existsSync(outputPath)) { const file = fs.readFileSync(`${path}.yaml`, 'utf8'); const fixes: any = YAML.parse(file); + if (fixes) { /* Example file: --- MainSourceFile: /root/fib.cpp Diagnostics: - - DiagnosticName: clang-diagnostic-unused-variable - Message: 'unused variable ''d''' - FileOffset: 142 - FilePath: /root/fib.cpp - Replacements: + - DiagnosticName: clang-diagnostic-unused-variable + Message: 'unused variable ''d''' + FileOffset: 142 + FilePath: /root/fib.cpp + Replacements: ... */ if (fixes.Diagnostics) { - return 100 - fixes.Diagnostics.length; + const score = 100 - fixes.Diagnostics.length; + const errors: LintErrorDTO[] = []; + + for (const diagnostic of fixes.Diagnostics) { + errors.push({ + message: diagnostic.Message, + line: diagnostic.FileOffset, + column: null, + offset: null, + }); + } + return { + score, + errors, + }; } } } - return 100; } catch (e) { throw new InternalServerErrorException(e); } + + return { + score: 100, + errors: [], + }; } } diff --git a/src/migrations/1627337341637-AddSubmissionsLintReports.ts b/src/migrations/1627337341637-AddSubmissionsLintReports.ts new file mode 100644 index 0000000..81ed2e0 --- /dev/null +++ b/src/migrations/1627337341637-AddSubmissionsLintReports.ts @@ -0,0 +1,14 @@ +import { MigrationInterface, QueryRunner } from "typeorm"; + +export class AddSubmissionsLintReports1627337341637 implements MigrationInterface { + name = 'AddSubmissionsLintReports1627337341637' + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "submissions" ADD "lintErrors" jsonb`); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "submissions" DROP COLUMN "lintErrors"`); + } + +} diff --git a/src/submissions/dto/insert-submission-dto.ts b/src/submissions/dto/insert-submission-dto.ts index 56ef8f7..4f78290 100644 --- a/src/submissions/dto/insert-submission-dto.ts +++ b/src/submissions/dto/insert-submission-dto.ts @@ -1,3 +1,4 @@ +import { LintErrorDTO } from 'src/lint/dto/lint-error.dto'; import { User } from 'src/users/user.entity'; export class InsertSubmissionDTO { @@ -8,4 +9,6 @@ export class InsertSubmissionDTO { code: string; user: User; + + lintErrors?: LintErrorDTO[]; } diff --git a/src/submissions/dto/submission-result.dto.ts b/src/submissions/dto/submission-result.dto.ts index 09d581a..6bf7201 100644 --- a/src/submissions/dto/submission-result.dto.ts +++ b/src/submissions/dto/submission-result.dto.ts @@ -1,4 +1,5 @@ import { QualityDTO } from 'src/code-quality/dto/quality.dto'; +import { LintErrorDTO } from 'src/lint/dto/lint-error.dto'; import { Submission } from '../submission.entity'; export class SubmissionResultDTO { @@ -7,4 +8,6 @@ export class SubmissionResultDTO { lint: { score: number }; quality: QualityDTO; + + lintErrors?: LintErrorDTO[]; } diff --git a/src/submissions/submission.entity.ts b/src/submissions/submission.entity.ts index 12239ee..3a3e0aa 100644 --- a/src/submissions/submission.entity.ts +++ b/src/submissions/submission.entity.ts @@ -1,5 +1,6 @@ /* eslint-disable @typescript-eslint/no-unused-vars */ import { ApiProperty } from '@nestjs/swagger'; +import { LintErrorDTO } from 'src/lint/dto/lint-error.dto'; import { User } from 'src/users/user.entity'; import { jsonMember, jsonObject } from 'typedjson'; import { @@ -92,6 +93,9 @@ export class Submission extends BaseEntity { @Column({ nullable: true }) lintScore: number; + @Column('jsonb', { nullable: true }) + lintErrors?: LintErrorDTO[]; + @jsonMember @Column({ nullable: true }) qualityScore: number; diff --git a/src/submissions/submissions.controller.ts b/src/submissions/submissions.controller.ts index e4e1423..64c5ba9 100644 --- a/src/submissions/submissions.controller.ts +++ b/src/submissions/submissions.controller.ts @@ -12,6 +12,7 @@ import { import { ValidatedJWTReq } from 'src/auth/dto/validated-jwt-req'; import { JwtAuthGuard } from 'src/auth/jwt-auth.guard'; import { CodeQualityService } from 'src/code-quality/code-quality.service'; +import { LintResultDTO } from 'src/lint/dto/lint-result.dto'; import { LintService } from 'src/lint/lint.service'; import { CreateSubmissionDTO } from './dto/create-submission-dto'; import { FindSubmissionDTO } from './dto/find-submission.dto'; @@ -34,25 +35,23 @@ export class SubmissionsController { @Request() req: ValidatedJWTReq, @Body() createSubmissionDTO: CreateSubmissionDTO, ): Promise { - let lintScore = { score: 100 }; + let lintScore: LintResultDTO; let qualityScore = { score: 100 }; switch (createSubmissionDTO.language) { case 'python': qualityScore = this.qualityService.run(createSubmissionDTO.code, 'py'); - lintScore.score = this.lintService.lintPython3( - createSubmissionDTO.code, - ); + lintScore = this.lintService.lintPython3(createSubmissionDTO.code); break; case 'cpp': qualityScore = this.qualityService.run(createSubmissionDTO.code, 'cpp'); - lintScore.score = this.lintService.lintCpp(createSubmissionDTO.code); + lintScore = this.lintService.lintCpp(createSubmissionDTO.code); break; case 'go': qualityScore = this.qualityService.run(createSubmissionDTO.code, 'go'); - lintScore.score = this.lintService.lintGolang(createSubmissionDTO.code); + lintScore = this.lintService.lintGolang(createSubmissionDTO.code); break; default: - lintScore = { score: 0 }; + lintScore = { score: 0, errors: [] }; qualityScore = { score: 0 }; } @@ -60,6 +59,7 @@ export class SubmissionsController { { ...createSubmissionDTO, user: req.user, + lintErrors: lintScore.errors, }, lintScore.score, qualityScore.score,