• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

Keylan / yamllint-ts / 21097328415

17 Jan 2026 04:17PM UTC coverage: 84.739%. Remained the same
21097328415

push

github

Keylan
Improve test coverage, fix bugs

1431 of 1720 branches covered (83.2%)

Branch coverage included in aggregate %.

82 of 89 new or added lines in 3 files covered. (92.13%)

71 existing lines in 2 files now uncovered.

1523 of 1766 relevant lines covered (86.24%)

3072.64 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

90.23
/src/cli-utils.ts
1
/**
2
 * yamllint-ts - TypeScript YAML Linter
3
 * CLI Utility Functions (testable without subprocess)
4
 *
5
 * Copyright (C) 2024
6
 * Licensed under GPL-3.0
7
 */
8

9
import chalk from 'chalk';
10
import * as fs from 'fs';
11
import * as path from 'path';
12
import * as os from 'os';
13
import type { LintProblem } from './linter.js';
14
import type { YamlLintConfig } from './config.js';
15

16
// =============================================================================
17
// Constants
18
// =============================================================================
19

20
export const APP_NAME = 'yamllint-ts';
1✔
21
export const APP_VERSION = '0.1.0';
1✔
22
export const APP_DESCRIPTION = 'A TypeScript YAML linter with feature parity to yamllint (Python)';
1✔
23

24
export const PROBLEM_LEVELS: Record<string, number> = {
1✔
25
  error: 2,
26
  warning: 1,
27
};
28

29
// =============================================================================
30
// File Finding
31
// =============================================================================
32

33
/**
34
 * Walk a directory recursively.
35
 */
36
export function* walkDirectory(dir: string): Generator<string> {
37
  const entries = fs.readdirSync(dir, { withFileTypes: true });
4✔
38

39
  for (const entry of entries) {
4✔
40
    const fullPath = path.join(dir, entry.name);
6✔
41

42
    if (entry.isDirectory()) {
6✔
43
      yield* walkDirectory(fullPath);
1✔
44
    } else if (entry.isFile()) {
5!
45
      yield fullPath;
5✔
46
    }
47
  }
48
}
49

50
/**
51
 * Find all YAML files recursively.
52
 */
53
export function* findFilesRecursively(items: string[], conf: YamlLintConfig): Generator<string> {
54
  for (const item of items) {
3✔
55
    const stat = fs.statSync(item, { throwIfNoEntry: false });
3✔
56

57
    if (stat?.isDirectory()) {
3✔
58
      // Recursively walk directory
59
      for (const entry of walkDirectory(item)) {
1✔
60
        if (conf.isYamlFile(entry) && !conf.isFileIgnored(entry)) {
3✔
61
          yield entry;
2✔
62
        }
63
      }
64
    } else if (stat?.isFile()) {
2✔
65
      yield item;
1✔
66
    }
67
  }
68
}
69

70
// =============================================================================
71
// Output Formatting
72
// =============================================================================
73

74
/**
75
 * Check if terminal supports colors.
76
 */
77
export function supportsColor(): boolean {
78
  if (process.platform === 'win32') {
2!
NEW
79
    return 'ANSICON' in process.env || process.env.TERM === 'ANSI';
×
80
  }
81
  return process.stdout.isTTY === true;
2✔
82
}
83

84
/**
85
 * Format functions for different output styles.
86
 */
