Skip to content

Commit

Permalink
Support project references (#392)
Browse files Browse the repository at this point in the history
  • Loading branch information
ajafff authored Sep 4, 2018
1 parent ae3b392 commit 2bd6128
Show file tree
Hide file tree
Showing 71 changed files with 724 additions and 89 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import { D } from "../d";
~~~~~~ [error local/quotemark: Prefer single quotes]

export class E extends D {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export class A {}
// this fixable failure ensures we only lint this project / file once
debugger;
~~~~~~~~~ [error no-debugger: 'debugger' statements are forbidden.]
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
declare var someGlobal: "a";
~~~ [error local/quotemark: Prefer single quotes]
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { B } from "../b";
~~~~~~ [error local/quotemark: Prefer single quotes]
import { A } from "../a";
~~~~~~ [error local/quotemark: Prefer single quotes]

export class C extends B {
prop = new A();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import { D } from "../d";
~~~~~~ [error local/quotemark: Prefer single quotes]

export class E extends D {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import { X } from "./x";

// this needs two fixer runs and ensures it still resolves to 'x.d.ts' instead of 'x.ts'
new X().prop;
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { X } from "./x";
~~~~~ [error local/quotemark: Prefer single quotes]

// this needs two fixer runs and ensures it still resolves to 'x.d.ts' instead of 'x.ts'
<string><string>new X().prop;
~~~~~~~~ [error no-useless-assertion: This assertion is unnecesary as it doesn't change the type of the expression.]
~~~~~~~~ [error no-useless-assertion: This assertion is unnecesary as it doesn't change the type of the expression.]
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
namespace foo {
console.log("a:", a);
~~~~ [error local/quotemark: Prefer single quotes]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export class A {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export class A {}
// this fixable failure ensures we only lint this project / file once
debugger;
~~~~~~~~~ [error no-debugger: 'debugger' statements are forbidden.]
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
declare var someGlobal: "a";
~~~ [error local/quotemark: Prefer single quotes]
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from "./a";
~~~~~ [error local/quotemark: Prefer single quotes]
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import { A } from "../a";
~~~~~~ [error local/quotemark: Prefer single quotes]

export class B extends A {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { B } from "../b";
~~~~~~ [error local/quotemark: Prefer single quotes]
import { A } from "../a";
~~~~~~ [error local/quotemark: Prefer single quotes]

export class C extends B {
prop = new A();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import { C } from "../c";
~~~~~~ [error local/quotemark: Prefer single quotes]

export class D extends C {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import { D } from "../d";
~~~~~~ [error local/quotemark: Prefer single quotes]

export class E extends D {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
namespace foo {
export const a = "a";
~~~ [error local/quotemark: Prefer single quotes]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
namespace foo {
console.log("a:", a);
~~~~ [error local/quotemark: Prefer single quotes]
}
6 changes: 4 additions & 2 deletions packages/wotan/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ Now you can run the linter with one of the following commands depending on your
wotan -p <path/to/tsconfig.json> # lint the whole project
wotan 'src/**/*.ts' -e '**/*.d.ts' # lint all typescript files excluding declaration files
wotan --fix # lint the whole project and fix all fixable errors
wotan -p tsconfig.json -r # lint the specified project and all projects in its 'references'
```

## Available Rules
Expand Down Expand Up @@ -134,12 +135,13 @@ Sometimes you need to enable or disable a specific rule or all rules for a secti

## CLI Options

* `-m --module <name>` specifies one or more packages with DI modules to load before starting the actual linter. These modules can be used to override the default behavior.
* `-c --config <name>` specifies the configuration to use for all files instead of looking for configuration files in parent directories. This can either be a file name, the name of a node module containing a shareable config, or the name of a builtin config like `wotan:recommended`
* `-e --exclude <glob>` excludes all files that match the given glob pattern from linting. This option can be used multiple times to specify multiple patterns. For example `-e '**/*.js' -e '**/*.d.ts'`. It is recommended to wrap the glob patterns in single quotes to prevent the shell from expanding them.
* `--fix [true|false|number]` automatically fixes all fixable failures in your code and writes the result back to disk. There are some precautions to prevent overlapping fixes from destroying you code. You should however commit your changes before using this feature. Given a number it will at most use the specified number of iterations for fixing before returning the result.
* `-f --formatter <name>` the name or path of a formatter. This can either be a file name, the name of a node module contianing a formatter, or the name of a builtin formatter. Currently available builtin formatters are `json` and `stylish` (default).
* `--fix [true|false]` automatically fixes all fixable failures in your code and writes the result back to disk. There are some precautions to prevent overlapping fixes from destroying you code. You should however commit your changes before using this feature.
* `-m --module <name>` specifies one or more packages with DI modules to load before starting the actual linter. These modules can be used to override the default behavior.
* `-p --project <name>` specifies the path to the `tsconfig.json` file to use. This option is used to find all files contained in your project. It also enables rules that require type information.
* `-r --references [true|false]` enables project references. Starting from the project specified with `-p --project` or the `tsconfig.json` in the current directory it will recursively follow all `"references"` and lint those projects.
* `[...FILES]` specifies the files to lint. You can specify paths and glob patterns here.

Note that all file paths are relative to the current working directory. Therefore `**/*.ts` doesn't match `../foo.ts`.
Expand Down
14 changes: 14 additions & 0 deletions packages/wotan/src/argparse.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ export function parseGlobalOptions(options: GlobalOptions | undefined): ParsedGl
files: [],
exclude: [],
project: undefined,
references: false,
formatter: undefined,
fix: false,
extensions: undefined,
Expand All @@ -60,6 +61,7 @@ export function parseGlobalOptions(options: GlobalOptions | undefined): ParsedGl
files: expectStringOrStringArray(options, 'files') || [],
exclude: expectStringOrStringArray(options, 'exclude') || [],
project: expectStringOption(options, 'project'),
references: expectBooleanOption(options, 'references'),
formatter: expectStringOption(options, 'formatter'),
fix: expectBooleanOrNumberOption(options, 'fix'),
extensions: (expectStringOrStringArray(options, 'extensions') || []).map(sanitizeExtensionArgument),
Expand All @@ -84,6 +86,14 @@ function expectStringOption(options: GlobalOptions, option: string): string | un
log("Expected a value of type 'string' for option '%s'.", option);
return;
}
function expectBooleanOption(options: GlobalOptions, option: string): boolean {
const value = options[option];
if (typeof value === 'boolean')
return value;
if (value !== undefined)
log("Expected a value of type 'boolean' for option '%s'.", option);
return false;
}
function expectBooleanOrNumberOption(options: GlobalOptions, option: string): boolean | number {
const value = options[option];
if (typeof value === 'boolean' || typeof value === 'number')
Expand Down Expand Up @@ -117,6 +127,10 @@ function parseLintCommand<T extends CommandName.Lint | CommandName.Save>(
case '--project':
result.project = expectStringArgument(args, ++i, arg) || undefined;
break;
case '-r':
case '--references':
({index: i, argument: result.references} = parseOptionalBoolean(args, i));
break;
case '-e':
case '--exclude':
result.exclude = exclude;
Expand Down
5 changes: 4 additions & 1 deletion packages/wotan/src/commands/save.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,10 @@ class SaveCommandRunner extends AbstractCommandRunner {
}

public run({command: _command, ...config}: LintCommand) {
const newContent = format({...this.options, ...config, fix: config.fix || undefined}, Format.Yaml);
const newContent = format(
{...this.options, ...config, fix: config.fix || undefined, references: config.references || undefined},
Format.Yaml,
);
const filePath = path.join(this.directories.getCurrentDirectory(), '.fimbullinter.yaml');
if (newContent.trim() === '{}') {
try {
Expand Down
1 change: 1 addition & 0 deletions packages/wotan/src/commands/test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,7 @@ class TestCommandRunner extends AbstractCommandRunner {
exclude: [],
files: [],
project: undefined,
references: false,
extensions: undefined,
...config,
fix: false,
Expand Down
48 changes: 44 additions & 4 deletions packages/wotan/src/project-host.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import * as ts from 'typescript';
import { resolveCachedResult, hasSupportedExtension } from './utils';
import { resolveCachedResult, hasSupportedExtension, mapDefined } from './utils';
import * as path from 'path';
import { ProcessorLoader } from './services/processor-loader';
import { FileKind, CachedFileSystem } from './services/cached-file-system';
Expand Down Expand Up @@ -39,8 +39,23 @@ export class ProjectHost implements ts.CompilerHost {
public getProcessedFileInfo(fileName: string) {
return this.processedFiles.get(fileName);
}
public getDirectoryEntries(dir: string): ts.FileSystemEntries {
return resolveCachedResult(this.directoryEntries, dir, this.processDirectory);
public readDirectory(
rootDir: string,
extensions: ReadonlyArray<string>,
excludes: ReadonlyArray<string> | undefined,
includes: ReadonlyArray<string>,
depth?: number,
) {
return ts.matchFiles(
rootDir,
extensions,
excludes,
includes,
this.useCaseSensitiveFileNames(),
this.cwd,
depth,
(dir) => resolveCachedResult(this.directoryEntries, dir, this.processDirectory),
);
}
/**
* Try to find and load the configuration for a file.
Expand Down Expand Up @@ -191,6 +206,23 @@ export class ProjectHost implements ts.CompilerHost {
);
}

public createProgram(
rootNames: ReadonlyArray<string>,
options: ts.CompilerOptions,
oldProgram: ts.Program | undefined,
projectReferences: ReadonlyArray<ts.ProjectReference> | undefined,
) {
return projectReferences === undefined
? ts.createProgram(rootNames, options, this, oldProgram) // for compatibility with TypeScript@<3.0.0
: ts.createProgram({
rootNames,
options,
oldProgram,
projectReferences,
host: this,
});
}

public updateSourceFile(
sourceFile: ts.SourceFile,
program: ts.Program,
Expand All @@ -200,7 +232,15 @@ export class ProjectHost implements ts.CompilerHost {
// TODO use updateSourceFile once https://github.com/Microsoft/TypeScript/issues/26166 is resolved
sourceFile = ts.createSourceFile(sourceFile.fileName, newContent, sourceFile.languageVersion, true);
this.sourceFileCache.set(sourceFile.fileName, sourceFile);
program = ts.createProgram(program.getRootFileNames(), program.getCompilerOptions(), this, program);
const references = program.getProjectReferences && // for compatibility with TypeScript@<3.0.0
program.getProjectReferences();

program = this.createProgram(
program.getRootFileNames(),
program.getCompilerOptions(),
program,
references && mapDefined(references, (ref) => ref && {path: ref.sourceFile.fileName}),
);
return {sourceFile, program};
}

Expand Down
Loading

0 comments on commit 2bd6128

Please sign in to comment.