Skip to content

Commit

Permalink
feat: Be able to specify files to spell check within the config. (#948)
Browse files Browse the repository at this point in the history
Related to #571

## Specify `files`

Make it possible to specify which files to check in the configuration file.

A new configuration field `files` has be added:

```js
{
    // tell cspell to check all JavaScript and Markdown files.
    files: ["**/*.js", "**/*.md"]
}
```
## Commits

* feat: Be able to specify files to spell check within the config.
* dev: Use files from config in cli application
* dev: add methods to support glob normalization to a common root.
* refactor: move the methods to a more logical place.
* dev: Correct the order to load configuration files to support VS Code Ext
* dev: Normalize globs passed in on the command line.
  - Added lots of test to ensure behavior.
  - Added support for `files` to be defined in the configuration.
  - Fixed some issues related to the root.
* dev: build lists of include and exclude globs.
* dev: File normalization is now done in cspell-glob.
* dev: Use a generator to flatten the results and make them unique.
* dev: Normalize relative paths
* dev: Use a single glob.
* dev: make single glob optional
* Use 0.2 for the main cspell.json file
* Update launch.json
  • Loading branch information
Jason3S authored Feb 15, 2021
1 parent 75eb739 commit 23f7a48
Show file tree
Hide file tree
Showing 22 changed files with 926 additions and 430 deletions.
2 changes: 1 addition & 1 deletion cspell.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"version": "0.1",
"version": "0.2",
"dictionaryDefinitions": [
{
"name": "workspace",
Expand Down
11 changes: 11 additions & 0 deletions cspell.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -488,6 +488,13 @@
},
"type": "array"
},
"files": {
"description": "Glob patterns of files to be checked. Glob patterns are relative to the `globRoot` of the configuration file that defines them.",
"items": {
"$ref": "#/definitions/Glob"
},
"type": "array"
},
"flagWords": {
"description": "list of words to always be considered incorrect.",
"items": {
Expand Down Expand Up @@ -611,6 +618,10 @@
"version": {
"default": "0.2",
"description": "Configuration format version of the setting file.",
"enum": [
"0.2",
"0.1"
],
"type": "string"
},
"words": {
Expand Down
103 changes: 66 additions & 37 deletions packages/cspell-glob/src/GlobMatcher.test.ts

Large diffs are not rendered by default.

134 changes: 55 additions & 79 deletions packages/cspell-glob/src/GlobMatcher.ts
Original file line number Diff line number Diff line change
@@ -1,49 +1,58 @@
import mm = require('micromatch');
import * as Path from 'path';
import { normalizeGlobPatterns } from './globHelper';
import { PathInterface, GlobMatch, GlobPattern, GlobPatternWithRoot } from './GlobMatcherTypes';

// cspell:ignore fname

export interface PathInterface {
normalize(p: string): string;
join(...paths: string[]): string;
resolve(...paths: string[]): string;
relative(from: string, to: string): string;
isAbsolute(p: string): boolean;
sep: string;
}

export type GlobMatch = GlobMatchRule | GlobMatchNoRule;

export interface GlobMatchRule {
matched: boolean;
glob: string;
root: string;
index: number;
isNeg: boolean;
}

export interface GlobMatchNoRule {
matched: false;
}

export type GlobMatchOptions = Partial<NormalizedGlobMatchOptions>;

export type MatcherMode = 'exclude' | 'include';

interface NormalizedGlobMatchOptions {
/**
* The matcher has two modes (`include` or `exclude`) that impact how globs behave.
*
* `include` - designed for searching for file. By default it matches a sub-set of file.
* In include mode, the globs need to be more explicit to match.
* - `dot` is by default false.
* - `nested` is by default false.
*
* `exclude` - designed to emulate `.gitignore`. By default it matches a larger range of files.
* - `dot` is by default true.
* - `nested` is by default true.
*
* @default: 'exclude'
*/
mode: MatcherMode;

/**
* The default directory from which a glob is relative.
* Any globs that are not relative to the root will ignored.
* @default: process.cwd()
*/
root: string;
dot: boolean;
nodePath: PathInterface;
}

export type GlobPattern = SimpleGlobPattern | GlobPatternWithRoot | GlobPatternWithOptionalRoot;
/**
* Allows matching against directories with a leading `.`.
*
* @default: mode == 'exclude'
*/
dot: boolean;

export type SimpleGlobPattern = string;
export interface GlobPatternWithOptionalRoot {
glob: string;
root?: string;
}
/**
* Allows matching against nested directories or files without needing to add `**`
*
* @default: mode == 'exclude'
*/
nested: boolean;

export interface GlobPatternWithRoot extends GlobPatternWithOptionalRoot {
root: string;
/**
* Mostly used for testing purposes. It allows explicitly specifying `path.win32` or `path.posix`.
*
* @default: require('path')
*/
nodePath: PathInterface;
}

export class GlobMatcher {
Expand Down Expand Up @@ -82,18 +91,27 @@ export class GlobMatcher {

const options =
typeof rootOrOptions === 'string' ? { root: rootOrOptions, nodePath: _nodePath } : rootOrOptions ?? {};
const { mode = 'exclude' } = options;
const isExcludeMode = mode !== 'include';

const { root = _nodePath.resolve(), dot = false, nodePath = _nodePath } = options;
const {
root = _nodePath.resolve(),
dot = isExcludeMode,
nodePath = _nodePath,
nested = isExcludeMode,
} = options;

const normalizedRoot = nodePath.resolve(nodePath.normalize(root));
this.options = { root: normalizedRoot, dot, nodePath };
this.options = { root: normalizedRoot, dot, nodePath, nested, mode };

patterns = Array.isArray(patterns)
? patterns
: typeof patterns === 'string'
? patterns.split(/\r?\n/g)
: [patterns];
const globPatterns = patterns.map((p) => normalizeGlobPatternWithRoot(p, normalizedRoot, nodePath));
const globPatterns = normalizeGlobPatterns(patterns, this.options)
// Only keep globs that do not match the root when using exclude mode.
.filter((g) => isExcludeMode || g.root === normalizedRoot);

this.patterns = globPatterns;
this.root = normalizedRoot;
Expand Down Expand Up @@ -183,45 +201,3 @@ function buildMatcherFn(patterns: GlobPatternWithRoot[], options: NormalizedGlob
};
return fn;
}

type MutationsToSupportGitIgnore = [RegExp, string];

const mutations: MutationsToSupportGitIgnore[] = [
[/^[^/#][^/]*$/, '**/{$&,$&/**}'], // no slashes will match files names or folders
[/^\/(?!\/)/, ''], // remove leading slash to match from the root
[/\/$/, '$&**'], // if it ends in a slash, make sure matches the folder
];

export function isGlobPatternWithOptionalRoot(g: GlobPattern): g is GlobPatternWithOptionalRoot {
return typeof g !== 'string' && typeof g.glob === 'string';
}

export function isGlobPatternWithRoot(g: GlobPatternWithRoot | GlobPatternWithOptionalRoot): g is GlobPatternWithRoot {
return !!g.root;
}

function normalizePattern(pattern: string): string {
pattern = pattern.replace(/^(!!)+/, '');
const isNeg = pattern.startsWith('!');
pattern = isNeg ? pattern.slice(1) : pattern;
pattern = mutations.reduce((p, [regex, replace]) => p.replace(regex, replace), pattern);
return isNeg ? '!' + pattern : pattern;
}

function normalizeGlobPatternWithRoot(g: GlobPattern, root: string, path: PathInterface): GlobPatternWithRoot {
g = !isGlobPatternWithOptionalRoot(g)
? {
glob: g.trim(),
root,
}
: g;

const gr = isGlobPatternWithRoot(g) ? g : { ...g, root };
if (gr.root.startsWith('${cwd}')) {
gr.root = path.join(path.resolve(), gr.root.replace('${cwd}', ''));
}
gr.root = path.resolve(root, path.normalize(gr.root));
gr.glob = normalizePattern(gr.glob);

return gr;
}
55 changes: 55 additions & 0 deletions packages/cspell-glob/src/GlobMatcherTypes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
// cspell:ignore fname

export interface PathInterface {
normalize(p: string): string;
join(...paths: string[]): string;
resolve(...paths: string[]): string;
relative(from: string, to: string): string;
isAbsolute(p: string): boolean;
sep: string;
}

export type GlobMatch = GlobMatchRule | GlobMatchNoRule;

export interface GlobMatchRule {
matched: boolean;
glob: string;
root: string;
index: number;
isNeg: boolean;
}

export interface GlobMatchNoRule {
matched: false;
}

export type GlobPattern = SimpleGlobPattern | GlobPatternWithRoot | GlobPatternWithOptionalRoot;

export type SimpleGlobPattern = string;

export interface GlobPatternWithOptionalRoot {
/**
* a glob pattern
*/
glob: string;
/**
* The root from which the glob pattern is relative.
* @default: options.root
*/
root?: string;
/**
* Optional value useful for tracing which file a glob pattern was defined in.
*/
source?: string;
}

export interface GlobPatternWithRoot extends GlobPatternWithOptionalRoot {
root: string;
}

export interface GlobPatternNormalized extends GlobPatternWithRoot {
/** the original glob pattern before it was normalized */
rawGlob: string;
/** the original root */
rawRoot: string | undefined;
}
Loading

0 comments on commit 23f7a48

Please sign in to comment.