Skip to content

Commit

Permalink
feat: Support --dot command line option. (#1985)
Browse files Browse the repository at this point in the history
* feat: Support `--dot` command line option.
* Update README.md
* Update app.test.ts.snap
* dev: Add config `enableGlobDot`
* Make linter aware of `enableGlobDot` config option.
* test: Increase coverage
  • Loading branch information
Jason3S committed Nov 13, 2021
1 parent 020e931 commit fa1aa11
Show file tree
Hide file tree
Showing 22 changed files with 131 additions and 153 deletions.
5 changes: 5 additions & 0 deletions cspell.schema.json
Expand Up @@ -771,6 +771,11 @@
"type": "array",
"uniqueItems": true
},
"enableGlobDot": {
"default": false,
"description": "Enable scanning files and directories beginning with `.` (period). By default, CSpell does not scan `hidden` files.",
"type": "boolean"
},
"enabled": {
"default": true,
"description": "Is the spell checker enabled",
Expand Down
5 changes: 5 additions & 0 deletions packages/cspell-types/cspell.schema.json
Expand Up @@ -771,6 +771,11 @@
"type": "array",
"uniqueItems": true
},
"enableGlobDot": {
"default": false,
"description": "Enable scanning files and directories beginning with `.` (period). By default, CSpell does not scan `hidden` files.",
"type": "boolean"
},
"enabled": {
"default": true,
"description": "Is the spell checker enabled",
Expand Down
8 changes: 8 additions & 0 deletions packages/cspell-types/src/CSpellSettingsDef.ts
Expand Up @@ -60,6 +60,14 @@ export interface FileSettings extends ExtendableSettings {
*/
files?: Glob[];

/**
* Enable scanning files and directories beginning with `.` (period).
* By default, CSpell does not scan `hidden` files.
*
* @default false
*/
enableGlobDot?: boolean;

/**
* Glob patterns of files to be ignored
* Glob patterns are relative to the `globRoot` of the configuration file that defines them.
Expand Down
5 changes: 4 additions & 1 deletion packages/cspell/README.md
Expand Up @@ -86,7 +86,7 @@ Options:
--language-id <language> Force programming language for unknown
extensions. i.e. "php" or "scala"
--wordsOnly Only output the words not found in the
--words-only Only output the words not found in the
dictionaries.
-u, --unique Only output the first instance of a word not
Expand Down Expand Up @@ -116,6 +116,9 @@ Options:
--cache-location <path> Path to the cache file or directory (default:
".cspellcache")
--dot Include files and directories starting with `.`
(period) when matching globs.
--gitignore Ignore files matching glob patterns found in
.gitignore files.
Expand Down
1 change: 1 addition & 0 deletions packages/cspell/samples/hidden-test/.hidden/.text.md
@@ -0,0 +1 @@
# Nested Hidden file.
1 change: 1 addition & 0 deletions packages/cspell/samples/hidden-test/.hidden/hidden.md
@@ -0,0 +1 @@
# Hidden MD
@@ -1,35 +1,27 @@
import type { CSpellReporter, Issue } from '@cspell/cspell-types';
import * as path from 'path';
import { CSpellApplicationOptions } from './options';
import { LinterOptions } from './options';
import { calcExcludeGlobInfo, GlobSrcInfo } from './util/glob';
import { IOptions } from './util/IOptions';
import * as util from './util/util';

const defaultContextRange = 20;

// cspell:word nocase
const defaultMinimatchOptions: IOptions = { nocase: true };
export const defaultConfigGlobOptions: IOptions = defaultMinimatchOptions;

export class CSpellApplicationConfiguration {
export class LinterConfiguration {
readonly uniqueFilter: (issue: Issue) => boolean;
readonly locale: string;

readonly configFile: string | undefined;
readonly configGlobOptions: IOptions = defaultConfigGlobOptions;
readonly excludes: GlobSrcInfo[];
readonly root: string;
readonly showContext: number;
readonly enableGlobDot: boolean | undefined;

constructor(
readonly files: string[],
readonly options: CSpellApplicationOptions,
readonly reporter: CSpellReporter
) {
constructor(readonly files: 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);
this.locale = options.locale || options.local || '';
this.enableGlobDot = options.dot;
this.uniqueFilter = options.unique ? util.uniqueFilterFnGenerator((issue: Issue) => issue.text) : () => true;
this.showContext =
options.showContext === true ? defaultContextRange : options.showContext ? options.showContext : 0;
Expand Down
2 changes: 2 additions & 0 deletions packages/cspell/src/__snapshots__/app.test.ts.snap
Expand Up @@ -326,6 +326,8 @@ Array [
" (choices: \\"metadata\\", \\"content\\")",
" --cache-location <path> Path to the cache file or directory (default:",
" \\".cspellcache\\")",
" --dot Include files and directories starting with \`.\`",
" (period) when matching globs.",
" --gitignore Ignore files matching glob patterns found in",
" .gitignore files.",
" --no-gitignore Do NOT use .gitignore files.",
Expand Down
2 changes: 1 addition & 1 deletion packages/cspell/src/app.ts
Expand Up @@ -7,7 +7,7 @@ import { commandTrace } from './commandTrace';
// eslint-disable-next-line @typescript-eslint/no-var-requires
const npmPackage = require(path.join(__dirname, '..', 'package.json'));

export { Options } from './commandLint';
export { LinterCliOptions as Options } from './commandLint';
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.test.ts
@@ -1,7 +1,7 @@
import type { Issue } from '@cspell/cspell-types';
import * as path from 'path';
import * as App from './application';
import { CSpellApplicationOptions } from './options';
import { LinterOptions } from './options';
import { InMemoryReporter } from './util/InMemoryReporter';

const getStdinResult = {
Expand Down Expand Up @@ -165,7 +165,7 @@ describe('Application, Validate Samples', () => {
interface SampleTest {
file: string;
issues: (string | Partial<Issue>)[];
options?: CSpellApplicationOptions;
options?: LinterOptions;
}

function sampleTests(): SampleTest[] {
Expand Down
8 changes: 4 additions & 4 deletions packages/cspell/src/application.ts
Expand Up @@ -2,18 +2,18 @@ import type { CSpellReporter, RunResult } from '@cspell/cspell-types';
import * as cspell from 'cspell-lib';
import { CheckTextInfo, TraceResult, traceWords } from 'cspell-lib';
import * as path from 'path';
import { CSpellApplicationConfiguration } from './CSpellApplicationConfiguration';
import { LinterConfiguration } from './LinterConfiguration';
import { calcFinalConfigInfo, readConfig, readFile } from './fileHelper';
import { runLint } from './lint';
import { BaseOptions, CSpellApplicationOptions, TraceOptions } from './options';
import { BaseOptions, LinterOptions, TraceOptions } from './options';
import * as util from './util/util';
export type { TraceResult } from 'cspell-lib';
export { IncludeExcludeFlag } from 'cspell-lib';

export type AppError = NodeJS.ErrnoException;

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

Expand Down
7 changes: 4 additions & 3 deletions packages/cspell/src/commandLint.ts
@@ -1,11 +1,11 @@
import { Command, Option as CommanderOption } from 'commander';
import * as App from './application';
import { getReporter } from './cli-reporter';
import { CSpellApplicationOptions } from './options';
import { LinterOptions } from './options';
import { DEFAULT_CACHE_LOCATION } from './util/cache';
import { CheckFailed } from './util/errors';

export interface Options extends CSpellApplicationOptions {
export interface LinterCliOptions extends LinterOptions {
files: string[];
legacy?: boolean;
summary: boolean;
Expand Down Expand Up @@ -92,14 +92,15 @@ export function commandLint(prog: Command): Command {
])
)
.option('--cache-location <path>', `Path to the cache file or directory`, DEFAULT_CACHE_LOCATION)
.option('--dot', 'Include files and directories starting with `.` (period) when matching globs.')
.option('--gitignore', 'Ignore files matching glob patterns found in .gitignore files.')
.option('--no-gitignore', 'Do NOT use .gitignore files.')
.option('--gitignore-root <path>', 'Prevent searching for .gitignore files past root.', collect)
.option('--no-color', 'Turn off color.')
.option('--color', 'Force color')
.addHelpText('after', usage)
.arguments('[files...]')
.action((files: string[], options: Options) => {
.action((files: string[], options: LinterCliOptions) => {
options.files = files;
const { mustFindFiles } = options;
const cliReporter = getReporter(options);
Expand Down
2 changes: 1 addition & 1 deletion packages/cspell/src/index.ts
@@ -1,3 +1,3 @@
export * from '@cspell/cspell-types';
export * from './application';
export type { BaseOptions, CSpellApplicationOptions, TraceOptions } from './options';
export type { BaseOptions, LinterOptions as CSpellApplicationOptions, TraceOptions } from './options';
18 changes: 0 additions & 18 deletions packages/cspell/src/lint.test.ts

This file was deleted.

1 change: 1 addition & 0 deletions packages/cspell/src/lint/index.ts
@@ -0,0 +1 @@
export { runLint } from './lint';
50 changes: 50 additions & 0 deletions packages/cspell/src/lint/lint.test.ts
@@ -0,0 +1,50 @@
import * as path from 'path';
import { LinterConfiguration } from '../LinterConfiguration';
import { InMemoryReporter } from '../util/InMemoryReporter';
import { runLint } from './lint';

const samples = path.resolve(__dirname, '../../samples');
const latexSamples = path.resolve(samples, 'latex');
const hiddenSamples = path.resolve(samples, 'hidden-test');

const oc = expect.objectContaining;
const j = path.join;

describe('Linter Validation Tests', () => {
test('globs on the command line override globs in the config.', async () => {
const options = { root: latexSamples };
const reporter = new InMemoryReporter();
const rWithoutFiles = await runLint(new LinterConfiguration([], options, reporter));
expect(rWithoutFiles.files).toBe(4);
const rWithFiles = await runLint(new LinterConfiguration(['**/ebook.tex'], options, reporter));
expect(rWithFiles.files).toBe(1);
});

