xref: /expo/tools/src/linting/SwiftLint.ts (revision f657683d)
1import { SpawnResult } from '@expo/spawn-async';
2import swiftlint from '@expo/swiftlint';
3import { ChildProcess } from 'child_process';
4
5import { EXPO_DIR } from '../Constants';
6
7/**
8 * Represents a single linter violation.
9 */
10export type LintViolation = {
11  /**
12   * Number of the line at which the violation starts.
13   */
14  line: number;
15
16  /**
17   * Column at which the violation starts or `null` when the entire line is violated.
18   */
19  column: number | null;
20
21  /**
22   * An ID of the violated rule.
23   */
24  ruleId: string;
25
26  /**
27   * Short description of the rule.
28   */
29  type: string;
30
31  /**
32   * Full explanation of the rule.
33   */
34  reason: string;
35
36  /**
37   * Level of the violation. Errors are serious violations and must be fixed.
38   */
39  severity: 'warning' | 'error';
40};
41
42/**
43 * Spawns swiftlint process.
44 */
45function runAsync(args: string[]): Promise<SpawnResult> {
46  return swiftlint(args, {
47    cwd: EXPO_DIR,
48  });
49}
50
51/**
52 * Parses JSON reported by `swiftlint` to the array of violations.
53 */
54function parseLintResultsFromJSONString(jsonString: string): LintViolation[] {
55  try {
56    const json = JSON.parse(jsonString);
57
58    return json.map(({ file, line, character, reason, severity, type, rule_id }) => ({
59      file,
60      line,
61      column: character ?? 0,
62      reason,
63      severity: severity.toLowerCase(),
64      type,
65      ruleId: rule_id,
66    }));
67  } catch (e) {
68    console.error('Unable to parse as JSON:', jsonString);
69    throw e;
70  }
71}
72
73/**
74 * Returns the version of `swiftlint` binary.
75 */
76export async function getVersionAsync(): Promise<string | null> {
77  try {
78    const { stdout } = await runAsync(['version']);
79    return stdout.trim();
80  } catch {
81    return null;
82  }
83}
84
85/**
86 * Lints Swift source code passed as string.
87 */
88export async function lintStringAsync(str: string): Promise<LintViolation[]> {
89  const promise = runAsync(['lint', '--reporter', 'json', '--quiet', '--use-stdin']);
90
91  // @ts-ignore
92  const child = promise.child as ChildProcess;
93  child.stdin?.write(str);
94  child.stdin?.end();
95
96  let stdout: string;
97  try {
98    stdout = (await promise).stdout;
99  } catch (error) {
100    stdout = error.stdout;
101  }
102
103  return parseLintResultsFromJSONString(stdout);
104}
105