Skip to content

Commit

Permalink
More docs enhancements (palantir#2500)
Browse files Browse the repository at this point in the history
  • Loading branch information
adidahiya authored Apr 5, 2017
1 parent fd88f97 commit 85b0bd7
Show file tree
Hide file tree
Showing 26 changed files with 99 additions and 83 deletions.
4 changes: 1 addition & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,7 @@
TSLint
======

An extensible linter for the [TypeScript language](https://github.com/Microsoft/TypeScript).

TSLint is a static analysis tool that checks TypeScript code for readability, maintainability, and functionality errors. It is widely supported across modern editors & build systems and can be customized with your own lint rules, configurations, and formatters.
TSLint is an extensible static analysis tool that checks [TypeScript](https://github.com/Microsoft/TypeScript) code for readability, maintainability, and functionality errors. It is widely supported across modern editors & build systems and can be customized with your own lint rules, configurations, and formatters.

TSLint supports:

Expand Down
8 changes: 4 additions & 4 deletions docs/_data/develop_sidebar.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,12 @@
"url": "/custom-rules/"
},
{
"title": "Custom Rules - Performance",
"url": "/custom-rules/performance.html"
"title": "Performance Tips",
"url": "/custom-rules/performance-tips"
},
{
"title": "Custom Rules - Migrating to AbstractWalker",
"url": "/custom-rules/migration.html"
"title": "Walker Design",
"url": "/custom-rules/walker-design"
},
{
"title": "Custom Formatters",
Expand Down
8 changes: 2 additions & 6 deletions docs/_data/usage_sidebar.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@
"url": "/library/"
},
{
"title": "tslint.json",
"url": "/tslint-json/"
"title": "Configuration",
"url": "/configuration/"
},
{
"title": "Rule Flags",
Expand All @@ -22,10 +22,6 @@
"title": "Type Checking",
"url": "/type-checking/"
},
{
"title": "Custom Rules",
"url": "/custom-rules/"
},
{
"title": "Third-Party Tools",
"url": "/third-party-tools/"
Expand Down
13 changes: 3 additions & 10 deletions docs/develop/contributing/index.md
Original file line number Diff line number Diff line change
@@ -1,25 +1,18 @@
---
layout: page
title: Contributing
title: Contributing to TSLint
permalink: /develop/contributing/
---

To develop TSLint simply clone the repository and install dependencies:
To develop TSLint, clone the repository and install its dependencies:

```bash
git clone [email protected]:palantir/tslint.git --config core.autocrlf=input --config core.eol=lf
yarn install
yarn
yarn compile
yarn test
```

#### `next` branch

The [`next` branch of the TSLint repo](https://github.com/palantir/tslint/tree/next) tracks the latest TypeScript
compiler as a `devDependency`. This allows you to develop the linter and its rules against the latest features of the
language. Releases from this branch are published to NPM with the `next` dist-tag, so you can get the latest dev
version of TSLint via `npm install tslint@next`.

#### Running a specific test

You can test a specific test by using the `--test` command line parameter followed by your test directory. For example:
Expand Down
2 changes: 1 addition & 1 deletion docs/develop/custom-rules/index.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
---
title: Custom Rules
title: Developing TSLint rules
layout: page
permalink: "/develop/custom-rules/"
---
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,15 @@
## Performance tips
---
title: TSLint performance tips
layout: page
permalink: "/develop/custom-rules/performance"
---

As TSLint has matured, we have developed some best practices for making rules run faster. TSLint 5.0 was a particularly
significant release that featured many performance enhancements using the below tips (some codebases experienced lint times
cut in half).

### Use the TypeChecker only when needed

The TypeChecker is a really mighty tool, but that comes with a cost. To create a TypeChecker the Program first has to locate, read, parse and bind all SourceFiles referenced.
To avoid that cost, try to avoid the TypeChecker where possible.

Expand All @@ -9,20 +18,23 @@ But there's another way: call `.getChildren()` on the FunctionDeclaration and se
Those nodes will precede other nodes in the array.

### Avoid walking the AST if possible

Some rules work directly on the content of the source file.

`max-file-line-count` and `linebreak-style` don't need to walk the AST at all.
For example, `max-file-line-count` and `linebreak-style` don't need to walk the AST at all.

Other rules define exceptions: `no-consecutive-blank-lines` ignores template strings.
To optimize for the best case, this rule can first look for failures in the source.
If and only if there are any failures, walk the AST to find the location of all template strings to filter the failures.

### Implement your own walking algorithm

Convenience comes with a price. When using `SyntaxWalker` or any subclass thereof like `RuleWalker` you pay the price for the
big switch statement in `visitNode` which then calls the appropriate `visitXXX` method for **every** node in the AST, even if you don't use them.

Use `AbstractWalker` instead and implement the `walk` method to fit the needs of your rule.
It's as simple as this:

```ts
class MyWalker extends Lint.AbstractWalker<MyOptionsType> {
public walk(sourceFile: ts.SourceFile) {
Expand All @@ -38,16 +50,17 @@ class MyWalker extends Lint.AbstractWalker<MyOptionsType> {
```
### Don't walk the whole AST if possible
__The Spec is your friend:__
The language spec defines where each statement can occur. If you are interested in `import` statements for example, you only need to search
in `sourceFile.statements` and nested `NamespaceDeclaration`s.
__Don't visit AST branches you're not interested in:__
For example `no-null-keyword` creates no failure if the null keyword is part of another type.
__The language specification is your friend__:
The language spec defines where each statement can occur.
For example, if you are interested in `import` statements, you only need to search in `sourceFile.statements` and nested `NamespaceDeclaration`s.
__Don't visit AST branches you're not interested in__:
For example, `no-null-keyword` creates no failure if the null keyword is part of another type.
There are two ways to achieve this:
* Recurse into the AST until you find a token of kind NullKeyword and then walk up its parent chain to find out if it is part of a type node
* Stop recursing deeper into that branch as soon as you hit a type node (preferred)
* Recurse into the AST until you find a token of kind NullKeyword and then walk up its parent chain to find out if it is part of a type node.
* Stop recursing deeper into that branch as soon as you hit a type node (preferred).
### Avoid frequently creating one-time closures in the hot path
```ts
Expand All @@ -62,17 +75,20 @@ class SomeClass {
}
}
```
Instead use the same closure for every call like the example in [Implement your own walking algorithm](#Implement_your_own_walking_algorithm).
Instead use the same closure for every call like the example above in __Implement your own walking algorithm__.
### Create small specialized functions / methods
Instead of stuffing the whole logic in a single closure, consider splitting it up into smaller functions or methods.
Each function should handle similar kinds of nodes. Don't worry too much about the function call, since V8 eventually inlines the function
if possible.
The AST nodes have different properties, therefore they have a different hidden class in V8. A function can only be optimized for a certain
amount of different hidden classes. Above that threshold the function will be deoptimized and is never optimized again.
### Pass the optional `sourceFile` parameter
### Supply the optional sourceFile parameter
There are serveral methods that have an optional parameter `sourceFile`. Don't omit this parameter if you care for performance.
If ommitted, typescript needs to walk up the node's parent chain until it reaches the SourceFile. This *can* be quite costly when done
frequently on deeply nested nodes.
Expand All @@ -86,14 +102,16 @@ Some examples:
* `node.getFirstToken()`
* `node.getLeadingTriviaWidth()`
### Avoid excessive calls to `node.getStart()`, `node.getWidth()` and `node.getText()`
### Avoid excessive calls to node.getStart(), node.getWidth() and node.getText()
`node.getStart()` scans the source to skip all the leading trivia. Although barely noticeable, this operation is not for free.
If you need the start position of a node more than once per function, consider caching it.
`node.getWidth()` is most of the time used together with `node.getStart()` to get the node's span. Internally it uses `node.getEnd() - node.getStart()` which effectively doubles the calls to `node.getStart()`. Consider using `node.getEnd()` instead and calculate the width yourself if necessary.
`node.getText()` calculates the start of the node and returns a substring until the end of the token.
Most of the time this not needed, because this substring is already contained in the node.
```ts
declare node: ts.Identifier;
node.getText() === node.text; // prefer node.text where available
Expand All @@ -104,10 +122,12 @@ you can use `node.getEnd() - width` to calculate the node's start.
`node.getEnd()` is effectively for free as it only returns the `end` property. This way you avoid the cost of skipping leading trivia.
### Make use of tail calls
Tail calls are function or method calls at the end of the control flow of a function. It's only a tail call if the return value of that call
is directly returned unchanged. Browsers can optimize this pattern for performance.
Further optimization is specced in ES2015 as "Proper Tail Calls".
With proper tail calls the browser reuses the stack frame of the current function. When done right this allows for infinite recursion.
```ts
function foo() {
if (condition)
Expand All @@ -116,6 +136,7 @@ function foo() {
return foo() + 1; // no tail call, return value is modified
return baz(); // tail call
}

function bas() {
if (cond)
return someGlobalVariable = bar(); // no tail call, return value is stored in value before it is returned
Expand All @@ -124,6 +145,7 @@ function bas() {
```
### Typeguards
Typeguard functions are very small by default. These functions will be inlined into the containing function.
After inlining you no longer pay the cost of the function call.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,19 @@
## Using `WalkContext<T>` and `Rule#applyWithFunction`
---
title: Designing rule walkers
layout: page
permalink: "/develop/custom-rules/walker-design"
---

## Using WalkContext and applyWithFunction

If you have a rule with a pretty simple implementation, you don't need to declare a class which extends the `Walker` class. Instead, you can define a callback function that accepts following argument:

- `ctx: WalkContext<T>`: An object containing rule information, an object `options: T` containing the parsed rule arguments, the `ts.sourceFile` object, and functions for adding failures

Use this callback as an argument to `applyWithFunction`. You can also pass your parsed rule arguments as optional 3rd parameter.

Let's look at `no-null-keyword` as an example:

```ts
import * as ts from "typescript";
import * as Lint from "tslint";
Expand Down Expand Up @@ -45,9 +53,10 @@ function walk(ctx: Lint.WalkContext<void>) {
}
```

## Using `AbstractWalker<T>`
## Using AbstractWalker

If your rule implementation is a bit more involved than the above example, you can also implement it as a class.
Simply extend `AbstractWalker` and implement the `walk` method.
Simply extend `AbstractWalker<T>` and implement the `walk` method.

```ts
import * as ts from "typescript";
Expand Down Expand Up @@ -97,6 +106,7 @@ class NoMagicNumbersWalker extends Lint.AbstractWalker<Set<string>> {
```

## Migrating from `RuleWalker` to `AbstractWalker`

The main difference between `RuleWalker` and `AbstractWalker` is that you need to implement the AST recursion yourself. But why would you want to do that?
__Performance!__ `RuleWalker` wants to be "one walker to rule them all" (pun intended). It's easy to use but that convenience
makes it slow by default. When implementing the walking yourself, you only need to do as much work as needed.
Expand All @@ -120,5 +130,3 @@ This table describes the equivalent methods between the two classes:
`this.getFailures()` | is available to be compatible, but prefer `this.failures`
`this.skip()` | just don't use it, it's a noop
`this.getRuleName()` | `this.ruleName`


2 changes: 1 addition & 1 deletion docs/formatters/checkstyle/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,5 @@
</checkstyle>
consumer: machine
layout: formatter
title: 'Formatter: checkstyle'
title: 'TSLint formatter: checkstyle'
---
2 changes: 1 addition & 1 deletion docs/formatters/codeFrame/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -18,5 +18,5 @@
26 | public render() {
consumer: human
layout: formatter
title: 'Formatter: codeFrame'
title: 'TSLint formatter: codeFrame'
---
2 changes: 1 addition & 1 deletion docs/formatters/filesList/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,5 @@
sample: directory/myFile.ts
consumer: machine
layout: formatter
title: 'Formatter: filesList'
title: 'TSLint formatter: filesList'
---
6 changes: 3 additions & 3 deletions docs/formatters/index.md
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
---
layout: page
title: Formatters
title: TSLint core formatters
permalink: /formatters/
menu: main
order: 2
---

_Formatters_ allow for transformation of lint results into various forms before outputting to stdout or a file.
Lint _formatters_ allow for transformation of lint results into various forms before outputting to stdout or a file.

### Built-in formatters

{% assign formatters = site.data.formatters | sort: "name" %}
{% for formatter in formatters %}
* [{{formatter.formatterName}}]({{formatter.formatterName}})</a> - {{formatter.description | markdownify | remove:"<p>" | remove: "</p>"}}
* [{{formatter.formatterName}}]({{formatter.formatterName}}) - {{formatter.description | markdownify | remove:"<p>" | remove: "</p>"}}
{% endfor %}
2 changes: 1 addition & 1 deletion docs/formatters/json/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -27,5 +27,5 @@
]
consumer: machine
layout: formatter
title: 'Formatter: json'
title: 'TSLint formatter: json'
---
2 changes: 1 addition & 1 deletion docs/formatters/msbuild/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,5 @@
sample: 'myFile.ts(1,14): warning: Missing semicolon'
consumer: machine
layout: formatter
title: 'Formatter: msbuild'
title: 'TSLint formatter: msbuild'
---
2 changes: 1 addition & 1 deletion docs/formatters/pmd/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,5 @@
</pmd>
consumer: machine
layout: formatter
title: 'Formatter: pmd'
title: 'TSLint formatter: pmd'
---
2 changes: 1 addition & 1 deletion docs/formatters/prose/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,5 @@
sample: 'ERROR: myFile.ts[1, 14]: Missing semicolon'
consumer: human
layout: formatter
title: 'Formatter: prose'
title: 'TSLint formatter: prose'
---
2 changes: 1 addition & 1 deletion docs/formatters/stylish/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,5 @@
1:14 semicolon Missing semicolon
consumer: human
layout: formatter
title: 'Formatter: stylish'
title: 'TSLint formatter: stylish'
---
2 changes: 1 addition & 1 deletion docs/formatters/tap/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -20,5 +20,5 @@
...
consumer: machine
layout: formatter
title: 'Formatter: tap'
title: 'TSLint formatter: tap'
---
2 changes: 1 addition & 1 deletion docs/formatters/verbose/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,5 @@
sample: 'ERROR: (semicolon) myFile.ts[1, 14]: Missing semicolon'
consumer: human
layout: formatter
title: 'Formatter: verbose'
title: 'TSLint formatter: verbose'
---
2 changes: 1 addition & 1 deletion docs/formatters/vso/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,5 @@
sample: '##vso[task.logissue type=warning;sourcepath=myFile.ts;linenumber=1;columnnumber=14;code=semicolon;]Missing semicolon'
consumer: machine
layout: formatter
title: 'Formatter: vso'
title: 'TSLint formatter: vso'
---
2 changes: 1 addition & 1 deletion docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ layout: default
subtitle: An extensible linter for the TypeScript language.
---

TSLint is a static analysis tool that checks your [TypeScript][0] code for readability, maintainability, and functionality errors. It is widely supported across modern editors & build systems and can be customized with your own lint rules, configurations, and formatters.
TSLint is an extensible static analysis tool that checks [TypeScript][0] code for readability, maintainability, and functionality errors. It is widely supported across modern editors & build systems and can be customized with your own lint rules, configurations, and formatters.

## Quick start

Expand Down
Loading

0 comments on commit 85b0bd7

Please sign in to comment.