// cspell:ignore Tufte
test.each`
files | options | expectedRunResult | expectedReport
${[]} | ${{ root: latexSamples }} | ${oc({ errors: 0, files: 4 })} | ${oc({ errorCount: 0, issues: [oc({ text: 'Tufte' })] })}
${['**/ebook.tex']} | ${{ root: latexSamples }} | ${oc({ errors: 0, files: 1 })} | ${oc({ errorCount: 0, issues: [] })}
${['**/ebook.tex']} | ${{ root: latexSamples, gitignore: true }} | ${oc({ errors: 0, files: 1 })} | ${oc({ errorCount: 0, issues: [] })}
${['**/hidden.md']} | ${{ root: hiddenSamples }} | ${oc({ errors: 0, files: 0 })} | ${oc({ errorCount: 0, issues: [] })}
${['**/hidden.md']} | ${{ root: hiddenSamples, dot: true }} | ${oc({ errors: 0, files: 1 })} | ${oc({ errorCount: 0, issues: [] })}
${['**/*.md']} | ${{ root: hiddenSamples, dot: false }} | ${oc({ errors: 0, files: 0 })} | ${oc({ errorCount: 0, issues: [] })}
${['**/*.md']} | ${{ root: hiddenSamples }} | ${oc({ errors: 0, files: 0 })} | ${oc({ errorCount: 0, issues: [] })}
${['**/*.md']} | ${{ root: hiddenSamples, dot: true }} | ${oc({ errors: 0, files: 2 })} | ${oc({ errorCount: 0, issues: [] })}
${['**']} | ${{ root: samples, config: j(samples, 'cspell-not-found.json') }} | ${oc({ errors: 1, files: 0 })} | ${oc({ errorCount: 1, errors: [expect.any(Error)], issues: [] })}
${['**']} | ${{ root: samples, config: j(samples, 'linked/cspell-import-missing.json') }} | ${oc({ errors: 1, files: 0 })} | ${oc({ errorCount: 1, errors: [expect.any(Error)], issues: [] })}
${['**/ebook.tex']} | ${{ root: samples, config: j(samples, 'cspell-missing-dict.json') }} | ${oc({ errors: 0, files: 0 })} | ${oc({ errorCount: 0, errors: [], issues: [] })}
${['**/ebook.tex']} | ${{ root: samples, config: j(samples, 'linked/cspell-import.json') }} | ${oc({ errors: 0, files: 1 })} | ${oc({ errorCount: 0, issues: [] })}
`('runLint $files $options', async ({ files, options, expectedRunResult, expectedReport }) => {
const reporter = new InMemoryReporter();
const runResult = await runLint(new LinterConfiguration(files, options, reporter));
expect(runResult).toEqual(expectedRunResult);
expect(runResult).toEqual(reporter.runResult);
expect(report(reporter)).toEqual(expectedReport);
});
});

