Skip to content

Commit

Permalink
Add TypeScript type definitions (#5582)
Browse files Browse the repository at this point in the history
* refactor: Resolve types inconsistencies, add test

Fixes #5580

- Types are refactored to be exportable and are now provided in the
  package:

  - `Stylelint` prefix removed where appropriate, types namespaced under
    `stylelint`.

  - Module export definition changed to point to namespace and public
    API, which allows `require('stylelint')` to be typed correctly.

  - Types renamed to match those in the `@types/stylelint` package to
    reduce incompatibilities/breakages downstream.

  - As a result of these changes, the separate types package can be
    deprecated in favour of the built-in type definitions.

- Internal API endpoints marked with `@internal` where appropriate.

- PostCSS types in types definition file scoped under `PostCSS` (e.g.
  `PostCSS.Root`) to make it clearer which types are imported.

- API types (`stylelint.lint`, `stylelint.createPlugin`, etc.) changed
  from `Function` to more specific types that actually reflect each
  function's signature.

- Refactored export types in public-facing exports (e.g. `lib/index.js`,
  `lib/createPlugin.js`) to reference API types in type definition file.
  This ensures that Typescript will report errors if the implementation/
  internal type annotations are changed to an incompatible signature.
  Any types that were only defined in those files as `@typedef` JSDoc
  comments have been moved to the `.d.ts` definition file.

- Fixed problem with `Rule` types that resulted in Typescript errors
  when using `createPlugin`. Required properties were added in #5418,
  which broke usage of `createPlugin` as the `rule` parameter would
  require functions to have the newly added properties, even though this
  doesn't reflect the implementation accurately. To fix this:

  - `Rule` type split into `RuleBase` and `Rule`. Added the `Plugin`
    type as an alias for `RuleBase` to achieve parity with former types
    at `@types/stylelint`. `RuleBase` represents the rule without any
    properties attached. `Rule` is a union of `RuleBase` and an object
    with the properties.

  - `createPlugin` updated to use `RuleBase`.

- Fixed Typescript errors being thrown when the configs passed to the
  `overrides` option would contain supported properties that were
  missing from the types definition:

  - `ConfigOverride` type updated to use `Omit` instead of `Pick`,
    removing the `overrides` property instead of allow-listing
    properties. This fix means the `ConfigOverride` type shouldn't need
    to be manually updated whenever a new configuration property is
    added.

- `RuleMessages` and related types fixed to avoid Typescript errors. The
  return type of `stylelint.utils.ruleMessages` was incorrectly set to
  the type of the `messages` parameter, which resulted in type errors in
  `lib/ruleMessages.js`. This change:

  - Changes the `ruleMessages` return type to `{ [K in keyof T]: T[K] }`
    which is correct. There is a distinction between returning the same
    type as being passed to the function and returning a type with the
    same keys and value types for those keys.

  - `RuleMessages` type changed to a mapped type to facilitate the
    change to `stylelint.utils.ruleMessages`. This allows Typescript to
    narrow types for the object returned by `ruleMessages`:

    ```js
    const messages = stylelint.utils.ruleMessages(ruleName, {
      violation: 'This a rule violation message',
      warning: (reason: string) => `This is not allowed because ${reason}`,
    });
    // If a `Record<...>` was used instead, the following lines would
    // result in Typescript error 2322.
    const violationMessage: string = messages.violation;
    const violationFunc: Function = messages.warning;
    ```

  - Type annotations in `lib/ruleMessages.js` updated to refect these
    changes and to avoid Typescript errors and allow type-checking.

- Type test file added to `types/stylelint/type-test.ts`. This file
  contains code that references the public API as a consuming package
  would use it. It is not executed, but is used so that if the API or
  types are changed in a breaking way, the change will be caught by
  Typescript. If this existed before, the breaking change to the
  `StylelintRule` type (now `stylelint.Rule`) would have been caught.

- Documentation comments added to public API endpoints (`stylelint.*`).

- Removed leftover duplicate `Options` type that had no references
  anywhere in the project.

* docs: Document added types in changelog

* docs: fix typo, document types in migration guide
  • Loading branch information
adalinesimonian committed Oct 11, 2021
1 parent d5edc48 commit 816b19a
Show file tree
Hide file tree
Showing 145 changed files with 730 additions and 540 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Expand Up @@ -22,6 +22,7 @@ All notable changes to this project are documented in this file.
- Added: `ignoreFunctions: []` to `length-zero-no-unit` ([#5314](https://github.com/stylelint/stylelint/pull/5314)).
- Added: `ignoreAtRules: []` to `no-invalid-position-at-import` ([#5520](https://github.com/stylelint/stylelint/pull/5520)).
- Added: `ignoreProperties: []` to `number-max-precision` ([#5421](https://github.com/stylelint/stylelint/pull/5421)).
- Added: TypeScript type definitions ([#5582](https://github.com/stylelint/stylelint/pull/5582)).
- Fixed: "No files matching the pattern" when using backslash paths on Windows ([#5386](https://github.com/stylelint/stylelint/pull/5386)).
- Fixed: `function-url-quotes` violation messages to be consistent with other `*-quotes` rules ([#5488](https://github.com/stylelint/stylelint/pull/5488)).
- Fixed: `length-zero-no-unit` false positives for `flex` property ([#5315](https://github.com/stylelint/stylelint/pull/5315)).
Expand Down
2 changes: 1 addition & 1 deletion docs/developer-guide/formatters.md
Expand Up @@ -45,7 +45,7 @@ Where the first argument (`results`) is an array of Stylelint result objects (ty
}
```

And the second argument (`returnValue`) is an object (type `StylelintStandaloneReturnValue`) with one or more of the following keys:
And the second argument (`returnValue`) is an object (type `LinterResult`) with one or more of the following keys:

```js
{
Expand Down
7 changes: 6 additions & 1 deletion docs/migration-guide/to-14.md
Expand Up @@ -108,10 +108,11 @@ The `function-calc-no-invalid` has be removed. You should remove it from your co

## Plugin authors

There are two changes that may affect you:
There are three changes that may affect you:

- version 8 of PostCSS is now used in stylelint
- a [`disableFix` secondary option](../user-guide/configure.md#disableFix) was added
- TypeScript type definitions were added to the package

### PostCSS 8

Expand All @@ -130,3 +131,7 @@ Even though version 8 of PostCSS is used in stylelint, you can't use the [new Vi
### `disableFix` secondary option

We previously suggested plugin authors provide this option. It is now available in Stylelint itself, and you should remove the option from your plugin.

### Built-in TypeScript definitions

The `stylelint` package exports its own TypeScript type definitions now. If you are using the `@types/stylelint` package, you should remove it from your dependencies.
16 changes: 8 additions & 8 deletions lib/augmentConfig.js
Expand Up @@ -8,14 +8,14 @@ const normalizeAllRuleSettings = require('./normalizeAllRuleSettings');
const normalizePath = require('normalize-path');
const path = require('path');

/** @typedef {import('stylelint').StylelintConfigPlugins} StylelintConfigPlugins */
/** @typedef {import('stylelint').StylelintConfigProcessor} StylelintConfigProcessor */
/** @typedef {import('stylelint').StylelintConfigProcessors} StylelintConfigProcessors */
/** @typedef {import('stylelint').StylelintConfigRules} StylelintConfigRules */
/** @typedef {import('stylelint').StylelintConfigOverride} StylelintConfigOverride */
/** @typedef {import('stylelint').StylelintInternalApi} StylelintInternalApi */
/** @typedef {import('stylelint').StylelintConfig} StylelintConfig */
/** @typedef {import('stylelint').StylelintCosmiconfigResult} StylelintCosmiconfigResult */
/** @typedef {import('stylelint').ConfigPlugins} StylelintConfigPlugins */
/** @typedef {import('stylelint').ConfigProcessor} StylelintConfigProcessor */
/** @typedef {import('stylelint').ConfigProcessors} StylelintConfigProcessors */
/** @typedef {import('stylelint').ConfigRules} StylelintConfigRules */
/** @typedef {import('stylelint').ConfigOverride} StylelintConfigOverride */
/** @typedef {import('stylelint').InternalApi} StylelintInternalApi */
/** @typedef {import('stylelint').Config} StylelintConfig */
/** @typedef {import('stylelint').CosmiconfigResult} StylelintCosmiconfigResult */

/**
* - Merges config and stylelint options
Expand Down
4 changes: 2 additions & 2 deletions lib/createPartialStylelintResult.js
@@ -1,11 +1,11 @@
'use strict';

/** @typedef {import('stylelint').PostcssResult} PostcssResult */
/** @typedef {import('stylelint').StylelintResult} StylelintResult */
/** @typedef {import('stylelint').LintResult} StylelintResult */

/**
* @param {PostcssResult} [postcssResult]
* @param {import('stylelint').StylelintCssSyntaxError} [cssSyntaxError]
* @param {import('stylelint').CssSyntaxError} [cssSyntaxError]
* @return {StylelintResult}
*/
module.exports = function (postcssResult, cssSyntaxError) {
Expand Down
8 changes: 5 additions & 3 deletions lib/createPlugin.js
@@ -1,15 +1,17 @@
'use strict';

/** @typedef {import('stylelint').StylelintRule} StylelintRule */
/** @typedef {import('stylelint').Rule} StylelintRule */

/**
* @param {string} ruleName
* @param {StylelintRule} rule
* @returns {{ruleName: string, rule: StylelintRule}}
*/
module.exports = function (ruleName, rule) {
function createPlugin(ruleName, rule) {
return {
ruleName,
rule,
};
};
}

module.exports = /** @type {typeof import('stylelint').createPlugin} */ (createPlugin);
12 changes: 7 additions & 5 deletions lib/createStylelint.js
Expand Up @@ -11,19 +11,19 @@ const { cosmiconfig } = require('cosmiconfig');
const IS_TEST = process.env.NODE_ENV === 'test';
const STOP_DIR = IS_TEST ? process.cwd() : undefined;

/** @typedef {import('stylelint').StylelintInternalApi} StylelintInternalApi */
/** @typedef {import('stylelint').InternalApi} StylelintInternalApi */

/**
* 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').StylelintStandaloneOptions} options
* @param {import('stylelint').LinterOptions} options
* @returns {StylelintInternalApi}
*/
module.exports = function createStylelint(options = {}) {
function createStylelint(options = {}) {
/** @type {StylelintInternalApi} */
// @ts-expect-error -- TS2740: Type '{ _options: StylelintStandaloneOptions; }' is missing the following properties from type 'StylelintInternalApi'
// @ts-expect-error -- TS2740: Type '{ _options: LinterOptions; }' is missing the following properties from type 'InternalApi'
const stylelint = { _options: options };

stylelint._extendExplorer = cosmiconfig('', {
Expand All @@ -41,4 +41,6 @@ module.exports = function createStylelint(options = {}) {
stylelint.isPathIgnored = isPathIgnored.bind(null, stylelint);

return stylelint;
};
}

module.exports = /** @type {typeof import('stylelint').createLinter} */ (createStylelint);
6 changes: 3 additions & 3 deletions lib/createStylelintResult.js
Expand Up @@ -3,13 +3,13 @@
const createPartialStylelintResult = require('./createPartialStylelintResult');

/** @typedef {import('stylelint').PostcssResult} PostcssResult */
/** @typedef {import('stylelint').StylelintResult} StylelintResult */
/** @typedef {import('stylelint').LintResult} StylelintResult */

/**
* @param {import('stylelint').StylelintInternalApi} stylelint
* @param {import('stylelint').InternalApi} stylelint
* @param {PostcssResult} [postcssResult]
* @param {string} [filePath]
* @param {import('stylelint').StylelintCssSyntaxError} [cssSyntaxError]
* @param {import('stylelint').CssSyntaxError} [cssSyntaxError]
* @return {Promise<StylelintResult>}
*/
module.exports = async function createStylelintResult(
Expand Down
4 changes: 2 additions & 2 deletions lib/descriptionlessDisables.js
Expand Up @@ -6,10 +6,10 @@ const validateDisableSettings = require('./validateDisableSettings');
/** @typedef {import('postcss').Comment} PostcssComment */
/** @typedef {import('stylelint').RangeType} RangeType */
/** @typedef {import('stylelint').DisableReportRange} DisableReportRange */
/** @typedef {import('stylelint').StylelintDisableOptionsReport} StylelintDisableOptionsReport */
/** @typedef {import('stylelint').DisableOptionsReport} StylelintDisableOptionsReport */

/**
* @param {import('stylelint').StylelintResult[]} results
* @param {import('stylelint').LintResult[]} results
*/
module.exports = function descriptionlessDisables(results) {
results.forEach((result) => {
Expand Down
5 changes: 4 additions & 1 deletion lib/formatters/index.js
Expand Up @@ -2,11 +2,14 @@

const importLazy = require('import-lazy');

module.exports = {
/** @type {typeof import('stylelint').formatters} */
const formatters = {
compact: importLazy(() => require('./compactFormatter'))('compactFormatter'),
json: importLazy(() => require('./jsonFormatter'))('jsonFormatter'),
string: importLazy(() => require('./stringFormatter'))('stringFormatter'),
tap: importLazy(() => require('./tapFormatter'))('tapFormatter'),
unix: importLazy(() => require('./unixFormatter'))('unixFormatter'),
verbose: importLazy(() => require('./verboseFormatter'))('verboseFormatter'),
};

module.exports = formatters;
8 changes: 4 additions & 4 deletions lib/formatters/stringFormatter.js
Expand Up @@ -30,7 +30,7 @@ const symbols = {
};

/**
* @param {import('stylelint').StylelintResult[]} results
* @param {import('stylelint').LintResult[]} results
* @returns {string}
*/
function deprecationsFormatter(results) {
Expand Down Expand Up @@ -60,7 +60,7 @@ function deprecationsFormatter(results) {
}

/**
* @param {import('stylelint').StylelintResult[]} results
* @param {import('stylelint').LintResult[]} results
* @return {string}
*/
function invalidOptionsFormatter(results) {
Expand Down Expand Up @@ -108,7 +108,7 @@ function getMessageWidth(columnWidths) {
}

/**
* @param {import('stylelint').StylelintWarning[]} messages
* @param {import('stylelint').Warning[]} messages
* @param {string} source
* @return {string}
*/
Expand Down Expand Up @@ -161,7 +161,7 @@ function formatter(messages, source) {
}

/**
* @param {import('stylelint').StylelintWarning} message
* @param {import('stylelint').Warning} message
* @return {string}
*/
function formatMessageText(message) {
Expand Down
2 changes: 1 addition & 1 deletion lib/formatters/verboseFormatter.js
Expand Up @@ -4,7 +4,7 @@ const stringFormatter = require('./stringFormatter');
const { underline, red, yellow, dim, green } = require('picocolors');

/** @typedef {import('stylelint').Formatter} Formatter */
/** @typedef {import('stylelint').StylelintWarning} StylelintWarning */
/** @typedef {import('stylelint').Warning} StylelintWarning */

/**
* @type {Formatter}
Expand Down
6 changes: 3 additions & 3 deletions lib/getConfigForFile.js
Expand Up @@ -8,9 +8,9 @@ const { cosmiconfig } = require('cosmiconfig');
const IS_TEST = process.env.NODE_ENV === 'test';
const STOP_DIR = IS_TEST ? process.cwd() : undefined;

/** @typedef {import('stylelint').StylelintInternalApi} StylelintInternalApi */
/** @typedef {import('stylelint').StylelintConfig} StylelintConfig */
/** @typedef {import('stylelint').StylelintCosmiconfigResult} StylelintCosmiconfigResult */
/** @typedef {import('stylelint').InternalApi} StylelintInternalApi */
/** @typedef {import('stylelint').Config} StylelintConfig */
/** @typedef {import('stylelint').CosmiconfigResult} StylelintCosmiconfigResult */

/**
* @param {StylelintInternalApi} stylelint
Expand Down
2 changes: 1 addition & 1 deletion lib/getPostcssResult.js
Expand Up @@ -9,7 +9,7 @@ const { promises: fs } = require('fs');
/** @typedef {import('postcss').Syntax} Syntax */
/** @typedef {import('stylelint').CustomSyntax} CustomSyntax */
/** @typedef {import('stylelint').GetPostcssOptions} GetPostcssOptions */
/** @typedef {import('stylelint').StylelintInternalApi} StylelintInternalApi */
/** @typedef {import('stylelint').InternalApi} StylelintInternalApi */

const postcssProcessor = postcss();

Expand Down
31 changes: 15 additions & 16 deletions lib/index.js
Expand Up @@ -11,20 +11,19 @@ const rules = require('./rules');
const standalone = require('./standalone');
const validateOptions = require('./utils/validateOptions');

/**
* @type {import('postcss').PluginCreator<import('stylelint').PostcssPluginOptions> & import('stylelint').StylelintPublicAPI}
*/
module.exports = postcssPlugin;
/** @type {import('stylelint').PublicApi} */
const stylelint = Object.assign(postcssPlugin, {
lint: standalone,
rules,
formatters,
createPlugin,
createLinter: createStylelint,
utils: {
report,
ruleMessages,
validateOptions,
checkAgainstRule,
},
});

module.exports.utils = {
report,
ruleMessages,
validateOptions,
checkAgainstRule,
};

module.exports.lint = standalone;
module.exports.rules = rules;
module.exports.formatters = formatters;
module.exports.createPlugin = createPlugin;
module.exports.createLinter = createStylelint;
module.exports = stylelint;
2 changes: 1 addition & 1 deletion lib/invalidScopeDisables.js
Expand Up @@ -6,7 +6,7 @@ const validateDisableSettings = require('./validateDisableSettings');
/** @typedef {import('stylelint').RangeType} RangeType */

/**
* @param {import('stylelint').StylelintResult[]} results
* @param {import('stylelint').LintResult[]} results
*/
module.exports = function invalidScopeDisables(results) {
results.forEach((result) => {
Expand Down
2 changes: 1 addition & 1 deletion lib/isPathIgnored.js
Expand Up @@ -10,7 +10,7 @@ const path = require('path');
* To find out if a path is ignored, we need to load the config,
* which may have an ignoreFiles property. We then check the path
* against these.
* @param {import('stylelint').StylelintInternalApi} stylelint
* @param {import('stylelint').InternalApi} stylelint
* @param {string} [filePath]
* @return {Promise<boolean>}
*/
Expand Down
6 changes: 3 additions & 3 deletions lib/lintPostcssResult.js
Expand Up @@ -5,12 +5,12 @@ const getOsEol = require('./utils/getOsEol');
const reportUnknownRuleNames = require('./reportUnknownRuleNames');
const rulesOrder = require('./rules');

/** @typedef {import('stylelint').StylelintStandaloneOptions} StylelintStandaloneOptions */
/** @typedef {import('stylelint').LinterOptions} LinterOptions */
/** @typedef {import('stylelint').PostcssResult} PostcssResult */
/** @typedef {import('stylelint').StylelintConfig} StylelintConfig */
/** @typedef {import('stylelint').Config} StylelintConfig */

/**
* @param {StylelintStandaloneOptions} stylelintOptions
* @param {LinterOptions} stylelintOptions
* @param {PostcssResult} postcssResult
* @param {StylelintConfig} config
* @returns {Promise<any>}
Expand Down
2 changes: 1 addition & 1 deletion lib/lintSource.js
Expand Up @@ -4,7 +4,7 @@ const isPathNotFoundError = require('./utils/isPathNotFoundError');
const lintPostcssResult = require('./lintPostcssResult');
const path = require('path');

/** @typedef {import('stylelint').StylelintInternalApi} StylelintInternalApi */
/** @typedef {import('stylelint').InternalApi} StylelintInternalApi */
/** @typedef {import('stylelint').GetLintSourceOptions} Options */
/** @typedef {import('postcss').Result} Result */
/** @typedef {import('stylelint').PostcssResult} PostcssResult */
Expand Down
2 changes: 1 addition & 1 deletion lib/needlessDisables.js
Expand Up @@ -10,7 +10,7 @@ const validateDisableSettings = require('./validateDisableSettings');
/** @typedef {import('stylelint').DisableReportRange} DisableReportRange */

/**
* @param {import('stylelint').StylelintResult[]} results
* @param {import('stylelint').LintResult[]} results
*/
module.exports = function needlessDisables(results) {
results.forEach((result) => {
Expand Down
4 changes: 2 additions & 2 deletions lib/normalizeAllRuleSettings.js
Expand Up @@ -3,8 +3,8 @@
const normalizeRuleSettings = require('./normalizeRuleSettings');
const rules = require('./rules');

/** @typedef {import('stylelint').StylelintConfigRules} StylelintConfigRules */
/** @typedef {import('stylelint').StylelintConfig} StylelintConfig */
/** @typedef {import('stylelint').ConfigRules} StylelintConfigRules */
/** @typedef {import('stylelint').Config} StylelintConfig */

/**
* @param {StylelintConfig} config
Expand Down
2 changes: 1 addition & 1 deletion lib/normalizeRuleSettings.js
Expand Up @@ -18,7 +18,7 @@ const { isPlainObject } = require('is-plain-object');
* null is returned
* @template T
* @template {Object} O
* @param {import('stylelint').StylelintConfigRuleSettings<T, O>} rawSettings
* @param {import('stylelint').ConfigRuleSettings<T, O>} rawSettings
* @param {string} ruleName
* @param {boolean} [primaryOptionArray] If primaryOptionArray is not provided, we try to get it from the rules themselves, which will not work for plugins
* @return {[T] | [T, O] | null}
Expand Down
2 changes: 1 addition & 1 deletion lib/postcssPlugin.js
Expand Up @@ -4,7 +4,7 @@ const createStylelint = require('./createStylelint');
const path = require('path');

/** @typedef {import('stylelint').PostcssPluginOptions} PostcssPluginOptions */
/** @typedef {import('stylelint').StylelintConfig} StylelintConfig */
/** @typedef {import('stylelint').Config} StylelintConfig */

/**
* @type {import('postcss').PluginCreator<PostcssPluginOptions>}
Expand Down
10 changes: 5 additions & 5 deletions lib/prepareReturnValue.js
Expand Up @@ -6,16 +6,16 @@ const needlessDisables = require('./needlessDisables');
const reportDisables = require('./reportDisables');

/** @typedef {import('stylelint').Formatter} Formatter */
/** @typedef {import('stylelint').StylelintResult} StylelintResult */
/** @typedef {import('stylelint').StylelintStandaloneOptions["maxWarnings"]} maxWarnings */
/** @typedef {import('stylelint').StylelintStandaloneReturnValue} StylelintStandaloneReturnValue */
/** @typedef {import('stylelint').LintResult} StylelintResult */
/** @typedef {import('stylelint').LinterOptions["maxWarnings"]} maxWarnings */
/** @typedef {import('stylelint').LinterResult} LinterResult */

/**
* @param {StylelintResult[]} stylelintResults
* @param {maxWarnings} maxWarnings
* @param {Formatter} formatter
*
* @returns {StylelintStandaloneReturnValue}
* @returns {LinterResult}
*/
function prepareReturnValue(stylelintResults, maxWarnings, formatter) {
reportDisables(stylelintResults);
Expand All @@ -30,7 +30,7 @@ function prepareReturnValue(stylelintResults, maxWarnings, formatter) {
result.warnings.some((warning) => warning.severity === 'error'),
);

/** @type {StylelintStandaloneReturnValue} */
/** @type {LinterResult} */
const returnValue = {
errored,
results: [],
Expand Down

0 comments on commit 816b19a

Please sign in to comment.