diff --git a/docs/Gemfile.lock b/docs/Gemfile.lock new file mode 100644 index 00000000000..3eaa6478c32 --- /dev/null +++ b/docs/Gemfile.lock @@ -0,0 +1,238 @@ +GEM + remote: https://rubygems.org/ + specs: + activesupport (4.2.9) + i18n (~> 0.7) + minitest (~> 5.1) + thread_safe (~> 0.3, >= 0.3.4) + tzinfo (~> 1.1) + addressable (2.5.2) + public_suffix (>= 2.0.2, < 4.0) + coffee-script (2.4.1) + coffee-script-source + execjs + coffee-script-source (1.11.1) + colorator (1.1.0) + commonmarker (0.17.7.1) + ruby-enum (~> 0.5) + concurrent-ruby (1.0.5) + ethon (0.11.0) + ffi (>= 1.3.0) + execjs (2.7.0) + faraday (0.13.1) + multipart-post (>= 1.2, < 3) + ffi (1.9.18) + forwardable-extended (2.6.0) + gemoji (3.0.0) + github-pages (172) + activesupport (= 4.2.9) + github-pages-health-check (= 1.3.5) + jekyll (= 3.6.2) + jekyll-avatar (= 0.5.0) + jekyll-coffeescript (= 1.0.2) + jekyll-commonmark-ghpages (= 0.1.3) + jekyll-default-layout (= 0.1.4) + jekyll-feed (= 0.9.2) + jekyll-gist (= 1.4.1) + jekyll-github-metadata (= 2.9.3) + jekyll-mentions (= 1.2.0) + jekyll-optional-front-matter (= 0.3.0) + jekyll-paginate (= 1.1.0) + jekyll-readme-index (= 0.2.0) + jekyll-redirect-from (= 0.12.1) + jekyll-relative-links (= 0.5.2) + jekyll-remote-theme (= 0.2.3) + jekyll-sass-converter (= 1.5.0) + jekyll-seo-tag (= 2.3.0) + jekyll-sitemap (= 1.1.1) + jekyll-swiss (= 0.4.0) + jekyll-theme-architect (= 0.1.0) + jekyll-theme-cayman (= 0.1.0) + jekyll-theme-dinky (= 0.1.0) + jekyll-theme-hacker (= 0.1.0) + jekyll-theme-leap-day (= 0.1.0) + jekyll-theme-merlot (= 0.1.0) + jekyll-theme-midnight (= 0.1.0) + jekyll-theme-minimal (= 0.1.0) + jekyll-theme-modernist (= 0.1.0) + jekyll-theme-primer (= 0.5.2) + jekyll-theme-slate (= 0.1.0) + jekyll-theme-tactile (= 0.1.0) + jekyll-theme-time-machine (= 0.1.0) + jekyll-titles-from-headings (= 0.5.0) + jemoji (= 0.8.1) + kramdown (= 1.14.0) + liquid (= 4.0.0) + listen (= 3.0.6) + mercenary (~> 0.3) + minima (= 2.1.1) + rouge (= 2.2.1) + terminal-table (~> 1.4) + github-pages-health-check (1.3.5) + addressable (~> 2.3) + net-dns (~> 0.8) + octokit (~> 4.0) + public_suffix (~> 2.0) + typhoeus (~> 0.7) + html-pipeline (2.7.1) + activesupport (>= 2) + nokogiri (>= 1.4) + i18n (0.9.1) + concurrent-ruby (~> 1.0) + jekyll (3.6.2) + addressable (~> 2.4) + colorator (~> 1.0) + jekyll-sass-converter (~> 1.0) + jekyll-watch (~> 1.1) + kramdown (~> 1.14) + liquid (~> 4.0) + mercenary (~> 0.3.3) + pathutil (~> 0.9) + rouge (>= 1.7, < 3) + safe_yaml (~> 1.0) + jekyll-avatar (0.5.0) + jekyll (~> 3.0) + jekyll-coffeescript (1.0.2) + coffee-script (~> 2.2) + coffee-script-source (~> 1.11.1) + jekyll-commonmark (1.1.0) + commonmarker (~> 0.14) + jekyll (>= 3.0, < 4.0) + jekyll-commonmark-ghpages (0.1.3) + commonmarker (~> 0.17.6) + jekyll-commonmark (~> 1) + rouge (~> 2) + jekyll-default-layout (0.1.4) + jekyll (~> 3.0) + jekyll-feed (0.9.2) + jekyll (~> 3.3) + jekyll-gist (1.4.1) + octokit (~> 4.2) + jekyll-github-metadata (2.9.3) + jekyll (~> 3.1) + octokit (~> 4.0, != 4.4.0) + jekyll-mentions (1.2.0) + activesupport (~> 4.0) + html-pipeline (~> 2.3) + jekyll (~> 3.0) + jekyll-optional-front-matter (0.3.0) + jekyll (~> 3.0) + jekyll-paginate (1.1.0) + jekyll-readme-index (0.2.0) + jekyll (~> 3.0) + jekyll-redirect-from (0.12.1) + jekyll (~> 3.3) + jekyll-relative-links (0.5.2) + jekyll (~> 3.3) + jekyll-remote-theme (0.2.3) + jekyll (~> 3.5) + rubyzip (>= 1.2.1, < 3.0) + typhoeus (>= 0.7, < 2.0) + jekyll-sass-converter (1.5.0) + sass (~> 3.4) + jekyll-seo-tag (2.3.0) + jekyll (~> 3.3) + jekyll-sitemap (1.1.1) + jekyll (~> 3.3) + jekyll-swiss (0.4.0) + jekyll-theme-architect (0.1.0) + jekyll (~> 3.5) + jekyll-seo-tag (~> 2.0) + jekyll-theme-cayman (0.1.0) + jekyll (~> 3.5) + jekyll-seo-tag (~> 2.0) + jekyll-theme-dinky (0.1.0) + jekyll (~> 3.5) + jekyll-seo-tag (~> 2.0) + jekyll-theme-hacker (0.1.0) + jekyll (~> 3.5) + jekyll-seo-tag (~> 2.0) + jekyll-theme-leap-day (0.1.0) + jekyll (~> 3.5) + jekyll-seo-tag (~> 2.0) + jekyll-theme-merlot (0.1.0) + jekyll (~> 3.5) + jekyll-seo-tag (~> 2.0) + jekyll-theme-midnight (0.1.0) + jekyll (~> 3.5) + jekyll-seo-tag (~> 2.0) + jekyll-theme-minimal (0.1.0) + jekyll (~> 3.5) + jekyll-seo-tag (~> 2.0) + jekyll-theme-modernist (0.1.0) + jekyll (~> 3.5) + jekyll-seo-tag (~> 2.0) + jekyll-theme-primer (0.5.2) + jekyll (~> 3.5) + jekyll-github-metadata (~> 2.9) + jekyll-seo-tag (~> 2.2) + jekyll-theme-slate (0.1.0) + jekyll (~> 3.5) + jekyll-seo-tag (~> 2.0) + jekyll-theme-tactile (0.1.0) + jekyll (~> 3.5) + jekyll-seo-tag (~> 2.0) + jekyll-theme-time-machine (0.1.0) + jekyll (~> 3.5) + jekyll-seo-tag (~> 2.0) + jekyll-titles-from-headings (0.5.0) + jekyll (~> 3.3) + jekyll-watch (1.5.1) + listen (~> 3.0) + jemoji (0.8.1) + activesupport (~> 4.0, >= 4.2.9) + gemoji (~> 3.0) + html-pipeline (~> 2.2) + jekyll (>= 3.0) + kramdown (1.14.0) + liquid (4.0.0) + listen (3.0.6) + rb-fsevent (>= 0.9.3) + rb-inotify (>= 0.9.7) + mercenary (0.3.6) + mini_portile2 (2.3.0) + minima (2.1.1) + jekyll (~> 3.3) + minitest (5.10.3) + multipart-post (2.0.0) + net-dns (0.8.0) + nokogiri (1.8.1) + mini_portile2 (~> 2.3.0) + octokit (4.8.0) + sawyer (~> 0.8.0, >= 0.5.3) + pathutil (0.16.1) + forwardable-extended (~> 2.6) + public_suffix (2.0.5) + rb-fsevent (0.10.2) + rb-inotify (0.9.10) + ffi (>= 0.5.0, < 2) + rouge (2.2.1) + ruby-enum (0.7.1) + i18n + rubyzip (1.2.1) + safe_yaml (1.0.4) + sass (3.5.4) + sass-listen (~> 4.0.0) + sass-listen (4.0.0) + rb-fsevent (~> 0.9, >= 0.9.4) + rb-inotify (~> 0.9, >= 0.9.7) + sawyer (0.8.1) + addressable (>= 2.3.5, < 2.6) + faraday (~> 0.8, < 1.0) + terminal-table (1.8.0) + unicode-display_width (~> 1.1, >= 1.1.1) + thread_safe (0.3.6) + typhoeus (0.8.0) + ethon (>= 0.8.0) + tzinfo (1.2.4) + thread_safe (~> 0.1) + unicode-display_width (1.3.0) + +PLATFORMS + ruby + +DEPENDENCIES + github-pages + +BUNDLED WITH + 1.16.1 diff --git a/docs/_layouts/rule.html b/docs/_layouts/rule.html index c24dac879c4..dbb94d88fe2 100644 --- a/docs/_layouts/rule.html +++ b/docs/_layouts/rule.html @@ -5,6 +5,7 @@ {% if page.descriptionDetails %} {{page.descriptionDetails | markdownify}} {% endif %} + {% if page.rationale %}
"{{page.ruleName}}": {{example}} @@ -39,3 +40,23 @@Schema
{{page.optionsJSON}}+ +{% if page.codeExamples %} +Code examples:
+ {% for codeExample in page.codeExamples %} +++ {% endfor %} +{% endif %} diff --git a/docs/_sass/_base.scss b/docs/_sass/_base.scss index 52e6af050e6..a1e7726d4de 100644 --- a/docs/_sass/_base.scss +++ b/docs/_sass/_base.scss @@ -173,3 +173,30 @@ figcaption { } } } + +.wrapper__code-example { + margin-bottom: 64px; + + &:last-of-type { + margin-bottom: 24px; + } + + h6.heading-fail { + // Strong red + color: #d14; + } + + .code-example { + // Light green + background-color: #f1fff1; + + &.code-example-fail { + // Light red + background-color: #fff5f5; + } + + pre, .highlight { + background: transparent; + } + } +} diff --git a/scripts/buildDocs.ts b/scripts/buildDocs.ts index 48da428a5ea..4f70d71cb32 100644 --- a/scripts/buildDocs.ts +++ b/scripts/buildDocs.ts @@ -33,19 +33,19 @@ import * as fs from "fs"; import * as glob from "glob"; -import stringify = require("json-stringify-pretty-compact"); import * as yaml from "js-yaml"; +import stringify = require("json-stringify-pretty-compact"); import * as path from "path"; import * as rimraf from "rimraf"; -import {IFormatterMetadata} from "../lib/language/formatter/formatter"; -import {IRuleMetadata} from "../lib/language/rule/rule"; +import { IFormatterMetadata } from "../lib/language/formatter/formatter"; +import { ICodeExample, IRuleMetadata } from "../lib/language/rule/rule"; type Metadata = IRuleMetadata | IFormatterMetadata; interface Documented { metadata: Metadata; -}; +} interface IDocumentation { /** @@ -71,7 +71,7 @@ interface IDocumentation { /** * Function to generate individual documentation pages. */ - pageGenerator: (metadata: any) => string; + pageGenerator(metadata: any): string; /** * Documentation subdirectory to output to. @@ -148,7 +148,7 @@ function buildSingleModuleDocumentation(documentation: IDocumentation, modulePat // tslint:disable-next-line:no-var-requires const module = require(modulePath); const DocumentedItem = module[documentation.exportName] as Documented; - if (DocumentedItem != null && DocumentedItem.metadata != null) { + if (DocumentedItem !== null && DocumentedItem.metadata !== null) { // Build the module's page. const { metadata } = DocumentedItem; const fileData = documentation.pageGenerator(metadata); @@ -195,6 +195,17 @@ function generateRuleFile(metadata: IRuleMetadata): string { typeof example === "string" ? example : stringify(example)); } + if (metadata.codeExamples) { + metadata.codeExamples = metadata.codeExamples.map((example: ICodeExample) => { + example.pass = `\`\`\`ts\n${example.pass.trim()}\n\`\`\``; + if (example.fail) { + example.fail = `\`\`\`ts\n${example.fail.trim()}\n\`\`\``; + } + example.config = `\`\`\`json\n${example.config.trim()}\n\`\`\``; + return example; + }); + } + const yamlData = generateJekyllData(metadata, "rule", "Rule", metadata.ruleName); yamlData.optionsJSON = JSON.stringify(metadata.options, undefined, 2); return `---\n${yaml.safeDump(yamlData, {lineWidth: 140} as any)}---`; diff --git a/src/language/rule/rule.ts b/src/language/rule/rule.ts index d52189eab2c..b3c7ea4a919 100644 --- a/src/language/rule/rule.ts +++ b/src/language/rule/rule.ts @@ -89,12 +89,24 @@ export interface IRuleMetadata { * Whether or not the rule use for TypeScript only. If `false`, this rule may be used with .js files. */ typescriptOnly: boolean; + + /** + * Examples demonstrating what the lint rule will pass and fail + */ + codeExamples?: ICodeExample[]; } export type RuleType = "functionality" | "maintainability" | "style" | "typescript"; export type RuleSeverity = "warning" | "error" | "off"; +export interface ICodeExample { + config: string; + description: string; + pass: string; + fail?: string; +} + export interface IOptions { ruleArguments: any[]; ruleSeverity: RuleSeverity; diff --git a/src/rules/code-examples/curly.examples.ts b/src/rules/code-examples/curly.examples.ts new file mode 100644 index 00000000000..795bf3c38bc --- /dev/null +++ b/src/rules/code-examples/curly.examples.ts @@ -0,0 +1,72 @@ +/** + * @license + * Copyright 2013 Palantir Technologies, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import * as Lint from "../../index"; + +// tslint:disable: object-literal-sort-keys +export const codeExamples = [ + { + description: "Require curly braces whenever possible (default)", + config: Lint.Utils.dedent` + "rules": { "curly": true } + `, + pass: Lint.Utils.dedent` + if (x > 0) { + doStuff(); + } + `, + fail: Lint.Utils.dedent` + if (x > 0) + doStuff(); + + if (x > 0) doStuff(); + `, + }, + { + description: "Make an exception for single-line instances", + config: Lint.Utils.dedent` + "rules": { "curly": [true, "ignore-same-line"] } + `, + pass: Lint.Utils.dedent` + if (x > 0) doStuff(); + `, + fail: Lint.Utils.dedent` + if (x > 0) + doStuff() + `, + }, + { + description: "Error on unnecessary curly braces", + config: Lint.Utils.dedent` + "rules": { "curly": [true, "as-needed"] } + `, + pass: Lint.Utils.dedent` + if (x > 0) + doStuff(); + + if (x > 0) { + customerUpdates.push(getInfo(customerId)); + return customerUpdates; + } + `, + fail: Lint.Utils.dedent` + if (x > 0) { + doStuff(); + } + `, + }, +]; diff --git a/src/rules/curlyRule.ts b/src/rules/curlyRule.ts index 099c7ea5120..97eaac21e39 100644 --- a/src/rules/curlyRule.ts +++ b/src/rules/curlyRule.ts @@ -22,8 +22,8 @@ import { isSameLine, } from "tsutils"; import * as ts from "typescript"; - import * as Lint from "../index"; +import { codeExamples } from "./code-examples/curly.examples"; const OPTION_AS_NEEDED = "as-needed"; const OPTION_IGNORE_SAME_LINE = "ignore-same-line"; @@ -45,7 +45,7 @@ export class Rule extends Lint.Rules.AbstractRule { \`\`\` In the code above, the author almost certainly meant for both \`foo++\` and \`bar++\` - to be executed only if \`foo === bar\`. However, he forgot braces and \`bar++\` will be executed + to be executed only if \`foo === bar\`. However, they forgot braces and \`bar++\` will be executed no matter what. This rule could prevent such a mistake.`, optionsDescription: Lint.Utils.dedent` One of the following options may be provided: @@ -72,6 +72,7 @@ export class Rule extends Lint.Rules.AbstractRule { type: "functionality", typescriptOnly: false, hasFix: true, + codeExamples, }; /* tslint:enable:object-literal-sort-keys */{{ codeExample.description }}
+ {{ codeExample.config | markdownify }} +Passes
++ {{ codeExample.pass | markdownify }} ++ {% if codeExample.fail %} +Fails
++ {{ codeExample.fail | markdownify }} ++ {% endif %} +