function report(reporter: InMemoryReporter) {
const { issues, errorCount, errors } = reporter;
return { issues, errorCount, errors };
}
35 changes: 23 additions & 12 deletions packages/cspell/src/lint.ts → packages/cspell/src/lint/lint.ts
@@ -1,24 +1,25 @@
import type { CSpellReporter, CSpellSettings, Glob, Issue, RunResult, TextDocumentOffset } from '@cspell/cspell-types';
import { MessageTypes } from '@cspell/cspell-types';
import * as commentJson from 'comment-json';
import { findRepoRoot, GitIgnore } from 'cspell-gitignore';
import type { GlobMatcher, GlobPatternNormalized, GlobPatternWithRoot } from 'cspell-glob';
import type { ValidationIssue } from 'cspell-lib';
import * as cspell from 'cspell-lib';
import { Logger } from 'cspell-lib';
import * as path from 'path';
import { format } from 'util';
import { URI } from 'vscode-uri';
import { CSpellApplicationConfiguration } from './CSpellApplicationConfiguration';
import { ConfigInfo, fileInfoToDocument, FileResult, findFiles, readConfig, readFileInfo } from './fileHelper';
import { createCache, CSpellLintResultCache } from './util/cache';
import { toError } from './util/errors';
import { buildGlobMatcher, extractGlobsFromMatcher, extractPatterns, normalizeGlobsToRoot } from './util/glob';
import { loadReporters, mergeReporters } from './util/reporters';
import { getTimeMeasurer } from './util/timer';
import * as util from './util/util';
import { findRepoRoot, GitIgnore } from 'cspell-gitignore';

