Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: Update gitignore README and normalize roots #1832

Merged
merged 2 commits into from
Oct 5, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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