Skip to content

Commit

Permalink
fix: Update gitignore README and normalize roots (#1832)
Browse files Browse the repository at this point in the history
* fix: Update `cspell-gitignore` README
* fix: normalize `gitignoreRoot`s
  • Loading branch information
Jason3S committed Oct 5, 2021
1 parent ca7d011 commit b9df331
Show file tree
Hide file tree
Showing 4 changed files with 93 additions and 18 deletions.
35 changes: 27 additions & 8 deletions packages/cspell-gitignore/README.md
Original file line number Diff line number Diff line change
@@ -1,17 +1,36 @@
# `cspell-glob`
# `cspell-gitignore`

A simple library for checking filenames against a set of glob rules. It attempts to emulate the `.gitignore` rules.
A library to assist reading and filtering out files matching glob patterns found in `.gitignore` files.

## Purpose
## Install

The purpose behind this library is a bit different than the other glob matchers.
The goal here is to see if a file name matches a glob, not to find files that match globs.
This library doesn't do any file i/o. It uses [micromatch](https://github.com/micromatch/micromatch#readme) under the hood for the actual matching.
```sh
npm install -S cspell-gitignore
```

## Usage

```ts
import { GitIgnore } from 'cspell-gitignore';

// ...

const gitIgnore = new GitIgnore();

const allFiles = glob('**');

const files = await gitIgnore.filterOutIgnored(allFiles);
```
const cspellGlob = require('cspell-glob');

// TODO: DEMONSTRATE API
## Logic

- For each file, search for the `.gitignore` files in the directory hierarchy.
- Ignore any files that match the globs found in the `.gitignore` files.

The `.gitignore` globs are evaluated from highest to lowest, matching the `git` behavior.

To prevent searching higher in the directory hierarchy, specify roots:

```ts
const gitIgnore = new GitIgnore([process.cwd()]);
```
4 changes: 3 additions & 1 deletion packages/cspell-gitignore/src/GitIgnore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,15 @@ import { GitIgnoreHierarchy, loadGitIgnore } from './GitIgnoreFile';
export class GitIgnore {
private resolvedGitIgnoreHierarchies = new Map<string, GitIgnoreHierarchy>();
private knownGitIgnoreHierarchies = new Map<string, Promise<GitIgnoreHierarchy>>();
readonly roots: string[];

/**
* @param roots - (search roots) an optional array of root paths to prevent searching for `.gitignore` files above the root.
* If a file is under multiple roots, the closest root will apply. If a file is not under any root, then
* the search for `.gitignore` will go all the way to the system root of the file.
*/
constructor(readonly roots: string[] = []) {
constructor(roots: string[] = []) {
this.roots = roots.map((a) => path.resolve(a));
this.roots.sort((a, b) => a.length - b.length);
Object.freeze(this.roots);
}
Expand Down
51 changes: 45 additions & 6 deletions packages/cspell-lib/src/Settings/CSpellSettingsServer.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -185,10 +185,15 @@ describe('Validate CSpellSettingsServer', () => {
expect(setting4).toBe(setting3);
});

test('tests loading a missing cSpell.json file', () => {
const filename = path.join(__dirname, '..', '..', 'cspell.config.json');
const settings = readSettings(filename);
expect(settings.__importRef?.filename).toBe(path.resolve(filename));
test.each`
filename | relativeTo | refFilename
${r('../../cspell.config.json')} | ${undefined} | ${r('../../cspell.config.json')}
${r('../../cspell.config.json')} | ${__dirname} | ${r('../../cspell.config.json')}
${'@cspell/cspell-bundled-dicts/cspell-default.json'} | ${__dirname} | ${require.resolve('@cspell/cspell-bundled-dicts/cspell-default.json')}
${'@cspell/cspell-bundled-dicts/cspell-default.json'} | ${undefined} | ${require.resolve('@cspell/cspell-bundled-dicts/cspell-default.json')}
`('tests readSettings $filename $relativeTo', ({ filename, relativeTo, refFilename }) => {
const settings = readSettings(filename, relativeTo);
expect(settings.__importRef?.filename).toBe(refFilename);
expect(settings.__importRef?.error).toBeUndefined();
expect(settings.import).toBeUndefined();
});
Expand Down Expand Up @@ -392,8 +397,12 @@ describe('Validate Glob resolution', () => {
);
});

test('globs from config file (search)', async () => {
const config = await searchForConfig(__dirname);
test.each`
from
${__dirname}
${undefined}
`('globs from config file (search) $from', async ({ from }) => {
const config = await searchForConfig(from);
expect(config?.ignorePaths).toEqual(
expect.arrayContaining([
{
Expand All @@ -418,6 +427,28 @@ describe('Validate Glob resolution', () => {
])
);
});

test.each`
settings | file | expected
${{}} | ${r('cspell.json')} | ${oc({ name: 'Settings/cspell.json' })}
${{ gitignoreRoot: '.' }} | ${r('cspell.json')} | ${oc({ name: 'Settings/cspell.json', gitignoreRoot: [__dirname] })}
${{ gitignoreRoot: '..' }} | ${r('cspell.json')} | ${oc({ gitignoreRoot: [r('..')] })}
${{ gitignoreRoot: ['.', '..'] }} | ${r('cspell.json')} | ${oc({ gitignoreRoot: [r('.'), r('..')] })}
${{ reporters: ['../../README.md'] }} | ${r('cspell.json')} | ${oc({ reporters: [r('../../README.md')] })}
${{ reporters: [['../../README.md']] }} | ${r('cspell.json')} | ${oc({ reporters: [[r('../../README.md')]] })}
${{ reporters: [['../../README.md', {}]] }} | ${r('cspell.json')} | ${oc({ reporters: [[r('../../README.md'), {}]] })}
`('normalizeSettings $settings', ({ settings, file, expected }) => {
expect(normalizeSettings(settings, file, {})).toEqual(expected);
});

test.each`
settings | file | expected
${{ reporters: ['./reporter.js'] }} | ${r('cspell.json')} | ${'Not found: "./reporter.js"'}
${{ reporters: [{}] }} | ${r('cspell.json')} | ${'Invalid Reporter'}
${{ reporters: [[{}]] }} | ${r('cspell.json')} | ${'Invalid Reporter'}
`('normalizeSettings with Error $settings', ({ settings, file, expected }) => {
expect(() => normalizeSettings(settings, file, {})).toThrowError(expected);
});
});

describe('Validate search/load config files', () => {
Expand Down Expand Up @@ -596,6 +627,14 @@ describe('Validate search/load config files', () => {
});
});

function p(...parts: string[]): string {
return path.join(...parts);
}

function r(...parts: string[]): string {
return path.resolve(__dirname, p(...parts));
}

function oc<T>(v: Partial<T>): T {
return expect.objectContaining(v);
}
Expand Down
21 changes: 18 additions & 3 deletions packages/cspell-lib/src/Settings/CSpellSettingsServer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,7 @@ function normalizeSettings(
const normalizedSettingsGlobs = normalizeSettingsGlobs(settings, pathToSettingsFile);
const normalizedOverrides = normalizeOverrides(settings, pathToSettingsFile);
const normalizedReporters = normalizeReporters(settings, pathToSettingsFile);
const normalizedGitignoreRoot = normalizeGitignoreRoot(settings, pathToSettingsFile);

const imports = typeof settings.import === 'string' ? [settings.import] : settings.import || [];
const source: Source = settings.source || {
Expand All @@ -176,6 +177,7 @@ function normalizeSettings(
...normalizedSettingsGlobs,
...normalizedOverrides,
...normalizedReporters,
...normalizedGitignoreRoot,
};
if (!imports.length) {
return fileSettings;
Expand Down Expand Up @@ -781,9 +783,8 @@ function normalizeReporters(settings: NormalizeReporters, pathToSettingsFile: st
}
if (!Array.isArray(s) || typeof s[0] !== 'string') throw new Error('Invalid Reporter');
// Preserve the shape of Reporter Setting while resolving the reporter file.
const r: [string, unknown] | [string] = s;
r[0] = resolve(s[0]);
return r;
const [r, ...rest] = s;
return [resolve(r), ...rest];
}

return {
Expand All @@ -802,6 +803,20 @@ function normalizeLanguageSettings(languageSettings: LanguageSetting[] | undefin
return languageSettings.map(fixLocale);
}

type NormalizeGitignoreRoot = Pick<CSpellSettings, 'gitignoreRoot'>;

function normalizeGitignoreRoot(settings: NormalizeGitignoreRoot, pathToSettingsFile: string): NormalizeGitignoreRoot {
const { gitignoreRoot } = settings;
if (!gitignoreRoot) return {};

const dir = path.dirname(pathToSettingsFile);
const roots = Array.isArray(gitignoreRoot) ? gitignoreRoot : [gitignoreRoot];

return {
gitignoreRoot: roots.map((p) => path.resolve(dir, p)),
};
}

interface NormalizeSettingsGlobs {
globRoot?: CSpellSettings['globRoot'];
ignorePaths?: CSpellSettings['ignorePaths'];
Expand Down

0 comments on commit b9df331

Please sign in to comment.