Skip to content

Commit eb85f4e

Browse files
committed
feat(edulint): show nice result
1 parent 5b21772 commit eb85f4e

File tree

15 files changed

+242
-26
lines changed

15 files changed

+242
-26
lines changed

src/app/components/tasks/task-module/task-module-programming/task-module-programming.component.html

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
<button class="btn btn-ksi download" (click)="download()">{{'tasks.module.programming.download'|translate}}</button>
66
<button class="btn btn-ksi upload" (click)="upload()">{{'tasks.module.programming.upload'|translate}}</button>
77
<button class="btn btn-ksi reset" (click)="reset()">{{'tasks.module.programming.reset'|translate}}</button>
8-
<button [disabled]="linting$ | async" class="btn btn-ksi" [tooltip]="'tasks.module.programming.lint.tooltip'|translate" (click)="lintCode()">{{'tasks.module.programming.lint.text'|translate}}</button>
98
</div>
109

1110
<input #uploadFile type="file" style="display: none" (change)="replaceCode($event)">
@@ -18,6 +17,32 @@
1817
<h6 class="report-heading">{{'tasks.module.programming.run-result'|translate}}</h6>
1918
<textarea #runOutput class="area-code-output" [value]="result.stdout || ''" [readOnly]="true"></textarea>
2019

20+
<ng-container *ngIf="linting$ !== null">
21+
<ksi-spinner [source]="linting$"></ksi-spinner>
22+
</ng-container>
23+
<ng-container *ngIf="(linting$ | async) as lintReport">
24+
<div *ngIf="!lintReport.problems.length; else lintProblems" class="alert alert-success lint-result">
25+
{{'tasks.module.programming.lint.no-problems-found'|translate}}
26+
</div>
27+
<ng-template #lintProblems>
28+
<div class="alert alert-warning lint-result">
29+
<div class="problems">
30+
{{'tasks.module.programming.lint.problems-found'|translate:{count: lintReport.problems.length} }}
31+
</div>
32+
<div>
33+
<a
34+
class="btn btn-ksi w-100"
35+
[href]="lintReport.editorUrl"
36+
target="_blank"
37+
[tooltip]="'tasks.module.programming.lint.tooltip'|translate"
38+
>
39+
{{'tasks.module.programming.lint.text'|translate}}
40+
</a>
41+
</div>
42+
</div>
43+
</ng-template>
44+
</ng-container>
45+
2146
<ng-container *ngIf="user.isOrg$ | async">
2247
<h6 class="report-heading">{{'tasks.module.report'|translate}}</h6>
2348
<textarea class="area-code-output" [value]="result.report || ''" [readOnly]="true"></textarea>

src/app/components/tasks/task-module/task-module-programming/task-module-programming.component.scss

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,4 +19,12 @@
1919
.report-heading {
2020
margin-top: $ksi-margin;
2121
}
22+
23+
.lint-result {
24+
text-align: center;
25+
26+
.problems {
27+
padding-bottom: $ksi-margin-small;
28+
}
29+
}
2230
}

