diff --git a/lib/cli.js b/lib/cli.js index df06cf6958..62507d445a 100644 --- a/lib/cli.js +++ b/lib/cli.js @@ -19,6 +19,72 @@ const { default: chalk } = require('chalk'); const EXIT_CODE_ERROR = 2; +/** + * @typedef {object} CLIFlags + * @property {boolean} [cache] + * @property {string} [cacheLocation] + * @property {string | false} config + * @property {string} [configBasedir] + * @property {string} [customSyntax] + * @property {string} [printConfig] + * @property {string} [color] + * @property {string} [customFormatter] + * @property {boolean} [disableDefaultIgnores] + * @property {boolean} [fix] + * @property {string} [formatter="string"] + * @property {string} [help] + * @property {boolean} [ignoreDisables] + * @property {string} [ignorePath] + * @property {string} [ignorePattern] + * @property {string} [noColor] + * @property {string} [outputFile] + * @property {string} [stdinFilename] + * @property {boolean} [reportNeedlessDisables] + * @property {boolean} [reportInvalidScopeDisables] + * @property {number} [maxWarnings] + * @property {string | boolean} quiet + * @property {string} [syntax] + * @property {string} [version] + * @property {boolean} [allowEmptyInput] + */ + +/** + * @typedef {object} CLIOptions + * @property {any} input + * @property {any} help + * @property {any} pkg + * @property {Function} showHelp + * @property {Function} showVersion + * @property {CLIFlags} flags + */ + +/** + * @typedef {object} OptionBaseType + * @property {any} formatter + * @property {boolean} [cache] + * @property {string} [configFile] + * @property {string} [cacheLocation] + * @property {string} [customSyntax] + * @property {string} [codeFilename] + * @property {string} [configBasedir] + * @property {{ quiet?: any }} configOverrides + * @property {any} [printConfig] + * @property {any} [printConfig] + * @property {boolean} [fix] + * @property {boolean} [ignoreDisables] + * @property {any} [ignorePath] + * @property {string} [outputFile] + * @property {boolean} [reportNeedlessDisables] + * @property {boolean} [reportInvalidScopeDisables] + * @property {boolean} [disableDefaultIgnores] + * @property {number} [maxWarnings] + * @property {string} [syntax] + * @property {any} [ignorePattern] + * @property {boolean} [allowEmptyInput] + * @property {string} [files] + * @property {string} [code] + */ + /*:: type meowOptionsType = { argv?: string[], autoHelp: boolean, @@ -395,11 +461,19 @@ const meowOptions /*: meowOptionsType*/ = { }, }, pkg: require('../package.json'), + argv: /** @type {string[]} */ ([]), }; +/** + * @param {string[]} argv + * @returns {Promise} + */ module.exports = (argv /*: string[]*/) /*: Promise|void*/ => { meowOptions.argv = argv; - const cli /*: cliType*/ = meow(meowOptions); + /** @type {CLIOptions} */ + const cli /*: cliType*/ = + // @ts-ignore TODO TYPES + meow(meowOptions); const invalidOptionsMessage = checkInvalidCLIOptions(meowOptions.flags, cli.flags); @@ -418,6 +492,7 @@ module.exports = (argv /*: string[]*/) /*: Promise|void*/ => { formatter = dynamicRequire(customFormatter); } + /** @type {OptionBaseType} */ const optionsBase /*: optionBaseType*/ = { formatter, configOverrides: {}, @@ -506,16 +581,16 @@ module.exports = (argv /*: string[]*/) /*: Promise|void*/ => { optionsBase.maxWarnings = maxWarnings; } - if (cli.flags.help || cli.flags.h) { + if (cli.flags.help) { cli.showHelp(0); - return; + return Promise.resolve(); } - if (cli.flags.version || cli.flags.v) { + if (cli.flags.version) { cli.showVersion(); - return; + return Promise.resolve(); } if (cli.flags.allowEmptyInput) { @@ -523,20 +598,27 @@ module.exports = (argv /*: string[]*/) /*: Promise|void*/ => { } return Promise.resolve() - .then(() => { - // Add input/code into options - if (cli.input.length) { - return Object.assign({}, optionsBase, { - files: cli.input, - }); - } - - return getStdin().then((stdin) => - Object.assign({}, optionsBase, { - code: stdin, - }), - ); - }) + .then( + /** + * @returns {Promise} + */ + () => { + // Add input/code into options + if (cli.input.length) { + return Promise.resolve( + Object.assign({}, optionsBase, { + files: /** @type {string} */ (cli.input), + }), + ); + } + + return getStdin().then((stdin) => + Object.assign({}, optionsBase, { + code: stdin, + }), + ); + }, + ) .then((options) => { if (cli.flags.printConfig) { return printConfig(options) @@ -557,7 +639,7 @@ module.exports = (argv /*: string[]*/) /*: Promise|void*/ => { const reports = []; if (reportNeedlessDisables) { - const report = disableOptionsReportStringFormatter(linted.needlessDisables); + const report = disableOptionsReportStringFormatter(linted.needlessDisables || []); if (report) { reports.push(report); @@ -565,7 +647,7 @@ module.exports = (argv /*: string[]*/) /*: Promise|void*/ => { } if (reportInvalidScopeDisables) { - const report = disableOptionsReportStringFormatter(linted.invalidScopeDisables); + const report = disableOptionsReportStringFormatter(linted.invalidScopeDisables || []); if (report) { reports.push(report); @@ -604,6 +686,10 @@ module.exports = (argv /*: string[]*/) /*: Promise|void*/ => { }); }; +/** + * @param {{ stack: any, code: any }} err + * @returns {void} + */ function handleError(err /*: { stack: any, code: any }*/) /*: void */ { console.log(err.stack); // eslint-disable-line no-console const exitCode = typeof err.code === 'number' ? err.code : 1; diff --git a/lib/createStylelint.js b/lib/createStylelint.js index 82dcfd308d..a0bd71481f 100644 --- a/lib/createStylelint.js +++ b/lib/createStylelint.js @@ -20,7 +20,7 @@ const STOP_DIR = IS_TEST ? path.resolve(__dirname, '..') : undefined; * The stylelint "internal API" is passed among functions * so that methods on a stylelint instance can invoke * each other while sharing options and caches - * @param {import('stylelint').StylelintOptions} options + * @param {import('stylelint').StylelintStandaloneOptions} options * @returns {StylelintInternalApi} */ module.exports = function(options /*: stylelint$options*/) /*: stylelint$internalApi*/ { diff --git a/lib/formatters/stringFormatter.js b/lib/formatters/stringFormatter.js index 0c06108f7d..6edfa33e43 100644 --- a/lib/formatters/stringFormatter.js +++ b/lib/formatters/stringFormatter.js @@ -6,7 +6,7 @@ const stringWidth = require('string-width'); const symbols = require('log-symbols'); const utils = require('postcss-reporter/lib/util'); const { default: chalk } = require('chalk'); - +/** @type {import('table')} */ let table; const MARGIN_WIDTHS = 9; @@ -15,8 +15,13 @@ const levelColors = { info: 'blue', warning: 'yellow', error: 'red', + success: undefined, }; +/** + * @param {import('stylelint').StylelintResult[]} results + * @returns {string} + */ function deprecationsFormatter(results) { const allDeprecationWarnings = _.flatMap(results, 'deprecations'); const uniqueDeprecationWarnings = _.uniqBy(allDeprecationWarnings, 'text'); @@ -38,6 +43,10 @@ function deprecationsFormatter(results) { }, '\n'); } +/** + * @param {import('stylelint').StylelintResult[]} results + * @return {string} + */ function invalidOptionsFormatter(results) { const allInvalidOptionWarnings = _.flatMap(results, (r) => r.invalidOptionWarnings.map((w) => w.text), @@ -52,6 +61,10 @@ function invalidOptionsFormatter(results) { }, '\n'); } +/** + * @param {string} fromValue + * @return {string} + */ function logFrom(fromValue) { if (fromValue.charAt(0) === '<') return fromValue; @@ -61,6 +74,10 @@ function logFrom(fromValue) { .join('/'); } +/** + * @param {{[k: number]: number}} columnWidths + * @return {number} + */ function getMessageWidth(columnWidths) { if (!process.stdout.isTTY) { return columnWidths[3]; @@ -77,6 +94,11 @@ function getMessageWidth(columnWidths) { return availableWidth - (fullWidth - columnWidths[3] + MARGIN_WIDTHS); } +/** + * @param {import('stylelint').StylelintWarning[]} messages + * @param {string} source + * @return {string} + */ function formatter(messages, source) { if (!messages.length) return ''; @@ -87,10 +109,17 @@ function formatter(messages, source) { (m) => m.column, ); - // Create a list of column widths, needed to calculate - // the size of the message column and if needed wrap it. + /** + * Create a list of column widths, needed to calculate + * the size of the message column and if needed wrap it. + * @type {{[k: string]: number}} + */ const columnWidths = { 0: 1, 1: 1, 2: 1, 3: 1, 4: 1 }; + /** + * @param {[string, string, string, string, string]} columns + * @return {[string, string, string, string, string]} + */ const calculateWidths = function(columns) { _.forOwn(columns, (value, key) => { const normalisedValue = value ? value.toString() : value; @@ -109,11 +138,16 @@ function formatter(messages, source) { const cleanedMessages = orderedMessages.map((message) => { const location = utils.getLocation(message); - const severity = message.severity; + const severity = /** @type {keyof import('log-symbols')} */ (message.severity); + /** + * @type {[string, string, string, string, string]} + */ const row = [ - location.line || '', - location.column || '', - symbols[severity] ? chalk[levelColors[severity]](symbols[severity]) : severity, + location.line ? location.line.toString() : '', + location.column ? location.column.toString() : '', + symbols[severity] + ? chalk[/** @type {'blue' | 'red' | 'yellow'} */ (levelColors[severity])](symbols[severity]) + : severity, message.text // Remove all control characters (newline, tab and etc) .replace(/[\x01-\x1A]+/g, ' ') // eslint-disable-line no-control-regex @@ -148,12 +182,22 @@ function formatter(messages, source) { drawHorizontalLine: () => false, }) .split('\n') - .map((el) => el.replace(/(\d+)\s+(\d+)/, (m, p1, p2) => chalk.dim(p1 + ':' + p2))) + .map( + /** + * @param {string} el + * @returns {string} + */ + (el) => el.replace(/(\d+)\s+(\d+)/, (m, p1, p2) => chalk.dim(p1 + ':' + p2)), + ) .join('\n'); return output; } +/** + * @param {import('stylelint').StylelintResult[]} results + * @returns {string} + */ module.exports = function(results) { let output = invalidOptionsFormatter(results); @@ -173,7 +217,7 @@ module.exports = function(results) { ); } - output += formatter(result.warnings, result.source); + output += formatter(result.warnings, result.source || ''); return output; }, output); diff --git a/lib/index.js b/lib/index.js index bddc2e148f..c1d565fb7f 100644 --- a/lib/index.js +++ b/lib/index.js @@ -13,13 +13,25 @@ const rules = require('./rules'); const standalone = require('./standalone'); const validateOptions = require('./utils/validateOptions'); +/** + * TODO TYPES change any to appropriated options + * @type {import('postcss').Plugin & Partial} + */ const api = postcssPlugin; -const requiredRules = rules.reduce((acc, cur) => { - acc[cur] = requireRule(cur); +const requiredRules = rules.reduce( + /** + * @param {{[k: string]: any}} acc + * @param {string} cur + * @return {{[k: string]: any}} + */ + (acc, cur) => { + acc[cur] = requireRule(cur); - return acc; -}, {}); + return acc; + }, + {}, +); api.utils = { report, diff --git a/lib/standalone.js b/lib/standalone.js index 082dd21ba9..dc1ff4c367 100644 --- a/lib/standalone.js +++ b/lib/standalone.js @@ -12,17 +12,25 @@ const fs = require('fs'); const getFormatterOptionsText = require('./utils/getFormatterOptionsText'); const globby /*: Function*/ = require('globby'); const hash = require('./utils/hash'); -const ignore = require('ignore'); const invalidScopeDisables /*: Function*/ = require('./invalidScopeDisables'); const needlessDisables /*: Function*/ = require('./needlessDisables'); const NoFilesFoundError = require('./utils/noFilesFoundError'); const path = require('path'); const pkg = require('../package.json'); +const { default: ignore } = require('ignore'); const DEFAULT_IGNORE_FILENAME = '.stylelintignore'; const FILE_NOT_FOUND_ERROR_CODE = 'ENOENT'; const ALWAYS_IGNORED_GLOBS = ['**/node_modules/**', '**/bower_components/**']; const writeFileAtomic = require('write-file-atomic'); +/** @typedef {import('stylelint').StylelintStandaloneOptions} StylelintOptions */ +/** @typedef {import('stylelint').StylelintStandaloneReturnValue} StylelintStandaloneReturnValue */ +/** @typedef {import('stylelint').StylelintResult} StylelintResult */ + +/** + * @param {StylelintOptions} options + * @returns {Promise} + */ module.exports = function( options /*: stylelint$standaloneOptions */, ) /*: Promise*/ { @@ -45,6 +53,7 @@ module.exports = function( const syntax = options.syntax; const allowEmptyInput = options.allowEmptyInput || false; const useCache = options.cache || false; + /** @type {FileCache} */ let fileCache; const startTime = Date.now(); @@ -62,6 +71,10 @@ module.exports = function( if (readError.code !== FILE_NOT_FOUND_ERROR_CODE) throw readError; } + /** + * TODO TYPES + * @type {any} + */ const ignorePattern = options.ignorePattern || []; const ignorer = ignore() .add(ignoreText) @@ -73,6 +86,7 @@ module.exports = function( throw new Error('You must pass stylelint a `files` glob or a `code` string, though not both'); } + /** @type {Function} */ let formatterFunction; if (typeof formatter === 'string') { @@ -223,13 +237,26 @@ module.exports = function( fileCache.removeEntry(absoluteFilepath); } - // If we're fixing, save the file with changed code + /** + * If we're fixing, save the file with changed code + * @type {Promise} + */ let fixFile = Promise.resolve(); - if (!postcssResult.stylelint.ignored && options.fix) { + if ( + postcssResult.root && + postcssResult.opts && + !postcssResult.stylelint.ignored && + options.fix + ) { + // @ts-ignore TODO TYPES toString accepts 0 arguments const fixedCss = postcssResult.root.toString(postcssResult.opts.syntax); - if (postcssResult.root.source.input.css !== fixedCss) { + if ( + postcssResult.root.source && + // @ts-ignore TODO TYPES css is unknown property + postcssResult.root.source.input.css !== fixedCss + ) { fixFile = writeFileAtomic(absoluteFilepath, fixedCss); } } @@ -256,12 +283,17 @@ module.exports = function( return prepareReturnValue(stylelintResults); }); + /** + * @param {StylelintResult[]} stylelintResults + * @returns {StylelintStandaloneReturnValue} + */ function prepareReturnValue( stylelintResults /*: Array*/, ) /*: stylelint$standaloneReturnValue*/ { const errored = stylelintResults.some( (result) => result.errored || result.parseErrors.length > 0, ); + /** @type {StylelintStandaloneReturnValue} */ const returnValue /*: stylelint$standaloneReturnValue*/ = { errored, output: formatterFunction(stylelintResults), @@ -275,7 +307,8 @@ module.exports = function( if (reportInvalidScopeDisables) { returnValue.invalidScopeDisables = invalidScopeDisables( stylelintResults, - stylelint._options.config, + // TODO TYPES possible undefined + /** @type {import('stylelint').StylelintConfig} */ (stylelint._options.config), ); } @@ -295,13 +328,19 @@ module.exports = function( } }; +/** + * @param {import('stylelint').StylelintInternalApi} stylelint + * @param {Object} error + * @param {string} [filePath] + * @return {Promise} + */ function handleError( stylelint /*: stylelint$internalApi */, error /*: Object */, - filePath = null, + filePath = undefined, ) /*: Promise */ { if (error.name === 'CssSyntaxError') { - return createStylelintResult(stylelint, null, filePath, error); + return createStylelintResult(stylelint, undefined, filePath, error); } else { throw error; } diff --git a/lib/utils/configurationError.js b/lib/utils/configurationError.js index b884719e9e..6dfbef2026 100644 --- a/lib/utils/configurationError.js +++ b/lib/utils/configurationError.js @@ -7,6 +7,7 @@ * @returns {Object} */ module.exports = function(text /*: string */) /* Object */ { + /** @type {Error & {code?: number}} */ const err /*: Object*/ = new Error(text); err.code = 78; diff --git a/lib/utils/getFileIgnorer.js b/lib/utils/getFileIgnorer.js index c22bca1972..f0e756387d 100644 --- a/lib/utils/getFileIgnorer.js +++ b/lib/utils/getFileIgnorer.js @@ -2,12 +2,18 @@ // Try to get file ignorer from '.stylelintignore' const fs = require('fs'); -const ignore = require('ignore'); const path = require('path'); +const { default: ignore } = require('ignore'); const DEFAULT_IGNORE_FILENAME = '.stylelintignore'; const FILE_NOT_FOUND_ERROR_CODE = 'ENOENT'; +/** @typedef {import('stylelint').StylelintStandaloneOptions} StylelintOptions */ + +/** + * @param {StylelintOptions} options + * @return {import('ignore').Ignore} + */ module.exports = function(options /*: stylelint$standaloneOptions */) { const ignoreFilePath = options.ignorePath || DEFAULT_IGNORE_FILENAME; const absoluteIgnoreFilePath = path.isAbsolute(ignoreFilePath) @@ -21,6 +27,10 @@ module.exports = function(options /*: stylelint$standaloneOptions */) { if (readError.code !== FILE_NOT_FOUND_ERROR_CODE) throw readError; } + /** + * TODO TYPES + * @type {any} + */ const ignorePattern = options.ignorePattern || []; const ignorer = ignore() .add(ignoreText) diff --git a/lib/utils/noFilesFoundError.js b/lib/utils/noFilesFoundError.js index 3d91124f80..e3300be1b0 100644 --- a/lib/utils/noFilesFoundError.js +++ b/lib/utils/noFilesFoundError.js @@ -1,6 +1,9 @@ 'use strict'; class NoFilesFoundError extends Error { + /** + * @param {string|string[]} fileList + */ constructor(fileList) { super(); diff --git a/package-lock.json b/package-lock.json index 31a1765df0..1695258312 100644 --- a/package-lock.json +++ b/package-lock.json @@ -674,6 +674,15 @@ "integrity": "sha512-ogI4g9W5qIQQUhXAclq6zhqgqNUr7UlFaqDHbch7WLSLeeM/7d3CRaw7GLajxvyFvhJqw4Rpcz5bhoaYtIx6Tg==", "dev": true }, + "@types/meow": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@types/meow/-/meow-5.0.0.tgz", + "integrity": "sha512-csdM22P8TFF5KwuHseifUBOzWiGtiLfYmQosrKPGpA/xdK3o3PNo3+4YOpiJ5BQBshwO2kuhdJ7NoAitQ6m/jg==", + "dev": true, + "requires": { + "@types/minimist-options": "*" + } + }, "@types/micromatch": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/@types/micromatch/-/micromatch-3.1.0.tgz", @@ -688,6 +697,21 @@ "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.3.tgz", "integrity": "sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA==" }, + "@types/minimist": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@types/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-aaI6OtKcrwCX8G7aWbNh7i8GOfY=", + "dev": true + }, + "@types/minimist-options": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/minimist-options/-/minimist-options-3.0.0.tgz", + "integrity": "sha512-FYeTlkAANOr9KR0mQL7X+v7MT7Nb/aWdNNHNKzJ8sPctpS2Ei8ucgMbvQRbLRjaYZH2OVN5AGDGGcHV5x8lSgQ==", + "dev": true, + "requires": { + "@types/minimist": "*" + } + }, "@types/node": { "version": "12.7.12", "resolved": "https://registry.npmjs.org/@types/node/-/node-12.7.12.tgz", diff --git a/package.json b/package.json index e1c4f0fc16..4ebc690dec 100644 --- a/package.json +++ b/package.json @@ -95,6 +95,7 @@ "@types/global-modules": "^2.0.0", "@types/globjoin": "^0.1.0", "@types/lodash": "^4.14.144", + "@types/meow": "^5.0.0", "@types/micromatch": "^3.1.0", "benchmark": "^2.1.4", "common-tags": "^1.8.0", @@ -130,7 +131,7 @@ "jest:detectleaks": "jest --detectLeaks", "lint:js": "eslint . --cache --max-warnings=0", "lint:md": "remark . --quiet --frail", - "lint:types": "tsc || true", + "lint:types": "tsc", "lint": "npm-run-all --parallel lint:*", "pretest": "npm-run-all --serial lint flow prettier:check", "prettier:check": "prettier \"**/*.js\" --list-different", diff --git a/types/postcss/index.d.ts b/types/postcss/index.d.ts index bf14cbf46a..8d9ac88955 100644 --- a/types/postcss/index.d.ts +++ b/types/postcss/index.d.ts @@ -32,3 +32,15 @@ declare module 'postcss-syntax' { export = result; } + +declare module 'postcss-reporter/lib/util' { + export function getLocation(message: Object): {line: number, column: number, file?: string}; +} + +declare module 'postcss/lib/result' { + import { + Result + } from 'postcss'; + + export = Result; +} diff --git a/types/stylelint/index.d.ts b/types/stylelint/index.d.ts index 6bfc873066..86a0f129f5 100644 --- a/types/stylelint/index.d.ts +++ b/types/stylelint/index.d.ts @@ -83,7 +83,7 @@ declare module 'stylelint' { export type GetLintSourceOptions = GetPostcssOptions & {existingPostcssResult?: Result}; export type StylelintInternalApi = { - _options: StylelintOptions, + _options: StylelintStandaloneOptions, _extendExplorer: { search: (s: string) => Promise, load: (s: string) => Promise @@ -129,7 +129,7 @@ declare module 'stylelint' { formatter?: "compact" | "json" | "string" | "unix" | "verbose" | Function, disableDefaultIgnores?: boolean, fix?: boolean, - allowEmptyInput: boolean + allowEmptyInput?: boolean }; export type StylelintCssSyntaxError = { @@ -190,5 +190,32 @@ declare module 'stylelint' { }> }; + export type StylelintStandaloneReturnValue = { + results: Array, + errored: boolean, + output: any, + maxWarningsExceeded?: { + maxWarnings: number, + foundWarnings: number + }, + needlessDisables?: StylelintDisableOptionsReport, + invalidScopeDisables?: StylelintDisableOptionsReport + }; + + export type StylelintPublicAPI = { + lint: Function, + rules: {[k: string]: any}, + formatters: {[k: string]: Function}, + createPlugin: Function, + createRuleTester: Function, + createLinter: Function, + utils: { + report: Function, + ruleMessages: Function, + validateOptions: Function, + checkAgainstRule: Function + } + }; + export type StylelintDisableOptionsReport = Array; } diff --git a/types/table/index.d.ts b/types/table/index.d.ts new file mode 100644 index 0000000000..7bdea26da6 --- /dev/null +++ b/types/table/index.d.ts @@ -0,0 +1,5 @@ +declare module 'table' { + var def: any; // TODO TYPES + + export = def; +}