-
-
Notifications
You must be signed in to change notification settings - Fork 20
Expand file tree
/
Copy pathAnalyzerImpl.ts
More file actions
135 lines (119 loc) · 3.44 KB
/
AnalyzerImpl.ts
File metadata and controls
135 lines (119 loc) · 3.44 KB
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
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
import type { Input, Logger } from '@exercism/static-analysis'
import { getProcessLogger } from '@exercism/static-analysis'
import type { Analyzer, Comment, Output } from '~src/interface'
import { AnalyzerOutput } from '~src/output/AnalyzerOutput'
class EarlyFinalization extends Error {
constructor() {
super('Early finalization')
Object.setPrototypeOf(this, EarlyFinalization.prototype)
Error.captureStackTrace(this, this.constructor)
}
}
export abstract class AnalyzerImpl implements Analyzer {
protected readonly logger: Logger
private output!: AnalyzerOutput
/**
* Creates an instance of an analyzer
*/
constructor() {
this.logger = getProcessLogger()
}
/**
* Runs the analyzer
*
* This is defined as a property instead of a method, so that it can not be
* overridden in a subclass. Subclasses should override @see execute instead.
*
* @returns The promise that resolves the analyzer output.
*
* @memberof BaseAnalyzer
*/
public async run(input: Input): Promise<Output> {
// Ensure each run has a fresh output
//
// Note: still need to wait for a run to complete before the next one can be
// started. We could work around this by providing an execution
// context that is fresh on each run.
//
// The reason output is not passed to execute, is that it doesn't _actually_
// enforce the implementing analyzer to not use local state, so we don't
// gain anything by it.
//
this.output = new AnalyzerOutput()
await this.execute(input).catch((err): void | never => {
if (err instanceof EarlyFinalization) {
this.logger.log(
`=> early finalization (${
this.output.summary ?? this.output.comments.length
})`
)
} else {
throw err
}
})
return this.output
}
/**
* Approve the solution early with an optional comment.
*
* @see disapprove
* @see redirect
*
* @param comment the optional comment to approve with
* @throws {EarlyFinalization} used as control flow in @see run
*/
protected approve(comment?: Comment): never {
this.comment(comment)
this.output.approve()
throw new EarlyFinalization()
}
/**
* Disapprove the solution early with an optional comment
*
* @see approve
* @see redirect
*
* @param comment the optional comment to disapprove with
* @throws {EarlyFinalization} used as control flow in @see run
*/
protected disapprove(comment?: Comment): never {
this.comment(comment)
this.output.disapprove()
throw new EarlyFinalization()
}
/**
* Refer the solution to the mentor early with an optional comment
*
* @see approve
* @see disapprove
*
* @param comment the optional comment to redirect with
* @throws {EarlyFinalization} used as control flow in @see run
*/
protected redirect(comment?: Comment): never {
this.comment(comment)
this.output.redirect()
throw new EarlyFinalization()
}
/**
* Add a comment to the output
*
* @param {Comment} [comment]
*/
protected comment(comment?: Comment): void {
if (!comment) {
return
}
this.output.add(comment)
}
/**
* Property that returns true if there is at least one comment in the output.
*/
public get hasCommentary(): boolean {
return this.output.comments.length > 0
}
/**
* Execute the analyzer
*/
protected abstract execute(input: Input): Promise<void>
}