src/app/components/tasks/task-module/task-module-programming/task-module-programming.component.ts

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
/* eslint-disable @typescript-eslint/ban-ts-comment */
12
import {
23
Component,
34
OnInit,
@@ -17,10 +18,12 @@ import { FormControl } from '@angular/forms';
1718
// @ts-ignore
1819
import * as CodeMirror from '../../../../../../node_modules/codemirror/lib/codemirror';
1920
import '../../../../../../node_modules/codemirror/mode/python/python';
20-
import { BehaviorSubject, Observable, Subscription } from 'rxjs';
21+
import { Observable, Subscription } from 'rxjs';
2122
import { EdulintService, ModalService, ModuleService, UserService } from '../../../../services';
22-
import { mapTo, tap } from 'rxjs/operators';
23+
import { distinct, mapTo, shareReplay, tap } from 'rxjs/operators';
24+
import { EdulintReport } from '../../../../models';
2325

26+
// noinspection JSUnresolvedReference
2427
@Component({
2528
selector: 'ksi-task-module-programming',
2629
templateUrl: './task-module-programming.component.html',
@@ -46,9 +49,7 @@ export class TaskModuleProgrammingComponent implements OnInit, OnDestroy {
4649

4750
submission$: Observable<void> | null = null;
4851
codeRun$: Observable<void> | null = null;
49-
50-
private readonly lintingSubject = new BehaviorSubject<boolean>(false);
51-
readonly linting$ = this.lintingSubject.asObservable();
52+
linting$: Observable<EdulintReport> | null = null;
5253

5354
private subs: Subscription[] = [];
5455

@@ -83,13 +84,18 @@ export class TaskModuleProgrammingComponent implements OnInit, OnDestroy {
8384

8485
this.moduleService.hideSubmit(this.module);
8586
this.codeRunResult$ = this.moduleService.runCode(this.module, this.code.value).pipe(
86-
tap(() => {
87+
shareReplay(1),
88+
tap((result) => {
8789
// Scroll stdout into view after run completed
8890
window.setTimeout(() => {
8991
if (this.runOutput && this.runOutput.nativeElement) {
9092
this.runOutput.nativeElement.scrollIntoView({behavior: 'smooth', block: 'start', inline: 'nearest'});
9193
}
9294
});
95+
96+
if (result.result === 'ok') {
97+
this.lintCode();
98+
}
9399
})
94100
);
95101
(this.codeRun$ = this.codeRunResult$.pipe(mapTo(undefined))).subscribe(() => {
@@ -166,6 +172,7 @@ export class TaskModuleProgrammingComponent implements OnInit, OnDestroy {
166172
});
167173
// map tab key to spaces
168174
editor.setOption('extraKeys', {
175+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
169176
Tab: function(cm: any) {
170177
const spaces = Array(cm.getOption('indentUnit') + 1).join(' ');
171178
cm.replaceSelection(spaces);
@@ -183,10 +190,8 @@ export class TaskModuleProgrammingComponent implements OnInit, OnDestroy {
183190
}
184191

185192
lintCode(): void {
186-
this.lintingSubject.next(true);
187-
this.lint.getCodeEditorUrl(this.code.value).subscribe((url) => {
188-
this.lintingSubject.next(false);
189-
window.open(url, '_blank');
190-
});
193+
this.linting$ = null;
194+
this.linting$ = this.lint.analyzeCode(this.code.value).pipe(distinct());
195+
this.linting$.subscribe();
191196
}
192197
}

src/app/models/edulint.service.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import { Problems } from '../../api/edulint';
2+
3+
export interface EdulintReport {
4+
problems: Problems,
5+
editorUrl: string
6+
}

src/app/models/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,4 @@ export * from './backend.service';
1212
export * from './task';
1313
export * from './prediction';
1414
export * from './admin-task.service';
15+
export * from './edulint.service';

src/app/services/tasks/edulint.service.ts

Lines changed: 26 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,24 +2,44 @@ import { Injectable } from '@angular/core';
22
import { HttpClient } from '@angular/common/http';
33
import { APIService, Configuration, WebService } from '../../../api/edulint';
44
import { environment } from '../../../environments/environment';
5-
import { Observable } from 'rxjs';
6-
import { map } from 'rxjs/operators';
5+
import { combineLatest, Observable, of } from 'rxjs';
6+
import { filter, map, mergeMap } from 'rxjs/operators';
7+
import { EdulintReport } from '../../models';
78

89
@Injectable({
910
providedIn: 'root'
1011
})
1112
export class EdulintService {
1213
readonly linter: APIService;
1314
readonly editor: WebService;
15+
readonly version: string;
16+
readonly url: string;
17+
readonly config: string;
1418

1519
constructor(private httpClient: HttpClient) {
16-
this.linter = new APIService(this.httpClient, environment.edulint, new Configuration());
17-
this.editor = new WebService(this.httpClient, environment.edulint, new Configuration());
20+
const config = environment.edulint;
21+
if (config === undefined) {
22+
throw new Error('EduLint environment is undefined');
23+
}
24+
this.version = config.version;
25+
this.url = config.url;
26+
this.config = config.config;
27+
28+
this.linter = new APIService(this.httpClient, this.url, new Configuration());
29+
this.editor = new WebService(this.httpClient, this.url, new Configuration());
1830
}
1931

20-
getCodeEditorUrl(code: string): Observable<string> {
32+
analyzeCode(code: string): Observable<EdulintReport> {
33+
code += `\n# edulint: config=${this.config}\n`;
34+
2135
return this.linter.apiCodePost({code}).pipe(
22-
map((response) => `${environment.edulint}editor/${response.hash}`)
36+
filter((response) => response.hash !== undefined),
37+
map(response => `${response.hash}`),
38+
mergeMap((hash) => combineLatest([this.linter.analyzeUploaded(this.version, hash), of(hash)])),
39+
map(([problems, hash]) => ({
40+
problems,
41+
editorUrl: `${this.url}/editor/${hash}`
42+
}))
2343
);
2444
}
2545
}

src/assets/edulint/ksi.toml

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
[pylint]
2+
enable = [
3+
"use-augmenting-assignment",
4+
"redefined-builtin",
5+
"use-foreach",
6+
"simplifiable-if-return",
7+
"simplifiable-if-nested",
8+
"unused-argument",
9+
"use-for-loop",
10+
"duplicate-if-branches",
11+
"redefined-outer-name",
12+
"no-is-bool",
13+
"redundant-arithmetic",
14+
"unreachable",
15+
"duplicate-value",
16+
"use-literal-letter",
17+
"use-ord-letter",
18+
"simplifiable-if-expr",
19+
"simplifiable-if-pass",
20+
"modifying-iterated-structure",
21+
"no-loop-else",
22+
"use-integral-division",
23+
"redefined-argument-from-local",
24+
"pointless-statement",
25+
"simplifiable-if-assignment",
26+
"attribute-defined-outside-init",
27+
"loop-shadows-control-variable",
28+
"changing-control-variable",
29+
"self-assigning-variable",
30+
"dangerous-default-value",
31+
"unnecessary-dunder-call",
32+
"unnecessary-dict-index-lookup",
33+
"unidiomatic-typecheck",
34+
"comparison-of-constants",
35+
"comparison-with-itself",
36+
"remove-for",
37+
"simplifiable-condition",
38+
"condition-evals-to-constant",
39+
"unreachable-else",
40+
"non-ascii-name",
41+
"no-self-argument",
42+
"comparison-with-callable",
43+
"for-target-subscript",
44+
"function-redefined",
45+
"missing-parentheses-for-call-in-test",
46+
"method-hidden",
47+
"duplicate-key",
48+
]
49+
50+
51+
[flake8]
52+
select = [
53+
"E501", # line too long (82 > 79 characters)
54+
"F841", # local variable name is assigned to but never used
55+
"E999", # Syntax error
56+
"F821", # undefined name name
57+
"E112", # expected an indented block
58+
"E712", # comparison to True should be ‘if cond is True:’ or ‘if cond:’
59+
"E711", # comparison to None should be ‘if cond is None:’
60+
"E701", # multiple statements on one line (colon)
61+
"E101", # indentation contains mixed spaces and tabs
62+
"E113", # unexpected indentation
63+
"E713", # test for membership should be ‘not in’
64+
"E902", # IOError
65+
"W605", # invalid escape sequence ‘x’
66+
"F811", # redefinition of unused name from line N
67+
"E131", # continuation line unaligned for hanging indent
68+
"F541", # f-string without any placeholders
69+
"F601", # dictionary key name repeated with different values
70+
"F632", # use ==/!= to compare str, bytes, and int literals
71+
"F823", # local variable name … referenced before assignment
72+
"E702", # multiple statements on one line (semicolon)
73+
"E714", # test for object identity should be ‘is not’
74+
"E721", # do not compare types, use ‘isinstance()’
75+
"E742", # do not define classes named ‘l’, ‘O’, or ‘I’
76+
"E743", # do not define functions named ‘l’, ‘O’, or ‘I’
77+
"E901", # SyntaxError or IndentationError
78+
"F402", # import module from line N shadowed by loop variable
79+
"F404", # future import(s) name after other statements
80+
"F406", # ‘from module import *’ only allowed at module level
81+
"F407", # an undefined __future__ feature name was imported
82+
"F501", # invalid % format literal
83+
"F502", # % format expected mapping but got sequence
84+
"F503", # % format expected sequence but got mapping
85+
"F504", # % format unused named arguments
86+
"F505", # % format missing named arguments
87+
"F506", # % format mixed positional and named arguments
88+
"F507", # % format mismatch of placeholder and argument count
89+
"F508", # % format with * specifier requires a sequence
90+
"F509", # % format with unsupported format character
91+
"F521", # .format(...) invalid format string
92+
"F522", # .format(...) unused named arguments
93+
"F523", # .format(...) unused positional arguments
94+
"F524", # .format(...) missing argument
95+
"F525", # .format(...) mixing automatic and manual numbering
96+
"F602", # dictionary key variable name repeated with different values
97+
"F621", # too many expressions in an assignment with star-unpacking
98+
"F622", # two or more starred expressions in an assignment (a, *b, *c = d)
99+
"F631", # assertion test is a tuple, which is always True
100+
"F633", # use of >> is invalid with print function
101+
"F634", # if test is a tuple, which is always True
102+
"F701", # a break statement outside of a while or for loop
103+
"F702", # a continue statement outside of a while or for loop
104+
"F703", # a continue statement in a finally block in a loop
105+
"F704", # a yield or yield from statement outside of a function
106+
"F705", # a return statement with arguments inside a generator
107+
"F706", # a return statement outside of a function/method
108+
"F707", # an except: block as not the last exception handler
109+
"F721", # syntax error in doctest
110+
"F722", # syntax error in forward annotation
111+
"F723", # syntax error in type comment
112+
"F822", # undefined name name in __all__
113+
"F831", # duplicate argument name in function definition
114+
"F901", # raise NotImplemented should be raise NotImplementedError
115+
"W601", # .has_key() is deprecated, use ‘in’
116+
"W602", # deprecated form of raising exception
117+
"W603", # ‘<>’ is deprecated, use ‘!=’
118+
"W604", # backticks are deprecated, use ‘repr()’
119+
"W606", # ‘async’ and ‘await’ are reserved keywords starting with Python 3.7
120+
]
121+
max-line-length = "200"

src/assets/i18n/cs.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -204,7 +204,9 @@
204204
"reset-confirm": "Tato akce smaže všechny tvoje úpravy. Vážně ji chceš provést?",
205205
"lint": {
206206
"text": "Zkontrolovat kvalitu kódu pomocí EduLintu",
207-
"tooltip": "Dojde k přesměřování na externí službu EduLint"
207+
"tooltip": "Dojde k přesměřování na externí službu EduLint",
208+
"problems-found": "Tvůj kód vypadá, že by se dal ještě vylepšit. EduLint našel {{count}} zlepšení.",
209+
"no-problems-found": "Skvělá práce! EduLint nenašel v tvém kódu žádná další zlepšení."
208210
}
209211
},
210212
"general": {

src/environments/environment.kleobis.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,11 @@ export const environment: Environment = {
88
production: true,
99
disableRegistration: false,
1010
backend: 'https://rest.ksi.fi.muni.cz/',
11-
edulint: 'https://edulint.rechtackova.cz/',
11+
edulint: {
12+
url: 'https://edulint.com',
13+
version: '2.10.2',
14+
config: 'https://ksi.fi.muni.cz/assets/edulint/ksi.toml'
15+
},
1216
urlPrefix: '/',
1317
logger: {
1418
log: console.log,

src/environments/environment.kyzikos.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,11 @@ import { Environment } from './model';
77
export const environment: Environment = {
88
production: true,
99
backend: 'https://kyzikos.fi.muni.cz:3000/',
10-
edulint: 'https://edulint.rechtackova.cz/',
10+
edulint: {
11+
url: 'https://edulint.com',
12+
version: '2.10.2',
13+
config: 'default'
14+
},
1115
urlPrefix: '/',
1216
logger: {
1317
log: console.log,

0 commit comments

Comments
 (0)