export async function runLint(cfg: CSpellApplicationConfiguration): Promise<RunResult> {
import { ConfigInfo, fileInfoToDocument, FileResult, findFiles, readConfig, readFileInfo } from '../fileHelper';
import { LinterConfiguration } from '../LinterConfiguration';
import { createCache, CSpellLintResultCache } from '../util/cache';
import { toError } from '../util/errors';
import type { GlobOptions } from '../util/glob';
import { buildGlobMatcher, extractGlobsFromMatcher, extractPatterns, normalizeGlobsToRoot } from '../util/glob';
import { loadReporters, mergeReporters } from '../util/reporters';
import { getTimeMeasurer } from '../util/timer';
import * as util from '../util/util';

export async function runLint(cfg: LinterConfiguration): Promise<RunResult> {
let { reporter } = cfg;
cspell.setLogger(getLoggerFromReporter(reporter));
const configErrors = new Set<string>();
Expand Down Expand Up @@ -212,7 +213,17 @@ export async function runLint(cfg: CSpellApplicationConfiguration): Promise<RunR
const globMatcher = buildGlobMatcher(globsToExclude, root, true);
const ignoreGlobs = extractGlobsFromMatcher(globMatcher);
// cspell:word nodir
const globOptions = { root, cwd: root, ignore: ignoreGlobs.concat(normalizedExcludes), nodir: true };
const globOptions: GlobOptions = {
root,
cwd: root,
ignore: ignoreGlobs.concat(normalizedExcludes),
nodir: true,
};
const enableGlobDot = cfg.enableGlobDot ?? configInfo.config.enableGlobDot;
if (enableGlobDot !== undefined) {
globOptions.dot = enableGlobDot;
}

const foundFiles = await findFiles(fileGlobs, globOptions);
const filtered = gitIgnore ? await gitIgnore.filterOutIgnored(foundFiles) : foundFiles;
const files = filterFiles(filtered, globMatcher);
Expand Down
8 changes: 7 additions & 1 deletion packages/cspell/src/options.ts
@@ -1,4 +1,4 @@
export interface CSpellApplicationOptions extends BaseOptions {
export interface LinterOptions extends BaseOptions {
/**
* Display verbose information
*/
Expand All @@ -23,6 +23,12 @@ export interface CSpellApplicationOptions extends BaseOptions {
* root directory, defaults to `cwd`
*/
root?: string;
/**
* Determine if files / directories starting with `.` should be part
* of the glob search.
* @default false
*/
dot?: boolean;
/**
* Show part of a line where an issue is found.
* if true, it will show the default number of characters on either side.
Expand Down

0 comments on commit fa1aa11

Please sign in to comment.