87
export const Format = {
1✔
88
  parsable(problem: LintProblem, filename: string): string {
89
    return `${filename}:${problem.line}:${problem.column}: [${problem.level}] ${problem.message}`;
2✔
90
  },
91

92
  standard(problem: LintProblem, _filename: string): string {
93
    let line = `  ${problem.line}:${problem.column}`;
2✔
94
    line = line.padEnd(12);
2✔
95
    line += problem.level;
2✔
96
    line = line.padEnd(21);
2✔
97
    line += problem.desc;
2✔
98
    if (problem.rule) {
2!
99
      line += `  (${problem.rule})`;
2✔
100
    }
101
    return line;
2✔
102
  },
103

104
  standardColor(problem: LintProblem, _filename: string): string {
105
    let line = `  ${chalk.dim(`${problem.line}:${problem.column}`)}`;
2✔
106
    // Add padding (accounting for ANSI codes)
107
    const visibleLen = `  ${problem.line}:${problem.column}`.length;
2✔
108
    line += ' '.repeat(Math.max(12 - visibleLen, 0));
2✔
109

110
    if (problem.level === 'warning') {
2✔
111
      line += chalk.yellow(problem.level);
1✔
112
    } else {
113
      line += chalk.red(problem.level);
1✔
114
    }
115

116
    // Add padding after level
117
    line += ' '.repeat(Math.max(9 - problem.level!.length, 0));
2✔
118
    line += problem.desc;
2✔
119

120
    if (problem.rule) {
2!
121
      line += `  ${chalk.dim(`(${problem.rule})`)}`;
2✔
122
    }
123
    return line;
2✔
124
  },
125

126
  github(problem: LintProblem, filename: string): string {
127
    let line = `::${problem.level} file=${filename},line=${problem.line},col=${problem.column}`;
2✔
128
    line += `::${problem.line}:${problem.column} `;
2✔
129
    if (problem.rule) {
2!
130
      line += `[${problem.rule}] `;
2✔
131
    }
132
    line += problem.desc;
2✔
133
    return line;
2✔
134
  },
135
};
136

137
/**
138
 * Determine effective format based on environment.
139
 */
140
export function getEffectiveFormat(format: string): string {
141
  if (format === 'auto') {
8✔
142
    if (process.env.GITHUB_ACTIONS && process.env.GITHUB_WORKFLOW) {
2✔
143
      return 'github';
1✔
144
    } else if (supportsColor()) {
1!
NEW
145
      return 'colored';
×
146
    } else {
147
      return 'standard';
1✔
148
    }
149
  }
150
  return format;
6✔
151
}
152

153
/**
154
 * Format a single problem for output.
155
 */
156
export function formatProblem(problem: LintProblem, filename: string, format: string): string {
157
  const effectiveFormat = getEffectiveFormat(format);
3✔
158

159
  if (effectiveFormat === 'parsable') {
3✔
160
    return Format.parsable(problem, filename);
1✔
161
  } else if (effectiveFormat === 'github') {
2✔
162
    return Format.github(problem, filename);
1✔
163
  } else if (effectiveFormat === 'colored') {
1!
NEW
164
    return Format.standardColor(problem, filename);
×
165
  } else {
166
    return Format.standard(problem, filename);
1✔
167
  }
168
}
169

170
// =============================================================================
171
// Configuration Finding
172
// =============================================================================
173

174
/**
175
 * Find project configuration file.
176
 */
177
export function findProjectConfigFilepath(startPath: string = '.'): string | null {
4✔
178
  const configNames = ['.yamllint', '.yamllint.yaml', '.yamllint.yml'];
4✔
179

180
  let currentPath = path.resolve(startPath);
4✔
181
  const homePath = os.homedir();
4✔
182
  const rootPath = path.parse(currentPath).root;
4✔
183

184
  while (currentPath !== homePath && currentPath !== rootPath) {
4✔
185
    for (const configName of configNames) {
6✔
186
      const configPath = path.join(currentPath, configName);
15✔
187
      if (fs.existsSync(configPath) && fs.statSync(configPath).isFile()) {
15✔
188
        return configPath;
3✔
189
      }
190
    }
191

192
    const parentPath = path.dirname(currentPath);
3✔
193
    if (parentPath === currentPath) break;
3!
194
    currentPath = parentPath;
3✔
195
  }
196

197
  return null;
1✔
198
}
199

200
/**
201
 * Get user global config path.
202
 */
203
export function getUserGlobalConfigPath(): string {
204
  if (process.env.YAMLLINT_CONFIG_FILE) {
3✔
205
    return path.resolve(process.env.YAMLLINT_CONFIG_FILE);
1✔
206
  }
207

208
  if (process.env.XDG_CONFIG_HOME) {
2✔
209
    return path.join(process.env.XDG_CONFIG_HOME, 'yamllint', 'config');
1✔
210
  }
211

212
  return path.join(os.homedir(), '.config', 'yamllint', 'config');
1✔
213
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc