Skip to content

Commit

Permalink
(v2.0) Strict mode by default (#19)
Browse files Browse the repository at this point in the history
* tests-jest | Converts tests to jest

* tests-jest | Type fixes

* tests-jest | Fixes cli test

* ts-strict | Adds file ignoring with @ts-strict-ignore comment

* ts-strict | Adds insert ignore comment functionality

* ts-strict | Unifies cli and plugin logic

* ts-strict | Adds file updating

* ts-strict | Unifies function declarations

* ts-strict | 2.0 init

* ts-strict | Readme update 1.0

* ts-strict | Readme update migration part

* ts-strict | Updated user messages

* ts-strict | Fixes test variable names

* ts-strict | Fixes type error

* ts-strict | Updates changelog

* ts-strict | Removes updateComments tests for now

* Refactor

* Bugfixes

* comments refactor

* Adds pluralization and cli unit tests

* Adds process exit expects to tsc-strict root files

* Fixes failing tests

* e2e fixture refactor

* typo fixes

* typo fixes

* cli structure refactor

* final test addition

* Fixes e2e test name

* fixes bin scripts paths

Co-authored-by: Kamil Krysiak <[email protected]>
  • Loading branch information
kamkry and Kamil Krysiak authored Jan 24, 2022
1 parent 268d9f0 commit 8ed4e01
Show file tree
Hide file tree
Showing 69 changed files with 5,539 additions and 17,352 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
node_modules
dist
.idea
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,13 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project
adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [2.0.0] - 2021-29-11

### Changed
- Strict by default without `@ts-strict` comment
- Ignores file with `@ts-strict-ignore` comment
- Migration tool `update-strict-comments` which updates comments in files which contain at least 1 strict error

## [1.1.2] - 2021-06-15

### Added
Expand Down
97 changes: 20 additions & 77 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,16 @@

Typescript plugin that allows turning on strict mode in specific files or directories.

## Do i need this plugin?
This plugin was created for bigger repositories that want to incorporate typescript strict mode, but project is so big that refactoring everything would take ages. This plugin allows user to simply put `//@ts-strict` comment to a top of a file and turn a strict mode to that file. If needed, strict mode can be turned on to directories too.
Plugins in general doesn't work in compile time. They will show errors in your IDE but they won't appear during compilation.
To check strict errors in marked files you can use our script `tsc-strict`.
This command line tool is created to check for files that should be checked with strict rules in compilation time.
It finds all files with `//@ts-strict` comment and files specified in `paths` parameter and checks for strict typescript errors only for that files.
## Do I need this plugin?
`typescript-strict-plugin` was created mainly for existing projects that want to incorporate typescript strict mode, but project is so big that refactoring everything would take ages.


Our plugin allows adding strict mode to a TypeScript project without fixing all the errors at once. By adding `//@ts-strict-ignore` comment at the top of a file, its whole content will be removed from strict type checking. To ease migrating a project to use this plugin, you can use `tsc-strict --updateComment` script, which adds the ignore comment to all files that contain at least one strict error.


TypeScript plugins don't work at compile-time. They will show errors in your IDE, but they won't appear during compilation.
To check strict errors in marked files you can use `tsc-strict` script. This command line tool is created to check for files that should be checked with strict rules in compilation time.
It finds all relevant files and checks for strict typescript errors only for that files.
Therefore, we have strict errors inside our files and during build time.


Expand All @@ -21,7 +25,7 @@ or yarn
```bash
yarn add -D typescript-strict-plugin
```
and add plugin to your `tsconfig.json`:
add plugin to your `tsconfig.json`:
```json
{
"compilerOptions": {
Expand All @@ -35,10 +39,14 @@ and add plugin to your `tsconfig.json`:
}
}
```
That's it! You should be able to use `@ts-strict` comment to strictly check your files.
and run the migration script
```
tsc-strict --updateComments
```
That's it! You should be able to see strict typechecking in files without the `@ts-strict-ignore` comment. To make these files strict too, just remove its' ignore comments.

## Configuration
Plugin takes one extra non-mandatory argument `paths` that is an array of relative or absolute paths of directories that should be included.
Plugin takes one extra non-mandatory argument `paths` that is an array of relative or absolute paths of directories that should be included. To add strict mode to files from ignored paths you can insert `//@ts-strict` comment.
```json
{
"compilerOptions": {
Expand Down Expand Up @@ -82,75 +90,10 @@ yarn tsc-strict --strictNullChecks false
would not check for the strict null check in your files. The `tsc-strict` accepts all the arguments that regular `tsc` command
accepts.

## Examples
Let's consider this type and a variable
```typescript
interface TestType {
bar: string;
}

const foo: TestType | undefined = undefined;
```
1. No `paths` argument
With `tsconfig.json` like this:
```json
{
"compilerOptions": {
...
"strict": false,
"plugins": [
{
"name": "typescript-strict-plugin"
}
]
}
}
```
Typescript will produce errors:
```typescript
//@ts-strict
...
const boo = foo.bar; // TS2532: Object is possibly 'undefined'.
```
Or not, depending on whether we used `ts-strict` or not:
```typescript
//no strict comment here
...
const boo = foo.bar; // no error here
```

2. With `paths` argument
With `tsconfig.json` like this:
```json
{
"compilerOptions": {
...
"strict": false,
"plugins": [
{
"name": "typescript-strict-plugin",
"path": "./src"
}
]
}
}
```
If file is in the directory typescript will produce errors even if `ts-strict` comment is not in the file :
```typescript
// ./src/index.ts
const boo = foo.bar; // TS2532: Object is possibly 'undefined'.
```
If file is not in the diretory there will be no error
```typescript
// ./lib/index.ts
const boo = foo.bar; // no error here
## Migrating to v2
Because of difficulties with migrating large projects to strict mode with original `//@ts-strict` comment, we've taken an another approach. Now in version 2.0+ typescript files are strict by default, and to ignore a file, you can use special `//@ts-strict-ignore` comment. It allows to have strict mode in newly created files without remembering about adding strict comment at the top of it. With version 2.0 script `tsc-strict` comes with a new flag, which detects all files with at least one strict error and adds the ignore comment to ease the migration. To update from v1 to v2, you just need to run:
```
If file is not in the diretory but there is `ts-strict` file will be check with strict mode:
```typescript
// ./lib/index.ts
//@ts-strict
...
const boo = foo.bar; // TS2532: Object is possibly 'undefined'.
tsc-strict --updateComments
```

## Testing the plugin
Expand Down
4 changes: 4 additions & 0 deletions e2e/fixtures/default-config/ignored.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
// @ts-strict-ignore
const text: string = null;

export {};
3 changes: 3 additions & 0 deletions e2e/fixtures/default-config/strict.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
const text: string = null;

export {};
17 changes: 17 additions & 0 deletions e2e/fixtures/default-config/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"compilerOptions": {
"target": "es2018",
"module": "commonjs",
"lib": ["es2018"],
"baseUrl": "./",
"outDir": "./dist",
"strict": false,
"esModuleInterop": true,
"noImplicitAny": true,
"plugins": [
{
"name": "../../dist/plugin"
}
]
}
}
3 changes: 3 additions & 0 deletions e2e/fixtures/path-config/.vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"typescript.tsdk": "node_modules/typescript/lib"
}
3 changes: 3 additions & 0 deletions e2e/fixtures/path-config/excluded/excluded.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
const text: string = null;

export {};
3 changes: 3 additions & 0 deletions e2e/fixtures/path-config/excluded/excluded2.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
const text: string = null;

export {};
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
// @ts-strict
const text: string = null;

export {};
3 changes: 3 additions & 0 deletions e2e/fixtures/path-config/included/included.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
const text: string = null;

export {};
3 changes: 3 additions & 0 deletions e2e/fixtures/path-config/included/included2.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
const text: string = null;

export {};
File renamed without changes.
20 changes: 20 additions & 0 deletions e2e/fixtures/paths.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import path from 'path';

export const fixtureWithDefaultConfig = {
projectPath: path.resolve(__dirname, 'default-config'),
filePaths: {
strict: 'strict.ts',
ignored: 'ignored.ts',
},
};

export const fixtureWithPathConfig = {
projectPath: path.resolve(__dirname, 'path-config'),
filePaths: {
included: 'included/included.ts',
included2: 'included/included2.ts',
excluded: 'excluded/excluded.ts',
excluded2: 'excluded/excluded2.ts',
excludedWithStrictComment: 'excluded/excludedWithStrictComment.ts',
},
};
15 changes: 15 additions & 0 deletions e2e/plugin/multipleFile.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { getMultipleDiagnostics } from './utils/getMultipleDiagnostics';
import { fixtureWithDefaultConfig } from '../fixtures/paths';

it('should show errors only on file with strict comment', async () => {
// given
const { projectPath, filePaths } = fixtureWithDefaultConfig;
const fileList = [filePaths.strict, filePaths.ignored];

// when
const diagnostics = await getMultipleDiagnostics(projectPath, fileList);

// then
expect(diagnostics[0]).toHaveLength(1);
expect(diagnostics[1]).toHaveLength(0);
});
48 changes: 48 additions & 0 deletions e2e/plugin/singleFile.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { getDiagnostics } from './utils/getDiagnostics';
import { fixtureWithDefaultConfig, fixtureWithPathConfig } from '../fixtures/paths';

describe('single file diagnostics', () => {
it('should enable strict mode by default in project without config', async () => {
// given
const { projectPath, filePaths } = fixtureWithDefaultConfig;

// when
const diagnostics = await getDiagnostics(projectPath, filePaths.strict);

// then
expect(diagnostics).toHaveLength(1);
});

it('should not enable strict mode in ignored file', async () => {
// given
const { projectPath, filePaths } = fixtureWithDefaultConfig;

// when
const diagnostics = await getDiagnostics(projectPath, filePaths.ignored);

// then
expect(diagnostics).toHaveLength(0);
});

it('should not enable strict mode when file is not on path', async () => {
// given
const { projectPath, filePaths } = fixtureWithPathConfig;

// when
const diagnostics = await getDiagnostics(projectPath, filePaths.excluded);

// then
expect(diagnostics).toHaveLength(0);
});

it('should enable strict mode when file is not on path and contains strict comment', async () => {
// given
const { projectPath, filePaths } = fixtureWithPathConfig;

// when
const diagnostics = await getDiagnostics(projectPath, filePaths.excludedWithStrictComment);

// then
expect(diagnostics).toHaveLength(1);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,9 @@ export class TSServer {
this._responseCommandEmitter = new EventEmitter();
const tsserverPath = require.resolve('typescript/lib/tsserver');

// to create ts log from tests
// process.env['TSS_LOG'] = '-logToFile true -file /path/typescript-strict-plugin/log1.txt -level verbose';
const server = fork(tsserverPath, {
cwd: join(__dirname, '../project-fixture/src'),
stdio: ['pipe', 'pipe', 'pipe', 'ipc'],
// env: { TSS_LOG: '-logToFile true -file ./ts.log -level verbose' }, // creates tsserver log from tests
});
this._exitPromise = new Promise((resolve, reject) => {
server.on('exit', (code: string) => resolve(code));
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
import { ServerResponse, TSServer } from './fixtures/lang-server';
import path from 'path';
import { ServerResponse, TSServer } from './TSServer';
import path, { resolve } from 'path';
import { readFileSync } from 'fs';

function findResponse(responses: ServerResponse[], eventName: string) {
return responses.find((response) => response.event === eventName);
}

export async function getDiagnostics(fileContent: string, fileName = 'src/notOnPath.ts') {
export async function getDiagnostics(projectPath: string, filePath: string) {
const server = new TSServer();

const file = path.resolve(__dirname, 'project-fixture', fileName);
const file = resolve(projectPath, filePath);

const fileContent = readFileSync(file, 'utf-8');

server.send({ command: 'open', arguments: { file, fileContent, scriptKindName: 'TS' } });

Expand All @@ -21,5 +24,6 @@ export async function getDiagnostics(fileContent: string, fileName = 'src/notOnP
await server.close();

const semanticDiagEvent = findResponse(server.responses, 'semanticDiag');

return semanticDiagEvent?.body.diagnostics;
}
Original file line number Diff line number Diff line change
@@ -1,24 +1,23 @@
import { ServerResponse, TSServer } from './fixtures/lang-server';
import { ServerResponse, TSServer } from './TSServer';
import path from 'path';

interface FileInfo {
fileContent: string;
fileName: string;
}
import * as fs from 'fs';

function findResponses(responses: ServerResponse[], eventName: string) {
return responses.filter((response) => response.event === eventName);
}

export async function getMultipleDiagnostics(fileInfoList: FileInfo[], rootPath: string) {
export async function getMultipleDiagnostics(projectPath: string, filePaths: string[]) {
const server = new TSServer();

const openFiles = fileInfoList.map((fileInfo) => ({
file: path.resolve(rootPath, fileInfo.fileName),
fileContent: fileInfo.fileContent,
projectRootPath: rootPath,
scriptKindName: 'TS',
}));
const openFiles = filePaths.map((filePath) => {
const file = path.resolve(projectPath, filePath);
return {
file,
fileContent: fs.readFileSync(file, 'utf-8'),
projectRootPath: projectPath,
scriptKindName: 'TS',
};
});

const openFilePaths = openFiles.map((file) => file.file);

Expand Down
Loading

0 comments on commit 8ed4e01

Please sign in to comment.