Skip to content

Commit

Permalink
feat: Support --file-list cli option (#2130)
Browse files Browse the repository at this point in the history
* feat: Support `--file-list` cli option
    Related to #1850
* dev: read fileLists when checking files.
* dev: Improve error handling
* Update GitPod config to reduce number of pre-builds
  • Loading branch information
Jason3S authored Dec 29, 2021
1 parent bccdbf2 commit eef7b92
Show file tree
Hide file tree
Showing 21 changed files with 274 additions and 74 deletions.
15 changes: 14 additions & 1 deletion .gitpod.yml
Original file line number Diff line number Diff line change
@@ -1,2 +1,15 @@
tasks:
- init: npm install && npm run build
- init: npm install
command: npm run build
vscode:
extensions:
- streetsidesoftware.code-spell-checker
github:
prebuilds:
master: true
branches: false
pullRequests: false
pullRequestsFromForks: false
addCheck: false
addComment: false
addBadge: false
1 change: 1 addition & 0 deletions cspell-dict.txt
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ packlist
pagekit
patreon
popd
prebuilds
pushd
pycontribs
rebased
Expand Down
7 changes: 7 additions & 0 deletions cspell.code-workspace
Original file line number Diff line number Diff line change
Expand Up @@ -93,5 +93,12 @@
"cSpell.customDictionaries": {
"workspace": true
}
},
"extensions": {
"recommendations": [
"streetsidesoftware.code-spell-checker",
"dbaeumer.vscode-eslint",
"esbenp.prettier-vscode"
]
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
missing-file.txt
../../../src/app.ts
../../../README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
../../../src/app.ts
../../../README.md
3 changes: 3 additions & 0 deletions packages/cspell/fixtures/fileHelper/file-list.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
file1
../file2
dir/file3
1 change: 1 addition & 0 deletions packages/cspell/fixtures/fileHelper/nested/file-list-2.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
file2.txt
12 changes: 7 additions & 5 deletions packages/cspell/src/__snapshots__/app.test.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ Array [
" -h, --help display help for command",
"",
"Commands:",
" lint [options] [files...] Check spelling",
" lint [options] [globs...] Check spelling",
" trace [options] <words...> Trace words",
" Search for words in the configuration and dictionaries.",
" check [options] <files...> Spell check file(s) and display the result. The",
Expand Down Expand Up @@ -289,7 +289,7 @@ exports[`Validate cli app must find with error Expect Error: [Function CheckFail
exports[`Validate cli app no-args Expect Error: outputHelp 1`] = `
Array [
"Usage: cspell lint [options] [files...]",
"Usage: cspell lint [options] [globs...]",
"",
"Check spelling",
"",
Expand All @@ -306,11 +306,12 @@ Array [
" dictionaries.",
" -u, --unique Only output the first instance of a word not",
" found in the dictionaries.",
" --debug Output information useful for debugging",
" cspell.json files.",
" -e, --exclude <glob> Exclude files matching the glob pattern. This",
" option can be used multiple times to add",
" multiple globs.",
" --file-list <path or stdin> Specify a list of files to be spell checked. The",
" list is filtered against the glob file patterns.",
" Note: the format is 1 file path per line.",
" --no-issues Do not show the spelling errors.",
" --no-progress Turn off progress messages",
" --no-summary Turn off summary message in console",
Expand All @@ -320,7 +321,6 @@ Array [
" --show-context Show the surrounding text around an issue.",
" --show-suggestions Show spelling suggestions.",
" --no-must-find-files Do not error if no files are found",
" --legacy Legacy output",
" --cache Only check changed files (default: false)",
" --cache-strategy <strategy> Strategy to use for detecting changed files",
" (choices: \\"metadata\\", \\"content\\")",
Expand All @@ -335,6 +335,8 @@ Array [
" root.",
" --no-color Turn off color.",
" --color Force color",
" --debug Output information useful for debugging",
" cspell.json files.",
" -h, --help display help for command",
"",
"",
Expand Down
2 changes: 1 addition & 1 deletion packages/cspell/src/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { ApplicationError } from './util/errors';
// eslint-disable-next-line @typescript-eslint/no-var-requires
const npmPackage = require(path.join(__dirname, '..', 'package.json'));

export { LinterCliOptions as Options } from './commandLint';
export { LinterCliOptions as Options } from './options';
export { CheckFailed } from './util/errors';

export async function run(program?: commander.Command, argv?: string[]): Promise<void> {
Expand Down
4 changes: 2 additions & 2 deletions packages/cspell/src/application.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ export type { TraceResult } from 'cspell-lib';

export type AppError = NodeJS.ErrnoException;

export function lint(files: string[], options: LinterOptions, emitters: CSpellReporter): Promise<RunResult> {
const cfg = new LintRequest(files, options, emitters);
export function lint(fileGlobs: string[], options: LinterOptions, emitters: CSpellReporter): Promise<RunResult> {
const cfg = new LintRequest(fileGlobs, options, emitters);
return runLint(cfg);
}

Expand Down
27 changes: 23 additions & 4 deletions packages/cspell/src/cli-reporter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ import chalk = require('chalk');
import type { CSpellReporter, Issue, MessageType, ProgressItem, RunResult } from '@cspell/cspell-types';
import { ImportError, isSpellingDictionaryLoadError, SpellingDictionaryLoadError } from 'cspell-lib';
import * as path from 'path';
import { Options } from './app';
import { URI } from 'vscode-uri';
import { LinterCliOptions } from './options';

const templateIssue = `{green $filename}:{yellow $row:$col} - $message ({red $text})`;
const templateIssueWithSuggestions = `{green $filename}:{yellow $row:$col} - $message ({red $text}) Suggestions: {yellow [$suggestions]}`;
Expand Down Expand Up @@ -79,7 +79,26 @@ function reportTime(elapsedTimeMs: number | undefined, cached: boolean): string
return color(elapsedTimeMs.toFixed(2) + 'ms');
}

export function getReporter(options: Options): CSpellReporter {
export interface ReporterOptions
extends Pick<
LinterCliOptions,
| 'debug'
| 'issues'
| 'legacy'
| 'progress'
| 'relative'
| 'root'
| 'showContext'
| 'showSuggestions'
| 'silent'
| 'summary'
| 'verbose'
| 'wordsOnly'
> {
fileGlobs: string[];
}

export function getReporter(options: ReporterOptions): CSpellReporter {
const issueTemplate = options.wordsOnly
? templateIssueWordsOnly
: options.legacy
Expand All @@ -91,7 +110,7 @@ export function getReporter(options: Options): CSpellReporter {
: options.showSuggestions
? templateIssueWithSuggestions
: templateIssue;
const { files, silent, summary, issues, progress, verbose, debug } = options;
const { fileGlobs, silent, summary, issues, progress, verbose, debug } = options;

const emitters: InfoEmitter = {
Debug: !silent && debug ? (s) => console.info(chalk.cyan(s)) : nullEmitter,
Expand All @@ -117,7 +136,7 @@ export function getReporter(options: Options): CSpellReporter {
}

const resultEmitter = (result: RunResult) => {
if (!files.length && !result.files) {
if (!fileGlobs.length && !result.files) {
return;
}
console.error(
Expand Down
40 changes: 17 additions & 23 deletions packages/cspell/src/commandLint.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,10 @@
import { Command, Option as CommanderOption } from 'commander';
import * as App from './application';
import { getReporter } from './cli-reporter';
import { LinterOptions } from './options';
import { LinterCliOptions, LinterOptions } from './options';
import { DEFAULT_CACHE_LOCATION } from './util/cache';
import { CheckFailed } from './util/errors';

export interface LinterCliOptions extends LinterOptions {
files: string[];
legacy?: boolean;
summary: boolean;
issues: boolean;
silent: boolean;
mustFindFiles: boolean;
progress?: boolean;
/**
* issues are shown with a relative path to the root or `cwd`
*/
relative?: boolean;
}
// interface InitOptions extends Options {}

const usage = `
Expand Down Expand Up @@ -63,12 +50,18 @@ export function commandLint(prog: Command): Command {
new CommanderOption('--wordsOnly', 'Only output the words not found in the dictionaries.').hideHelp()
)
.option('-u, --unique', 'Only output the first instance of a word not found in the dictionaries.')
.option('--debug', 'Output information useful for debugging cspell.json files.')
.option(
'-e, --exclude <glob>',
'Exclude files matching the glob pattern. This option can be used multiple times to add multiple globs. ',
collect
)
.option(
'--file-list <path or stdin>',
'Specify a list of files to be spell checked.' +
' The list is filtered against the glob file patterns.' +
' Note: the format is 1 file path per line.',
collect
)
.option('--no-issues', 'Do not show the spelling errors.')
.option('--no-progress', 'Turn off progress messages')
.option('--no-summary', 'Turn off summary message in console')
Expand All @@ -82,7 +75,7 @@ export function commandLint(prog: Command): Command {
// The following options are planned features
// .option('-w, --watch', 'Watch for any changes to the matching files and report any errors')
// .option('--force', 'Force the exit value to always be 0')
.option('--legacy', 'Legacy output')
.addOption(new CommanderOption('--legacy', 'Legacy output').hideHelp())
.addOption(new CommanderOption('--local <local>', 'Deprecated -- Use: --locale').hideHelp())
.option('--cache', 'Only check changed files', false)
.addOption(
Expand All @@ -98,14 +91,15 @@ export function commandLint(prog: Command): Command {
.option('--gitignore-root <path>', 'Prevent searching for .gitignore files past root.', collect)
.option('--no-color', 'Turn off color.')
.option('--color', 'Force color')
.option('--debug', 'Output information useful for debugging cspell.json files.')
.addHelpText('after', usage)
.arguments('[files...]')
.action((files: string[], options: LinterCliOptions) => {
options.files = files;
const { mustFindFiles } = options;
const cliReporter = getReporter(options);
return App.lint(files, options, cliReporter).then((result) => {
if (!files.length && !result.files) {
.arguments('[globs...]')
.action((fileGlobs: string[], options: LinterCliOptions) => {
const { mustFindFiles, fileList } = options;
const cliReporter = getReporter({ ...options, fileGlobs });
const lintOptions: LinterOptions = { ...options, fileLists: fileList };
return App.lint(fileGlobs, lintOptions, cliReporter).then((result) => {
if (!fileGlobs.length && !result.files && !result.errors) {
spellCheckCommand.outputHelp();
throw new CheckFailed('outputHelp', 1);
}
Expand Down
36 changes: 36 additions & 0 deletions packages/cspell/src/fileHelper.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { readFileListFile, readFileListFiles } from './fileHelper';
import * as path from 'path';

const fixtures = path.join(__dirname, '../fixtures/fileHelper');
const fileListFile = path.join(fixtures, 'file-list.txt');
const fileListFile2 = path.join(fixtures, 'nested/file-list-2.txt');

const oc = expect.objectContaining;

describe('fileHelper', () => {
test('readFileListFile', async () => {
try {
const files = ['a', 'b', 'c'];
process.stdin.isTTY = false;
const pResult = readFileListFile('stdin');
process.stdin.push(files.join('\n'));
// need to delay the `end` event or it might become a race condition.
setTimeout(() => process.stdin.emit('end'), 1);
const r = await pResult;
expect(r).toEqual(files.map((f) => path.resolve(f)));
} finally {
process.stdin.isTTY = true;
}
});

test('readFileListFiles', async () => {
const files = ['file1', '../file2', 'dir/file3', 'nested/file2.txt'];
const r = await readFileListFiles([fileListFile, fileListFile2]);
expect(r).toEqual(files.map((f) => path.resolve(fixtures, f)));
});

test('readFileListFiles Error', () => {
const r = readFileListFiles(['not-found.txt']);
return expect(r).rejects.toEqual(oc({ message: 'Error reading file: "not-found.txt"' }));
});
});
43 changes: 39 additions & 4 deletions packages/cspell/src/fileHelper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import getStdin from 'get-stdin';
import { GlobOptions, globP } from './util/glob';
import * as path from 'path';
import { CSpellUserSettings, Document, fileToDocument, Issue } from 'cspell-lib';
import { toApplicationError } from './util/errors';

const UTF8: BufferEncoding = 'utf8';
const STDIN = 'stdin';
Expand Down Expand Up @@ -70,10 +71,7 @@ export function readFileInfo(filename: string, encoding: string = UTF8): Promise
(error) => {
return error.code === 'EISDIR'
? Promise.resolve({ text: '', filename })
: Promise.reject({
...error,
message: `Error reading file: "${filename}"`,
});
: Promise.reject(toApplicationError(error, `Error reading file: "${filename}"`));
}
);
}
Expand Down Expand Up @@ -117,3 +115,40 @@ export function calcFinalConfigInfo(
languageIds,
};
}

/**
* Read
* @param listFiles - array of file paths to read that will contain a list of files. Paths contained in each
* file will be resolved relative to the containing file.
* @returns - a list of files to be processed.
*/
export async function readFileListFiles(listFiles: string[]): Promise<string[]> {
return flatten(await Promise.all(listFiles.map(readFileListFile)));
}

/**
* Read a `listFile` and return the containing file paths resolved relative to the `listFile`.
* @param listFiles - array of file paths to read that will contain a list of files. Paths contained in each
* file will be resolved relative to the containing file.
* @returns - a list of files to be processed.
*/
export async function readFileListFile(listFile: string): Promise<string[]> {
const relTo = path.resolve(path.dirname(listFile));
const content = await readFile(listFile);
const lines = content
.split('\n')
.map((a) => a.trim())
.filter((a) => !!a)
.map((file) => path.resolve(relTo, file));
return lines;
}

function flatten(fileLists: string[][]): string[] {
function* f() {
for (const list of fileLists) {
yield* list;
}
}

return [...f()];
}
4 changes: 3 additions & 1 deletion packages/cspell/src/lint/LintRequest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,9 @@ export class LintRequest {
readonly root: string;
readonly showContext: number;
readonly enableGlobDot: boolean | undefined;
readonly fileLists: string[];

constructor(readonly files: string[], readonly options: LinterOptions, readonly reporter: CSpellReporter) {
constructor(readonly fileGlobs: string[], readonly options: LinterOptions, readonly reporter: CSpellReporter) {
this.root = path.resolve(options.root || process.cwd());
this.configFile = options.config;
this.excludes = calcExcludeGlobInfo(this.root, options.exclude);
Expand All @@ -25,5 +26,6 @@ export class LintRequest {
this.uniqueFilter = options.unique ? util.uniqueFilterFnGenerator((issue: Issue) => issue.text) : () => true;
this.showContext =
options.showContext === true ? defaultContextRange : options.showContext ? options.showContext : 0;
this.fileLists = options.fileLists || [];
}
}
Loading

0 comments on commit eef7b92

Please sign in to comment.