Skip to content

Commit 0894d4a

Browse files
jeeyojeeyoencX
authored
Add SwiftLint support (#148)
Co-authored-by: jeeyo <[email protected]> Co-authored-by: Plai <[email protected]>
1 parent 7de43b7 commit 0894d4a

File tree

8 files changed

+210
-7
lines changed

8 files changed

+210
-7
lines changed

README.md

+5
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ So it can be integrated with any CI with ease.
2121
- ScalaStyle
2222
- AndroidLint
2323
- DartLint
24+
- SwiftLint (0.49.1 or later)
2425

2526
#### Supported source controls
2627
- GitHub
@@ -145,5 +146,9 @@ Use `-o <filename>` to output lint result to file
145146
Use `-o <filename>` on output lint result created by command `dart analyze > <filename>` in dart project
146147
(_[ref.](https://dart-lang.github.io/linter/lints/)_)
147148

149+
#### SwiftLint
150+
Use `--output <filename>` to output lint result to file and `--reporter json` to format logs as JSON.
151+
(_[ref.](https://github.com/realm/SwiftLint#command-line)_)
152+
148153
### Contribute
149154
For contribution guidelines and project dev setup. Please see [CONTRIBUTING.md](CONTRIBUTING.md)
+65
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
[
2+
{
3+
"character" : null,
4+
"file" : "\/Users\/master\/builds\/DxeaNTET\/0\/myswiftproject\/Folder1\/SubFolder1\/File1.swift",
5+
"line" : 61,
6+
"reason" : "Lines should not have trailing whitespace",
7+
"rule_id" : "trailing_whitespace",
8+
"severity" : "Warning",
9+
"type" : "Trailing Whitespace"
10+
},
11+
{
12+
"character" : 18,
13+
"file" : "\/Users\/master\/builds\/DxeaNTET\/0\/myswiftproject\/Folder2\/SubFolder2\/File2.swift",
14+
"line" : 4,
15+
"reason" : "Imports should be sorted",
16+
"rule_id" : "sorted_imports",
17+
"severity" : "Warning",
18+
"type" : "Sorted Imports"
19+
},
20+
{
21+
"character" : null,
22+
"file" : "\/Users\/master\/builds\/DxeaNTET\/0\/myswiftproject\/Folder1\/SubFolder1\/File3.swift",
23+
"line" : 22,
24+
"reason" : "Lines should not have trailing whitespace",
25+
"rule_id" : "trailing_whitespace",
26+
"severity" : "Warning",
27+
"type" : "Trailing Whitespace"
28+
},
29+
{
30+
"character" : null,
31+
"file" : "\/Users\/master\/builds\/DxeaNTET\/0\/myswiftproject\/Folder1\/SubFolder1\/File4.swift",
32+
"line" : 140,
33+
"reason" : "Lines should not have trailing whitespace",
34+
"rule_id" : "trailing_whitespace",
35+
"severity" : "Warning",
36+
"type" : "Trailing Whitespace"
37+
},
38+
{
39+
"character" : null,
40+
"file" : "\/Users\/master\/builds\/DxeaNTET\/0\/myswiftproject\/Folder1\/SubFolder1\/File5.swift",
41+
"line" : 130,
42+
"reason" : "Line should be 120 characters or less; currently it has 125 characters",
43+
"rule_id" : "line_length",
44+
"severity" : "Warning",
45+
"type" : "Line Length"
46+
},
47+
{
48+
"character" : 7,
49+
"file" : "\/Users\/master\/builds\/DxeaNTET\/0\/myswiftproject\/Folder3\/File6.swift",
50+
"line" : 9,
51+
"reason" : "Type body should span 400 lines or less excluding comments and whitespace: currently spans 448 lines",
52+
"rule_id" : "type_body_length",
53+
"severity" : "Error",
54+
"type" : "Type Body Length"
55+
},
56+
{
57+
"character" : 5,
58+
"file" : "\/Users\/master\/builds\/DxeaNTET\/0\/myswiftproject\/Folder4\/File7.swift",
59+
"line" : 10,
60+
"reason" : "All test functions should be throwable.",
61+
"rule_id" : "throwable_tests",
62+
"severity" : "Error",
63+
"type" : "Throwable testcases"
64+
}
65+
]

src/Config/@enums/projectType.ts

+1
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,5 @@ export enum ProjectType {
66
scalastyle = 'scalastyle',
77
androidlint = 'androidlint',
88
dartlint = 'dartlint',
9+
swiftlint = 'swiftlint',
910
}

src/Parser/@types/SwiftLintLog.ts

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
// https://github.com/realm/SwiftLint/blob/main/Source/SwiftLintCore/Reporters/JSONReporter.swift
2+
export type SwiftLintLog = {
3+
character: number | null;
4+
file: string | null;
5+
line: number | null;
6+
reason: string;
7+
rule_id: string;
8+
severity: string;
9+
type: string;
10+
};

src/Parser/SwiftLintParser.spec.ts

+74
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
import { LogSeverity } from './@enums/log.severity.enum';
2+
import { SwiftLintParser } from './SwiftLintParser';
3+
4+
describe('SwiftLintParser tests', () => {
5+
const cwd = '/Users/master/builds/DxeaNTET/0/myswiftproject';
6+
const mockedContent = [
7+
{
8+
character: null,
9+
file:
10+
'/Users/master/builds/DxeaNTET/0/myswiftproject/Folder1/SubFolder1/File5.swift',
11+
line: 130,
12+
reason: 'Line should be 120 characters or less; currently it has 125 characters',
13+
rule_id: 'line_length',
14+
severity: 'Warning',
15+
type: 'Line Length',
16+
},
17+
{
18+
character: 7,
19+
file: '',
20+
line: 9,
21+
reason:
22+
'Type body should span 400 lines or less excluding comments and whitespace: currently spans 448 lines',
23+
rule_id: 'type_body_length',
24+
severity: 'Error',
25+
type: 'Type Body Length',
26+
},
27+
];
28+
29+
const mockedContentString = JSON.stringify(mockedContent);
30+
31+
it('Should parse correctly', () => {
32+
const result = new SwiftLintParser(cwd).parse(mockedContentString);
33+
expect(result).toHaveLength(2);
34+
expect(result[0]).toEqual({
35+
ruleId: 'line_length',
36+
source: `Folder1/SubFolder1/File5.swift`,
37+
severity: LogSeverity.warning,
38+
line: 130,
39+
lineOffset: 0,
40+
msg: `Line should be 120 characters or less; currently it has 125 characters`,
41+
log: JSON.stringify(mockedContent[0]),
42+
valid: true,
43+
type: 'swiftlint',
44+
});
45+
expect(result[1]).toEqual({
46+
ruleId: 'type_body_length',
47+
source: ``,
48+
severity: LogSeverity.error,
49+
line: 9,
50+
lineOffset: 7,
51+
msg: `Type body should span 400 lines or less excluding comments and whitespace: currently spans 448 lines`,
52+
log: JSON.stringify(mockedContent[1]),
53+
valid: false,
54+
type: 'swiftlint',
55+
});
56+
});
57+
58+
it('Should do nothing if put empty string', () => {
59+
const result = new SwiftLintParser(cwd).parse('');
60+
expect(result).toHaveLength(0);
61+
});
62+
63+
it('Should parse with valid/invalid correctly', () => {
64+
const result = new SwiftLintParser(cwd).parse(mockedContentString);
65+
const valid = result.filter((el) => el.valid);
66+
const invalid = result.filter((el) => !el.valid);
67+
expect(valid).toHaveLength(1);
68+
expect(invalid).toHaveLength(1);
69+
});
70+
71+
it('Should throw error if the line not match the rule', () => {
72+
expect(() => new SwiftLintParser(cwd).parse(':')).toThrow();
73+
});
74+
});

src/Parser/SwiftLintParser.ts

+40
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import { Log } from '../Logger';
2+
import { getRelativePath } from './utils/path.util';
3+
import { Parser } from './@interfaces/parser.interface';
4+
import { LogType } from './@types';
5+
import { ProjectType } from '../Config/@enums';
6+
import { SwiftLintLog } from './@types/SwiftLintLog';
7+
import { LogSeverity } from './@enums/log.severity.enum';
8+
9+
export class SwiftLintParser extends Parser {
10+
parse(content: string): LogType[] {
11+
try {
12+
if (!content) return [];
13+
14+
const logsJson = JSON.parse(content) as SwiftLintLog[];
15+
return logsJson.map((el) => this.toLog(el));
16+
} catch (err) {
17+
Log.warn('SwiftLint Parser: parse with content via JSON error', content);
18+
throw err;
19+
}
20+
}
21+
22+
private toLog(log: SwiftLintLog): LogType {
23+
const parsed: LogType = {
24+
ruleId: log.rule_id,
25+
log: JSON.stringify(log),
26+
line: log.line ?? 0,
27+
lineOffset: log.character ?? 0,
28+
msg: log.reason,
29+
source: '',
30+
severity: log.severity.toLowerCase() as LogSeverity,
31+
valid: true,
32+
type: ProjectType.swiftlint,
33+
};
34+
35+
const source = getRelativePath(this.cwd, log.file ?? '');
36+
if (!source) return { ...parsed, valid: false };
37+
38+
return { ...parsed, source };
39+
}
40+
}

src/Parser/index.ts

+2
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ export { TSLintParser } from './TSLintParser';
44
export { ESLintParser } from './ESLintParser';
55
export { ScalaStyleParser } from './ScalaStyleParser';
66
export { AndroidLintStyleParser } from './AndroidLintStyleParser';
7+
export { DartLintParser } from './DartLintParser';
8+
export { SwiftLintParser } from './SwiftLintParser';
79
export { Parser } from './@interfaces/parser.interface';
810
export { LogType } from './@types';
911
export { LogSeverity } from './@enums/log.severity.enum';

src/app.ts

+13-7
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,9 @@ import {
1212
Parser,
1313
ScalaStyleParser,
1414
TSLintParser,
15+
DartLintParser,
16+
SwiftLintParser,
1517
} from './Parser';
16-
import { DartLintParser } from './Parser/DartLintParser';
1718
import { GitHubPRService, VCS } from './Provider';
1819
import { GitLabMRService } from './Provider/GitLab/GitLabMRService';
1920
import { GitHubAdapter } from './Provider/GitHub/GitHubAdapter';
@@ -26,13 +27,16 @@ class App {
2627
private vcs: VCS | null = null;
2728

2829
async start(): Promise<void> {
29-
const adapter = App.getAdapter();
30-
if (!adapter) {
31-
Log.error('VCS adapter is not found');
32-
process.exit(1);
30+
if (!configs.dryRun) {
31+
const adapter = App.getAdapter();
32+
if (!adapter) {
33+
Log.error('VCS adapter is not found');
34+
process.exit(1);
35+
}
36+
const analyzer = new AnalyzerBot(configs);
37+
this.vcs = new VCSEngine(configs, analyzer, adapter);
3338
}
34-
const analyzer = new AnalyzerBot(configs);
35-
this.vcs = new VCSEngine(configs, analyzer, adapter);
39+
3640
const logs = await this.parseBuildData(configs.buildLogFile);
3741
Log.info('Build data parsing completed');
3842

@@ -74,6 +78,8 @@ class App {
7478
return new AndroidLintStyleParser(cwd);
7579
case ProjectType.dartlint:
7680
return new DartLintParser(cwd);
81+
case ProjectType.swiftlint:
82+
return new SwiftLintParser(cwd);
7783
}
7884
}
7985

0 commit comments

Comments
 (0)