This commit is contained in:
darenhsu
2022-07-17 13:16:16 +08:00
parent 84759556ff
commit befd344ab0
28070 changed files with 4008428 additions and 1 deletions
+75
View File
@@ -0,0 +1,75 @@
'use strict';
const path = require('path');
const importModules = require('import-modules');
module.exports = {
rules: importModules(path.resolve(__dirname, 'rules'), {camelize: false}),
configs: {
recommended: {
env: {
es6: true
},
parserOptions: {
ecmaVersion: 2020,
sourceType: 'module'
},
plugins: [
'unicorn'
],
rules: {
'unicorn/better-regex': 'error',
'unicorn/catch-error-name': 'error',
'unicorn/consistent-function-scoping': 'error',
'unicorn/custom-error-definition': 'off',
'unicorn/error-message': 'error',
'unicorn/escape-case': 'error',
'unicorn/expiring-todo-comments': 'error',
'unicorn/explicit-length-check': 'error',
'unicorn/filename-case': 'error',
'unicorn/import-index': 'error',
'unicorn/new-for-builtins': 'error',
'unicorn/no-abusive-eslint-disable': 'error',
'unicorn/no-array-instanceof': 'error',
'unicorn/no-console-spaces': 'error',
'unicorn/no-fn-reference-in-iterator': 'error',
'unicorn/no-for-loop': 'error',
'unicorn/no-hex-escape': 'error',
'unicorn/no-keyword-prefix': 'off',
'no-nested-ternary': 'off',
'unicorn/no-nested-ternary': 'error',
'unicorn/no-new-buffer': 'error',
'unicorn/no-null': 'error',
'unicorn/no-process-exit': 'error',
'unicorn/no-unreadable-array-destructuring': 'error',
'unicorn/no-unsafe-regex': 'off',
'unicorn/no-unused-properties': 'off',
'unicorn/no-zero-fractions': 'error',
'unicorn/number-literal-case': 'error',
'unicorn/prefer-add-event-listener': 'error',
'unicorn/prefer-dataset': 'error',
'unicorn/prefer-event-key': 'error',
'unicorn/prefer-flat-map': 'error',
'unicorn/prefer-includes': 'error',
'unicorn/prefer-modern-dom-apis': 'error',
'unicorn/prefer-negative-index': 'error',
'unicorn/prefer-node-append': 'error',
'unicorn/prefer-node-remove': 'error',
'unicorn/prefer-number-properties': 'error',
'unicorn/prefer-query-selector': 'error',
'unicorn/prefer-reflect-apply': 'error',
// TODO: Enable this by default when it's shipping in a Node.js LTS version.
'unicorn/prefer-replace-all': 'off',
'unicorn/prefer-set-has': 'error',
'unicorn/prefer-spread': 'error',
'unicorn/prefer-starts-ends-with': 'error',
'unicorn/prefer-string-slice': 'error',
'unicorn/prefer-text-content': 'error',
'unicorn/prefer-trim-start-end': 'error',
'unicorn/prefer-type-error': 'error',
'unicorn/prevent-abbreviations': 'error',
'unicorn/string-content': 'off',
'unicorn/throw-new-error': 'error'
}
}
}
};
+9
View File
@@ -0,0 +1,9 @@
MIT License
Copyright (c) Sindre Sorhus <sindresorhus@gmail.com> (https://sindresorhus.com)
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+78
View File
@@ -0,0 +1,78 @@
# Changelog
## v2.0.0
Breaking changes:
* Drop support for Node.js end-of-life versions: 0.10, 0.12, 4, 5, 7,
and 9
* Team Foundation Server will now be detected as Azure Pipelines. The
constant `ci.TFS` no longer exists - use `ci.AZURE_PIPELINES` instead
* Remove deprecated `ci.TDDIUM` constant - use `ci.SOLANDO` instead
New features:
* feat: support Azure Pipelines ([#23](https://github.com/watson/ci-info/pull/23))
* feat: support Netlify CI ([#26](https://github.com/watson/ci-info/pull/26))
* feat: support Bitbucket pipelines PR detection ([#27](https://github.com/watson/ci-info/pull/27))
## v1.6.0
* feat: add Sail CI support
* feat: add Buddy support
* feat: add Bitrise support
* feat: detect Jenkins PRs
* feat: detect Drone PRs
## v1.5.1
* fix: use full path to vendors.json
## v1.5.0
* feat: add dsari detection ([#15](https://github.com/watson/ci-info/pull/15))
* feat: add ci.isPR ([#16](https://github.com/watson/ci-info/pull/16))
## v1.4.0
* feat: add Cirrus CI detection ([#13](https://github.com/watson/ci-info/pull/13))
* feat: add Shippable CI detection ([#14](https://github.com/watson/ci-info/pull/14))
## v1.3.1
* chore: reduce npm package size by not including `.github` folder content ([#11](https://github.com/watson/ci-info/pull/11))
## v1.3.0
* feat: add support for Strider CD
* chore: deprecate vendor constant `TDDIUM` in favor of `SOLANO`
* docs: add missing vendor constant to docs
## v1.2.0
* feat: detect solano-ci ([#9](https://github.com/watson/ci-info/pull/9))
## v1.1.3
* fix: fix spelling of Hunson in `ci.name`
## v1.1.2
* fix: no more false positive matches for Jenkins
## v1.1.1
* docs: sort lists of CI servers in README.md
* docs: add missing AWS CodeBuild to the docs
## v1.1.0
* feat: add AWS CodeBuild to CI detection ([#2](https://github.com/watson/ci-info/pull/2))
## v1.0.1
* chore: reduce npm package size by using an `.npmignore` file ([#3](https://github.com/watson/ci-info/pull/3))
## v1.0.0
* Initial release
+21
View File
@@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2016-2018 Thomas Watson Steen
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+108
View File
@@ -0,0 +1,108 @@
# ci-info
Get details about the current Continuous Integration environment.
Please [open an
issue](https://github.com/watson/ci-info/issues/new?template=ci-server-not-detected.md)
if your CI server isn't properly detected :)
[![npm](https://img.shields.io/npm/v/ci-info.svg)](https://www.npmjs.com/package/ci-info)
[![Build status](https://travis-ci.org/watson/ci-info.svg?branch=master)](https://travis-ci.org/watson/ci-info)
[![js-standard-style](https://img.shields.io/badge/code%20style-standard-brightgreen.svg?style=flat)](https://github.com/feross/standard)
## Installation
```bash
npm install ci-info --save
```
## Usage
```js
var ci = require('ci-info')
if (ci.isCI) {
console.log('The name of the CI server is:', ci.name)
} else {
console.log('This program is not running on a CI server')
}
```
## Supported CI tools
Officially supported CI servers:
| Name | Constant | isPR |
|------|----------|------|
| [AWS CodeBuild](https://aws.amazon.com/codebuild/) | `ci.CODEBUILD` | 🚫 |
| [AppVeyor](http://www.appveyor.com) | `ci.APPVEYOR` | ✅ |
| [Azure Pipelines](https://azure.microsoft.com/en-us/services/devops/pipelines/) | `ci.AZURE_PIPELINES` | ✅ |
| [Bamboo](https://www.atlassian.com/software/bamboo) by Atlassian | `ci.BAMBOO` | 🚫 |
| [Bitbucket Pipelines](https://bitbucket.org/product/features/pipelines) | `ci.BITBUCKET` | ✅ |
| [Bitrise](https://www.bitrise.io/) | `ci.BITRISE` | ✅ |
| [Buddy](https://buddy.works/) | `ci.BUDDY` | ✅ |
| [Buildkite](https://buildkite.com) | `ci.BUILDKITE` | ✅ |
| [CircleCI](http://circleci.com) | `ci.CIRCLE` | ✅ |
| [Cirrus CI](https://cirrus-ci.org) | `ci.CIRRUS` | ✅ |
| [Codeship](https://codeship.com) | `ci.CODESHIP` | 🚫 |
| [Drone](https://drone.io) | `ci.DRONE` | ✅ |
| [dsari](https://github.com/rfinnie/dsari) | `ci.DSARI` | 🚫 |
| [GitLab CI](https://about.gitlab.com/gitlab-ci/) | `ci.GITLAB` | 🚫 |
| [GoCD](https://www.go.cd/) | `ci.GOCD` | 🚫 |
| [Hudson](http://hudson-ci.org) | `ci.HUDSON` | 🚫 |
| [Jenkins CI](https://jenkins-ci.org) | `ci.JENKINS` | ✅ |
| [Magnum CI](https://magnum-ci.com) | `ci.MAGNUM` | 🚫 |
| [Netlify CI](https://www.netlify.com/) | `ci.NETLIFY` | ✅ |
| [Sail CI](https://sail.ci/) | `ci.SAIL` | ✅ |
| [Semaphore](https://semaphoreci.com) | `ci.SEMAPHORE` | ✅ |
| [Shippable](https://www.shippable.com/) | `ci.SHIPPABLE` | ✅ |
| [Solano CI](https://www.solanolabs.com/) | `ci.SOLANO` | ✅ |
| [Strider CD](https://strider-cd.github.io/) | `ci.STRIDER` | 🚫 |
| [TaskCluster](http://docs.taskcluster.net) | `ci.TASKCLUSTER` | 🚫 |
| [TeamCity](https://www.jetbrains.com/teamcity/) by JetBrains | `ci.TEAMCITY` | 🚫 |
| [Travis CI](http://travis-ci.org) | `ci.TRAVIS` | ✅ |
## API
### `ci.name`
Returns a string containing name of the CI server the code is running on.
If CI server is not detected, it returns `null`.
Don't depend on the value of this string not to change for a specific
vendor. If you find your self writing `ci.name === 'Travis CI'`, you
most likely want to use `ci.TRAVIS` instead.
### `ci.isCI`
Returns a boolean. Will be `true` if the code is running on a CI server,
otherwise `false`.
Some CI servers not listed here might still trigger the `ci.isCI`
boolean to be set to `true` if they use certain vendor neutral
environment variables. In those cases `ci.name` will be `null` and no
vendor specific boolean will be set to `true`.
### `ci.isPR`
Returns a boolean if PR detection is supported for the current CI server. Will
be `true` if a PR is being tested, otherwise `false`. If PR detection is
not supported for the current CI server, the value will be `null`.
### `ci.<VENDOR-CONSTANT>`
A vendor specific boolean constant is exposed for each support CI
vendor. A constant will be `true` if the code is determined to run on
the given CI server, otherwise `false`.
Examples of vendor constants are `ci.TRAVIS` or `ci.APPVEYOR`. For a
complete list, see the support table above.
Deprecated vendor constants that will be removed in the next major
release:
- `ci.TDDIUM` (Solano CI) This have been renamed `ci.SOLANO`
## License
[MIT](LICENSE)
+66
View File
@@ -0,0 +1,66 @@
'use strict'
var vendors = require('./vendors.json')
var env = process.env
// Used for testing only
Object.defineProperty(exports, '_vendors', {
value: vendors.map(function (v) { return v.constant })
})
exports.name = null
exports.isPR = null
vendors.forEach(function (vendor) {
var envs = Array.isArray(vendor.env) ? vendor.env : [vendor.env]
var isCI = envs.every(function (obj) {
return checkEnv(obj)
})
exports[vendor.constant] = isCI
if (isCI) {
exports.name = vendor.name
switch (typeof vendor.pr) {
case 'string':
// "pr": "CIRRUS_PR"
exports.isPR = !!env[vendor.pr]
break
case 'object':
if ('env' in vendor.pr) {
// "pr": { "env": "BUILDKITE_PULL_REQUEST", "ne": "false" }
exports.isPR = vendor.pr.env in env && env[vendor.pr.env] !== vendor.pr.ne
} else if ('any' in vendor.pr) {
// "pr": { "any": ["ghprbPullId", "CHANGE_ID"] }
exports.isPR = vendor.pr.any.some(function (key) {
return !!env[key]
})
} else {
// "pr": { "DRONE_BUILD_EVENT": "pull_request" }
exports.isPR = checkEnv(vendor.pr)
}
break
default:
// PR detection not supported for this vendor
exports.isPR = null
}
}
})
exports.isCI = !!(
env.CI || // Travis CI, CircleCI, Cirrus CI, Gitlab CI, Appveyor, CodeShip, dsari
env.CONTINUOUS_INTEGRATION || // Travis CI, Cirrus CI
env.BUILD_NUMBER || // Jenkins, TeamCity
env.RUN_ID || // TaskCluster, dsari
exports.name ||
false
)
function checkEnv (obj) {
if (typeof obj === 'string') return !!env[obj]
return Object.keys(obj).every(function (k) {
return env[k] === obj[k]
})
}
+69
View File
@@ -0,0 +1,69 @@
{
"_args": [
[
"ci-info@2.0.0",
"/mnt/Foxconn/Digitalent/Deverloper/liff-push_2series"
]
],
"_development": true,
"_from": "ci-info@2.0.0",
"_id": "ci-info@2.0.0",
"_inBundle": false,
"_integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==",
"_location": "/eslint-plugin-unicorn/ci-info",
"_phantomChildren": {},
"_requested": {
"type": "version",
"registry": true,
"raw": "ci-info@2.0.0",
"name": "ci-info",
"escapedName": "ci-info",
"rawSpec": "2.0.0",
"saveSpec": null,
"fetchSpec": "2.0.0"
},
"_requiredBy": [
"/eslint-plugin-unicorn"
],
"_resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz",
"_spec": "2.0.0",
"_where": "/mnt/Foxconn/Digitalent/Deverloper/liff-push_2series",
"author": {
"name": "Thomas Watson Steen",
"email": "w@tson.dk",
"url": "https://twitter.com/wa7son"
},
"bugs": {
"url": "https://github.com/watson/ci-info/issues"
},
"coordinates": [
55.778231,
12.593179
],
"dependencies": {},
"description": "Get details about the current Continuous Integration environment",
"devDependencies": {
"clear-require": "^1.0.1",
"standard": "^12.0.1",
"tape": "^4.9.1"
},
"homepage": "https://github.com/watson/ci-info",
"keywords": [
"ci",
"continuous",
"integration",
"test",
"detect"
],
"license": "MIT",
"main": "index.js",
"name": "ci-info",
"repository": {
"type": "git",
"url": "git+https://github.com/watson/ci-info.git"
},
"scripts": {
"test": "standard && node test.js"
},
"version": "2.0.0"
}
+153
View File
@@ -0,0 +1,153 @@
[
{
"name": "AppVeyor",
"constant": "APPVEYOR",
"env": "APPVEYOR",
"pr": "APPVEYOR_PULL_REQUEST_NUMBER"
},
{
"name": "Azure Pipelines",
"constant": "AZURE_PIPELINES",
"env": "SYSTEM_TEAMFOUNDATIONCOLLECTIONURI",
"pr": "SYSTEM_PULLREQUEST_PULLREQUESTID"
},
{
"name": "Bamboo",
"constant": "BAMBOO",
"env": "bamboo_planKey"
},
{
"name": "Bitbucket Pipelines",
"constant": "BITBUCKET",
"env": "BITBUCKET_COMMIT",
"pr": "BITBUCKET_PR_ID"
},
{
"name": "Bitrise",
"constant": "BITRISE",
"env": "BITRISE_IO",
"pr": "BITRISE_PULL_REQUEST"
},
{
"name": "Buddy",
"constant": "BUDDY",
"env": "BUDDY_WORKSPACE_ID",
"pr": "BUDDY_EXECUTION_PULL_REQUEST_ID"
},
{
"name": "Buildkite",
"constant": "BUILDKITE",
"env": "BUILDKITE",
"pr": { "env": "BUILDKITE_PULL_REQUEST", "ne": "false" }
},
{
"name": "CircleCI",
"constant": "CIRCLE",
"env": "CIRCLECI",
"pr": "CIRCLE_PULL_REQUEST"
},
{
"name": "Cirrus CI",
"constant": "CIRRUS",
"env": "CIRRUS_CI",
"pr": "CIRRUS_PR"
},
{
"name": "AWS CodeBuild",
"constant": "CODEBUILD",
"env": "CODEBUILD_BUILD_ARN"
},
{
"name": "Codeship",
"constant": "CODESHIP",
"env": { "CI_NAME": "codeship" }
},
{
"name": "Drone",
"constant": "DRONE",
"env": "DRONE",
"pr": { "DRONE_BUILD_EVENT": "pull_request" }
},
{
"name": "dsari",
"constant": "DSARI",
"env": "DSARI"
},
{
"name": "GitLab CI",
"constant": "GITLAB",
"env": "GITLAB_CI"
},
{
"name": "GoCD",
"constant": "GOCD",
"env": "GO_PIPELINE_LABEL"
},
{
"name": "Hudson",
"constant": "HUDSON",
"env": "HUDSON_URL"
},
{
"name": "Jenkins",
"constant": "JENKINS",
"env": ["JENKINS_URL", "BUILD_ID"],
"pr": { "any": ["ghprbPullId", "CHANGE_ID"] }
},
{
"name": "Magnum CI",
"constant": "MAGNUM",
"env": "MAGNUM"
},
{
"name": "Netlify CI",
"constant": "NETLIFY",
"env": "NETLIFY_BUILD_BASE",
"pr": { "env": "PULL_REQUEST", "ne": "false" }
},
{
"name": "Sail CI",
"constant": "SAIL",
"env": "SAILCI",
"pr": "SAIL_PULL_REQUEST_NUMBER"
},
{
"name": "Semaphore",
"constant": "SEMAPHORE",
"env": "SEMAPHORE",
"pr": "PULL_REQUEST_NUMBER"
},
{
"name": "Shippable",
"constant": "SHIPPABLE",
"env": "SHIPPABLE",
"pr": { "IS_PULL_REQUEST": "true" }
},
{
"name": "Solano CI",
"constant": "SOLANO",
"env": "TDDIUM",
"pr": "TDDIUM_PR_ID"
},
{
"name": "Strider CD",
"constant": "STRIDER",
"env": "STRIDER"
},
{
"name": "TaskCluster",
"constant": "TASKCLUSTER",
"env": ["TASK_ID", "RUN_ID"]
},
{
"name": "TeamCity",
"constant": "TEAMCITY",
"env": "TEAMCITY_VERSION"
},
{
"name": "Travis CI",
"constant": "TRAVIS",
"env": "TRAVIS",
"pr": { "env": "TRAVIS_PULL_REQUEST", "ne": "false" }
}
]
+137
View File
@@ -0,0 +1,137 @@
import {Options as LocatePathOptions} from 'locate-path';
declare const stop: unique symbol;
declare namespace findUp {
interface Options extends LocatePathOptions {}
type StopSymbol = typeof stop;
type Match = string | StopSymbol | undefined;
}
declare const findUp: {
/**
Find a file or directory by walking up parent directories.
@param name - Name of the file or directory to find. Can be multiple.
@returns The first path found (by respecting the order of `name`s) or `undefined` if none could be found.
@example
```
// /
// └── Users
// └── sindresorhus
// ├── unicorn.png
// └── foo
// └── bar
// ├── baz
// └── example.js
// example.js
import findUp = require('find-up');
(async () => {
console.log(await findUp('unicorn.png'));
//=> '/Users/sindresorhus/unicorn.png'
console.log(await findUp(['rainbow.png', 'unicorn.png']));
//=> '/Users/sindresorhus/unicorn.png'
})();
```
*/
(name: string | string[], options?: findUp.Options): Promise<string | undefined>;
/**
Find a file or directory by walking up parent directories.
@param matcher - Called for each directory in the search. Return a path or `findUp.stop` to stop the search.
@returns The first path found or `undefined` if none could be found.
@example
```
import path = require('path');
import findUp = require('find-up');
(async () => {
console.log(await findUp(async directory => {
const hasUnicorns = await findUp.exists(path.join(directory, 'unicorn.png'));
return hasUnicorns && directory;
}, {type: 'directory'}));
//=> '/Users/sindresorhus'
})();
```
*/
(matcher: (directory: string) => (findUp.Match | Promise<findUp.Match>), options?: findUp.Options): Promise<string | undefined>;
sync: {
/**
Synchronously find a file or directory by walking up parent directories.
@param name - Name of the file or directory to find. Can be multiple.
@returns The first path found (by respecting the order of `name`s) or `undefined` if none could be found.
*/
(name: string | string[], options?: findUp.Options): string | undefined;
/**
Synchronously find a file or directory by walking up parent directories.
@param matcher - Called for each directory in the search. Return a path or `findUp.stop` to stop the search.
@returns The first path found or `undefined` if none could be found.
@example
```
import path = require('path');
import findUp = require('find-up');
console.log(findUp.sync(directory => {
const hasUnicorns = findUp.sync.exists(path.join(directory, 'unicorn.png'));
return hasUnicorns && directory;
}, {type: 'directory'}));
//=> '/Users/sindresorhus'
```
*/
(matcher: (directory: string) => findUp.Match, options?: findUp.Options): string | undefined;
/**
Synchronously check if a path exists.
@param path - Path to the file or directory.
@returns Whether the path exists.
@example
```
import findUp = require('find-up');
console.log(findUp.sync.exists('/Users/sindresorhus/unicorn.png'));
//=> true
```
*/
exists(path: string): boolean;
}
/**
Check if a path exists.
@param path - Path to a file or directory.
@returns Whether the path exists.
@example
```
import findUp = require('find-up');
(async () => {
console.log(await findUp.exists('/Users/sindresorhus/unicorn.png'));
//=> true
})();
```
*/
exists(path: string): Promise<boolean>;
/**
Return this in a `matcher` function to stop the search and force `findUp` to immediately return `undefined`.
*/
readonly stop: findUp.StopSymbol;
};
export = findUp;
+89
View File
@@ -0,0 +1,89 @@
'use strict';
const path = require('path');
const locatePath = require('locate-path');
const pathExists = require('path-exists');
const stop = Symbol('findUp.stop');
module.exports = async (name, options = {}) => {
let directory = path.resolve(options.cwd || '');
const {root} = path.parse(directory);
const paths = [].concat(name);
const runMatcher = async locateOptions => {
if (typeof name !== 'function') {
return locatePath(paths, locateOptions);
}
const foundPath = await name(locateOptions.cwd);
if (typeof foundPath === 'string') {
return locatePath([foundPath], locateOptions);
}
return foundPath;
};
// eslint-disable-next-line no-constant-condition
while (true) {
// eslint-disable-next-line no-await-in-loop
const foundPath = await runMatcher({...options, cwd: directory});
if (foundPath === stop) {
return;
}
if (foundPath) {
return path.resolve(directory, foundPath);
}
if (directory === root) {
return;
}
directory = path.dirname(directory);
}
};
module.exports.sync = (name, options = {}) => {
let directory = path.resolve(options.cwd || '');
const {root} = path.parse(directory);
const paths = [].concat(name);
const runMatcher = locateOptions => {
if (typeof name !== 'function') {
return locatePath.sync(paths, locateOptions);
}
const foundPath = name(locateOptions.cwd);
if (typeof foundPath === 'string') {
return locatePath.sync([foundPath], locateOptions);
}
return foundPath;
};
// eslint-disable-next-line no-constant-condition
while (true) {
const foundPath = runMatcher({...options, cwd: directory});
if (foundPath === stop) {
return;
}
if (foundPath) {
return path.resolve(directory, foundPath);
}
if (directory === root) {
return;
}
directory = path.dirname(directory);
}
};
module.exports.exists = pathExists;
module.exports.sync.exists = pathExists.sync;
module.exports.stop = stop;
+9
View File
@@ -0,0 +1,9 @@
MIT License
Copyright (c) Sindre Sorhus <sindresorhus@gmail.com> (sindresorhus.com)
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+89
View File
@@ -0,0 +1,89 @@
{
"_args": [
[
"find-up@4.1.0",
"/mnt/Foxconn/Digitalent/Deverloper/liff-push_2series"
]
],
"_development": true,
"_from": "find-up@4.1.0",
"_id": "find-up@4.1.0",
"_inBundle": false,
"_integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==",
"_location": "/eslint-plugin-unicorn/find-up",
"_phantomChildren": {},
"_requested": {
"type": "version",
"registry": true,
"raw": "find-up@4.1.0",
"name": "find-up",
"escapedName": "find-up",
"rawSpec": "4.1.0",
"saveSpec": null,
"fetchSpec": "4.1.0"
},
"_requiredBy": [
"/eslint-plugin-unicorn/read-pkg-up"
],
"_resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz",
"_spec": "4.1.0",
"_where": "/mnt/Foxconn/Digitalent/Deverloper/liff-push_2series",
"author": {
"name": "Sindre Sorhus",
"email": "sindresorhus@gmail.com",
"url": "sindresorhus.com"
},
"bugs": {
"url": "https://github.com/sindresorhus/find-up/issues"
},
"dependencies": {
"locate-path": "^5.0.0",
"path-exists": "^4.0.0"
},
"description": "Find a file or directory by walking up parent directories",
"devDependencies": {
"ava": "^2.1.0",
"is-path-inside": "^2.1.0",
"tempy": "^0.3.0",
"tsd": "^0.7.3",
"xo": "^0.24.0"
},
"engines": {
"node": ">=8"
},
"files": [
"index.js",
"index.d.ts"
],
"homepage": "https://github.com/sindresorhus/find-up#readme",
"keywords": [
"find",
"up",
"find-up",
"findup",
"look-up",
"look",
"file",
"search",
"match",
"package",
"resolve",
"parent",
"parents",
"folder",
"directory",
"walk",
"walking",
"path"
],
"license": "MIT",
"name": "find-up",
"repository": {
"type": "git",
"url": "git+https://github.com/sindresorhus/find-up.git"
},
"scripts": {
"test": "xo && ava && tsd"
},
"version": "4.1.0"
}
+156
View File
@@ -0,0 +1,156 @@
# find-up [![Build Status](https://travis-ci.org/sindresorhus/find-up.svg?branch=master)](https://travis-ci.org/sindresorhus/find-up)
> Find a file or directory by walking up parent directories
## Install
```
$ npm install find-up
```
## Usage
```
/
└── Users
└── sindresorhus
├── unicorn.png
└── foo
└── bar
├── baz
└── example.js
```
`example.js`
```js
const path = require('path');
const findUp = require('find-up');
(async () => {
console.log(await findUp('unicorn.png'));
//=> '/Users/sindresorhus/unicorn.png'
console.log(await findUp(['rainbow.png', 'unicorn.png']));
//=> '/Users/sindresorhus/unicorn.png'
console.log(await findUp(async directory => {
const hasUnicorns = await findUp.exists(path.join(directory, 'unicorn.png'));
return hasUnicorns && directory;
}, {type: 'directory'}));
//=> '/Users/sindresorhus'
})();
```
## API
### findUp(name, options?)
### findUp(matcher, options?)
Returns a `Promise` for either the path or `undefined` if it couldn't be found.
### findUp([...name], options?)
Returns a `Promise` for either the first path found (by respecting the order of the array) or `undefined` if none could be found.
### findUp.sync(name, options?)
### findUp.sync(matcher, options?)
Returns a path or `undefined` if it couldn't be found.
### findUp.sync([...name], options?)
Returns the first path found (by respecting the order of the array) or `undefined` if none could be found.
#### name
Type: `string`
Name of the file or directory to find.
#### matcher
Type: `Function`
A function that will be called with each directory until it returns a `string` with the path, which stops the search, or the root directory has been reached and nothing was found. Useful if you want to match files with certain patterns, set of permissions, or other advanced use-cases.
When using async mode, the `matcher` may optionally be an async or promise-returning function that returns the path.
#### options
Type: `object`
##### cwd
Type: `string`<br>
Default: `process.cwd()`
Directory to start from.
##### type
Type: `string`<br>
Default: `'file'`<br>
Values: `'file'` `'directory'`
The type of paths that can match.
##### allowSymlinks
Type: `boolean`<br>
Default: `true`
Allow symbolic links to match if they point to the chosen path type.
### findUp.exists(path)
Returns a `Promise<boolean>` of whether the path exists.
### findUp.sync.exists(path)
Returns a `boolean` of whether the path exists.
#### path
Type: `string`
Path to a file or directory.
### findUp.stop
A [`Symbol`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol) that can be returned by a `matcher` function to stop the search and cause `findUp` to immediately return `undefined`. Useful as a performance optimization in case the current working directory is deeply nested in the filesystem.
```js
const path = require('path');
const findUp = require('find-up');
(async () => {
await findUp(directory => {
return path.basename(directory) === 'work' ? findUp.stop : 'logo.png';
});
})();
```
## Related
- [find-up-cli](https://github.com/sindresorhus/find-up-cli) - CLI for this module
- [pkg-up](https://github.com/sindresorhus/pkg-up) - Find the closest package.json file
- [pkg-dir](https://github.com/sindresorhus/pkg-dir) - Find the root directory of an npm package
- [resolve-from](https://github.com/sindresorhus/resolve-from) - Resolve the path of a module like `require.resolve()` but from a given path
---
<div align="center">
<b>
<a href="https://tidelift.com/subscription/pkg/npm-find-up?utm_source=npm-find-up&utm_medium=referral&utm_campaign=readme">Get professional support for 'find-up' with a Tidelift subscription</a>
</b>
<br>
<sub>
Tidelift helps make open source sustainable for maintainers while giving companies<br>assurances about security, maintenance, and licensing for their dependencies.
</sub>
</div>
+83
View File
@@ -0,0 +1,83 @@
declare namespace locatePath {
interface Options {
/**
Current working directory.
@default process.cwd()
*/
readonly cwd?: string;
/**
Type of path to match.
@default 'file'
*/
readonly type?: 'file' | 'directory';
/**
Allow symbolic links to match if they point to the requested path type.
@default true
*/
readonly allowSymlinks?: boolean;
}
interface AsyncOptions extends Options {
/**
Number of concurrently pending promises. Minimum: `1`.
@default Infinity
*/
readonly concurrency?: number;
/**
Preserve `paths` order when searching.
Disable this to improve performance if you don't care about the order.
@default true
*/
readonly preserveOrder?: boolean;
}
}
declare const locatePath: {
/**
Get the first path that exists on disk of multiple paths.
@param paths - Paths to check.
@returns The first path that exists or `undefined` if none exists.
@example
```
import locatePath = require('locate-path');
const files = [
'unicorn.png',
'rainbow.png', // Only this one actually exists on disk
'pony.png'
];
(async () => {
console(await locatePath(files));
//=> 'rainbow'
})();
```
*/
(paths: Iterable<string>, options?: locatePath.AsyncOptions): Promise<
string | undefined
>;
/**
Synchronously get the first path that exists on disk of multiple paths.
@param paths - Paths to check.
@returns The first path that exists or `undefined` if none exists.
*/
sync(
paths: Iterable<string>,
options?: locatePath.Options
): string | undefined;
};
export = locatePath;
+65
View File
@@ -0,0 +1,65 @@
'use strict';
const path = require('path');
const fs = require('fs');
const {promisify} = require('util');
const pLocate = require('p-locate');
const fsStat = promisify(fs.stat);
const fsLStat = promisify(fs.lstat);
const typeMappings = {
directory: 'isDirectory',
file: 'isFile'
};
function checkType({type}) {
if (type in typeMappings) {
return;
}
throw new Error(`Invalid type specified: ${type}`);
}
const matchType = (type, stat) => type === undefined || stat[typeMappings[type]]();
module.exports = async (paths, options) => {
options = {
cwd: process.cwd(),
type: 'file',
allowSymlinks: true,
...options
};
checkType(options);
const statFn = options.allowSymlinks ? fsStat : fsLStat;
return pLocate(paths, async path_ => {
try {
const stat = await statFn(path.resolve(options.cwd, path_));
return matchType(options.type, stat);
} catch (_) {
return false;
}
}, options);
};
module.exports.sync = (paths, options) => {
options = {
cwd: process.cwd(),
allowSymlinks: true,
type: 'file',
...options
};
checkType(options);
const statFn = options.allowSymlinks ? fs.statSync : fs.lstatSync;
for (const path_ of paths) {
try {
const stat = statFn(path.resolve(options.cwd, path_));
if (matchType(options.type, stat)) {
return path_;
}
} catch (_) {
}
}
};
+9
View File
@@ -0,0 +1,9 @@
MIT License
Copyright (c) Sindre Sorhus <sindresorhus@gmail.com> (sindresorhus.com)
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,81 @@
{
"_args": [
[
"locate-path@5.0.0",
"/mnt/Foxconn/Digitalent/Deverloper/liff-push_2series"
]
],
"_development": true,
"_from": "locate-path@5.0.0",
"_id": "locate-path@5.0.0",
"_inBundle": false,
"_integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==",
"_location": "/eslint-plugin-unicorn/locate-path",
"_phantomChildren": {},
"_requested": {
"type": "version",
"registry": true,
"raw": "locate-path@5.0.0",
"name": "locate-path",
"escapedName": "locate-path",
"rawSpec": "5.0.0",
"saveSpec": null,
"fetchSpec": "5.0.0"
},
"_requiredBy": [
"/eslint-plugin-unicorn/find-up"
],
"_resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz",
"_spec": "5.0.0",
"_where": "/mnt/Foxconn/Digitalent/Deverloper/liff-push_2series",
"author": {
"name": "Sindre Sorhus",
"email": "sindresorhus@gmail.com",
"url": "sindresorhus.com"
},
"bugs": {
"url": "https://github.com/sindresorhus/locate-path/issues"
},
"dependencies": {
"p-locate": "^4.1.0"
},
"description": "Get the first path that exists on disk of multiple paths",
"devDependencies": {
"ava": "^1.4.1",
"tsd": "^0.7.2",
"xo": "^0.24.0"
},
"engines": {
"node": ">=8"
},
"files": [
"index.js",
"index.d.ts"
],
"homepage": "https://github.com/sindresorhus/locate-path#readme",
"keywords": [
"locate",
"path",
"paths",
"file",
"files",
"exists",
"find",
"finder",
"search",
"searcher",
"array",
"iterable",
"iterator"
],
"license": "MIT",
"name": "locate-path",
"repository": {
"type": "git",
"url": "git+https://github.com/sindresorhus/locate-path.git"
},
"scripts": {
"test": "xo && ava && tsd"
},
"version": "5.0.0"
}
+122
View File
@@ -0,0 +1,122 @@
# locate-path [![Build Status](https://travis-ci.org/sindresorhus/locate-path.svg?branch=master)](https://travis-ci.org/sindresorhus/locate-path)
> Get the first path that exists on disk of multiple paths
## Install
```
$ npm install locate-path
```
## Usage
Here we find the first file that exists on disk, in array order.
```js
const locatePath = require('locate-path');
const files = [
'unicorn.png',
'rainbow.png', // Only this one actually exists on disk
'pony.png'
];
(async () => {
console(await locatePath(files));
//=> 'rainbow'
})();
```
## API
### locatePath(paths, [options])
Returns a `Promise<string>` for the first path that exists or `undefined` if none exists.
#### paths
Type: `Iterable<string>`
Paths to check.
#### options
Type: `Object`
##### concurrency
Type: `number`<br>
Default: `Infinity`<br>
Minimum: `1`
Number of concurrently pending promises.
##### preserveOrder
Type: `boolean`<br>
Default: `true`
Preserve `paths` order when searching.
Disable this to improve performance if you don't care about the order.
##### cwd
Type: `string`<br>
Default: `process.cwd()`
Current working directory.
##### type
Type: `string`<br>
Default: `file`<br>
Values: `file` `directory`
The type of paths that can match.
##### allowSymlinks
Type: `boolean`<br>
Default: `true`
Allow symbolic links to match if they point to the chosen path type.
### locatePath.sync(paths, [options])
Returns the first path that exists or `undefined` if none exists.
#### paths
Type: `Iterable<string>`
Paths to check.
#### options
Type: `Object`
##### cwd
Same as above.
##### type
Same as above.
##### allowSymlinks
Same as above.
## Related
- [path-exists](https://github.com/sindresorhus/path-exists) - Check if a path exists
## License
MIT © [Sindre Sorhus](https://sindresorhus.com)
+64
View File
@@ -0,0 +1,64 @@
declare namespace pLocate {
interface Options {
/**
Number of concurrently pending promises returned by `tester`. Minimum: `1`.
@default Infinity
*/
readonly concurrency?: number;
/**
Preserve `input` order when searching.
Disable this to improve performance if you don't care about the order.
@default true
*/
readonly preserveOrder?: boolean;
}
}
declare const pLocate: {
/**
Get the first fulfilled promise that satisfies the provided testing function.
@param input - An iterable of promises/values to test.
@param tester - This function will receive resolved values from `input` and is expected to return a `Promise<boolean>` or `boolean`.
@returns A `Promise` that is fulfilled when `tester` resolves to `true` or the iterable is done, or rejects if any of the promises reject. The fulfilled value is the current iterable value or `undefined` if `tester` never resolved to `true`.
@example
```
import pathExists = require('path-exists');
import pLocate = require('p-locate');
const files = [
'unicorn.png',
'rainbow.png', // Only this one actually exists on disk
'pony.png'
];
(async () => {
const foundPath = await pLocate(files, file => pathExists(file));
console.log(foundPath);
//=> 'rainbow'
})();
```
*/
<ValueType>(
input: Iterable<PromiseLike<ValueType> | ValueType>,
tester: (element: ValueType) => PromiseLike<boolean> | boolean,
options?: pLocate.Options
): Promise<ValueType | undefined>;
// TODO: Remove this for the next major release, refactor the whole definition to:
// declare function pLocate<ValueType>(
// input: Iterable<PromiseLike<ValueType> | ValueType>,
// tester: (element: ValueType) => PromiseLike<boolean> | boolean,
// options?: pLocate.Options
// ): Promise<ValueType | undefined>;
// export = pLocate;
default: typeof pLocate;
};
export = pLocate;
+52
View File
@@ -0,0 +1,52 @@
'use strict';
const pLimit = require('p-limit');
class EndError extends Error {
constructor(value) {
super();
this.value = value;
}
}
// The input can also be a promise, so we await it
const testElement = async (element, tester) => tester(await element);
// The input can also be a promise, so we `Promise.all()` them both
const finder = async element => {
const values = await Promise.all(element);
if (values[1] === true) {
throw new EndError(values[0]);
}
return false;
};
const pLocate = async (iterable, tester, options) => {
options = {
concurrency: Infinity,
preserveOrder: true,
...options
};
const limit = pLimit(options.concurrency);
// Start all the promises concurrently with optional limit
const items = [...iterable].map(element => [element, limit(testElement, element, tester)]);
// Check the promises either serially or concurrently
const checkLimit = pLimit(options.preserveOrder ? 1 : Infinity);
try {
await Promise.all(items.map(element => checkLimit(finder, element)));
} catch (error) {
if (error instanceof EndError) {
return error.value;
}
throw error;
}
};
module.exports = pLocate;
// TODO: Remove this for the next major release
module.exports.default = pLocate;
+9
View File
@@ -0,0 +1,9 @@
MIT License
Copyright (c) Sindre Sorhus <sindresorhus@gmail.com> (sindresorhus.com)
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+89
View File
@@ -0,0 +1,89 @@
{
"_args": [
[
"p-locate@4.1.0",
"/mnt/Foxconn/Digitalent/Deverloper/liff-push_2series"
]
],
"_development": true,
"_from": "p-locate@4.1.0",
"_id": "p-locate@4.1.0",
"_inBundle": false,
"_integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==",
"_location": "/eslint-plugin-unicorn/p-locate",
"_phantomChildren": {},
"_requested": {
"type": "version",
"registry": true,
"raw": "p-locate@4.1.0",
"name": "p-locate",
"escapedName": "p-locate",
"rawSpec": "4.1.0",
"saveSpec": null,
"fetchSpec": "4.1.0"
},
"_requiredBy": [
"/eslint-plugin-unicorn/locate-path"
],
"_resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz",
"_spec": "4.1.0",
"_where": "/mnt/Foxconn/Digitalent/Deverloper/liff-push_2series",
"author": {
"name": "Sindre Sorhus",
"email": "sindresorhus@gmail.com",
"url": "sindresorhus.com"
},
"bugs": {
"url": "https://github.com/sindresorhus/p-locate/issues"
},
"dependencies": {
"p-limit": "^2.2.0"
},
"description": "Get the first fulfilled promise that satisfies the provided testing function",
"devDependencies": {
"ava": "^1.4.1",
"delay": "^4.1.0",
"in-range": "^1.0.0",
"time-span": "^3.0.0",
"tsd": "^0.7.2",
"xo": "^0.24.0"
},
"engines": {
"node": ">=8"
},
"files": [
"index.js",
"index.d.ts"
],
"homepage": "https://github.com/sindresorhus/p-locate#readme",
"keywords": [
"promise",
"locate",
"find",
"finder",
"search",
"searcher",
"test",
"array",
"collection",
"iterable",
"iterator",
"race",
"fulfilled",
"fastest",
"async",
"await",
"promises",
"bluebird"
],
"license": "MIT",
"name": "p-locate",
"repository": {
"type": "git",
"url": "git+https://github.com/sindresorhus/p-locate.git"
},
"scripts": {
"test": "xo && ava && tsd"
},
"version": "4.1.0"
}
+90
View File
@@ -0,0 +1,90 @@
# p-locate [![Build Status](https://travis-ci.org/sindresorhus/p-locate.svg?branch=master)](https://travis-ci.org/sindresorhus/p-locate)
> Get the first fulfilled promise that satisfies the provided testing function
Think of it like an async version of [`Array#find`](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Array/find).
## Install
```
$ npm install p-locate
```
## Usage
Here we find the first file that exists on disk, in array order.
```js
const pathExists = require('path-exists');
const pLocate = require('p-locate');
const files = [
'unicorn.png',
'rainbow.png', // Only this one actually exists on disk
'pony.png'
];
(async () => {
const foundPath = await pLocate(files, file => pathExists(file));
console.log(foundPath);
//=> 'rainbow'
})();
```
*The above is just an example. Use [`locate-path`](https://github.com/sindresorhus/locate-path) if you need this.*
## API
### pLocate(input, tester, [options])
Returns a `Promise` that is fulfilled when `tester` resolves to `true` or the iterable is done, or rejects if any of the promises reject. The fulfilled value is the current iterable value or `undefined` if `tester` never resolved to `true`.
#### input
Type: `Iterable<Promise | unknown>`
An iterable of promises/values to test.
#### tester(element)
Type: `Function`
This function will receive resolved values from `input` and is expected to return a `Promise<boolean>` or `boolean`.
#### options
Type: `Object`
##### concurrency
Type: `number`<br>
Default: `Infinity`<br>
Minimum: `1`
Number of concurrently pending promises returned by `tester`.
##### preserveOrder
Type: `boolean`<br>
Default: `true`
Preserve `input` order when searching.
Disable this to improve performance if you don't care about the order.
## Related
- [p-map](https://github.com/sindresorhus/p-map) - Map over promises concurrently
- [p-filter](https://github.com/sindresorhus/p-filter) - Filter promises concurrently
- [p-any](https://github.com/sindresorhus/p-any) - Wait for any promise to be fulfilled
- [More…](https://github.com/sindresorhus/promise-fun)
## License
MIT © [Sindre Sorhus](https://sindresorhus.com)
+50
View File
@@ -0,0 +1,50 @@
'use strict';
const errorEx = require('error-ex');
const fallback = require('json-parse-better-errors');
const {default: LinesAndColumns} = require('lines-and-columns');
const {codeFrameColumns} = require('@babel/code-frame');
const JSONError = errorEx('JSONError', {
fileName: errorEx.append('in %s'),
codeFrame: errorEx.append('\n\n%s\n')
});
module.exports = (string, reviver, filename) => {
if (typeof reviver === 'string') {
filename = reviver;
reviver = null;
}
try {
try {
return JSON.parse(string, reviver);
} catch (error) {
fallback(string, reviver);
throw error;
}
} catch (error) {
error.message = error.message.replace(/\n/g, '');
const indexMatch = error.message.match(/in JSON at position (\d+) while parsing near/);
const jsonError = new JSONError(error);
if (filename) {
jsonError.fileName = filename;
}
if (indexMatch && indexMatch.length > 0) {
const lines = new LinesAndColumns(string);
const index = Number(indexMatch[1]);
const location = lines.locationForIndex(index);
const codeFrame = codeFrameColumns(
string,
{start: {line: location.line + 1, column: location.column + 1}},
{highlightCode: true}
);
jsonError.codeFrame = codeFrame;
}
throw jsonError;
}
};
+9
View File
@@ -0,0 +1,9 @@
MIT License
Copyright (c) Sindre Sorhus <sindresorhus@gmail.com> (sindresorhus.com)
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,80 @@
{
"_args": [
[
"parse-json@5.0.0",
"/mnt/Foxconn/Digitalent/Deverloper/liff-push_2series"
]
],
"_development": true,
"_from": "parse-json@5.0.0",
"_id": "parse-json@5.0.0",
"_inBundle": false,
"_integrity": "sha512-OOY5b7PAEFV0E2Fir1KOkxchnZNCdowAJgQ5NuxjpBKTRP3pQhwkrkxqQjeoKJ+fO7bCpmIZaogI4eZGDMEGOw==",
"_location": "/eslint-plugin-unicorn/parse-json",
"_phantomChildren": {},
"_requested": {
"type": "version",
"registry": true,
"raw": "parse-json@5.0.0",
"name": "parse-json",
"escapedName": "parse-json",
"rawSpec": "5.0.0",
"saveSpec": null,
"fetchSpec": "5.0.0"
},
"_requiredBy": [
"/eslint-plugin-unicorn/read-pkg"
],
"_resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.0.0.tgz",
"_spec": "5.0.0",
"_where": "/mnt/Foxconn/Digitalent/Deverloper/liff-push_2series",
"author": {
"name": "Sindre Sorhus",
"email": "sindresorhus@gmail.com",
"url": "sindresorhus.com"
},
"bugs": {
"url": "https://github.com/sindresorhus/parse-json/issues"
},
"dependencies": {
"@babel/code-frame": "^7.0.0",
"error-ex": "^1.3.1",
"json-parse-better-errors": "^1.0.1",
"lines-and-columns": "^1.1.6"
},
"description": "Parse JSON with more helpful errors",
"devDependencies": {
"ava": "^1.4.1",
"nyc": "^14.1.1",
"xo": "^0.24.0"
},
"engines": {
"node": ">=8"
},
"files": [
"index.js",
"vendor"
],
"homepage": "https://github.com/sindresorhus/parse-json#readme",
"keywords": [
"parse",
"json",
"graceful",
"error",
"message",
"humanize",
"friendly",
"helpful",
"string"
],
"license": "MIT",
"name": "parse-json",
"repository": {
"type": "git",
"url": "git+https://github.com/sindresorhus/parse-json.git"
},
"scripts": {
"test": "xo && nyc ava"
},
"version": "5.0.0"
}
+101
View File
@@ -0,0 +1,101 @@
# parse-json [![Build Status](https://travis-ci.org/sindresorhus/parse-json.svg?branch=master)](https://travis-ci.org/sindresorhus/parse-json)
> Parse JSON with more helpful errors
## Install
```
$ npm install parse-json
```
## Usage
```js
const parseJson = require('parse-json');
const json = '{\n\t"foo": true,\n}';
JSON.parse(json);
/*
undefined:3
}
^
SyntaxError: Unexpected token }
*/
parseJson(json);
/*
JSONError: Unexpected token } in JSON at position 16 while parsing near '{ "foo": true,}'
1 | {
2 | "foo": true,
> 3 | }
| ^
*/
parseJson(json, 'foo.json');
/*
JSONError: Unexpected token } in JSON at position 16 while parsing near '{ "foo": true,}' in foo.json
1 | {
2 | "foo": true,
> 3 | }
| ^
*/
// You can also add the filename at a later point
try {
parseJson(json);
} catch (error) {
error.fileName = 'foo.json';
throw error;
}
/*
JSONError: Unexpected token } in JSON at position 16 while parsing near '{ "foo": true,}' in foo.json
1 | {
2 | "foo": true,
> 3 | }
| ^
*/
```
## API
### parseJson(string, reviver?, filename?)
#### string
Type: `string`
#### reviver
Type: `Function`
Prescribes how the value originally produced by parsing is transformed, before being returned. See [`JSON.parse` docs](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/parse#Using_the_reviver_parameter
) for more.
#### filename
Type: `string`
Filename displayed in the error message.
---
<div align="center">
<b>
<a href="https://tidelift.com/subscription/pkg/npm-parse-json?utm_source=npm-parse-json&utm_medium=referral&utm_campaign=readme">Get professional support for this package with a Tidelift subscription</a>
</b>
<br>
<sub>
Tidelift helps make open source sustainable for maintainers while giving companies<br>assurances about security, maintenance, and licensing for their dependencies.
</sub>
</div>
+28
View File
@@ -0,0 +1,28 @@
declare const pathExists: {
/**
Check if a path exists.
@returns Whether the path exists.
@example
```
// foo.ts
import pathExists = require('path-exists');
(async () => {
console.log(await pathExists('foo.ts'));
//=> true
})();
```
*/
(path: string): Promise<boolean>;
/**
Synchronously check if a path exists.
@returns Whether the path exists.
*/
sync(path: string): boolean;
};
export = pathExists;
+23
View File
@@ -0,0 +1,23 @@
'use strict';
const fs = require('fs');
const {promisify} = require('util');
const pAccess = promisify(fs.access);
module.exports = async path => {
try {
await pAccess(path);
return true;
} catch (_) {
return false;
}
};
module.exports.sync = path => {
try {
fs.accessSync(path);
return true;
} catch (_) {
return false;
}
};
+9
View File
@@ -0,0 +1,9 @@
MIT License
Copyright (c) Sindre Sorhus <sindresorhus@gmail.com> (sindresorhus.com)
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,75 @@
{
"_args": [
[
"path-exists@4.0.0",
"/mnt/Foxconn/Digitalent/Deverloper/liff-push_2series"
]
],
"_development": true,
"_from": "path-exists@4.0.0",
"_id": "path-exists@4.0.0",
"_inBundle": false,
"_integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==",
"_location": "/eslint-plugin-unicorn/path-exists",
"_phantomChildren": {},
"_requested": {
"type": "version",
"registry": true,
"raw": "path-exists@4.0.0",
"name": "path-exists",
"escapedName": "path-exists",
"rawSpec": "4.0.0",
"saveSpec": null,
"fetchSpec": "4.0.0"
},
"_requiredBy": [
"/eslint-plugin-unicorn/find-up"
],
"_resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
"_spec": "4.0.0",
"_where": "/mnt/Foxconn/Digitalent/Deverloper/liff-push_2series",
"author": {
"name": "Sindre Sorhus",
"email": "sindresorhus@gmail.com",
"url": "sindresorhus.com"
},
"bugs": {
"url": "https://github.com/sindresorhus/path-exists/issues"
},
"description": "Check if a path exists",
"devDependencies": {
"ava": "^1.4.1",
"tsd": "^0.7.2",
"xo": "^0.24.0"
},
"engines": {
"node": ">=8"
},
"files": [
"index.js",
"index.d.ts"
],
"homepage": "https://github.com/sindresorhus/path-exists#readme",
"keywords": [
"path",
"exists",
"exist",
"file",
"filepath",
"fs",
"filesystem",
"file-system",
"access",
"stat"
],
"license": "MIT",
"name": "path-exists",
"repository": {
"type": "git",
"url": "git+https://github.com/sindresorhus/path-exists.git"
},
"scripts": {
"test": "xo && ava && tsd"
},
"version": "4.0.0"
}
+52
View File
@@ -0,0 +1,52 @@
# path-exists [![Build Status](https://travis-ci.org/sindresorhus/path-exists.svg?branch=master)](https://travis-ci.org/sindresorhus/path-exists)
> Check if a path exists
NOTE: `fs.existsSync` has been un-deprecated in Node.js since 6.8.0. If you only need to check synchronously, this module is not needed.
While [`fs.exists()`](https://nodejs.org/api/fs.html#fs_fs_exists_path_callback) is being [deprecated](https://github.com/iojs/io.js/issues/103), there's still a genuine use-case of being able to check if a path exists for other purposes than doing IO with it.
Never use this before handling a file though:
> In particular, checking if a file exists before opening it is an anti-pattern that leaves you vulnerable to race conditions: another process may remove the file between the calls to `fs.exists()` and `fs.open()`. Just open the file and handle the error when it's not there.
## Install
```
$ npm install path-exists
```
## Usage
```js
// foo.js
const pathExists = require('path-exists');
(async () => {
console.log(await pathExists('foo.js'));
//=> true
})();
```
## API
### pathExists(path)
Returns a `Promise<boolean>` of whether the path exists.
### pathExists.sync(path)
Returns a `boolean` of whether the path exists.
## Related
- [path-exists-cli](https://github.com/sindresorhus/path-exists-cli) - CLI for this module
## License
MIT © [Sindre Sorhus](https://sindresorhus.com)
+87
View File
@@ -0,0 +1,87 @@
import {Except} from 'type-fest';
import readPkg = require('read-pkg');
declare namespace readPkgUp {
type Options = {
/**
Directory to start looking for a package.json file.
@default process.cwd()
*/
cwd?: string;
} & Except<readPkg.Options, 'cwd'>;
type NormalizeOptions = {
/**
Directory to start looking for a package.json file.
@default process.cwd()
*/
cwd?: string;
} & Except<readPkg.NormalizeOptions, 'cwd'>;
type PackageJson = readPkg.PackageJson;
type NormalizedPackageJson = readPkg.NormalizedPackageJson;
interface ReadResult {
packageJson: PackageJson;
path: string;
}
interface NormalizedReadResult {
packageJson: NormalizedPackageJson;
path: string;
}
}
declare const readPkgUp: {
/**
Read the closest `package.json` file.
@example
```
import readPkgUp = require('read-pkg-up');
(async () => {
console.log(await readPkgUp());
// {
// packageJson: {
// name: 'awesome-package',
// version: '1.0.0',
// …
// },
// path: '/Users/sindresorhus/dev/awesome-package/package.json'
// }
})();
```
*/
(options?: readPkgUp.NormalizeOptions): Promise<
readPkgUp.NormalizedReadResult | undefined
>;
(options: readPkgUp.Options): Promise<readPkgUp.ReadResult | undefined>;
/**
Synchronously read the closest `package.json` file.
@example
```
import readPkgUp = require('read-pkg-up');
console.log(readPkgUp.sync());
// {
// packageJson: {
// name: 'awesome-package',
// version: '1.0.0',
// …
// },
// path: '/Users/sindresorhus/dev/awesome-package/package.json'
// }
```
*/
sync(
options?: readPkgUp.NormalizeOptions
): readPkgUp.NormalizedReadResult | undefined;
sync(options: readPkgUp.Options): readPkgUp.ReadResult | undefined;
};
export = readPkgUp;
+30
View File
@@ -0,0 +1,30 @@
'use strict';
const path = require('path');
const findUp = require('find-up');
const readPkg = require('read-pkg');
module.exports = async options => {
const filePath = await findUp('package.json', options);
if (!filePath) {
return;
}
return {
packageJson: await readPkg({...options, cwd: path.dirname(filePath)}),
path: filePath
};
};
module.exports.sync = options => {
const filePath = findUp.sync('package.json', options);
if (!filePath) {
return;
}
return {
packageJson: readPkg.sync({...options, cwd: path.dirname(filePath)}),
path: filePath
};
};
+9
View File
@@ -0,0 +1,9 @@
MIT License
Copyright (c) Sindre Sorhus <sindresorhus@gmail.com> (sindresorhus.com)
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,95 @@
{
"_args": [
[
"read-pkg-up@7.0.1",
"/mnt/Foxconn/Digitalent/Deverloper/liff-push_2series"
]
],
"_development": true,
"_from": "read-pkg-up@7.0.1",
"_id": "read-pkg-up@7.0.1",
"_inBundle": false,
"_integrity": "sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==",
"_location": "/eslint-plugin-unicorn/read-pkg-up",
"_phantomChildren": {},
"_requested": {
"type": "version",
"registry": true,
"raw": "read-pkg-up@7.0.1",
"name": "read-pkg-up",
"escapedName": "read-pkg-up",
"rawSpec": "7.0.1",
"saveSpec": null,
"fetchSpec": "7.0.1"
},
"_requiredBy": [
"/eslint-plugin-unicorn"
],
"_resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-7.0.1.tgz",
"_spec": "7.0.1",
"_where": "/mnt/Foxconn/Digitalent/Deverloper/liff-push_2series",
"author": {
"name": "Sindre Sorhus",
"email": "sindresorhus@gmail.com",
"url": "sindresorhus.com"
},
"bugs": {
"url": "https://github.com/sindresorhus/read-pkg-up/issues"
},
"dependencies": {
"find-up": "^4.1.0",
"read-pkg": "^5.2.0",
"type-fest": "^0.8.1"
},
"description": "Read the closest package.json file",
"devDependencies": {
"ava": "^2.4.0",
"tsd": "^0.9.0",
"xo": "^0.25.3"
},
"engines": {
"node": ">=8"
},
"files": [
"index.js",
"index.d.ts"
],
"funding": "https://github.com/sponsors/sindresorhus",
"homepage": "https://github.com/sindresorhus/read-pkg-up#readme",
"keywords": [
"json",
"read",
"parse",
"file",
"fs",
"graceful",
"load",
"package",
"find",
"up",
"find-up",
"findup",
"look-up",
"look",
"search",
"match",
"resolve",
"parent",
"parents",
"folder",
"directory",
"walk",
"walking",
"path"
],
"license": "MIT",
"name": "read-pkg-up",
"repository": {
"type": "git",
"url": "git+https://github.com/sindresorhus/read-pkg-up.git"
},
"scripts": {
"test": "xo && ava && tsd"
},
"version": "7.0.1"
}
+77
View File
@@ -0,0 +1,77 @@
# read-pkg-up [![Build Status](https://travis-ci.org/sindresorhus/read-pkg-up.svg?branch=master)](https://travis-ci.org/sindresorhus/read-pkg-up)
> Read the closest package.json file
## Why
- [Finds the closest package.json](https://github.com/sindresorhus/find-up)
- [Gracefully handles filesystem issues](https://github.com/isaacs/node-graceful-fs)
- [Throws more helpful JSON errors](https://github.com/sindresorhus/parse-json)
- [Normalizes the data](https://github.com/npm/normalize-package-data#what-normalization-currently-entails)
## Install
```
$ npm install read-pkg-up
```
## Usage
```js
const readPkgUp = require('read-pkg-up');
(async () => {
console.log(await readPkgUp());
/*
{
packageJson: {
name: 'awesome-package',
version: '1.0.0',
},
path: '/Users/sindresorhus/dev/awesome-package/package.json'
}
*/
})();
```
## API
### readPkgUp(options?)
Returns a `Promise<object>` or `Promise<undefined>` if no `package.json` was found.
### readPkgUp.sync(options?)
Returns the result object or `undefined` if no `package.json` was found.
#### options
Type: `object`
##### cwd
Type: `string`\
Default: `process.cwd()`
Directory to start looking for a package.json file.
##### normalize
Type: `boolean`\
Default: `true`
[Normalize](https://github.com/npm/normalize-package-data#what-normalization-currently-entails) the package data.
## read-pkg-up for enterprise
Available as part of the Tidelift Subscription.
The maintainers of read-pkg-up and thousands of other packages are working with Tidelift to deliver commercial support and maintenance for the open source dependencies you use to build your applications. Save time, reduce risk, and improve code health, while paying the maintainers of the exact dependencies you use. [Learn more.](https://tidelift.com/subscription/pkg/npm-read-pkg-up?utm_source=npm-read-pkg-up&utm_medium=referral&utm_campaign=enterprise&utm_term=repo)
## Related
- [read-pkg](https://github.com/sindresorhus/read-pkg) - Read a package.json file
- [pkg-up](https://github.com/sindresorhus/pkg-up) - Find the closest package.json file
- [find-up](https://github.com/sindresorhus/find-up) - Find a file by walking up parent directories
- [pkg-conf](https://github.com/sindresorhus/pkg-conf) - Get namespaced config from the closest package.json
+67
View File
@@ -0,0 +1,67 @@
import * as typeFest from 'type-fest';
import normalize = require('normalize-package-data');
declare namespace readPkg {
interface Options {
/**
[Normalize](https://github.com/npm/normalize-package-data#what-normalization-currently-entails) the package data.
@default true
*/
readonly normalize?: boolean;
/**
Current working directory.
@default process.cwd()
*/
readonly cwd?: string;
}
interface NormalizeOptions extends Options {
readonly normalize?: true;
}
type NormalizedPackageJson = PackageJson & normalize.Package;
type PackageJson = typeFest.PackageJson;
}
declare const readPkg: {
/**
@returns The parsed JSON.
@example
```
import readPkg = require('read-pkg');
(async () => {
console.log(await readPkg());
//=> {name: 'read-pkg', …}
console.log(await readPkg({cwd: 'some-other-directory'});
//=> {name: 'unicorn', …}
})();
```
*/
(options?: readPkg.NormalizeOptions): Promise<readPkg.NormalizedPackageJson>;
(options: readPkg.Options): Promise<readPkg.PackageJson>;
/**
@returns The parsed JSON.
@example
```
import readPkg = require('read-pkg');
console.log(readPkg.sync());
//=> {name: 'read-pkg', …}
console.log(readPkg.sync({cwd: 'some-other-directory'});
//=> {name: 'unicorn', …}
```
*/
sync(options?: readPkg.NormalizeOptions): readPkg.NormalizedPackageJson;
sync(options: readPkg.Options): readPkg.PackageJson;
};
export = readPkg;
+41
View File
@@ -0,0 +1,41 @@
'use strict';
const {promisify} = require('util');
const fs = require('fs');
const path = require('path');
const parseJson = require('parse-json');
const readFileAsync = promisify(fs.readFile);
module.exports = async options => {
options = {
cwd: process.cwd(),
normalize: true,
...options
};
const filePath = path.resolve(options.cwd, 'package.json');
const json = parseJson(await readFileAsync(filePath, 'utf8'));
if (options.normalize) {
require('normalize-package-data')(json);
}
return json;
};
module.exports.sync = options => {
options = {
cwd: process.cwd(),
normalize: true,
...options
};
const filePath = path.resolve(options.cwd, 'package.json');
const json = parseJson(fs.readFileSync(filePath, 'utf8'));
if (options.normalize) {
require('normalize-package-data')(json);
}
return json;
};
+9
View File
@@ -0,0 +1,9 @@
MIT License
Copyright (c) Sindre Sorhus <sindresorhus@gmail.com> (sindresorhus.com)
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,15 @@
// Basic
export * from './source/basic';
// Utilities
export {Except} from './source/except';
export {Mutable} from './source/mutable';
export {Merge} from './source/merge';
export {MergeExclusive} from './source/merge-exclusive';
export {RequireAtLeastOne} from './source/require-at-least-one';
export {ReadonlyDeep} from './source/readonly-deep';
export {LiteralUnion} from './source/literal-union';
export {Promisable} from './source/promisable';
// Miscellaneous
export {PackageJson} from './source/package-json';
@@ -0,0 +1,9 @@
MIT License
Copyright (c) Sindre Sorhus <sindresorhus@gmail.com> (sindresorhus.com)
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,87 @@
{
"_args": [
[
"type-fest@0.6.0",
"/mnt/Foxconn/Digitalent/Deverloper/liff-push_2series"
]
],
"_development": true,
"_from": "type-fest@0.6.0",
"_id": "type-fest@0.6.0",
"_inBundle": false,
"_integrity": "sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==",
"_location": "/eslint-plugin-unicorn/read-pkg/type-fest",
"_phantomChildren": {},
"_requested": {
"type": "version",
"registry": true,
"raw": "type-fest@0.6.0",
"name": "type-fest",
"escapedName": "type-fest",
"rawSpec": "0.6.0",
"saveSpec": null,
"fetchSpec": "0.6.0"
},
"_requiredBy": [
"/eslint-plugin-unicorn/read-pkg"
],
"_resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.6.0.tgz",
"_spec": "0.6.0",
"_where": "/mnt/Foxconn/Digitalent/Deverloper/liff-push_2series",
"author": {
"name": "Sindre Sorhus",
"email": "sindresorhus@gmail.com",
"url": "sindresorhus.com"
},
"bugs": {
"url": "https://github.com/sindresorhus/type-fest/issues"
},
"description": "A collection of essential TypeScript types",
"devDependencies": {
"@sindresorhus/tsconfig": "^0.4.0",
"@typescript-eslint/eslint-plugin": "^1.9.0",
"@typescript-eslint/parser": "^1.10.2",
"eslint-config-xo-typescript": "^0.14.0",
"tsd": "^0.7.3",
"xo": "^0.24.0"
},
"engines": {
"node": ">=8"
},
"files": [
"index.d.ts",
"source"
],
"homepage": "https://github.com/sindresorhus/type-fest#readme",
"keywords": [
"typescript",
"ts",
"types",
"utility",
"util",
"utilities",
"omit",
"merge",
"json"
],
"license": "(MIT OR CC0-1.0)",
"name": "type-fest",
"repository": {
"type": "git",
"url": "git+https://github.com/sindresorhus/type-fest.git"
},
"scripts": {
"test": "xo && tsd"
},
"version": "0.6.0",
"xo": {
"extends": "xo-typescript",
"extensions": [
"ts"
],
"rules": {
"import/no-unresolved": "off",
"@typescript-eslint/indent": "off"
}
}
}
@@ -0,0 +1,119 @@
<div align="center">
<br>
<br>
<img src="media/logo.svg" alt="type-fest" height="300">
<br>
<br>
<b>A collection of essential TypeScript types</b>
<br>
<hr>
</div>
<br>
<br>
[![Build Status](https://travis-ci.com/sindresorhus/type-fest.svg?branch=master)](https://travis-ci.com/sindresorhus/type-fest)
[![](https://img.shields.io/badge/unicorn-approved-ff69b4.svg)](https://www.youtube.com/watch?v=9auOCbH5Ns4)
<!-- Commented out until they actually show anything
[![npm dependents](https://badgen.net/npm/dependents/type-fest)](https://www.npmjs.com/package/type-fest?activeTab=dependents) [![npm downloads](https://badgen.net/npm/dt/type-fest)](https://www.npmjs.com/package/type-fest)
-->
Many of the types here should have been built-in. You can help by suggesting some of them to the [TypeScript project](https://github.com/Microsoft/TypeScript/blob/master/CONTRIBUTING.md).
Either add this package as a dependency or copy-paste the needed types. No credit required. 👌
PR welcome for additional commonly needed types and docs improvements. Read the [contributing guidelines](.github/contributing.md) first.
## Install
```
$ npm install type-fest
```
*Requires TypeScript >=3.2*
## Usage
```ts
import {Except} from 'type-fest';
type Foo = {
unicorn: string;
rainbow: boolean;
};
type FooWithoutRainbow = Except<Foo, 'rainbow'>;
//=> {unicorn: string}
```
## API
Click the type names for complete docs.
### Basic
- [`Primitive`](source/basic.d.ts) - Matches any [primitive value](https://developer.mozilla.org/en-US/docs/Glossary/Primitive).
- [`Class`](source/basic.d.ts) - Matches a [`class` constructor](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes).
- [`TypedArray`](source/basic.d.ts) - Matches any [typed array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/TypedArray), like `Uint8Array` or `Float64Array`.
- [`JsonObject`](source/basic.d.ts) - Matches a JSON object.
- [`JsonArray`](source/basic.d.ts) - Matches a JSON array.
- [`JsonValue`](source/basic.d.ts) - Matches any valid JSON value.
- [`ObservableLike`](source/basic.d.ts) - Matches a value that is like an [Observable](https://github.com/tc39/proposal-observable).
### Utilities
- [`Except`](source/except.d.ts) - Create a type from an object type without certain keys. This is a stricter version of [`Omit`](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-5.html#the-omit-helper-type).
- [`Mutable`](source/mutable.d.ts) - Convert an object with `readonly` properties into a mutable object. Inverse of `Readonly<T>`.
- [`Merge`](source/merge.d.ts) - Merge two types into a new type. Keys of the second type overrides keys of the first type.
- [`MergeExclusive`](source/merge-exclusive.d.ts) - Create a type that has mutually exclusive properties.
- [`RequireAtLeastOne`](source/require-at-least-one.d.ts) - Create a type that requires at least one of the given properties.
- [`ReadonlyDeep`](source/readonly-deep.d.ts) - Create a deeply immutable version of a `object`/`Map`/`Set`/`Array` type.
- [`LiteralUnion`](source/literal-union.d.ts) - Create a union type by combining primitive types and literal types without sacrificing auto-completion in IDEs for the literal type part of the union. Workaround for [Microsoft/TypeScript#29729](https://github.com/Microsoft/TypeScript/issues/29729).
- [`Promisable`](source/promisable.d.ts) - Create a type that represents either the value or the value wrapped in `PromiseLike`.
### Miscellaneous
- [`PackageJson`](source/package-json.d.ts) - Type for [npm's `package.json` file](https://docs.npmjs.com/creating-a-package-json-file).
## Declined types
*If we decline a type addition, we will make sure to document the better solution here.*
- [`Diff` and `Spread`](https://github.com/sindresorhus/type-fest/pull/7) - The PR author didn't provide any real-world use-cases and the PR went stale. If you think this type is useful, provide some real-world use-cases and we might reconsider.
## Tips
### Built-in types
There are many advanced types most users don't know about.
- [`Partial<T>`](https://github.com/Microsoft/TypeScript/blob/2961bc3fc0ea1117d4e53bc8e97fa76119bc33e3/src/lib/es5.d.ts#L1401-L1406) - Make all properties in `T` optional.
- [`Required<T>`](https://github.com/Microsoft/TypeScript/blob/2961bc3fc0ea1117d4e53bc8e97fa76119bc33e3/src/lib/es5.d.ts#L1408-L1413) - Make all properties in `T` required.
- [`Readonly<T>`](https://github.com/Microsoft/TypeScript/blob/2961bc3fc0ea1117d4e53bc8e97fa76119bc33e3/src/lib/es5.d.ts#L1415-L1420) - Make all properties in `T` readonly.
- [`Pick<T, K>`](https://github.com/Microsoft/TypeScript/blob/2961bc3fc0ea1117d4e53bc8e97fa76119bc33e3/src/lib/es5.d.ts#L1422-L1427) - From `T`, pick a set of properties whose keys are in the union `K`.
- [`Record<K, T>`](https://github.com/Microsoft/TypeScript/blob/2961bc3fc0ea1117d4e53bc8e97fa76119bc33e3/src/lib/es5.d.ts#L1429-L1434) - Construct a type with a set of properties `K` of type `T`.
- [`Exclude<T, U>`](https://github.com/Microsoft/TypeScript/blob/2961bc3fc0ea1117d4e53bc8e97fa76119bc33e3/src/lib/es5.d.ts#L1436-L1439) - Exclude from `T` those types that are assignable to `U`.
- [`Extract<T, U>`](https://github.com/Microsoft/TypeScript/blob/2961bc3fc0ea1117d4e53bc8e97fa76119bc33e3/src/lib/es5.d.ts#L1441-L1444) - Extract from `T` those types that are assignable to `U`.
- [`NonNullable<T>`](https://github.com/Microsoft/TypeScript/blob/2961bc3fc0ea1117d4e53bc8e97fa76119bc33e3/src/lib/es5.d.ts#L1446-L1449) - Exclude `null` and `undefined` from `T`.
- [`Parameters<T>`](https://github.com/Microsoft/TypeScript/blob/2961bc3fc0ea1117d4e53bc8e97fa76119bc33e3/src/lib/es5.d.ts#L1451-L1454) - Obtain the parameters of a function type in a tuple.
- [`ConstructorParameters<T>`](https://github.com/Microsoft/TypeScript/blob/2961bc3fc0ea1117d4e53bc8e97fa76119bc33e3/src/lib/es5.d.ts#L1456-L1459) - Obtain the parameters of a constructor function type in a tuple.
- [`ReturnType<T>`](https://github.com/Microsoft/TypeScript/blob/2961bc3fc0ea1117d4e53bc8e97fa76119bc33e3/src/lib/es5.d.ts#L1461-L1464) Obtain the return type of a function type.
- [`InstanceType<T>`](https://github.com/Microsoft/TypeScript/blob/2961bc3fc0ea1117d4e53bc8e97fa76119bc33e3/src/lib/es5.d.ts#L1466-L1469) Obtain the instance type of a constructor function type.
You can find some examples in the [TypeScript docs](https://www.typescriptlang.org/docs/handbook/advanced-types.html#predefined-conditional-types).
## Maintainers
- [Sindre Sorhus](https://github.com/sindresorhus)
- [Jarek Radosz](https://github.com/CvX)
- [Dimitri Benin](https://github.com/BendingBender)
## License
(MIT OR CC0-1.0)
@@ -0,0 +1,67 @@
/// <reference lib="esnext"/>
// TODO: This can just be `export type Primitive = not object` when the `not` keyword is out.
/**
Matches any [primitive value](https://developer.mozilla.org/en-US/docs/Glossary/Primitive).
*/
export type Primitive =
| null
| undefined
| string
| number
| boolean
| symbol
| bigint;
// TODO: Remove the `= unknown` sometime in the future when most users are on TS 3.5 as it's now the default
/**
Matches a [`class` constructor](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes).
*/
export type Class<T = unknown> = new(...arguments_: any[]) => T;
/**
Matches any [typed array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/TypedArray), like `Uint8Array` or `Float64Array`.
*/
export type TypedArray =
| Int8Array
| Uint8Array
| Uint8ClampedArray
| Int16Array
| Uint16Array
| Int32Array
| Uint32Array
| Float32Array
| Float64Array
| BigInt64Array
| BigUint64Array;
/**
Matches a JSON object.
This type can be useful to enforce some input to be JSON-compatible or as a super-type to be extended from. Don't use this as a direct return type as the user would have to double-cast it: `jsonObject as unknown as CustomResponse`. Instead, you could extend your CustomResponse type from it to ensure your type only uses JSON-compatible types: `interface CustomResponse extends JsonObject { … }`.
*/
export type JsonObject = {[key: string]: JsonValue};
/**
Matches a JSON array.
*/
export interface JsonArray extends Array<JsonValue> {}
/**
Matches any valid JSON value.
*/
export type JsonValue = string | number | boolean | null | JsonObject | JsonArray;
declare global {
interface SymbolConstructor {
readonly observable: symbol;
}
}
/**
Matches a value that is like an [Observable](https://github.com/tc39/proposal-observable).
*/
export interface ObservableLike {
subscribe(observer: (value: unknown) => void): void;
[Symbol.observable](): ObservableLike;
}
@@ -0,0 +1,22 @@
/**
Create a type from an object type without certain keys.
This type is a stricter version of [`Omit`](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-5.html#the-omit-helper-type). The `Omit` type does not restrict the omitted keys to be keys present on the given type, while `Except` does. The benefits of a stricter type are avoiding typos and allowing the compiler to pick up on rename refactors automatically.
Please upvote [this issue](https://github.com/microsoft/TypeScript/issues/30825) if you want to have the stricter version as a built-in in TypeScript.
@example
```
import {Except} from 'type-fest';
type Foo = {
a: number;
b: string;
c: boolean;
};
type FooWithoutA = Except<Foo, 'a' | 'c'>;
//=> {b: string};
```
*/
export type Except<ObjectType, KeysType extends keyof ObjectType> = Pick<ObjectType, Exclude<keyof ObjectType, KeysType>>;
@@ -0,0 +1,33 @@
import {Primitive} from './basic';
/**
Allows creating a union type by combining primitive types and literal types without sacrificing auto-completion in IDEs for the literal type part of the union.
Currently, when a union type of a primitive type is combined with literal types, TypeScript loses all information about the combined literals. Thus, when such type is used in an IDE with autocompletion, no suggestions are made for the declared literals.
This type is a workaround for [Microsoft/TypeScript#29729](https://github.com/Microsoft/TypeScript/issues/29729). It will be removed as soon as it's not needed anymore.
@example
```
import {LiteralUnion} from 'type-fest';
// Before
type Pet = 'dog' | 'cat' | string;
const pet: Pet = '';
// Start typing in your TypeScript-enabled IDE.
// You **will not** get auto-completion for `dog` and `cat` literals.
// After
type Pet2 = LiteralUnion<'dog' | 'cat', string>;
const pet: Pet2 = '';
// You **will** get auto-completion for `dog` and `cat` literals.
```
*/
export type LiteralUnion<
LiteralType extends BaseType,
BaseType extends Primitive
> = LiteralType | (BaseType & {_?: never});
@@ -0,0 +1,39 @@
// Helper type. Not useful on its own.
type Without<FirstType, SecondType> = {[KeyType in Exclude<keyof FirstType, keyof SecondType>]?: never};
/**
Create a type that has mutually exclusive properties.
This type was inspired by [this comment](https://github.com/Microsoft/TypeScript/issues/14094#issuecomment-373782604).
This type works with a helper type, called `Without`. `Without<FirstType, SecondType>` produces a type that has only keys from `FirstType` which are not present on `SecondType` and sets the value type for these keys to `never`. This helper type is then used in `MergeExclusive` to remove keys from either `FirstType` or `SecondType`.
@example
```
import {MergeExclusive} from 'type-fest';
interface ExclusiveVariation1 {
exclusive1: boolean;
}
interface ExclusiveVariation2 {
exclusive2: string;
}
type ExclusiveOptions = MergeExclusive<ExclusiveVariation1, ExclusiveVariation2>;
let exclusiveOptions: ExclusiveOptions;
exclusiveOptions = {exclusive1: true};
//=> Works
exclusiveOptions = {exclusive2: 'hi'};
//=> Works
exclusiveOptions = {exclusive1: true, exclusive2: 'hi'};
//=> Error
```
*/
export type MergeExclusive<FirstType, SecondType> =
(FirstType | SecondType) extends object ?
(Without<FirstType, SecondType> & SecondType) | (Without<SecondType, FirstType> & FirstType) :
FirstType | SecondType;
@@ -0,0 +1,22 @@
import {Except} from './except';
/**
Merge two types into a new type. Keys of the second type overrides keys of the first type.
@example
```
import {Merge} from 'type-fest';
type Foo = {
a: number;
b: string;
};
type Bar = {
b: number;
};
const ab: Merge<Foo, Bar> = {a: 1, b: 2};
```
*/
export type Merge<FirstType, SecondType> = Except<FirstType, Extract<keyof FirstType, keyof SecondType>> & SecondType;
@@ -0,0 +1,22 @@
/**
Convert an object with `readonly` properties into a mutable object. Inverse of `Readonly<T>`.
This can be used to [store and mutate options within a class](https://github.com/sindresorhus/pageres/blob/4a5d05fca19a5fbd2f53842cbf3eb7b1b63bddd2/source/index.ts#L72), [edit `readonly` objects within tests](https://stackoverflow.com/questions/50703834), and [construct a `readonly` object within a function](https://github.com/Microsoft/TypeScript/issues/24509).
@example
```
import {Mutable} from 'type-fest';
type Foo = {
readonly a: number;
readonly b: string;
};
const mutableFoo: Mutable<Foo> = {a: 1, b: '2'};
mutableFoo.a = 3;
```
*/
export type Mutable<ObjectType> = {
// For each `Key` in the keys of `ObjectType`, make a mapped type by removing the `readonly` modifier from the property.
-readonly [KeyType in keyof ObjectType]: ObjectType[KeyType];
};
@@ -0,0 +1,501 @@
import {LiteralUnion} from '..';
declare namespace PackageJson {
/**
A person who has been involved in creating or maintaining the package.
*/
export type Person =
| string
| {
name: string;
url?: string;
email?: string;
};
export type BugsLocation =
| string
| {
/**
The URL to the package's issue tracker.
*/
url?: string;
/**
The email address to which issues should be reported.
*/
email?: string;
};
export interface DirectoryLocations {
/**
Location for executable scripts. Sugar to generate entries in the `bin` property by walking the folder.
*/
bin?: string;
/**
Location for Markdown files.
*/
doc?: string;
/**
Location for example scripts.
*/
example?: string;
/**
Location for the bulk of the library.
*/
lib?: string;
/**
Location for man pages. Sugar to generate a `man` array by walking the folder.
*/
man?: string;
/**
Location for test files.
*/
test?: string;
[directoryType: string]: unknown;
}
export type Scripts = {
/**
Run **before** the package is published (Also run on local `npm install` without any arguments).
*/
prepublish?: string;
/**
Run both **before** the package is packed and published, and on local `npm install` without any arguments. This is run **after** `prepublish`, but **before** `prepublishOnly`.
*/
prepare?: string;
/**
Run **before** the package is prepared and packed, **only** on `npm publish`.
*/
prepublishOnly?: string;
/**
Run **before** a tarball is packed (on `npm pack`, `npm publish`, and when installing git dependencies).
*/
prepack?: string;
/**
Run **after** the tarball has been generated and moved to its final destination.
*/
postpack?: string;
/**
Run **after** the package is published.
*/
publish?: string;
/**
Run **after** the package is published.
*/
postpublish?: string;
/**
Run **before** the package is installed.
*/
preinstall?: string;
/**
Run **after** the package is installed.
*/
install?: string;
/**
Run **after** the package is installed and after `install`.
*/
postinstall?: string;
/**
Run **before** the package is uninstalled and before `uninstall`.
*/
preuninstall?: string;
/**
Run **before** the package is uninstalled.
*/
uninstall?: string;
/**
Run **after** the package is uninstalled.
*/
postuninstall?: string;
/**
Run **before** bump the package version and before `version`.
*/
preversion?: string;
/**
Run **before** bump the package version.
*/
version?: string;
/**
Run **after** bump the package version.
*/
postversion?: string;
/**
Run with the `npm test` command, before `test`.
*/
pretest?: string;
/**
Run with the `npm test` command.
*/
test?: string;
/**
Run with the `npm test` command, after `test`.
*/
posttest?: string;
/**
Run with the `npm stop` command, before `stop`.
*/
prestop?: string;
/**
Run with the `npm stop` command.
*/
stop?: string;
/**
Run with the `npm stop` command, after `stop`.
*/
poststop?: string;
/**
Run with the `npm start` command, before `start`.
*/
prestart?: string;
/**
Run with the `npm start` command.
*/
start?: string;
/**
Run with the `npm start` command, after `start`.
*/
poststart?: string;
/**
Run with the `npm restart` command, before `restart`. Note: `npm restart` will run the `stop` and `start` scripts if no `restart` script is provided.
*/
prerestart?: string;
/**
Run with the `npm restart` command. Note: `npm restart` will run the `stop` and `start` scripts if no `restart` script is provided.
*/
restart?: string;
/**
Run with the `npm restart` command, after `restart`. Note: `npm restart` will run the `stop` and `start` scripts if no `restart` script is provided.
*/
postrestart?: string;
} & {
[scriptName: string]: string;
};
/**
Dependencies of the package. The version range is a string which has one or more space-separated descriptors. Dependencies can also be identified with a tarball or Git URL.
*/
export interface Dependency {
[packageName: string]: string;
}
export interface NonStandardEntryPoints {
/**
An ECMAScript module ID that is the primary entry point to the program.
*/
module?: string;
/**
A module ID with untranspiled code that is the primary entry point to the program.
*/
esnext?:
| string
| {
main?: string;
browser?: string;
[moduleName: string]: string | undefined;
};
/**
A hint to JavaScript bundlers or component tools when packaging modules for client side use.
*/
browser?:
| string
| {
[moduleName: string]: string | false;
};
}
export interface TypeScriptConfiguration {
/**
Location of the bundled TypeScript declaration file.
*/
types?: string;
/**
Location of the bundled TypeScript declaration file. Alias of `types`.
*/
typings?: string;
}
export interface YarnConfiguration {
/**
If your package only allows one version of a given dependency, and youd like to enforce the same behavior as `yarn install --flat` on the command line, set this to `true`.
Note that if your `package.json` contains `"flat": true` and other packages depend on yours (e.g. you are building a library rather than an application), those other packages will also need `"flat": true` in their `package.json` or be installed with `yarn install --flat` on the command-line.
*/
flat?: boolean;
/**
Selective version resolutions. Allows the definition of custom package versions inside dependencies without manual edits in the `yarn.lock` file.
*/
resolutions?: Dependency;
}
export interface JSPMConfiguration {
/**
JSPM configuration.
*/
jspm?: PackageJson;
}
}
/**
Type for [npm's `package.json` file](https://docs.npmjs.com/creating-a-package-json-file). Also includes types for fields used by other popular projects, like TypeScript and Yarn.
*/
export type PackageJson = {
/**
The name of the package.
*/
name?: string;
/**
Package version, parseable by [`node-semver`](https://github.com/npm/node-semver).
*/
version?: string;
/**
Package description, listed in `npm search`.
*/
description?: string;
/**
Keywords associated with package, listed in `npm search`.
*/
keywords?: string[];
/**
The URL to the package's homepage.
*/
homepage?: LiteralUnion<'.', string>;
/**
The URL to the package's issue tracker and/or the email address to which issues should be reported.
*/
bugs?: PackageJson.BugsLocation;
/**
The license for the package.
*/
license?: string;
/**
The licenses for the package.
*/
licenses?: Array<{
type?: string;
url?: string;
}>;
author?: PackageJson.Person;
/**
A list of people who contributed to the package.
*/
contributors?: PackageJson.Person[];
/**
A list of people who maintain the package.
*/
maintainers?: PackageJson.Person[];
/**
The files included in the package.
*/
files?: string[];
/**
The module ID that is the primary entry point to the program.
*/
main?: string;
/**
The executable files that should be installed into the `PATH`.
*/
bin?:
| string
| {
[binary: string]: string;
};
/**
Filenames to put in place for the `man` program to find.
*/
man?: string | string[];
/**
Indicates the structure of the package.
*/
directories?: PackageJson.DirectoryLocations;
/**
Location for the code repository.
*/
repository?:
| string
| {
type: string;
url: string;
};
/**
Script commands that are run at various times in the lifecycle of the package. The key is the lifecycle event, and the value is the command to run at that point.
*/
scripts?: PackageJson.Scripts;
/**
Is used to set configuration parameters used in package scripts that persist across upgrades.
*/
config?: {
[configKey: string]: unknown;
};
/**
The dependencies of the package.
*/
dependencies?: PackageJson.Dependency;
/**
Additional tooling dependencies that are not required for the package to work. Usually test, build, or documentation tooling.
*/
devDependencies?: PackageJson.Dependency;
/**
Dependencies that are skipped if they fail to install.
*/
optionalDependencies?: PackageJson.Dependency;
/**
Dependencies that will usually be required by the package user directly or via another dependency.
*/
peerDependencies?: PackageJson.Dependency;
/**
Package names that are bundled when the package is published.
*/
bundledDependencies?: string[];
/**
Alias of `bundledDependencies`.
*/
bundleDependencies?: string[];
/**
Engines that this package runs on.
*/
engines?: {
[EngineName in 'npm' | 'node' | string]: string;
};
/**
@deprecated
*/
engineStrict?: boolean;
/**
Operating systems the module runs on.
*/
os?: Array<LiteralUnion<
| 'aix'
| 'darwin'
| 'freebsd'
| 'linux'
| 'openbsd'
| 'sunos'
| 'win32'
| '!aix'
| '!darwin'
| '!freebsd'
| '!linux'
| '!openbsd'
| '!sunos'
| '!win32',
string
>>;
/**
CPU architectures the module runs on.
*/
cpu?: Array<LiteralUnion<
| 'arm'
| 'arm64'
| 'ia32'
| 'mips'
| 'mipsel'
| 'ppc'
| 'ppc64'
| 's390'
| 's390x'
| 'x32'
| 'x64'
| '!arm'
| '!arm64'
| '!ia32'
| '!mips'
| '!mipsel'
| '!ppc'
| '!ppc64'
| '!s390'
| '!s390x'
| '!x32'
| '!x64',
string
>>;
/**
If set to `true`, a warning will be shown if package is installed locally. Useful if the package is primarily a command-line application that should be installed globally.
@deprecated
*/
preferGlobal?: boolean;
/**
If set to `true`, then npm will refuse to publish it.
*/
private?: boolean;
/**
* A set of config values that will be used at publish-time. It's especially handy to set the tag, registry or access, to ensure that a given package is not tagged with 'latest', published to the global public registry or that a scoped module is private by default.
*/
publishConfig?: {
[config: string]: unknown;
};
} &
PackageJson.NonStandardEntryPoints &
PackageJson.TypeScriptConfiguration &
PackageJson.YarnConfiguration &
PackageJson.JSPMConfiguration & {
[key: string]: unknown;
};
@@ -0,0 +1,23 @@
/**
Create a type that represents either the value or the value wrapped in `PromiseLike`.
Use-cases:
- A function accepts a callback that may either return a value synchronously or may return a promised value.
- This type could be the return type of `Promise#then()`, `Promise#catch()`, and `Promise#finally()` callbacks.
Please upvote [this issue](https://github.com/microsoft/TypeScript/issues/31394) if you want to have this type as a built-in in TypeScript.
@example
```
import {Promisable} from 'type-fest';
async function logger(getLogEntry: () => Promisable<string>): Promise<void> {
const entry = await getLogEntry();
console.log(entry);
}
logger(() => 'foo');
logger(() => Promise.resolve('bar'));
```
*/
export type Promisable<T> = T | PromiseLike<T>;
@@ -0,0 +1,59 @@
import {Primitive} from './basic';
/**
Convert `object`s, `Map`s, `Set`s, and `Array`s and all of their properties/elements into immutable structures recursively.
This is useful when a deeply nested structure needs to be exposed as completely immutable, for example, an imported JSON module or when receiving an API response that is passed around.
Please upvote [this issue](https://github.com/microsoft/TypeScript/issues/13923) if you want to have this type as a built-in in TypeScript.
@example
```
// data.json
{
"foo": ["bar"]
}
// main.ts
import {ReadonlyDeep} from 'type-fest';
import dataJson = require('./data.json');
const data: ReadonlyDeep<typeof dataJson> = dataJson;
export default data;
// test.ts
import data from './main';
data.foo.push('bar');
//=> error TS2339: Property 'push' does not exist on type 'readonly string[]'
```
*/
export type ReadonlyDeep<T> = T extends Primitive | ((...arguments: any[]) => unknown)
? T
: T extends ReadonlyMap<infer KeyType, infer ValueType>
? ReadonlyMapDeep<KeyType, ValueType>
: T extends ReadonlySet<infer ItemType>
? ReadonlySetDeep<ItemType>
: T extends object
? ReadonlyObjectDeep<T>
: unknown;
/**
Same as `ReadonlyDeep`, but accepts only `ReadonlyMap`s as inputs. Internal helper for `ReadonlyDeep`.
*/
interface ReadonlyMapDeep<KeyType, ValueType>
extends ReadonlyMap<ReadonlyDeep<KeyType>, ReadonlyDeep<ValueType>> {}
/**
Same as `ReadonlyDeep`, but accepts only `ReadonlySet`s as inputs. Internal helper for `ReadonlyDeep`.
*/
interface ReadonlySetDeep<ItemType>
extends ReadonlySet<ReadonlyDeep<ItemType>> {}
/**
Same as `ReadonlyDeep`, but accepts only `object`s as inputs. Internal helper for `ReadonlyDeep`.
*/
type ReadonlyObjectDeep<ObjectType extends object> = {
readonly [PropertyType in keyof ObjectType]: ReadonlyDeep<ObjectType[PropertyType]>
};
@@ -0,0 +1,32 @@
import {Except} from './except';
/**
Create a type that requires at least one of the given properties. The remaining properties are kept as is.
@example
```
import {RequireAtLeastOne} from 'type-fest';
type Responder = {
text?: () => string;
json?: () => string;
secure?: boolean;
};
const responder: RequireAtLeastOne<Responder, 'text' | 'json'> = {
json: () => '{"message": "ok"}',
secure: true
};
```
*/
export type RequireAtLeastOne<ObjectType, KeysType extends keyof ObjectType = keyof ObjectType> =
{
// For each Key in KeysType make a mapped type
[Key in KeysType]: (
// …by picking that Key's type and making it required
Required<Pick<ObjectType, Key>>
)
}[KeysType]
// …then, make intersection types by adding the remaining properties to each mapped type.
& Except<ObjectType, KeysType>;
+85
View File
@@ -0,0 +1,85 @@
{
"_args": [
[
"read-pkg@5.2.0",
"/mnt/Foxconn/Digitalent/Deverloper/liff-push_2series"
]
],
"_development": true,
"_from": "read-pkg@5.2.0",
"_id": "read-pkg@5.2.0",
"_inBundle": false,
"_integrity": "sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==",
"_location": "/eslint-plugin-unicorn/read-pkg",
"_phantomChildren": {},
"_requested": {
"type": "version",
"registry": true,
"raw": "read-pkg@5.2.0",
"name": "read-pkg",
"escapedName": "read-pkg",
"rawSpec": "5.2.0",
"saveSpec": null,
"fetchSpec": "5.2.0"
},
"_requiredBy": [
"/eslint-plugin-unicorn/read-pkg-up"
],
"_resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz",
"_spec": "5.2.0",
"_where": "/mnt/Foxconn/Digitalent/Deverloper/liff-push_2series",
"author": {
"name": "Sindre Sorhus",
"email": "sindresorhus@gmail.com",
"url": "sindresorhus.com"
},
"bugs": {
"url": "https://github.com/sindresorhus/read-pkg/issues"
},
"dependencies": {
"@types/normalize-package-data": "^2.4.0",
"normalize-package-data": "^2.5.0",
"parse-json": "^5.0.0",
"type-fest": "^0.6.0"
},
"description": "Read a package.json file",
"devDependencies": {
"ava": "^2.2.0",
"tsd": "^0.7.2",
"xo": "^0.24.0"
},
"engines": {
"node": ">=8"
},
"files": [
"index.js",
"index.d.ts"
],
"homepage": "https://github.com/sindresorhus/read-pkg#readme",
"keywords": [
"json",
"read",
"parse",
"file",
"fs",
"graceful",
"load",
"package",
"normalize"
],
"license": "MIT",
"name": "read-pkg",
"repository": {
"type": "git",
"url": "git+https://github.com/sindresorhus/read-pkg.git"
},
"scripts": {
"test": "xo && ava && tsd"
},
"version": "5.2.0",
"xo": {
"ignores": [
"test/test.js"
]
}
}
+81
View File
@@ -0,0 +1,81 @@
# read-pkg [![Build Status](https://travis-ci.org/sindresorhus/read-pkg.svg?branch=master)](https://travis-ci.org/sindresorhus/read-pkg)
> Read a package.json file
## Why
- [Gracefully handles filesystem issues](https://github.com/isaacs/node-graceful-fs)
- [Throws more helpful JSON errors](https://github.com/sindresorhus/parse-json)
- [Normalizes the data](https://github.com/npm/normalize-package-data#what-normalization-currently-entails)
## Install
```
$ npm install read-pkg
```
## Usage
```js
const readPkg = require('read-pkg');
(async () => {
console.log(await readPkg());
//=> {name: 'read-pkg', …}
console.log(await readPkg({cwd: 'some-other-directory'}));
//=> {name: 'unicorn', …}
})();
```
## API
### readPkg(options?)
Returns a `Promise<object>` with the parsed JSON.
### readPkg.sync(options?)
Returns the parsed JSON.
#### options
Type: `object`
##### cwd
Type: `string`<br>
Default: `process.cwd()`
Current working directory.
##### normalize
Type: `boolean`<br>
Default: `true`
[Normalize](https://github.com/npm/normalize-package-data#what-normalization-currently-entails) the package data.
## Related
- [read-pkg-up](https://github.com/sindresorhus/read-pkg-up) - Read the closest package.json file
- [write-pkg](https://github.com/sindresorhus/write-pkg) - Write a `package.json` file
- [load-json-file](https://github.com/sindresorhus/load-json-file) - Read and parse a JSON file
---
<div align="center">
<b>
<a href="https://tidelift.com/subscription/pkg/npm-read-pkg?utm_source=npm-read-pkg&utm_medium=referral&utm_campaign=readme">Get professional support for this package with a Tidelift subscription</a>
</b>
<br>
<sub>
Tidelift helps make open source sustainable for maintainers while giving companies<br>assurances about security, maintenance, and licensing for their dependencies.
</sub>
</div>
+17
View File
@@ -0,0 +1,17 @@
sudo: required
language: node_js
node_js:
- "10"
- "12"
install:
- npm install
script:
- npm test
cache:
directories:
- node_modules
@@ -0,0 +1,20 @@
# v2
## v2.0
## v2.0.0
1. Update README.
2. Switch AST library from ret to regexp-tree.
3. Fix incorrect handling of nested quantifiers in disjunctions.
4. Enhance test suite.
Contributors:
- [davisjam](https://github.com/davisjam)
# v1
This is the historic release.
Contributors:
- [substack](https://github.com/substack)
+23
View File
@@ -0,0 +1,23 @@
Copyright 2019-present is held by the authors of the safe-regex module.
This software is released under the MIT license:
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
Original author: James Halliday @substack
Maintainer: James C. (Jamie) Davis @davisjam
+97
View File
@@ -0,0 +1,97 @@
# safe-regex
Detect potentially
[catastrophic](http://regular-expressions.mobi/catastrophic.html)
[exponential-time](http://perlgeek.de/blog-en/perl-tips/in-search-of-an-exponetial-regexp.html)
regular expressions by limiting the
[star height](https://en.wikipedia.org/wiki/Star_height) to 1.
WARNING: This module has both false positives and false negatives.
Use [vuln-regex-detector](https://github.com/davisjam/vuln-regex-detector) for improved accuracy.
[![Build Status](https://travis-ci.org/davisjam/safe-regex.svg?branch=master)](https://travis-ci.org/davisjam/safe-regex)
## Example
Suppose you have a script named `safe.js`:
``` js
var safe = require('safe-regex');
var regex = process.argv.slice(2).join(' ');
console.log(safe(regex));
```
This is its behavior:
```
$ node safe.js '(x+x+)+y'
false
$ node safe.js '(beep|boop)*'
true
$ node safe.js '(a+){10}'
false
$ node safe.js '\blocation\s*:[^:\n]+\b(Oakland|San Francisco)\b'
true
```
## Methods
``` js
const safe = require('safe-regex')
```
### const ok = safe(re, opts={})
Return a boolean `ok` whether or not the regex `re` is safe and not possibly
catastrophic.
`re` can be a `RegExp` object or just a string.
If the `re` is a string and is an invalid regex, returns `false`.
* `opts.limit` - maximum number of allowed repetitions in the entire regex.
Default: `25`.
## Install
With [npm](https://npmjs.org) do:
```
npm install safe-regex
```
## Resources
### What should I do if my project has a super-linear regex?
1. Confirm that it is *reachable* by untrusted input.
2. If it is, you can consider whether you can prevent worst-case behavior by trimming the input, revising the regex, or replacing the regex with another algorithm like string functions. For examples, see Table 5 in [this article](http://people.cs.vt.edu/davisjam/downloads/publications/DavisCoghlanServantLee-EcosystemREDOS-ESECFSE18.pdf).
3. If none of those solutions looks feasible, you might also consider changing regex engines. The [RE2 bindings](https://www.npmjs.com/package/re2) might work, though test carefully to confirm there are no [semantic portability problems](https://medium.com/@davisjam/why-arent-regexes-a-lingua-franca-esecfse19-a36348df3a2?source=friends_link&sk=d21be7f8f723e2080dc993385c6973d1).
### Further reading
The following documents may be edifying:
- [Research brief on the extent of super-linear regexes in practice](https://medium.com/@davisjam/introduction-987fdc4c7b0?source=friends_link&sk=ceefa4a4ca9617e08ab782c3b1580aea)
- [Research brief on the variability of super-linear regex behavior across programming languages](https://medium.com/@davisjam/why-arent-regexes-a-lingua-franca-esecfse19-a36348df3a2?source=friends_link&sk=d21be7f8f723e2080dc993385c6973d1)
- [Comparing regex matching algorithms](https://swtch.com/~rsc/regexp/regexp1.html)
## Project policies
### Versioning
This project follows [Semantic Versioning 2.0 (semver)](https://semver.org/).
Here are the project-specific meanings of MAJOR, MINOR, and PATCH updates:
- MAJOR: "Incompatible" API changes were introduced. There are two types in this module:
- Changes that modify the interface
- Changes that cause any regexes to be marked as unsafe that were formerly marked as safe
- MINOR: Functionality was added in a backwards-compatible manner. There are two types in this module:
- Refactoring the analyses but not changing their results
- Modifying the analyses to reduce false positives, without affecting negatives (false or true)
- PATCH: I don't anticipate using PATCH for this module
### License
[MIT](https://github.com/davisjam/safe-regex/blob/master/LICENSE)
+37
View File
@@ -0,0 +1,37 @@
#!/usr/bin/env node
// CLI for querying safe-regex for safety analysis
// Input: JSON-formatted file with an object with key 'pattern'
// Output: STDOUT: JSON-formatted object with new key 'isSafe' and value 0 or 1
// STDERR: Progress updates
var safe = require('../'),
fs = require('fs');
if (process.argv.length != 3) {
console.error(`Usage: ${process.argv[1]} pattern.json`);
process.exit(1);
}
const file = process.argv[2];
// Get pattern
const cont = fs.readFileSync(file, 'utf-8');
let pattern = JSON.parse(cont);
// Can analyze? Is safe?
let canAnalyze = 0;
let isSafe = 0;
try {
isSafe = safe(pattern.pattern) ? 1 : 0;
canAnalyze = 1;
} catch (e) {
canAnalyze = 0;
isSafe = 'unknown';
}
pattern.canAnalyze = canAnalyze;
pattern.isSafe = isSafe;
// Emit.
console.error(`Pattern /${pattern.pattern}/: canAnalyze ${pattern.canAnalyze} isSafe ${pattern.isSafe}`);
console.log(JSON.stringify(pattern));
process.exit(0);
@@ -0,0 +1,3 @@
var safe = require('../');
var regex = process.argv.slice(2).join(' ');
console.log(safe(regex));
+73
View File
@@ -0,0 +1,73 @@
const analyzer = require('./lib/analyzer');
const analyzerFamily = require('./lib/analyzer-family');
const DEFAULT_SAFE_REP_LIMIT = 25;
const RET_IS_SAFE = true;
const RET_IS_VULNERABLE = false;
class Args {
constructor(regExp, analyzerOptions) {
this.regExp = regExp;
this.analyzerOptions = analyzerOptions;
}
}
function safeRegex(re, opts) {
try {
const args = buildArgs(re, opts);
const analyzerResponses = askAnalyzersIfVulnerable(args);
// Did any analyzer say true?
if (analyzerResponses.find((isVulnerable) => isVulnerable)) {
return RET_IS_VULNERABLE;
} else {
return RET_IS_SAFE;
}
} catch (err) {
// Invalid or unparseable input
return false;
}
}
function buildArgs(re, opts) {
// Build AnalyzerOptions
if (!opts) opts = {};
const heuristic_replimit = opts.limit === undefined ? DEFAULT_SAFE_REP_LIMIT : opts.limit;
const analyzerOptions = new analyzer.AnalyzerOptions(heuristic_replimit);
// Build RegExp
let regExp = null;
// Construct a RegExp object
if (re instanceof RegExp) {
regExp = re;
} else if (typeof re === 'string') {
regExp = new RegExp(re);
} else {
regExp = new RegExp(String(re));
}
return new Args(regExp, analyzerOptions);
}
function askAnalyzersIfVulnerable(args) {
let analyzerSaysVulnerable = [];
// Query the Analyzers
let Analyzer;
for (Analyzer of analyzerFamily) {
try {
const analyzer = new Analyzer(args.analyzerOptions);
analyzerSaysVulnerable.push(analyzer.isVulnerable(args.regExp));
} catch (err) {
/* istanbul ignore next */ // No need to worry about code coverage here.
analyzerSaysVulnerable.push(false);
}
}
return analyzerSaysVulnerable;
}
// Export
module.exports = safeRegex;
@@ -0,0 +1,4 @@
// Load the analyzers
const heuristicAnalyzer = require("./heuristic-analyzer");
module.exports = [heuristicAnalyzer];
@@ -0,0 +1,83 @@
// Generic options
class AnalyzerOptions {
constructor(heuristic_replimit) {
this.heuristic_replimit = heuristic_replimit;
}
}
class AttackString {
constructor(prefixAndPumpList, suffix) {
this.prefixAndPumpList = prefixAndPumpList;
this.suffix = suffix;
}
}
// Abstract class
class Analyzer {
constructor(analyzerOptions) {
this.options = analyzerOptions;
}
// Subclasser must implement
// Return boolean
isVulnerable(regExp) {
return false;
}
// Subclass must implement
// Returns an AttackString or null
genAttackString(regExp) {
return null;
}
}
module.exports = function(re, replimit) {
// Build an AST
let myRegExp = null;
let ast = null;
try {
// Construct a RegExp object
if (re instanceof RegExp) {
myRegExp = re;
} else if (typeof re === "string") {
myRegExp = new RegExp(re);
} else {
myRegExp = new RegExp(String(re));
}
// Build an AST
ast = regexpTree.parse(myRegExp);
} catch (err) {
// Invalid or unparseable input
return false;
}
let currentStarHeight = 0;
let maxObservedStarHeight = 0;
let repetitionCount = 0;
regexpTree.traverse(ast, {
Repetition: {
pre({ node }) {
repetitionCount++;
currentStarHeight++;
if (maxObservedStarHeight < currentStarHeight) {
maxObservedStarHeight = currentStarHeight;
}
},
post({ node }) {
currentStarHeight--;
}
}
});
return maxObservedStarHeight <= 1 && repetitionCount <= replimit;
};
module.exports = {
"AnalyzerOptions": AnalyzerOptions,
"Analyzer": Analyzer,
};
@@ -0,0 +1,72 @@
// Exports an Analyzer subclass
const regexpTree = require("regexp-tree");
const analyzer = require("./analyzer");
class HeuristicAnalyzer extends analyzer.Analyzer {
constructor(analyzerOptions) {
super(analyzerOptions);
}
isVulnerable(regExp) {
// Heuristic #1: Star height > 1
const starHeight = this._measureStarHeight(regExp);
if (starHeight > 1) {
return true;
}
// Heuristic #2: # repetitions > limit
// TODO This is a poor heuristic
const nRepetitions = this._measureRepetitions(regExp);
if (nRepetitions > this.options.heuristic_replimit) {
return true;
}
return false;
}
genAttackString(regExp) {
return null;
}
_measureStarHeight(regExp) {
let currentStarHeight = 0;
let maxObservedStarHeight = 0;
const ast = regexpTree.parse(regExp);
regexpTree.traverse(ast, {
Repetition: {
pre({ node }) {
currentStarHeight++;
if (maxObservedStarHeight < currentStarHeight) {
maxObservedStarHeight = currentStarHeight;
}
},
post({ node }) {
currentStarHeight--;
}
}
});
return maxObservedStarHeight;
}
_measureRepetitions(regExp) {
let nRepetitions = 0;
const ast = regexpTree.parse(regExp);
regexpTree.traverse(ast, {
Repetition: {
pre({ node }) {
nRepetitions++;
}
}
});
return nRepetitions;
}
}
module.exports = HeuristicAnalyzer;
@@ -0,0 +1,88 @@
{
"_args": [
[
"safe-regex@2.1.1",
"/mnt/Foxconn/Digitalent/Deverloper/liff-push_2series"
]
],
"_development": true,
"_from": "safe-regex@2.1.1",
"_id": "safe-regex@2.1.1",
"_inBundle": false,
"_integrity": "sha512-rx+x8AMzKb5Q5lQ95Zoi6ZbJqwCLkqi3XuJXp5P3rT8OEc6sZCJG5AE5dU3lsgRr/F4Bs31jSlVN+j5KrsGu9A==",
"_location": "/eslint-plugin-unicorn/safe-regex",
"_phantomChildren": {},
"_requested": {
"type": "version",
"registry": true,
"raw": "safe-regex@2.1.1",
"name": "safe-regex",
"escapedName": "safe-regex",
"rawSpec": "2.1.1",
"saveSpec": null,
"fetchSpec": "2.1.1"
},
"_requiredBy": [
"/eslint-plugin-unicorn"
],
"_resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-2.1.1.tgz",
"_spec": "2.1.1",
"_where": "/mnt/Foxconn/Digitalent/Deverloper/liff-push_2series",
"author": {
"name": "James C.",
"email": "davisjam@vt.edu",
"url": "Jamie"
},
"bugs": {
"url": "https://github.com/davisjam/safe-regex/issues"
},
"dependencies": {
"regexp-tree": "~0.1.1"
},
"description": "detect possibly catastrophic, exponential-time regular expressions",
"devDependencies": {
"jest": "^24.9.0"
},
"homepage": "https://github.com/davisjam/safe-regex",
"jest": {
"moduleFileExtensions": [
"js"
],
"testRegex": "test.*\\.spec\\.js$",
"collectCoverage": true,
"coverageReporters": [
"text-summary",
"html",
"lcov"
],
"collectCoverageFrom": [
"*.js"
],
"coverageThreshold": {
"global": {
"statements": 100,
"branches": 100,
"functions": 100,
"lines": 100
}
}
},
"keywords": [
"catastrophic",
"exponential",
"regex",
"safe",
"sandbox"
],
"license": "MIT",
"main": "index.js",
"name": "safe-regex",
"repository": {
"type": "git",
"url": "git://github.com/davisjam/safe-regex.git"
},
"scripts": {
"test": "jest"
},
"version": "2.1.1"
}
@@ -0,0 +1,153 @@
const safeRegex = require("../");
const REPETITION_LIMIT = 25;
const REPETITION_TOO_MUCH = REPETITION_LIMIT + 1;
// TODO Named character classes
test("linear-time regexes are safe", () => {
const linTime = [
// regular RE's
/a/,
/a*/,
/a+/,
/a?/,
/a{3,5}/,
/a|b/,
/(ab)/,
/(ab)\1/,
/\bOakland\b/,
/(a+)|(b+)/,
// RE's in a string
"^foo/bar",
// non-RE, non-string
1,
];
linTime.forEach(re => {
expect(safeRegex(re)).toBe(true);
});
});
test("linear-time regexes are safe, under varying repetition limits", () => {
const re1 = RegExp("a?".repeat(REPETITION_LIMIT) + "a".repeat(REPETITION_LIMIT));
expect(safeRegex(re1)).toBe(true);
const LOW_REPETITION_LIMIT = 3;
const re2 = RegExp(Array(LOW_REPETITION_LIMIT).join("a?"));
expect(safeRegex(re2, { limit: LOW_REPETITION_LIMIT })).toBe(true);
});
test("poly-time regexes are safe (at least according to our heuristics)", () => {
const polyTime = [
/^a+a+$/, // QOA
/^a+aa+$/, // QOA with obvious intermediate run
/^a+aaaa+$/, // QOA with obvious intermediate run
/^a+[a-z]a+$/, // QOA with obvious intermediate run
/^a+\wa+$/, // QOA with intermediate character class
/^a+(\w|\d)a+$/, // QOA with valid path through
/^a+b?a+$/, // QOA with valid path through
/^a+(cde)*a+$/, // QOA with valid path through
/^.*a*$/, // QOA by subset
/^\w*\d*$/, // QOA by intersection
/^\S+@\S+\.\S+$/, // Example from Django
/a+$/, // QOA under partial-match
/abc.*$/, // QOA under partial-match
// TODO It would be nice to have one of the regexes that are poly-time even when they match, due to non-greedy quantifiers (p-NFA)
];
polyTime.forEach(re => {
expect(safeRegex(re)).toBe(true);
});
});
test("exp-time regexes due to star height are unsafe", () => {
const expTime = [
// Straightforward star height
/(a*)*$/,
/(a?)*$/,
/(a*)?$/,
/(a*)+$/,
/(a+)*$/,
/(\wa+)*$/, // Prefix
/(\..*)*$/, // Suffix
// Branching and nesting.
/(a*|b)+$/,
/(a|b*)+$/,
/(((b*)))+$/,
/(((b*))+)$/,
/(((b*)+))$/,
/(((b)*)+)$/,
/(((b)*))+$/,
// Misc. more complex cases
/^(a?){25}(a){25}$/,
/(x+x+)+y/,
/foo|(x+x+)+y/,
/(a+){10}y/,
/(a+){2}y/,
/(.*){1,32000}[bc]/,
// RE's in a string
"(a+)+",
];
expTime.forEach(re => {
expect(safeRegex(re)).toBe(false);
});
});
test("linear-time regexes with star height > 1", () => {
// TODO These are false positives, Fix once we improve analysis
const linTime = [
/(ab*)+$/,
/(b*a)+$/,
];
linTime.forEach(re => {
expect(safeRegex(re)).toBe(false);
});
});
test("exp-time regexes due to disjunction are safe (according to current heuristics)", () => {
// TODO These are false negatives. Fix once we improve analysis
const expTime = [
/(a|a)*$/, // QOD: obvious
/(a|\w)*$/, // QOD due to overlap
/([abc]|b)*$/, // QOD due to overlap
/(\w\w\w|bab)*$/, // QOD due to overlap, with multi-step internal paths
];
expTime.forEach(re => {
expect(safeRegex(re)).toBe(true);
});
});
test("regex that exceeds repetition limit is unsafe", () => {
const re1 = RegExp("a?".repeat(REPETITION_TOO_MUCH) + "a".repeat(REPETITION_TOO_MUCH));
expect(safeRegex(re1)).toBe(false);
const LOW_REPETITION_LIMIT = 3;
const re2 = RegExp("a?".repeat(LOW_REPETITION_LIMIT + 1));
expect(safeRegex(re2, { limit: LOW_REPETITION_LIMIT })).toBe(false);
});
test("invalid regexes default to unsafe", () => {
const invalid = [
"(a+",
"[a-z",
"*Oakland*",
"hey(yoo) )",
"abcde(?>hellow)",
"[abc",
];
invalid.forEach(re => {
expect(safeRegex(re)).toBe(false);
});
});
+20
View File
@@ -0,0 +1,20 @@
// Basic
export * from './source/basic';
// Utilities
export {Except} from './source/except';
export {Mutable} from './source/mutable';
export {Merge} from './source/merge';
export {MergeExclusive} from './source/merge-exclusive';
export {RequireAtLeastOne} from './source/require-at-least-one';
export {RequireExactlyOne} from './source/require-exactly-one';
export {PartialDeep} from './source/partial-deep';
export {ReadonlyDeep} from './source/readonly-deep';
export {LiteralUnion} from './source/literal-union';
export {Promisable} from './source/promisable';
export {Opaque} from './source/opaque';
export {SetOptional} from './source/set-optional';
export {SetRequired} from './source/set-required';
// Miscellaneous
export {PackageJson} from './source/package-json';
+9
View File
@@ -0,0 +1,9 @@
MIT License
Copyright (c) Sindre Sorhus <sindresorhus@gmail.com> (sindresorhus.com)
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+87
View File
@@ -0,0 +1,87 @@
{
"_args": [
[
"type-fest@0.8.1",
"/mnt/Foxconn/Digitalent/Deverloper/liff-push_2series"
]
],
"_development": true,
"_from": "type-fest@0.8.1",
"_id": "type-fest@0.8.1",
"_inBundle": false,
"_integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==",
"_location": "/eslint-plugin-unicorn/type-fest",
"_phantomChildren": {},
"_requested": {
"type": "version",
"registry": true,
"raw": "type-fest@0.8.1",
"name": "type-fest",
"escapedName": "type-fest",
"rawSpec": "0.8.1",
"saveSpec": null,
"fetchSpec": "0.8.1"
},
"_requiredBy": [
"/eslint-plugin-unicorn/read-pkg-up"
],
"_resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz",
"_spec": "0.8.1",
"_where": "/mnt/Foxconn/Digitalent/Deverloper/liff-push_2series",
"author": {
"name": "Sindre Sorhus",
"email": "sindresorhus@gmail.com",
"url": "sindresorhus.com"
},
"bugs": {
"url": "https://github.com/sindresorhus/type-fest/issues"
},
"description": "A collection of essential TypeScript types",
"devDependencies": {
"@sindresorhus/tsconfig": "^0.4.0",
"@typescript-eslint/eslint-plugin": "^2.2.0",
"@typescript-eslint/parser": "^2.2.0",
"eslint-config-xo-typescript": "^0.18.0",
"tsd": "^0.7.3",
"xo": "^0.24.0"
},
"engines": {
"node": ">=8"
},
"files": [
"index.d.ts",
"source"
],
"homepage": "https://github.com/sindresorhus/type-fest#readme",
"keywords": [
"typescript",
"ts",
"types",
"utility",
"util",
"utilities",
"omit",
"merge",
"json"
],
"license": "(MIT OR CC0-1.0)",
"name": "type-fest",
"repository": {
"type": "git",
"url": "git+https://github.com/sindresorhus/type-fest.git"
},
"scripts": {
"test": "xo && tsd"
},
"version": "0.8.1",
"xo": {
"extends": "xo-typescript",
"extensions": [
"ts"
],
"rules": {
"import/no-unresolved": "off",
"@typescript-eslint/indent": "off"
}
}
}
+635
View File
@@ -0,0 +1,635 @@
<div align="center">
<br>
<br>
<img src="media/logo.svg" alt="type-fest" height="300">
<br>
<br>
<b>A collection of essential TypeScript types</b>
<br>
<hr>
</div>
<br>
<br>
[![Build Status](https://travis-ci.com/sindresorhus/type-fest.svg?branch=master)](https://travis-ci.com/sindresorhus/type-fest)
[![](https://img.shields.io/badge/unicorn-approved-ff69b4.svg)](https://www.youtube.com/watch?v=9auOCbH5Ns4)
<!-- Commented out until they actually show anything
[![npm dependents](https://badgen.net/npm/dependents/type-fest)](https://www.npmjs.com/package/type-fest?activeTab=dependents) [![npm downloads](https://badgen.net/npm/dt/type-fest)](https://www.npmjs.com/package/type-fest)
-->
Many of the types here should have been built-in. You can help by suggesting some of them to the [TypeScript project](https://github.com/Microsoft/TypeScript/blob/master/CONTRIBUTING.md).
Either add this package as a dependency or copy-paste the needed types. No credit required. 👌
PR welcome for additional commonly needed types and docs improvements. Read the [contributing guidelines](.github/contributing.md) first.
## Install
```
$ npm install type-fest
```
*Requires TypeScript >=3.2*
## Usage
```ts
import {Except} from 'type-fest';
type Foo = {
unicorn: string;
rainbow: boolean;
};
type FooWithoutRainbow = Except<Foo, 'rainbow'>;
//=> {unicorn: string}
```
## API
Click the type names for complete docs.
### Basic
- [`Primitive`](source/basic.d.ts) - Matches any [primitive value](https://developer.mozilla.org/en-US/docs/Glossary/Primitive).
- [`Class`](source/basic.d.ts) - Matches a [`class` constructor](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes).
- [`TypedArray`](source/basic.d.ts) - Matches any [typed array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/TypedArray), like `Uint8Array` or `Float64Array`.
- [`JsonObject`](source/basic.d.ts) - Matches a JSON object.
- [`JsonArray`](source/basic.d.ts) - Matches a JSON array.
- [`JsonValue`](source/basic.d.ts) - Matches any valid JSON value.
- [`ObservableLike`](source/basic.d.ts) - Matches a value that is like an [Observable](https://github.com/tc39/proposal-observable).
### Utilities
- [`Except`](source/except.d.ts) - Create a type from an object type without certain keys. This is a stricter version of [`Omit`](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-5.html#the-omit-helper-type).
- [`Mutable`](source/mutable.d.ts) - Convert an object with `readonly` keys into a mutable object. The inverse of `Readonly<T>`.
- [`Merge`](source/merge.d.ts) - Merge two types into a new type. Keys of the second type overrides keys of the first type.
- [`MergeExclusive`](source/merge-exclusive.d.ts) - Create a type that has mutually exclusive keys.
- [`RequireAtLeastOne`](source/require-at-least-one.d.ts) - Create a type that requires at least one of the given keys.
- [`RequireExactlyOne`](source/require-one.d.ts) - Create a type that requires exactly a single key of the given keys and disallows more.
- [`PartialDeep`](source/partial-deep.d.ts) - Create a deeply optional version of another type. Use [`Partial<T>`](https://github.com/Microsoft/TypeScript/blob/2961bc3fc0ea1117d4e53bc8e97fa76119bc33e3/src/lib/es5.d.ts#L1401-L1406) if you only need one level deep.
- [`ReadonlyDeep`](source/readonly-deep.d.ts) - Create a deeply immutable version of an `object`/`Map`/`Set`/`Array` type. Use [`Readonly<T>`](https://github.com/Microsoft/TypeScript/blob/2961bc3fc0ea1117d4e53bc8e97fa76119bc33e3/src/lib/es5.d.ts#L1415-L1420) if you only need one level deep.
- [`LiteralUnion`](source/literal-union.d.ts) - Create a union type by combining primitive types and literal types without sacrificing auto-completion in IDEs for the literal type part of the union. Workaround for [Microsoft/TypeScript#29729](https://github.com/Microsoft/TypeScript/issues/29729).
- [`Promisable`](source/promisable.d.ts) - Create a type that represents either the value or the value wrapped in `PromiseLike`.
- [`Opaque`](source/opaque.d.ts) - Create an [opaque type](https://codemix.com/opaque-types-in-javascript/).
- [`SetOptional`](source/set-optional.d.ts) - Create a type that makes the given keys optional.
- [`SetRequired`](source/set-required.d.ts) - Create a type that makes the given keys required.
### Miscellaneous
- [`PackageJson`](source/package-json.d.ts) - Type for [npm's `package.json` file](https://docs.npmjs.com/creating-a-package-json-file).
## Declined types
*If we decline a type addition, we will make sure to document the better solution here.*
- [`Diff` and `Spread`](https://github.com/sindresorhus/type-fest/pull/7) - The PR author didn't provide any real-world use-cases and the PR went stale. If you think this type is useful, provide some real-world use-cases and we might reconsider.
- [`Dictionary`](https://github.com/sindresorhus/type-fest/issues/33) - You only save a few characters (`Dictionary<number>` vs `Record<string, number>`) from [`Record`](https://github.com/Microsoft/TypeScript/blob/2961bc3fc0ea1117d4e53bc8e97fa76119bc33e3/src/lib/es5.d.ts#L1429-L1434), which is more flexible and well-known. Also, you shouldn't use an object as a dictionary. We have `Map` in JavaScript now.
## Tips
### Built-in types
There are many advanced types most users don't know about.
- [`Partial<T>`](https://github.com/Microsoft/TypeScript/blob/2961bc3fc0ea1117d4e53bc8e97fa76119bc33e3/src/lib/es5.d.ts#L1401-L1406) - Make all properties in `T` optional.
<details>
<summary>
Example
</summary>
[Playground](https://typescript-play.js.org/?target=6#code/KYOwrgtgBAMg9gcxsAbsANlA3gKClAeQDMiAaPKAEWACMwFz8BRAJxbhcagDEBDAF17ocAXxw4AliH7AWRXgGNgUAHJwAJsADCcEEQkJsFXgAcTK3hGAAuKAGd+LKQgDcFEx363wEGrLf46IjIaOi28EioGG5iOArovHZ2qhrAAIJmAEJgEuiaLEb4Jk4oAsoKuvoIYCwCErq2apo6egZQALyF+FCm5pY2UABETelmg1xFnrYAzAAM8xNQQZGh4cFR6AB0xEQUIm4UFa0IABRHVbYACrws-BJCADwjLVUAfACUXfhEHFBnug4oABrYAATygcCIhBoACtgAp+JsQaC7P9ju9Prhut0joCwCZ1GUAGpCMDKTrnAwAbWRPWSyMhKWalQMAF0Dtj8BIoSd8YSZCT0GSOu1OmAQJp9CBgOpPkc7uBgBzOfwABYSOybSnVWp3XQ0sF04FgxnPFkIVkdKB84mkpUUfCxbEsYD8GogKBqjUBKBiWIAen9UGut3u6CeqReBlePXQQQA7skwMl+HAoMU4CgJJoISB0ODeOmbvwIVC1cAcIGmdpzVApDI5IpgJscNL49WMiZsrl8id3lrzScsD0zBYrLZBgAVOCUOCdwa+95uIA)
```ts
interface NodeConfig {
appName: string;
port: number;
}
class NodeAppBuilder {
private configuration: NodeConfig = {
appName: 'NodeApp',
port: 3000
};
config(config: Partial<NodeConfig>) {
type NodeConfigKey = keyof NodeConfig;
for (const key of Object.keys(config) as NodeConfigKey[]) {
const updateValue = config[key];
if (updateValue === undefined) {
continue;
}
this.configuration[key] = updateValue;
}
return this;
}
}
// `Partial<NodeConfig>`` allows us to provide only a part of the
// NodeConfig interface.
new NodeAppBuilder().config({appName: 'ToDoApp'});
```
</details>
- [`Required<T>`](https://github.com/Microsoft/TypeScript/blob/2961bc3fc0ea1117d4e53bc8e97fa76119bc33e3/src/lib/es5.d.ts#L1408-L1413) - Make all properties in `T` required.
<details>
<summary>
Example
</summary>
[Playground](https://typescript-play.js.org/?target=6#code/AQ4SwOwFwUwJwGYEMDGNgGED21VQGJZwC2wA3gFCjXAzFJgA2A-AFzADOUckA5gNxUaIYjA4ckvGG07c+g6gF8KQkAgCuEFFDA5O6gEbEwUbLm2ESwABQIixACJIoSdgCUYAR3Vg4MACYAPGYuFvYAfACU5Ko0APRxwADKMBD+wFAAFuh2Vv7OSBlYGdmc8ABu8LHKsRyGxqY4oQT21pTCIHQMjOwA5DAAHgACxAAOjDAAdChYxL0ANLHUouKSMH0AEmAAhJhY6ozpAJ77GTCMjMCiV0ToSAb7UJPPC9WRgrEJwAAqR6MwSRQPFGUFocDgRHYxnEfGAowh-zgUCOwF6KwkUl6tXqJhCeEsxDaS1AXSYfUGI3GUxmc0WSneQA)
```ts
interface ContactForm {
email?: string;
message?: string;
}
function submitContactForm(formData: Required<ContactForm>) {
// Send the form data to the server.
}
submitContactForm({
email: 'ex@mple.com',
message: 'Hi! Could you tell me more about…',
});
// TypeScript error: missing property 'message'
submitContactForm({
email: 'ex@mple.com',
});
```
</details>
- [`Readonly<T>`](https://github.com/Microsoft/TypeScript/blob/2961bc3fc0ea1117d4e53bc8e97fa76119bc33e3/src/lib/es5.d.ts#L1415-L1420) - Make all properties in `T` readonly.
<details>
<summary>
Example
</summary>
[Playground](https://typescript-play.js.org/?target=6#code/AQ4UwOwVwW2AZA9gc3mAbmANsA3gKFCOAHkAzMgGkOJABEwAjKZa2kAUQCcvEu32AMQCGAF2FYBIAL4BufDRABLCKLBcywgMZgEKZOoDCiCGSXI8i4hGEwwALmABnUVxXJ57YFgzZHSVF8sT1BpBSItLGEnJz1kAy5LLy0TM2RHACUwYQATEywATwAeAITjU3MAPnkrCJMXLigtUT4AClxgGztKbyDgaX99I1TzAEokr1BRAAslJwA6FIqLAF48TtswHp9MHDla9hJGACswZvmyLjAwAC8wVpm5xZHkUZDaMKIwqyWXYCW0oN4sNlsA1h0ug5gAByACyBQAggAHJHQ7ZBIFoXbzBjMCz7OoQP5YIaJNYQMAAdziCVaALGNSIAHomcAACoFJFgADKWjcSNEwG4vC4ji0wggEEQguiTnMEGALWAV1yAFp8gVgEjeFyuKICvMrCTgVxnst5jtsGC4ljsPNhXxGaAWcAAOq6YRXYDCRg+RWIcA5JSC+kWdCepQ+v3RYCU3RInzRMCGwlpC19NYBW1Ye08R1AA)
```ts
enum LogLevel {
Off,
Debug,
Error,
Fatal
};
interface LoggerConfig {
name: string;
level: LogLevel;
}
class Logger {
config: Readonly<LoggerConfig>;
constructor({name, level}: LoggerConfig) {
this.config = {name, level};
Object.freeze(this.config);
}
}
const config: LoggerConfig = {
name: 'MyApp',
level: LogLevel.Debug
};
const logger = new Logger(config);
// TypeScript Error: cannot assign to read-only property.
logger.config.level = LogLevel.Error;
// We are able to edit config variable as we please.
config.level = LogLevel.Error;
```
</details>
- [`Pick<T, K>`](https://github.com/Microsoft/TypeScript/blob/2961bc3fc0ea1117d4e53bc8e97fa76119bc33e3/src/lib/es5.d.ts#L1422-L1427) - From `T`, pick a set of properties whose keys are in the union `K`.
<details>
<summary>
Example
</summary>
[Playground](https://typescript-play.js.org/?target=6#code/AQ4SwOwFwUwJwGYEMDGNgEE5TCgNugN4BQoZwOUBAXMAM5RyQDmA3KeSFABYCuAtgCMISMHloMmENh04oA9tBjQJjFuzIBfYrOAB6PcADCcGElh1gEGAHcKATwAO6ebyjB5CTNlwFwSxFR0BX5HeToYABNgBDh5fm8cfBg6AHIKG3ldA2BHOOcfFNpUygJ0pAhokr4hETFUgDpswywkggAFUwA3MFtgAF5gQgowKhhVKTYKGuFRcXo1aVZgbTIoJ3RW3xhOmB6+wfbcAGsAHi3kgBpgEtGy4AAfG54BWfqAPnZm4AAlZUj4MAkMA8GAGB4vEgfMlLLw6CwPBA8PYRmMgZVgAC6CgmI4cIommQELwICh8RBgKZKvALh1ur0bHQABR5PYMui0Wk7em2ADaAF0AJS0AASABUALIAGQAogR+Mp3CROCAFBBwVC2ikBpj5CgBIqGjizLA5TAFdAmalImAuqlBRoVQh5HBgEy1eDWfs7J5cjzGYKhroVfpDEhHM4MV6GRR5NN0JrtnRg6BVirTFBeHAKYmYY6QNpdB73LmCJZBlSAXAubtvczeSmQMNSuMbmKNgBlHFgPEUNwusBIPAAQlS1xetTmxT0SDoESgdD0C4aACtHMwxytLrohawgA)
```ts
interface Article {
title: string;
thumbnail: string;
content: string;
}
// Creates new type out of the `Article` interface composed
// from the Articles' two properties: `title` and `thumbnail`.
// `ArticlePreview = {title: string; thumbnail: string}`
type ArticlePreview = Pick<Article, 'title' | 'thumbnail'>;
// Render a list of articles using only title and description.
function renderArticlePreviews(previews: ArticlePreview[]): HTMLElement {
const articles = document.createElement('div');
for (const preview of previews) {
// Append preview to the articles.
}
return articles;
}
const articles = renderArticlePreviews([
{
title: 'TypeScript tutorial!',
thumbnail: '/assets/ts.jpg'
}
]);
```
</details>
- [`Record<K, T>`](https://github.com/Microsoft/TypeScript/blob/2961bc3fc0ea1117d4e53bc8e97fa76119bc33e3/src/lib/es5.d.ts#L1429-L1434) - Construct a type with a set of properties `K` of type `T`.
<details>
<summary>
Example
</summary>
[Playground](https://typescript-play.js.org/?target=6#code/AQ4ejYAUHsGcCWAXBMB2dgwGbAKYC2ADgDYwCeeemCaWArgE7ADGMxAhmuQHQBQoYEnJE8wALKEARnkaxEKdMAC8wAOS0kstGuAAfdQBM8ANzxlRjXQbVaWACwC0JPB0NqA3HwGgIwAJJoWozYHCxixnAsjAhStADmwESMMJYo1Fi4HMCIaPEu+MRklHj8gpqyoeHAAKJFFFTAAN4+giDYCIxwSAByHAR4AFw5SDF5Xm2gJBzdfQPD3WPxE5PAlBxdAPLYNQAelgh4aOHDaPQEMowrIAC+3oJ+AMKMrlrAXFhSAFZ4LEhC9g4-0BmA4JBISXgiCkBQABpILrJ5MhUGhYcATGD6Bk4Hh-jNgABrPDkOBlXyQAAq9ngYmJpOAAHcEOCRjAXqwYODfoo6DhakUSph+Uh7GI4P0xER4Cj0OSQGwMP8tP1hgAlX7swwAHgRl2RvIANALSA08ABtAC6AD4VM1Wm0Kow0MMrYaHYJjGYLLJXZb3at1HYnC43Go-QHQDcvA6-JsmEJXARgCDgMYWAhjIYhDAU+YiMAAFIwex0ZmilMITCGF79TLAGRsAgJYAAZRwSEZGzEABFTOZUrJ5Yn+jwnWgeER6HB7AAKJrADpdXqS4ZqYultTG6azVfqHswPBbtauLY7fayQ7HIbAAAMwBuAEoYw9IBq2Ixs9h2eFMOQYPQObALQKJgggABeYhghCIpikkKRpOQRIknAsZUiIeCttECBEP8NSMCkjDDAARMGziuIYxHwYOjDCMBmDNnAuTxA6irdCOBB1Lh5Dqpqn66tISIykawBnOCtqqC0gbjqc9DgpGkxegOliyfJDrRkAA)
```ts
// Positions of employees in our company.
type MemberPosition = 'intern' | 'developer' | 'tech-lead';
// Interface describing properties of a single employee.
interface Employee {
firstName: string;
lastName: string;
yearsOfExperience: number;
}
// Create an object that has all possible `MemberPosition` values set as keys.
// Those keys will store a collection of Employees of the same position.
const team: Record<MemberPosition, Employee[]> = {
intern: [],
developer: [],
'tech-lead': [],
};
// Our team has decided to help John with his dream of becoming Software Developer.
team.intern.push({
firstName: 'John',
lastName: 'Doe',
yearsOfExperience: 0
});
// `Record` forces you to initialize all of the property keys.
// TypeScript Error: "tech-lead" property is missing
const teamEmpty: Record<MemberPosition, null> = {
intern: null,
developer: null,
};
```
</details>
- [`Exclude<T, U>`](https://github.com/Microsoft/TypeScript/blob/2961bc3fc0ea1117d4e53bc8e97fa76119bc33e3/src/lib/es5.d.ts#L1436-L1439) - Exclude from `T` those types that are assignable to `U`.
<details>
<summary>
Example
</summary>
[Playground](https://typescript-play.js.org/?target=6#code/JYOwLgpgTgZghgYwgAgMrQG7QMIHsQzADmyA3gFDLIAOuUYAXMiAK4A2byAPsgM5hRQJHqwC2AI2gBucgF9y5MAE9qKAEoQAjiwj8AEnBAATNtGQBeZAAooWphu26wAGmS3e93bRC8IASgsAPmRDJRlyAHoI5ABRAA8ENhYjFFYOZGVVZBgoXFFkAAM0zh5+QRBhZhYJaAKAOkjogEkQZAQ4X2QAdwALCFbaemRgXmQtFjhOMFwq9K6ULuB0lk6U+HYwZAxJnQaYFhAEMGB8ZCIIMAAFOjAANR2IK0HGWISklIAedCgsKDwCYgAbQA5M9gQBdVzFQJ+JhiSRQMiUYYwayZCC4VHPCzmSzAspCYEBWxgFhQAZwKC+FpgJ43VwARgADH4ZFQSWSBjcZPJyPtDsdTvxKWBvr8rD1DCZoJ5HPopaYoK4EPhCEQmGKcKriLCtrhgEYkVQVT5Nr4fmZLLZtMBbFZgT0wGBqES6ghbHBIJqoBKFdBWQpjfh+DQbhY2tqiHVsbjLMVkAB+ZAAZiZaeQTHOVxu9ySjxNaujNwDVHNvzqbBGkBAdPoAfkQA)
```ts
interface ServerConfig {
port: null | string | number;
}
type RequestHandler = (request: Request, response: Response) => void;
// Exclude `null` type from `null | string | number`.
// In case the port is equal to `null`, we will use default value.
function getPortValue(port: Exclude<ServerConfig['port'], null>): number {
if (typeof port === 'string') {
return parseInt(port, 10);
}
return port;
}
function startServer(handler: RequestHandler, config: ServerConfig): void {
const server = require('http').createServer(handler);
const port = config.port === null ? 3000 : getPortValue(config.port);
server.listen(port);
}
```
</details>
- [`Extract<T, U>`](https://github.com/Microsoft/TypeScript/blob/2961bc3fc0ea1117d4e53bc8e97fa76119bc33e3/src/lib/es5.d.ts#L1441-L1444) - Extract from `T` those types that are assignable to `U`.
<details>
<summary>
Example
</summary>
[Playground](https://typescript-play.js.org/?target=6#code/CYUwxgNghgTiAEAzArgOzAFwJYHtXzSwEdkQBJYACgEoAueVZAWwCMQYBuAKDDwGcM8MgBF4AXngBlAJ6scESgHIRi6ty5ZUGdoihgEABXZ888AN5d48ANoiAuvUat23K6ihMQ9ATE0BzV3goPy8GZjZOLgBfLi4Aejj4AEEICBwAdz54MAALKFQQ+BxEeAAHY1NgKAwoIKy0grr4DByEUpgccpgMaXgAaxBerCzi+B9-ZulygDouFHRsU1z8kKMYE1RhaqgAHkt4AHkWACt4EAAPbVRgLLWNgBp9gGlBs8uQa6yAUUuYPQwdgNpKM7nh7mMML4CgA+R5WABqUAgpDeVxuhxO1he0jsXGh8EoOBO9COx3BQPo2PBADckaR6IjkSA6PBqTgsMBzPsicdrEC7OJWXSQNwYvFEgAVTS9JLXODpeDpKBZFg4GCoWa8VACIJykAKiQWKy2YQOAioYikCg0OEMDyhRSy4DyxS24KhAAMjyi6gS8AAwjh5OD0iBFHAkJoEOksC1mnkMJq8gUQKDNttKPlnfrwYp3J5XfBHXqoKpfYkAOI4ansTxaeDADmoRSCCBYAbxhC6TDx6rwYHIRX5bScjA4bLJwoDmDwDkfbA9JMrVMVdM1TN69LgkTgwgkchUahqIA)
```ts
declare function uniqueId(): number;
const ID = Symbol('ID');
interface Person {
[ID]: number;
name: string;
age: number;
}
// Allows changing the person data as long as the property key is of string type.
function changePersonData<
Obj extends Person,
Key extends Extract<keyof Person, string>,
Value extends Obj[Key]
> (obj: Obj, key: Key, value: Value): void {
obj[key] = value;
}
// Tiny Andrew was born.
const andrew = {
[ID]: uniqueId(),
name: 'Andrew',
age: 0,
};
// Cool, we're fine with that.
changePersonData(andrew, 'name', 'Pony');
// Goverment didn't like the fact that you wanted to change your identity.
changePersonData(andrew, ID, uniqueId());
```
</details>
- [`NonNullable<T>`](https://github.com/Microsoft/TypeScript/blob/2961bc3fc0ea1117d4e53bc8e97fa76119bc33e3/src/lib/es5.d.ts#L1446-L1449) - Exclude `null` and `undefined` from `T`.
<details>
<summary>
Example
</summary>
Works with <code>strictNullChecks</code> set to <code>true</code>. (Read more <a href="https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-0.html">here</a>)
[Playground](https://typescript-play.js.org/?target=6#code/C4TwDgpgBACg9gJ2AOQK4FsBGEFQLxQDOwCAlgHYDmUAPlORtrnQwDasDcAUFwPQBU-WAEMkUOADMowqAGNWwwoSgATCBIqlgpOOSjAAFsOBRSy1IQgr9cKJlSlW1mZYQA3HFH68u8xcoBlHA8EACEHJ08Aby4oKDBUTFZSWXjEFEYcAEIALihkXTR2YSSIAB54JDQsHAA+blj4xOTUsHSACkMzPKD3HHDHNQQAGjSkPMqMmoQASh7g-oihqBi4uNIpdraxPAI2VhmVxrX9AzMAOm2ppnwoAA4ABifuE4BfKAhWSyOTuK7CS7pao3AhXF5rV48E4ICDAVAIPT-cGQyG+XTEIgLMJLTx7CAAdygvRCA0iCHaMwarhJOIQjUBSHaACJHk8mYdeLwxtdcVAAOSsh58+lXdr7Dlcq7A3n3J4PEUdADMcspUE53OluAIUGVTx46oAKuAIAFZGQwCYAKIIBCILjUxaDHAMnla+iodjcIA)
```ts
type PortNumber = string | number | null;
/** Part of a class definition that is used to build a server */
class ServerBuilder {
portNumber!: NonNullable<PortNumber>;
port(this: ServerBuilder, port: PortNumber): ServerBuilder {
if (port == null) {
this.portNumber = 8000;
} else {
this.portNumber = port;
}
return this;
}
}
const serverBuilder = new ServerBuilder();
serverBuilder
.port('8000') // portNumber = '8000'
.port(null) // portNumber = 8000
.port(3000); // portNumber = 3000
// TypeScript error
serverBuilder.portNumber = null;
```
</details>
- [`Parameters<T>`](https://github.com/Microsoft/TypeScript/blob/2961bc3fc0ea1117d4e53bc8e97fa76119bc33e3/src/lib/es5.d.ts#L1451-L1454) - Obtain the parameters of a function type in a tuple.
<details>
<summary>
Example
</summary>
[Playground](https://typescript-play.js.org/?target=6#code/GYVwdgxgLglg9mABAZwBYmMANgUwBQxgAOIUAXIgIZgCeA2gLoCUFAbnDACaIDeAUIkQB6IYgCypSlBxUATrMo1ECsJzgBbLEoipqAc0J7EMKMgDkiHLnU4wp46pwAPHMgB0fAL58+oSLARECEosLAA5ABUYG2QAHgAxJGdpVWREPDdMylk9ZApqemZEAF4APipacrw-CApEgBogkKwAYThwckQwEHUAIxxZJl4BYVEImiIZKF0oZRwiWVdbeygJmThgOYgcGFYcbhqApCJsyhtpWXcR1cnEePBoeDAABVPzgbTixFeFd8uEsClADcIxGiygIFkSEOT3SmTc2VydQeRx+ZxwF2QQ34gkEwDgsnSuFmMBKiAADEDjIhYk1Qm0OlSYABqZnYka4xA1DJZHJYkGc7yCbyeRA+CAIZCzNAYbA4CIAdxg2zJwVCkWirjwMswuEaACYmCCgA)
```ts
function shuffle(input: any[]): void {
// Mutate array randomly changing its' elements indexes.
}
function callNTimes<Fn extends (...args: any[]) => any> (func: Fn, callCount: number) {
// Type that represents the type of the received function parameters.
type FunctionParameters = Parameters<Fn>;
return function (...args: FunctionParameters) {
for (let i = 0; i < callCount; i++) {
func(...args);
}
}
}
const shuffleTwice = callNTimes(shuffle, 2);
```
</details>
- [`ConstructorParameters<T>`](https://github.com/Microsoft/TypeScript/blob/2961bc3fc0ea1117d4e53bc8e97fa76119bc33e3/src/lib/es5.d.ts#L1456-L1459) - Obtain the parameters of a constructor function type in a tuple.
<details>
<summary>
Example
</summary>
[Playground](https://typescript-play.js.org/?target=6#code/MYGwhgzhAECCBOAXAlqApgWQPYBM0mgG8AoaaFRENALmgkXmQDsBzAblOmCycTV4D8teo1YdO3JiICuwRFngAKClWENmLAJRFOZRAAtkEAHQq00ALzlklNBzIBfYk+KhIMAJJTEYJsDQAwmDA+mgAPAAq0GgAHnxMODCKTGgA7tCKxllg8CwQtL4AngDaALraFgB80EWa1SRkAA6MAG5gfNAB4FABPDJyCrQR9tDNyG0dwMGhtBhgjWEiGgA00F70vv4RhY3hEZXVVinpc42KmuJkkv3y8Bly8EPaDWTkhiZd7r3e8LK3llwGCMXGQWGhEOsfH5zJlsrl8p0+gw-goAAo5MAAW3BaHgEEilU0tEhmzQ212BJ0ry4SOg+kg+gBBiMximIGA0nAfAQLGk2N4EAAEgzYcYcnkLsRdDTvNEYkYUKwSdCme9WdM0MYwYhFPSIPpJdTkAAzDKxBUaZX+aAAQgsVmkCTQxuYaBw2ng4Ok8CYcotSu8pMur09iG9vuObxZnx6SN+AyUWTF8MN0CcZE4Ywm5jZHK5aB5fP4iCFIqT4oRRTKRLo6lYVNeAHpG50wOzOe1zHr9NLQ+HoABybsD4HOKXXRA1JCoKhBELmI5pNaB6Fz0KKBAodDYPAgSUTmqYsAALx4m5nC6nW9nGq14KtaEUA9gR9PvuNCjQ9BgACNvcwNBtAcLiAA)
```ts
class ArticleModel {
title: string;
content?: string;
constructor(title: string) {
this.title = title;
}
}
class InstanceCache<T extends (new (...args: any[]) => any)> {
private ClassConstructor: T;
private cache: Map<string, InstanceType<T>> = new Map();
constructor (ctr: T) {
this.ClassConstructor = ctr;
}
getInstance (...args: ConstructorParameters<T>): InstanceType<T> {
const hash = this.calculateArgumentsHash(...args);
const existingInstance = this.cache.get(hash);
if (existingInstance !== undefined) {
return existingInstance;
}
return new this.ClassConstructor(...args);
}
private calculateArgumentsHash(...args: any[]): string {
// Calculate hash.
return 'hash';
}
}
const articleCache = new InstanceCache(ArticleModel);
const amazonArticle = articleCache.getInstance('Amazon forests burining!');
```
</details>
- [`ReturnType<T>`](https://github.com/Microsoft/TypeScript/blob/2961bc3fc0ea1117d4e53bc8e97fa76119bc33e3/src/lib/es5.d.ts#L1461-L1464) Obtain the return type of a function type.
<details>
<summary>
Example
</summary>
[Playground](https://typescript-play.js.org/?target=6#code/MYGwhgzhAECSAmICmBlJAnAbgS2E6A3gFDTTwD2AcuQC4AW2AdgOYAUAlAFzSbnbyEAvkWFFQkGJSQB3GMVI1sNZNwg10TZgG4S0YOUY0kh1es07d+xmvQBXYDXLpWi5UlMaWAGj0GjJ6BtNdkJdBQYIADpXZGgAXmgYpB1ScOwoq38aeN9DYxoU6GFRKzVoJjUwRjwAYXJbPPRuAFkwAAcAHgAxBodsAx9GWwBbACMMAD4cxhloVraOCyYjdAAzMDxoOut1e0d0UNIZ6WhWSPOwdGYIbiqATwBtAF0uaHudUQB6ACpv6ABpJBINqJdAbADW0Do5BOw3u5R2VTwMHIq2gAANtjZ0bkbHsnFCwJh8ONjHp0EgwEZ4JFoN9PkRVr1FAZoMwkDRYIjqkgOrosepoEgAB7+eAwAV2BxOLy6ACCVxgIrFEoMeOl6AACpcwMMORgIB1JRMiBNWKVdhruJKfOdIpdrtwFddXlzKjyACp3Nq842HaDIbL6BrZBIVGhIpB1EMYSLsmjmtWW-YhAA+qegAAYLKQLQj3ZsEsdccmnGcLor2Dn8xGedHGpEIBzEzspfsfMHDNAANTQACMVaIljV5GQkRA5DYmIpVKQAgAJARO9le33BDXIyi0YuLW2nJFGLqkOvxFB0YPdBSaLZ0IwNzyPkO8-xkGgsLh8Al427a3hWAhXwwHA8EHT5PmgAB1bAQBAANJ24adKWpft72RaBUTgRBUCAj89HAM8xCTaBjggABRQx0DuHJv25P9dCkWRZVIAAiBjoFImpmjlFBgA0NpsjadByDacgIDAEAIAAQmYpjoGYgAZSBsmGPw6DtZiiFA8CoJguDmAQmoZ2QvtUKQLdoAYmBTwgdEiCAA)
```ts
/** Provides every element of the iterable `iter` into the `callback` function and stores the results in an array. */
function mapIter<
Elem,
Func extends (elem: Elem) => any,
Ret extends ReturnType<Func>
>(iter: Iterable<Elem>, callback: Func): Ret[] {
const mapped: Ret[] = [];
for (const elem of iter) {
mapped.push(callback(elem));
}
return mapped;
}
const setObject: Set<string> = new Set();
const mapObject: Map<number, string> = new Map();
mapIter(setObject, (value: string) => value.indexOf('Foo')); // number[]
mapIter(mapObject, ([key, value]: [number, string]) => {
return key % 2 === 0 ? value : 'Odd';
}); // string[]
```
</details>
- [`InstanceType<T>`](https://github.com/Microsoft/TypeScript/blob/2961bc3fc0ea1117d4e53bc8e97fa76119bc33e3/src/lib/es5.d.ts#L1466-L1469) Obtain the instance type of a constructor function type.
<details>
<summary>
Example
</summary>
[Playground](https://typescript-play.js.org/?target=6#code/MYGwhgzhAECSAmICmBlJAnAbgS2E6A3gFDTTwD2AcuQC4AW2AdgOYAUAlAFzSbnbyEAvkWFFQkGJSQB3GMVI1sNZNwg10TZgG4S0YOUY0kh1es07d+xmvQBXYDXLpWi5UlMaWAGj0GjJ6BtNdkJdBQYIADpXZGgAXmgYpB1ScOwoq38aeN9DYxoU6GFRKzVoJjUwRjwAYXJbPPRuAFkwAAcAHgAxBodsAx9GWwBbACMMAD4cxhloVraOCyYjdAAzMDxoOut1e0d0UNIZ6WhWSPOwdGYIbiqATwBtAF0uaHudUQB6ACpv6ABpJBINqJdAbADW0Do5BOw3u5R2VTwMHIq2gAANtjZ0bkbHsnFCwJh8ONjHp0EgwEZ4JFoN9PkRVr1FAZoMwkDRYIjqkgOrosepoEgAB7+eAwAV2BxOLy6ACCVxgIrFEoMeOl6AACpcwMMORgIB1JRMiBNWKVdhruJKfOdIpdrtwFddXlzKjyACp3Nq842HaDIbL6BrZBIVGhIpB1EMYSLsmjmtWW-YhAA+qegAAYLKQLQj3ZsEsdccmnGcLor2Dn8xGedHGpEIBzEzspfsfMHDNAANTQACMVaIljV5GQkRA5DYmIpVKQAgAJARO9le33BDXIyi0YuLW2nJFGLqkOvxFB0YPdBSaLZ0IwNzyPkO8-xkGgsLh8Al427a3hWAhXwwHA8EHT5PmgAB1bAQBAANJ24adKWpft72RaBUTgRBUCAj89HAM8xCTaBjggABRQx0DuHJv25P9dCkWRZVIAAiBjoFImpmjlFBgA0NpsjadByDacgIDAEAIAAQmYpjoGYgAZSBsmGPw6DtZiiFA8CoJguDmAQmoZ2QvtUKQLdoAYmBTwgdEiCAA)
```ts
class IdleService {
doNothing (): void {}
}
class News {
title: string;
content: string;
constructor(title: string, content: string) {
this.title = title;
this.content = content;
}
}
const instanceCounter: Map<Function, number> = new Map();
interface Constructor {
new(...args: any[]): any;
}
// Keep track how many instances of `Constr` constructor have been created.
function getInstance<
Constr extends Constructor,
Args extends ConstructorParameters<Constr>
>(constructor: Constr, ...args: Args): InstanceType<Constr> {
let count = instanceCounter.get(constructor) || 0;
const instance = new constructor(...args);
instanceCounter.set(constructor, count + 1);
console.log(`Created ${count + 1} instances of ${Constr.name} class`);
return instance;
}
const idleService = getInstance(IdleService);
// Will log: `Created 1 instances of IdleService class`
const newsEntry = getInstance(News, 'New ECMAScript proposals!', 'Last month...');
// Will log: `Created 1 instances of News class`
```
</details>
- [`Omit<T, K>`](https://github.com/microsoft/TypeScript/blob/71af02f7459dc812e85ac31365bfe23daf14b4e4/src/lib/es5.d.ts#L1446) Constructs a type by picking all properties from T and then removing K.
<details>
<summary>
Example
</summary>
[Playground](https://typescript-play.js.org/?target=6#code/JYOwLgpgTgZghgYwgAgIImAWzgG2QbwChlks4BzCAVShwC5kBnMKUcgbmKYAcIFgIjBs1YgOXMpSFMWbANoBdTiW5woFddwAW0kfKWEAvoUIB6U8gDCUCHEiNkICAHdkYAJ69kz4GC3JcPG4oAHteKDABBxCYNAxsPFBIWEQUCAAPJG4wZABySUFcgJAAEzMLXNV1ck0dIuCw6EjBADpy5AB1FAQ4EGQAV0YUP2AHDy8wEOQbUugmBLwtEIA3OcmQnEjuZBgQqE7gAGtgZAhwKHdkHFGwNvGUdDIcAGUliIBJEF3kAF5kAHlML4ADyPBIAGjyBUYRQAPnkqho4NoYQA+TiEGD9EAISIhPozErQMG4AASK2gn2+AApek9pCSXm8wFSQooAJQMUkAFQAsgAZACiOAgmDOOSIJAQ+OYyGl4DgoDmf2QJRCCH6YvALQQNjsEGFovF1NyJWAy1y7OUyHMyE+yRAuFImG4Iq1YDswHxbRINjA-SgfXlHqVUE4xiAA)
```ts
interface Animal {
imageUrl: string;
species: string;
images: string[];
paragraphs: string[];
}
// Creates new type with all properties of the `Animal` interface
// except 'images' and 'paragraphs' properties. We can use this
// type to render small hover tooltip for a wiki entry list.
type AnimalShortInfo = Omit<Animal, 'images' | 'paragraphs'>;
function renderAnimalHoverInfo (animals: AnimalShortInfo[]): HTMLElement {
const container = document.createElement('div');
// Internal implementation.
return container;
}
```
</details>
You can find some examples in the [TypeScript docs](https://www.typescriptlang.org/docs/handbook/advanced-types.html#predefined-conditional-types).
## Maintainers
- [Sindre Sorhus](https://github.com/sindresorhus)
- [Jarek Radosz](https://github.com/CvX)
- [Dimitri Benin](https://github.com/BendingBender)
## License
(MIT OR CC0-1.0)
---
<div align="center">
<b>
<a href="https://tidelift.com/subscription/pkg/npm-type-fest?utm_source=npm-type-fest&utm_medium=referral&utm_campaign=readme">Get professional support for this package with a Tidelift subscription</a>
</b>
<br>
<sub>
Tidelift helps make open source sustainable for maintainers while giving companies<br>assurances about security, maintenance, and licensing for their dependencies.
</sub>
</div>
@@ -0,0 +1,67 @@
/// <reference lib="esnext"/>
// TODO: This can just be `export type Primitive = not object` when the `not` keyword is out.
/**
Matches any [primitive value](https://developer.mozilla.org/en-US/docs/Glossary/Primitive).
*/
export type Primitive =
| null
| undefined
| string
| number
| boolean
| symbol
| bigint;
// TODO: Remove the `= unknown` sometime in the future when most users are on TS 3.5 as it's now the default
/**
Matches a [`class` constructor](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes).
*/
export type Class<T = unknown, Arguments extends any[] = any[]> = new(...arguments_: Arguments) => T;
/**
Matches any [typed array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/TypedArray), like `Uint8Array` or `Float64Array`.
*/
export type TypedArray =
| Int8Array
| Uint8Array
| Uint8ClampedArray
| Int16Array
| Uint16Array
| Int32Array
| Uint32Array
| Float32Array
| Float64Array
| BigInt64Array
| BigUint64Array;
/**
Matches a JSON object.
This type can be useful to enforce some input to be JSON-compatible or as a super-type to be extended from. Don't use this as a direct return type as the user would have to double-cast it: `jsonObject as unknown as CustomResponse`. Instead, you could extend your CustomResponse type from it to ensure your type only uses JSON-compatible types: `interface CustomResponse extends JsonObject { … }`.
*/
export type JsonObject = {[key: string]: JsonValue};
/**
Matches a JSON array.
*/
export interface JsonArray extends Array<JsonValue> {}
/**
Matches any valid JSON value.
*/
export type JsonValue = string | number | boolean | null | JsonObject | JsonArray;
declare global {
interface SymbolConstructor {
readonly observable: symbol;
}
}
/**
Matches a value that is like an [Observable](https://github.com/tc39/proposal-observable).
*/
export interface ObservableLike {
subscribe(observer: (value: unknown) => void): void;
[Symbol.observable](): ObservableLike;
}
@@ -0,0 +1,22 @@
/**
Create a type from an object type without certain keys.
This type is a stricter version of [`Omit`](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-5.html#the-omit-helper-type). The `Omit` type does not restrict the omitted keys to be keys present on the given type, while `Except` does. The benefits of a stricter type are avoiding typos and allowing the compiler to pick up on rename refactors automatically.
Please upvote [this issue](https://github.com/microsoft/TypeScript/issues/30825) if you want to have the stricter version as a built-in in TypeScript.
@example
```
import {Except} from 'type-fest';
type Foo = {
a: number;
b: string;
c: boolean;
};
type FooWithoutA = Except<Foo, 'a' | 'c'>;
//=> {b: string};
```
*/
export type Except<ObjectType, KeysType extends keyof ObjectType> = Pick<ObjectType, Exclude<keyof ObjectType, KeysType>>;
@@ -0,0 +1,33 @@
import {Primitive} from './basic';
/**
Allows creating a union type by combining primitive types and literal types without sacrificing auto-completion in IDEs for the literal type part of the union.
Currently, when a union type of a primitive type is combined with literal types, TypeScript loses all information about the combined literals. Thus, when such type is used in an IDE with autocompletion, no suggestions are made for the declared literals.
This type is a workaround for [Microsoft/TypeScript#29729](https://github.com/Microsoft/TypeScript/issues/29729). It will be removed as soon as it's not needed anymore.
@example
```
import {LiteralUnion} from 'type-fest';
// Before
type Pet = 'dog' | 'cat' | string;
const pet: Pet = '';
// Start typing in your TypeScript-enabled IDE.
// You **will not** get auto-completion for `dog` and `cat` literals.
// After
type Pet2 = LiteralUnion<'dog' | 'cat', string>;
const pet: Pet2 = '';
// You **will** get auto-completion for `dog` and `cat` literals.
```
*/
export type LiteralUnion<
LiteralType extends BaseType,
BaseType extends Primitive
> = LiteralType | (BaseType & {_?: never});
@@ -0,0 +1,39 @@
// Helper type. Not useful on its own.
type Without<FirstType, SecondType> = {[KeyType in Exclude<keyof FirstType, keyof SecondType>]?: never};
/**
Create a type that has mutually exclusive keys.
This type was inspired by [this comment](https://github.com/Microsoft/TypeScript/issues/14094#issuecomment-373782604).
This type works with a helper type, called `Without`. `Without<FirstType, SecondType>` produces a type that has only keys from `FirstType` which are not present on `SecondType` and sets the value type for these keys to `never`. This helper type is then used in `MergeExclusive` to remove keys from either `FirstType` or `SecondType`.
@example
```
import {MergeExclusive} from 'type-fest';
interface ExclusiveVariation1 {
exclusive1: boolean;
}
interface ExclusiveVariation2 {
exclusive2: string;
}
type ExclusiveOptions = MergeExclusive<ExclusiveVariation1, ExclusiveVariation2>;
let exclusiveOptions: ExclusiveOptions;
exclusiveOptions = {exclusive1: true};
//=> Works
exclusiveOptions = {exclusive2: 'hi'};
//=> Works
exclusiveOptions = {exclusive1: true, exclusive2: 'hi'};
//=> Error
```
*/
export type MergeExclusive<FirstType, SecondType> =
(FirstType | SecondType) extends object ?
(Without<FirstType, SecondType> & SecondType) | (Without<SecondType, FirstType> & FirstType) :
FirstType | SecondType;
@@ -0,0 +1,22 @@
import {Except} from './except';
/**
Merge two types into a new type. Keys of the second type overrides keys of the first type.
@example
```
import {Merge} from 'type-fest';
type Foo = {
a: number;
b: string;
};
type Bar = {
b: number;
};
const ab: Merge<Foo, Bar> = {a: 1, b: 2};
```
*/
export type Merge<FirstType, SecondType> = Except<FirstType, Extract<keyof FirstType, keyof SecondType>> & SecondType;
@@ -0,0 +1,22 @@
/**
Convert an object with `readonly` keys into a mutable object. Inverse of `Readonly<T>`.
This can be used to [store and mutate options within a class](https://github.com/sindresorhus/pageres/blob/4a5d05fca19a5fbd2f53842cbf3eb7b1b63bddd2/source/index.ts#L72), [edit `readonly` objects within tests](https://stackoverflow.com/questions/50703834), and [construct a `readonly` object within a function](https://github.com/Microsoft/TypeScript/issues/24509).
@example
```
import {Mutable} from 'type-fest';
type Foo = {
readonly a: number;
readonly b: string;
};
const mutableFoo: Mutable<Foo> = {a: 1, b: '2'};
mutableFoo.a = 3;
```
*/
export type Mutable<ObjectType> = {
// For each `Key` in the keys of `ObjectType`, make a mapped type by removing the `readonly` modifier from the key.
-readonly [KeyType in keyof ObjectType]: ObjectType[KeyType];
};
@@ -0,0 +1,40 @@
/**
Create an opaque type, which hides its internal details from the public, and can only be created by being used explicitly.
The generic type parameter can be anything. It doesn't have to be an object.
[Read more about opaque types.](https://codemix.com/opaque-types-in-javascript/)
There have been several discussions about adding this feature to TypeScript via the `opaque type` operator, similar to how Flow does it. Unfortunately, nothing has (yet) moved forward:
- [Microsoft/TypeScript#15408](https://github.com/Microsoft/TypeScript/issues/15408)
- [Microsoft/TypeScript#15807](https://github.com/Microsoft/TypeScript/issues/15807)
@example
```
import {Opaque} from 'type-fest';
type AccountNumber = Opaque<number>;
type AccountBalance = Opaque<number>;
function createAccountNumber(): AccountNumber {
return 2 as AccountNumber;
}
function getMoneyForAccount(accountNumber: AccountNumber): AccountBalance {
return 4 as AccountBalance;
}
// This will compile successfully.
getMoneyForAccount(createAccountNumber());
// But this won't, because it has to be explicitly passed as an `AccountNumber` type.
getMoneyForAccount(2);
// You can use opaque values like they aren't opaque too.
const accountNumber = createAccountNumber();
// This will compile successfully.
accountNumber + 2;
```
*/
export type Opaque<Type> = Type & {readonly __opaque__: unique symbol};
@@ -0,0 +1,501 @@
import {LiteralUnion} from '..';
declare namespace PackageJson {
/**
A person who has been involved in creating or maintaining the package.
*/
export type Person =
| string
| {
name: string;
url?: string;
email?: string;
};
export type BugsLocation =
| string
| {
/**
The URL to the package's issue tracker.
*/
url?: string;
/**
The email address to which issues should be reported.
*/
email?: string;
};
export interface DirectoryLocations {
/**
Location for executable scripts. Sugar to generate entries in the `bin` property by walking the folder.
*/
bin?: string;
/**
Location for Markdown files.
*/
doc?: string;
/**
Location for example scripts.
*/
example?: string;
/**
Location for the bulk of the library.
*/
lib?: string;
/**
Location for man pages. Sugar to generate a `man` array by walking the folder.
*/
man?: string;
/**
Location for test files.
*/
test?: string;
[directoryType: string]: unknown;
}
export type Scripts = {
/**
Run **before** the package is published (Also run on local `npm install` without any arguments).
*/
prepublish?: string;
/**
Run both **before** the package is packed and published, and on local `npm install` without any arguments. This is run **after** `prepublish`, but **before** `prepublishOnly`.
*/
prepare?: string;
/**
Run **before** the package is prepared and packed, **only** on `npm publish`.
*/
prepublishOnly?: string;
/**
Run **before** a tarball is packed (on `npm pack`, `npm publish`, and when installing git dependencies).
*/
prepack?: string;
/**
Run **after** the tarball has been generated and moved to its final destination.
*/
postpack?: string;
/**
Run **after** the package is published.
*/
publish?: string;
/**
Run **after** the package is published.
*/
postpublish?: string;
/**
Run **before** the package is installed.
*/
preinstall?: string;
/**
Run **after** the package is installed.
*/
install?: string;
/**
Run **after** the package is installed and after `install`.
*/
postinstall?: string;
/**
Run **before** the package is uninstalled and before `uninstall`.
*/
preuninstall?: string;
/**
Run **before** the package is uninstalled.
*/
uninstall?: string;
/**
Run **after** the package is uninstalled.
*/
postuninstall?: string;
/**
Run **before** bump the package version and before `version`.
*/
preversion?: string;
/**
Run **before** bump the package version.
*/
version?: string;
/**
Run **after** bump the package version.
*/
postversion?: string;
/**
Run with the `npm test` command, before `test`.
*/
pretest?: string;
/**
Run with the `npm test` command.
*/
test?: string;
/**
Run with the `npm test` command, after `test`.
*/
posttest?: string;
/**
Run with the `npm stop` command, before `stop`.
*/
prestop?: string;
/**
Run with the `npm stop` command.
*/
stop?: string;
/**
Run with the `npm stop` command, after `stop`.
*/
poststop?: string;
/**
Run with the `npm start` command, before `start`.
*/
prestart?: string;
/**
Run with the `npm start` command.
*/
start?: string;
/**
Run with the `npm start` command, after `start`.
*/
poststart?: string;
/**
Run with the `npm restart` command, before `restart`. Note: `npm restart` will run the `stop` and `start` scripts if no `restart` script is provided.
*/
prerestart?: string;
/**
Run with the `npm restart` command. Note: `npm restart` will run the `stop` and `start` scripts if no `restart` script is provided.
*/
restart?: string;
/**
Run with the `npm restart` command, after `restart`. Note: `npm restart` will run the `stop` and `start` scripts if no `restart` script is provided.
*/
postrestart?: string;
} & {
[scriptName: string]: string;
};
/**
Dependencies of the package. The version range is a string which has one or more space-separated descriptors. Dependencies can also be identified with a tarball or Git URL.
*/
export interface Dependency {
[packageName: string]: string;
}
export interface NonStandardEntryPoints {
/**
An ECMAScript module ID that is the primary entry point to the program.
*/
module?: string;
/**
A module ID with untranspiled code that is the primary entry point to the program.
*/
esnext?:
| string
| {
main?: string;
browser?: string;
[moduleName: string]: string | undefined;
};
/**
A hint to JavaScript bundlers or component tools when packaging modules for client side use.
*/
browser?:
| string
| {
[moduleName: string]: string | false;
};
}
export interface TypeScriptConfiguration {
/**
Location of the bundled TypeScript declaration file.
*/
types?: string;
/**
Location of the bundled TypeScript declaration file. Alias of `types`.
*/
typings?: string;
}
export interface YarnConfiguration {
/**
If your package only allows one version of a given dependency, and youd like to enforce the same behavior as `yarn install --flat` on the command line, set this to `true`.
Note that if your `package.json` contains `"flat": true` and other packages depend on yours (e.g. you are building a library rather than an application), those other packages will also need `"flat": true` in their `package.json` or be installed with `yarn install --flat` on the command-line.
*/
flat?: boolean;
/**
Selective version resolutions. Allows the definition of custom package versions inside dependencies without manual edits in the `yarn.lock` file.
*/
resolutions?: Dependency;
}
export interface JSPMConfiguration {
/**
JSPM configuration.
*/
jspm?: PackageJson;
}
}
/**
Type for [npm's `package.json` file](https://docs.npmjs.com/creating-a-package-json-file). Also includes types for fields used by other popular projects, like TypeScript and Yarn.
*/
export type PackageJson = {
/**
The name of the package.
*/
name?: string;
/**
Package version, parseable by [`node-semver`](https://github.com/npm/node-semver).
*/
version?: string;
/**
Package description, listed in `npm search`.
*/
description?: string;
/**
Keywords associated with package, listed in `npm search`.
*/
keywords?: string[];
/**
The URL to the package's homepage.
*/
homepage?: LiteralUnion<'.', string>;
/**
The URL to the package's issue tracker and/or the email address to which issues should be reported.
*/
bugs?: PackageJson.BugsLocation;
/**
The license for the package.
*/
license?: string;
/**
The licenses for the package.
*/
licenses?: Array<{
type?: string;
url?: string;
}>;
author?: PackageJson.Person;
/**
A list of people who contributed to the package.
*/
contributors?: PackageJson.Person[];
/**
A list of people who maintain the package.
*/
maintainers?: PackageJson.Person[];
/**
The files included in the package.
*/
files?: string[];
/**
The module ID that is the primary entry point to the program.
*/
main?: string;
/**
The executable files that should be installed into the `PATH`.
*/
bin?:
| string
| {
[binary: string]: string;
};
/**
Filenames to put in place for the `man` program to find.
*/
man?: string | string[];
/**
Indicates the structure of the package.
*/
directories?: PackageJson.DirectoryLocations;
/**
Location for the code repository.
*/
repository?:
| string
| {
type: string;
url: string;
};
/**
Script commands that are run at various times in the lifecycle of the package. The key is the lifecycle event, and the value is the command to run at that point.
*/
scripts?: PackageJson.Scripts;
/**
Is used to set configuration parameters used in package scripts that persist across upgrades.
*/
config?: {
[configKey: string]: unknown;
};
/**
The dependencies of the package.
*/
dependencies?: PackageJson.Dependency;
/**
Additional tooling dependencies that are not required for the package to work. Usually test, build, or documentation tooling.
*/
devDependencies?: PackageJson.Dependency;
/**
Dependencies that are skipped if they fail to install.
*/
optionalDependencies?: PackageJson.Dependency;
/**
Dependencies that will usually be required by the package user directly or via another dependency.
*/
peerDependencies?: PackageJson.Dependency;
/**
Package names that are bundled when the package is published.
*/
bundledDependencies?: string[];
/**
Alias of `bundledDependencies`.
*/
bundleDependencies?: string[];
/**
Engines that this package runs on.
*/
engines?: {
[EngineName in 'npm' | 'node' | string]: string;
};
/**
@deprecated
*/
engineStrict?: boolean;
/**
Operating systems the module runs on.
*/
os?: Array<LiteralUnion<
| 'aix'
| 'darwin'
| 'freebsd'
| 'linux'
| 'openbsd'
| 'sunos'
| 'win32'
| '!aix'
| '!darwin'
| '!freebsd'
| '!linux'
| '!openbsd'
| '!sunos'
| '!win32',
string
>>;
/**
CPU architectures the module runs on.
*/
cpu?: Array<LiteralUnion<
| 'arm'
| 'arm64'
| 'ia32'
| 'mips'
| 'mipsel'
| 'ppc'
| 'ppc64'
| 's390'
| 's390x'
| 'x32'
| 'x64'
| '!arm'
| '!arm64'
| '!ia32'
| '!mips'
| '!mipsel'
| '!ppc'
| '!ppc64'
| '!s390'
| '!s390x'
| '!x32'
| '!x64',
string
>>;
/**
If set to `true`, a warning will be shown if package is installed locally. Useful if the package is primarily a command-line application that should be installed globally.
@deprecated
*/
preferGlobal?: boolean;
/**
If set to `true`, then npm will refuse to publish it.
*/
private?: boolean;
/**
* A set of config values that will be used at publish-time. It's especially handy to set the tag, registry or access, to ensure that a given package is not tagged with 'latest', published to the global public registry or that a scoped module is private by default.
*/
publishConfig?: {
[config: string]: unknown;
};
} &
PackageJson.NonStandardEntryPoints &
PackageJson.TypeScriptConfiguration &
PackageJson.YarnConfiguration &
PackageJson.JSPMConfiguration & {
[key: string]: unknown;
};
@@ -0,0 +1,72 @@
import {Primitive} from './basic';
/**
Create a type from another type with all keys and nested keys set to optional.
Use-cases:
- Merging a default settings/config object with another object, the second object would be a deep partial of the default object.
- Mocking and testing complex entities, where populating an entire object with its keys would be redundant in terms of the mock or test.
@example
```
import {PartialDeep} from 'type-fest';
const settings: Settings = {
textEditor: {
fontSize: 14;
fontColor: '#000000';
fontWeight: 400;
}
autocomplete: false;
autosave: true;
};
const applySavedSettings = (savedSettings: PartialDeep<Settings>) => {
return {...settings, ...savedSettings};
}
settings = applySavedSettings({textEditor: {fontWeight: 500}});
```
*/
export type PartialDeep<T> = T extends Primitive
? Partial<T>
: T extends Map<infer KeyType, infer ValueType>
? PartialMapDeep<KeyType, ValueType>
: T extends Set<infer ItemType>
? PartialSetDeep<ItemType>
: T extends ReadonlyMap<infer KeyType, infer ValueType>
? PartialReadonlyMapDeep<KeyType, ValueType>
: T extends ReadonlySet<infer ItemType>
? PartialReadonlySetDeep<ItemType>
: T extends ((...arguments: any[]) => unknown)
? T | undefined
: T extends object
? PartialObjectDeep<T>
: unknown;
/**
Same as `PartialDeep`, but accepts only `Map`s and as inputs. Internal helper for `PartialDeep`.
*/
interface PartialMapDeep<KeyType, ValueType> extends Map<PartialDeep<KeyType>, PartialDeep<ValueType>> {}
/**
Same as `PartialDeep`, but accepts only `Set`s as inputs. Internal helper for `PartialDeep`.
*/
interface PartialSetDeep<T> extends Set<PartialDeep<T>> {}
/**
Same as `PartialDeep`, but accepts only `ReadonlyMap`s as inputs. Internal helper for `PartialDeep`.
*/
interface PartialReadonlyMapDeep<KeyType, ValueType> extends ReadonlyMap<PartialDeep<KeyType>, PartialDeep<ValueType>> {}
/**
Same as `PartialDeep`, but accepts only `ReadonlySet`s as inputs. Internal helper for `PartialDeep`.
*/
interface PartialReadonlySetDeep<T> extends ReadonlySet<PartialDeep<T>> {}
/**
Same as `PartialDeep`, but accepts only `object`s as inputs. Internal helper for `PartialDeep`.
*/
type PartialObjectDeep<ObjectType extends object> = {
[KeyType in keyof ObjectType]?: PartialDeep<ObjectType[KeyType]>
};
@@ -0,0 +1,23 @@
/**
Create a type that represents either the value or the value wrapped in `PromiseLike`.
Use-cases:
- A function accepts a callback that may either return a value synchronously or may return a promised value.
- This type could be the return type of `Promise#then()`, `Promise#catch()`, and `Promise#finally()` callbacks.
Please upvote [this issue](https://github.com/microsoft/TypeScript/issues/31394) if you want to have this type as a built-in in TypeScript.
@example
```
import {Promisable} from 'type-fest';
async function logger(getLogEntry: () => Promisable<string>): Promise<void> {
const entry = await getLogEntry();
console.log(entry);
}
logger(() => 'foo');
logger(() => Promise.resolve('bar'));
```
*/
export type Promisable<T> = T | PromiseLike<T>;
@@ -0,0 +1,59 @@
import {Primitive} from './basic';
/**
Convert `object`s, `Map`s, `Set`s, and `Array`s and all of their keys/elements into immutable structures recursively.
This is useful when a deeply nested structure needs to be exposed as completely immutable, for example, an imported JSON module or when receiving an API response that is passed around.
Please upvote [this issue](https://github.com/microsoft/TypeScript/issues/13923) if you want to have this type as a built-in in TypeScript.
@example
```
// data.json
{
"foo": ["bar"]
}
// main.ts
import {ReadonlyDeep} from 'type-fest';
import dataJson = require('./data.json');
const data: ReadonlyDeep<typeof dataJson> = dataJson;
export default data;
// test.ts
import data from './main';
data.foo.push('bar');
//=> error TS2339: Property 'push' does not exist on type 'readonly string[]'
```
*/
export type ReadonlyDeep<T> = T extends Primitive | ((...arguments: any[]) => unknown)
? T
: T extends ReadonlyMap<infer KeyType, infer ValueType>
? ReadonlyMapDeep<KeyType, ValueType>
: T extends ReadonlySet<infer ItemType>
? ReadonlySetDeep<ItemType>
: T extends object
? ReadonlyObjectDeep<T>
: unknown;
/**
Same as `ReadonlyDeep`, but accepts only `ReadonlyMap`s as inputs. Internal helper for `ReadonlyDeep`.
*/
interface ReadonlyMapDeep<KeyType, ValueType>
extends ReadonlyMap<ReadonlyDeep<KeyType>, ReadonlyDeep<ValueType>> {}
/**
Same as `ReadonlyDeep`, but accepts only `ReadonlySet`s as inputs. Internal helper for `ReadonlyDeep`.
*/
interface ReadonlySetDeep<ItemType>
extends ReadonlySet<ReadonlyDeep<ItemType>> {}
/**
Same as `ReadonlyDeep`, but accepts only `object`s as inputs. Internal helper for `ReadonlyDeep`.
*/
type ReadonlyObjectDeep<ObjectType extends object> = {
readonly [KeyType in keyof ObjectType]: ReadonlyDeep<ObjectType[KeyType]>
};
@@ -0,0 +1,32 @@
import {Except} from './except';
/**
Create a type that requires at least one of the given keys. The remaining keys are kept as is.
@example
```
import {RequireAtLeastOne} from 'type-fest';
type Responder = {
text?: () => string;
json?: () => string;
secure?: boolean;
};
const responder: RequireAtLeastOne<Responder, 'text' | 'json'> = {
json: () => '{"message": "ok"}',
secure: true
};
```
*/
export type RequireAtLeastOne<ObjectType, KeysType extends keyof ObjectType = keyof ObjectType> =
{
// For each Key in KeysType make a mapped type
[Key in KeysType]: (
// …by picking that Key's type and making it required
Required<Pick<ObjectType, Key>>
)
}[KeysType]
// …then, make intersection types by adding the remaining keys to each mapped type.
& Except<ObjectType, KeysType>;
@@ -0,0 +1,36 @@
// TODO: Remove this when we target TypeScript >=3.5.
// eslint-disable-next-line @typescript-eslint/generic-type-naming
type _Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>;
/**
Create a type that requires exactly one of the given keys and disallows more. The remaining keys are kept as is.
Use-cases:
- Creating interfaces for components that only need one of the keys to display properly.
- Declaring generic keys in a single place for a single use-case that gets narrowed down via `RequireExactlyOne`.
The caveat with `RequireExactlyOne` is that TypeScript doesn't always know at compile time every key that will exist at runtime. Therefore `RequireExactlyOne` can't do anything to prevent extra keys it doesn't know about.
@example
```
import {RequireExactlyOne} from 'type-fest';
type Responder = {
text: () => string;
json: () => string;
secure: boolean;
};
const responder: RequireExactlyOne<Responder, 'text' | 'json'> = {
// Adding a `text` key here would cause a compile error.
json: () => '{"message": "ok"}',
secure: true
};
```
*/
export type RequireExactlyOne<ObjectType, KeysType extends keyof ObjectType = keyof ObjectType> =
{[Key in KeysType]: (
Required<Pick<ObjectType, Key>> &
Partial<Record<Exclude<KeysType, Key>, never>>
)}[KeysType] & _Omit<ObjectType, KeysType>;
@@ -0,0 +1,32 @@
/**
Create a type that makes the given keys optional. The remaining keys are kept as is. The sister of the `SetRequired` type.
Use-case: You want to define a single model where the only thing that changes is whether or not some of the keys are optional.
@example
```
import {SetOptional} from 'type-fest';
type Foo = {
a: number;
b?: string;
c: boolean;
}
type SomeOptional = SetOptional<Foo, 'b' | 'c'>;
// type SomeOptional = {
// a: number;
// b?: string; // Was already optional and still is.
// c?: boolean; // Is now optional.
// }
```
*/
export type SetOptional<BaseType, Keys extends keyof BaseType = keyof BaseType> =
// Pick just the keys that are not optional from the base type.
Pick<BaseType, Exclude<keyof BaseType, Keys>> &
// Pick the keys that should be optional from the base type and make them optional.
Partial<Pick<BaseType, Keys>> extends
// If `InferredType` extends the previous, then for each key, use the inferred type key.
infer InferredType
? {[KeyType in keyof InferredType]: InferredType[KeyType]}
: never;
@@ -0,0 +1,32 @@
/**
Create a type that makes the given keys required. The remaining keys are kept as is. The sister of the `SetOptional` type.
Use-case: You want to define a single model where the only thing that changes is whether or not some of the keys are required.
@example
```
import {SetRequired} from 'type-fest';
type Foo = {
a?: number;
b: string;
c?: boolean;
}
type SomeRequired = SetRequired<Foo, 'b' | 'c'>;
// type SomeRequired = {
// a?: number;
// b: string; // Was already required and still is.
// c: boolean; // Is now required.
// }
```
*/
export type SetRequired<BaseType, Keys extends keyof BaseType = keyof BaseType> =
// Pick just the keys that are not required from the base type.
Pick<BaseType, Exclude<keyof BaseType, Keys>> &
// Pick the keys that should be required from the base type and make them required.
Required<Pick<BaseType, Keys>> extends
// If `InferredType` extends the previous, then for each key, use the inferred type key.
infer InferredType
? {[KeyType in keyof InferredType]: InferredType[KeyType]}
: never;
+158
View File
@@ -0,0 +1,158 @@
{
"_args": [
[
"eslint-plugin-unicorn@19.0.1",
"/mnt/Foxconn/Digitalent/Deverloper/liff-push_2series"
]
],
"_development": true,
"_from": "eslint-plugin-unicorn@19.0.1",
"_id": "eslint-plugin-unicorn@19.0.1",
"_inBundle": false,
"_integrity": "sha512-fu0/h5mHXfBC6EkA3i2vCjsfC8j53+T9txGhNL4fpxJ+1JKsUKfv+tmXDgy0XnLHhFjnOZp4tRWJWbcykeIP2Q==",
"_location": "/eslint-plugin-unicorn",
"_phantomChildren": {
"@babel/code-frame": "7.10.4",
"@types/normalize-package-data": "2.4.0",
"error-ex": "1.3.2",
"json-parse-better-errors": "1.0.2",
"lines-and-columns": "1.1.6",
"normalize-package-data": "2.5.0",
"p-limit": "2.3.0",
"regexp-tree": "0.1.21"
},
"_requested": {
"type": "version",
"registry": true,
"raw": "eslint-plugin-unicorn@19.0.1",
"name": "eslint-plugin-unicorn",
"escapedName": "eslint-plugin-unicorn",
"rawSpec": "19.0.1",
"saveSpec": null,
"fetchSpec": "19.0.1"
},
"_requiredBy": [
"/@nuxtjs/eslint-config"
],
"_resolved": "https://registry.npmjs.org/eslint-plugin-unicorn/-/eslint-plugin-unicorn-19.0.1.tgz",
"_spec": "19.0.1",
"_where": "/mnt/Foxconn/Digitalent/Deverloper/liff-push_2series",
"author": {
"name": "Sindre Sorhus",
"email": "sindresorhus@gmail.com",
"url": "https://sindresorhus.com"
},
"ava": {
"babel": true,
"files": [
"test/*.js"
]
},
"bugs": {
"url": "https://github.com/sindresorhus/eslint-plugin-unicorn/issues"
},
"dependencies": {
"ci-info": "^2.0.0",
"clean-regexp": "^1.0.0",
"eslint-ast-utils": "^1.1.0",
"eslint-template-visitor": "^1.1.0",
"eslint-utils": "^2.0.0",
"import-modules": "^2.0.0",
"lodash": "^4.17.15",
"read-pkg-up": "^7.0.1",
"regexp-tree": "^0.1.21",
"reserved-words": "^0.1.2",
"safe-regex": "^2.1.1",
"semver": "^7.1.3"
},
"description": "Various awesome ESLint rules",
"devDependencies": {
"@ava/babel": "^1.0.1",
"@lubien/fixture-beta-package": "^1.0.0-beta.1",
"@typescript-eslint/parser": "^2.24.0",
"ava": "^3.5.0",
"babel-eslint": "^10.1.0",
"chalk": "^3.0.0",
"del": "^5.1.0",
"eslint": "^6.8.0",
"eslint-ava-rule-tester": "^4.0.0",
"eslint-plugin-eslint-plugin": "^2.2.1",
"execa": "^4.0.0",
"listr": "^0.14.3",
"nyc": "^15.0.0",
"outdent": "^0.7.1",
"pify": "^5.0.0",
"typescript": "^3.8.3",
"vue-eslint-parser": "^7.0.0",
"xo": "^0.28.0"
},
"engines": {
"node": ">=10"
},
"files": [
"index.js",
"rules"
],
"funding": "https://github.com/sindresorhus/eslint-plugin-unicorn?sponsor=1",
"homepage": "https://github.com/sindresorhus/eslint-plugin-unicorn#readme",
"keywords": [
"eslint",
"eslintplugin",
"eslint-plugin",
"unicorn",
"linter",
"lint",
"style",
"xo"
],
"license": "MIT",
"name": "eslint-plugin-unicorn",
"nyc": {
"reporter": [
"text",
"lcov"
]
},
"peerDependencies": {
"eslint": ">=6.8.0"
},
"repository": {
"type": "git",
"url": "git+https://github.com/sindresorhus/eslint-plugin-unicorn.git"
},
"scripts": {
"integration": "node ./test/integration/test.js",
"lint": "node ./test/lint/lint.js",
"test": "xo && nyc ava"
},
"version": "19.0.1",
"xo": {
"parserOptions": {
"sourceType": "script"
},
"plugins": [
"eslint-plugin"
],
"extends": [
"plugin:eslint-plugin/all"
],
"overrides": [
{
"files": "rules/utils/*.js",
"rules": {
"eslint-plugin/require-meta-docs-url": "off"
}
},
{
"files": "test/*.js",
"parserOptions": {
"sourceType": "module"
}
}
],
"rules": {
"strict": "error",
"unicorn/string-content": "off"
}
}
}
+179
View File
@@ -0,0 +1,179 @@
# eslint-plugin-unicorn [![Build Status](https://github.com/sindresorhus/eslint-plugin-unicorn/workflows/CI/badge.svg?branch=master)](https://github.com/sindresorhus/eslint-plugin-unicorn/actions?query=branch%3Amaster+workflow%3ACI) [![Coverage Status](https://codecov.io/gh/sindresorhus/eslint-plugin-unicorn/branch/master/graph/badge.svg)](https://codecov.io/gh/sindresorhus/eslint-plugin-unicorn/branch/master)
<img src="https://cloud.githubusercontent.com/assets/170270/18659176/1cc373d0-7f33-11e6-890f-0ba35362ee7e.jpg" width="180" align="right">
> Various awesome ESLint rules
You might want to check out [XO](https://github.com/xojs/xo), which includes this plugin.
[**Propose or contribute a new rule ➡**](.github/contributing.md)
## Install
```console
$ npm install --save-dev eslint eslint-plugin-unicorn
```
## Usage
Configure it in `package.json`.
```json
{
"name": "my-awesome-project",
"eslintConfig": {
"env": {
"es6": true
},
"parserOptions": {
"ecmaVersion": 2020,
"sourceType": "module"
},
"plugins": [
"unicorn"
],
"rules": {
"unicorn/better-regex": "error",
"unicorn/catch-error-name": "error",
"unicorn/consistent-function-scoping": "error",
"unicorn/custom-error-definition": "off",
"unicorn/error-message": "error",
"unicorn/escape-case": "error",
"unicorn/expiring-todo-comments": "error",
"unicorn/explicit-length-check": "error",
"unicorn/filename-case": "error",
"unicorn/import-index": "error",
"unicorn/new-for-builtins": "error",
"unicorn/no-abusive-eslint-disable": "error",
"unicorn/no-array-instanceof": "error",
"unicorn/no-console-spaces": "error",
"unicorn/no-fn-reference-in-iterator": "error",
"unicorn/no-for-loop": "error",
"unicorn/no-hex-escape": "error",
"unicorn/no-keyword-prefix": "off",
"no-nested-ternary": "off",
"unicorn/no-nested-ternary": "error",
"unicorn/no-new-buffer": "error",
"unicorn/no-null": "error",
"unicorn/no-process-exit": "error",
"unicorn/no-unreadable-array-destructuring": "error",
"unicorn/no-unsafe-regex": "off",
"unicorn/no-unused-properties": "off",
"unicorn/no-zero-fractions": "error",
"unicorn/number-literal-case": "error",
"unicorn/prefer-add-event-listener": "error",
"unicorn/prefer-dataset": "error",
"unicorn/prefer-event-key": "error",
"unicorn/prefer-flat-map": "error",
"unicorn/prefer-includes": "error",
"unicorn/prefer-modern-dom-apis": "error",
"unicorn/prefer-negative-index": "error",
"unicorn/prefer-node-append": "error",
"unicorn/prefer-node-remove": "error",
"unicorn/prefer-number-properties": "error",
"unicorn/prefer-query-selector": "error",
"unicorn/prefer-reflect-apply": "error",
"unicorn/prefer-replace-all": "off",
"unicorn/prefer-set-has": "error",
"unicorn/prefer-spread": "error",
"unicorn/prefer-starts-ends-with": "error",
"unicorn/prefer-string-slice": "error",
"unicorn/prefer-text-content": "error",
"unicorn/prefer-trim-start-end": "error",
"unicorn/prefer-type-error": "error",
"unicorn/prevent-abbreviations": "error",
"unicorn/string-content": "off",
"unicorn/throw-new-error": "error"
}
}
}
```
## Rules
- [better-regex](docs/rules/better-regex.md) - Improve regexes by making them shorter, consistent, and safer. *(fixable)*
- [catch-error-name](docs/rules/catch-error-name.md) - Enforce a specific parameter name in catch clauses. *(fixable)*
- [consistent-function-scoping](docs/rules/consistent-function-scoping.md) - Move function definitions to the highest possible scope.
- [custom-error-definition](docs/rules/custom-error-definition.md) - Enforce correct `Error` subclassing. *(fixable)*
- [error-message](docs/rules/error-message.md) - Enforce passing a `message` value when throwing a built-in error.
- [escape-case](docs/rules/escape-case.md) - Require escape sequences to use uppercase values. *(fixable)*
- [expiring-todo-comments](docs/rules/expiring-todo-comments.md) - Add expiration conditions to TODO comments.
- [explicit-length-check](docs/rules/explicit-length-check.md) - Enforce explicitly comparing the `length` property of a value. *(partly fixable)*
- [filename-case](docs/rules/filename-case.md) - Enforce a case style for filenames.
- [import-index](docs/rules/import-index.md) - Enforce importing index files with `.`. *(fixable)*
- [new-for-builtins](docs/rules/new-for-builtins.md) - Enforce the use of `new` for all builtins, except `String`, `Number`, `Boolean`, `Symbol` and `BigInt`. *(fixable)*
- [no-abusive-eslint-disable](docs/rules/no-abusive-eslint-disable.md) - Enforce specifying rules to disable in `eslint-disable` comments.
- [no-array-instanceof](docs/rules/no-array-instanceof.md) - Require `Array.isArray()` instead of `instanceof Array`. *(fixable)*
- [no-console-spaces](docs/rules/no-console-spaces.md) - Do not use leading/trailing space between `console.log` parameters. *(fixable)*
- [no-fn-reference-in-iterator](docs/rules/no-fn-reference-in-iterator.md) - Prevent passing a function reference directly to iterator methods.
- [no-for-loop](docs/rules/no-for-loop.md) - Do not use a `for` loop that can be replaced with a `for-of` loop. *(partly fixable)*
- [no-hex-escape](docs/rules/no-hex-escape.md) - Enforce the use of Unicode escapes instead of hexadecimal escapes. *(fixable)*
- [no-keyword-prefix](docs/rules/no-keyword-prefix.md) - Disallow identifiers starting with `new` or `class`.
- [no-nested-ternary](docs/rules/no-nested-ternary.md) - Disallow nested ternary expressions. *(partly fixable)*
- [no-new-buffer](docs/rules/no-new-buffer.md) - Enforce the use of `Buffer.from()` and `Buffer.alloc()` instead of the deprecated `new Buffer()`. *(fixable)*
- [no-null](docs/rules/no-null.md) - Disallow the use of the `null` literal.
- [no-process-exit](docs/rules/no-process-exit.md) - Disallow `process.exit()`.
- [no-unreadable-array-destructuring](docs/rules/no-unreadable-array-destructuring.md) - Disallow unreadable array destructuring.
- [no-unsafe-regex](docs/rules/no-unsafe-regex.md) - Disallow unsafe regular expressions.
- [no-unused-properties](docs/rules/no-unused-properties.md) - Disallow unused object properties.
- [no-zero-fractions](docs/rules/no-zero-fractions.md) - Disallow number literals with zero fractions or dangling dots. *(fixable)*
- [number-literal-case](docs/rules/number-literal-case.md) - Enforce proper case for numeric literals. *(fixable)*
- [prefer-add-event-listener](docs/rules/prefer-add-event-listener.md) - Prefer `.addEventListener()` and `.removeEventListener()` over `on`-functions. *(partly fixable)*
- [prefer-dataset](docs/rules/prefer-dataset.md) - Prefer using `.dataset` on DOM elements over `.setAttribute(…)`. *(fixable)*
- [prefer-event-key](docs/rules/prefer-event-key.md) - Prefer `KeyboardEvent#key` over `KeyboardEvent#keyCode`. *(partly fixable)*
- [prefer-flat-map](docs/rules/prefer-flat-map.md) - Prefer `.flatMap(…)` over `.map(…).flat()`. *(fixable)*
- [prefer-includes](docs/rules/prefer-includes.md) - Prefer `.includes()` over `.indexOf()` when checking for existence or non-existence. *(fixable)*
- [prefer-modern-dom-apis](docs/rules/prefer-modern-dom-apis.md) - Prefer `.before()` over `.insertBefore()`, `.replaceWith()` over `.replaceChild()`, prefer one of `.before()`, `.after()`, `.append()` or `.prepend()` over `insertAdjacentText()` and `insertAdjacentElement()`. *(fixable)*
- [prefer-negative-index](docs/rules/prefer-negative-index.md) - Prefer negative index over `.length - index` for `{String,Array,TypedArray}#slice()` and `Array#splice()`. *(fixable)*
- [prefer-node-append](docs/rules/prefer-node-append.md) - Prefer `Node#append()` over `Node#appendChild()`. *(fixable)*
- [prefer-node-remove](docs/rules/prefer-node-remove.md) - Prefer `childNode.remove()` over `parentNode.removeChild(childNode)`. *(fixable)*
- [prefer-number-properties](docs/rules/prefer-number-properties.md) - Prefer `Number` static properties over global ones. *(fixable)*
- [prefer-query-selector](docs/rules/prefer-query-selector.md) - Prefer `.querySelector()` over `.getElementById()`, `.querySelectorAll()` over `.getElementsByClassName()` and `.getElementsByTagName()`. *(partly fixable)*
- [prefer-reflect-apply](docs/rules/prefer-reflect-apply.md) - Prefer `Reflect.apply()` over `Function#apply()`. *(fixable)*
- [prefer-replace-all](docs/rules/prefer-replace-all.md) - Prefer `String#replaceAll()` over regex searches with the global flag. *(fixable)*
- [prefer-set-has](docs/rules/prefer-set-has.md) - Prefer `Set#has()` over `Array#includes()` when checking for existence or non-existence. *(fixable)*
- [prefer-spread](docs/rules/prefer-spread.md) - Prefer the spread operator over `Array.from()`. *(fixable)*
- [prefer-starts-ends-with](docs/rules/prefer-starts-ends-with.md) - Prefer `String#startsWith()` & `String#endsWith()` over more complex alternatives.
- [prefer-string-slice](docs/rules/prefer-string-slice.md) - Prefer `String#slice()` over `String#substr()` and `String#substring()`. *(partly fixable)*
- [prefer-text-content](docs/rules/prefer-text-content.md) - Prefer `.textContent` over `.innerText`. *(fixable)*
- [prefer-trim-start-end](docs/rules/prefer-trim-start-end.md) - Prefer `String#trimStart()` / `String#trimEnd()` over `String#trimLeft()` / `String#trimRight()`. *(fixable)*
- [prefer-type-error](docs/rules/prefer-type-error.md) - Enforce throwing `TypeError` in type checking conditions. *(fixable)*
- [prevent-abbreviations](docs/rules/prevent-abbreviations.md) - Prevent abbreviations. *(partly fixable)*
- [string-content](docs/rules/string-content.md) - Enforce better string content. *(fixable)*
- [throw-new-error](docs/rules/throw-new-error.md) - Require `new` when throwing an error. *(fixable)*
## Deprecated Rules
- [prefer-exponentiation-operator](docs/rules/prefer-exponentiation-operator.md) - Use the built-in ESLint [`prefer-exponentiation-operator`](https://eslint.org/docs/rules/prefer-exponentiation-operator) rule instead.
- [regex-shorthand](docs/rules/regex-shorthand.md) - Renamed to [`better-regex`](docs/rules/better-regex.md).
## Recommended config
This plugin exports a [`recommended` config](index.js) that enforces good practices.
Enable it in your `package.json` with the `extends` option:
```json
{
"name": "my-awesome-project",
"eslintConfig": {
"extends": "plugin:unicorn/recommended"
}
}
```
See the [ESLint docs](https://eslint.org/docs/user-guide/configuring#extending-configuration-files) for more information about extending config files.
**Note**: This config will also enable the correct [parser options](https://eslint.org/docs/user-guide/configuring#specifying-parser-options) and [environment](https://eslint.org/docs/user-guide/configuring#specifying-environments).
## Maintainers
- [Sindre Sorhus](https://github.com/sindresorhus)
- [Adam Babcock](https://github.com/MrHen)
- [futpib](https://github.com/futpib)
- [Fisker Cheung](https://github.com/fisker)
###### Former
- [Jeroen Engels](https://github.com/jfmengels)
- [Sam Verschueren](https://github.com/SamVerschueren)
Binary file not shown.
+104
View File
@@ -0,0 +1,104 @@
'use strict';
const cleanRegexp = require('clean-regexp');
const {optimize} = require('regexp-tree');
const getDocumentationUrl = require('./utils/get-documentation-url');
const quoteString = require('./utils/quote-string');
const message = '{{original}} can be optimized to {{optimized}}';
const create = context => {
const {sortCharacterClasses} = context.options[0] || {};
const blacklist = [];
if (sortCharacterClasses === false) {
blacklist.push('charClassClassrangesMerge');
}
return {
'Literal[regex]': node => {
const {raw: original, regex} = node;
// Regular Expressions with `u` flag are not well handled by `regexp-tree`
// https://github.com/DmitrySoshnikov/regexp-tree/issues/162
if (regex.flags.includes('u')) {
return;
}
let optimized = original;
try {
optimized = optimize(original, undefined, {blacklist}).toString();
} catch (_) {}
if (original === optimized) {
return;
}
context.report({
node,
message,
data: {
original,
optimized
},
fix: fixer => fixer.replaceText(node, optimized)
});
},
'NewExpression[callee.type="Identifier"][callee.name="RegExp"][arguments.length>=1][arguments.0.type="Literal"]': node => {
const [patternNode, flagsNode] = node.arguments;
if (typeof patternNode.value !== 'string') {
return;
}
const oldPattern = patternNode.value;
const flags = flagsNode &&
flagsNode.type === 'Literal' &&
typeof flagsNode.value === 'string' ?
flagsNode.value :
'';
const newPattern = cleanRegexp(oldPattern, flags);
if (oldPattern !== newPattern) {
context.report({
node,
message,
data: {
original: oldPattern,
optimized: newPattern
},
fix: fixer => fixer.replaceText(
patternNode,
quoteString(newPattern)
)
});
}
}
};
};
const schema = [
{
type: 'object',
properties: {
sortCharacterClasses: {
type: 'boolean',
default: true
}
}
}
];
module.exports = {
create,
meta: {
type: 'suggestion',
docs: {
url: getDocumentationUrl(__filename)
},
fixable: 'code',
schema
}
};
+131
View File
@@ -0,0 +1,131 @@
'use strict';
const {findVariable} = require('eslint-utils');
const avoidCapture = require('./utils/avoid-capture');
const getDocumentationUrl = require('./utils/get-documentation-url');
const renameVariable = require('./utils/rename-variable');
const methodSelector = require('./utils/method-selector');
const ERROR_MESSAGE_ID = 'error';
const promiseMethodSelector = (method, argumentsLength, argumentIndex) => [
methodSelector({
name: method,
length: argumentsLength
}),
`:matches(${
[
'FunctionExpression',
'ArrowFunctionExpression'
].map(type => `[arguments.${argumentIndex}.type="${type}"]`).join(', ')
})`,
`[arguments.${argumentIndex}.params.length=1]`,
`[arguments.${argumentIndex}.params.0.type="Identifier"]`
].join('');
// Matches `promise.catch([FunctionExpression | ArrowFunctionExpression])`
const promiseCatchSelector = promiseMethodSelector('catch', 1, 0);
// Matches `promise.then(any, [FunctionExpression | ArrowFunctionExpression])`
const promiseThenSelector = promiseMethodSelector('then', 2, 1);
const catchSelector = [
'CatchClause',
'>',
'Identifier.param'
].join('');
const create = context => {
const {ecmaVersion} = context.parserOptions;
const sourceCode = context.getSourceCode();
const options = {
name: 'error',
ignore: [],
...context.options[0]
};
const {name: expectedName} = options;
const ignore = options.ignore.map(
pattern => pattern instanceof RegExp ? pattern : new RegExp(pattern, 'u')
);
const isNameAllowed = name =>
name === expectedName ||
ignore.some(regexp => regexp.test(name)) ||
name.endsWith(expectedName) ||
name.endsWith(expectedName.charAt(0).toUpperCase() + expectedName.slice(1));
function check(node) {
const originalName = node.name;
if (
isNameAllowed(originalName) ||
isNameAllowed(originalName.replace(/_+$/g, ''))
) {
return;
}
const scope = context.getScope();
const variable = findVariable(scope, node);
if (originalName === '_' && variable.references.length === 0) {
return;
}
const scopes = [
variable.scope,
...variable.references.map(({from}) => from)
];
const fixedName = avoidCapture(expectedName, scopes, ecmaVersion);
context.report({
node,
messageId: ERROR_MESSAGE_ID,
data: {
originalName,
fixedName
},
fix: fixer => renameVariable(variable, fixedName, fixer, sourceCode)
});
}
return {
[promiseCatchSelector]: node => {
check(node.arguments[0].params[0]);
},
[promiseThenSelector]: node => {
check(node.arguments[1].params[0]);
},
[catchSelector]: node => {
check(node);
}
};
};
const schema = [
{
type: 'object',
properties: {
name: {
type: 'string'
},
ignore: {
type: 'array',
uniqueItems: true
}
}
}
];
module.exports = {
create,
meta: {
type: 'suggestion',
docs: {
url: getDocumentationUrl(__filename)
},
fixable: 'code',
schema,
messages: {
[ERROR_MESSAGE_ID]: 'The catch parameter `{{originalName}}` should be named `{{fixedName}}`.'
}
}
};
+195
View File
@@ -0,0 +1,195 @@
'use strict';
const getDocumentationUrl = require('./utils/get-documentation-url');
const getReferences = require('./utils/get-references');
const MESSAGE_ID_NAMED = 'named';
const MESSAGE_ID_ANONYMOUS = 'anonymous';
const isSameScope = (scope1, scope2) =>
scope1 && scope2 && (scope1 === scope2 || scope1.block === scope2.block);
function checkReferences(scope, parent, scopeManager) {
const hitReference = references => references.some(reference => {
if (isSameScope(parent, reference.from)) {
return true;
}
const {resolved} = reference;
const [definition] = resolved.defs;
// Skip recursive function name
if (definition && definition.type === 'FunctionName' && resolved.name === definition.name.name) {
return false;
}
return isSameScope(parent, resolved.scope);
});
const hitDefinitions = definitions => definitions.some(definition => {
const scope = scopeManager.acquire(definition.node);
return isSameScope(parent, scope);
});
// This check looks for neighboring function definitions
const hitIdentifier = identifiers => identifiers.some(identifier => {
// Only look at identifiers that live in a FunctionDeclaration
if (
!identifier.parent ||
identifier.parent.type !== 'FunctionDeclaration'
) {
return false;
}
const identifierScope = scopeManager.acquire(identifier);
// If we have a scope, the earlier checks should have worked so ignore them here
if (identifierScope) {
return false;
}
const identifierParentScope = scopeManager.acquire(identifier.parent);
if (!identifierParentScope) {
return false;
}
// Ignore identifiers from our own scope
if (isSameScope(scope, identifierParentScope)) {
return false;
}
// Look at the scope above the function definition to see if lives
// next to the reference being checked
return isSameScope(parent, identifierParentScope.upper);
});
return getReferences(scope)
.map(({resolved}) => resolved)
.filter(Boolean)
.some(variable =>
hitReference(variable.references) ||
hitDefinitions(variable.defs) ||
hitIdentifier(variable.identifiers)
);
}
// https://reactjs.org/docs/hooks-reference.html
const reactHooks = new Set([
'useState',
'useEffect',
'useContext',
'useReducer',
'useCallback',
'useMemo',
'useRef',
'useImperativeHandle',
'useLayoutEffect',
'useDebugValue'
]);
const isReactHook = scope =>
scope.block &&
scope.block.parent &&
scope.block.parent.callee &&
scope.block.parent.callee.type === 'Identifier' &&
reactHooks.has(scope.block.parent.callee.name);
const isArrowFunctionWithThis = scope =>
scope.type === 'function' &&
scope.block &&
scope.block.type === 'ArrowFunctionExpression' &&
(scope.thisFound || scope.childScopes.some(scope => isArrowFunctionWithThis(scope)));
function checkNode(node, scopeManager) {
const scope = scopeManager.acquire(node);
if (!scope || isArrowFunctionWithThis(scope)) {
return true;
}
let parentNode = node.parent;
if (!parentNode) {
return true;
}
// Skip over junk like the block statement inside of a function declaration
// or the various pieces of an arrow function.
if (parentNode.type === 'VariableDeclarator') {
parentNode = parentNode.parent;
}
if (parentNode.type === 'VariableDeclaration') {
parentNode = parentNode.parent;
}
if (parentNode.type === 'BlockStatement') {
parentNode = parentNode.parent;
}
const parentScope = scopeManager.acquire(parentNode);
if (!parentScope || parentScope.type === 'global' || isReactHook(parentScope)) {
return true;
}
return checkReferences(scope, parentScope, scopeManager);
}
const create = context => {
const sourceCode = context.getSourceCode();
const {scopeManager} = sourceCode;
const functions = [];
let hasJsx = false;
return {
'ArrowFunctionExpression, FunctionDeclaration': node => functions.push(node),
JSXElement: () => {
// Turn off this rule if we see a JSX element because scope
// references does not include JSXElement nodes.
hasJsx = true;
},
':matches(ArrowFunctionExpression, FunctionDeclaration):exit': node => {
if (!hasJsx && !checkNode(node, scopeManager)) {
const functionType = node.type === 'ArrowFunctionExpression' ? 'arrow function' : 'function';
let functionName = '';
if (node.id) {
functionName = node.id.name;
} else if (
node.parent &&
node.parent.type === 'VariableDeclarator' &&
node.parent.id &&
node.parent.id.type === 'Identifier'
) {
functionName = node.parent.id.name;
}
context.report({
node,
messageId: functionName ? MESSAGE_ID_NAMED : MESSAGE_ID_ANONYMOUS,
data: {
functionType,
functionName
}
});
}
functions.pop();
if (functions.length === 0) {
hasJsx = false;
}
}
};
};
module.exports = {
create,
meta: {
type: 'suggestion',
docs: {
url: getDocumentationUrl(__filename)
},
messages: {
[MESSAGE_ID_NAMED]: 'Move {{functionType}} `{{functionName}}` to the outer scope.',
[MESSAGE_ID_ANONYMOUS]: 'Move {{functionType}} to the outer scope.'
}
}
};
+225
View File
@@ -0,0 +1,225 @@
'use strict';
const {upperFirst} = require('lodash');
const getDocumentationUrl = require('./utils/get-documentation-url');
const MESSAGE_ID_INVALID_EXPORT = 'invalidExport';
const nameRegexp = /^(?:[A-Z][\da-z]*)*Error$/;
const getClassName = name => upperFirst(name).replace(/(?:error|)$/i, 'Error');
const getConstructorMethod = className => `
constructor() {
super();
this.name = '${className}';
}
`;
const hasValidSuperClass = node => {
if (!node.superClass) {
return false;
}
let {name} = node.superClass;
if (node.superClass.type === 'MemberExpression') {
({name} = node.superClass.property);
}
return nameRegexp.test(name);
};
const isSuperExpression = node =>
node.type === 'ExpressionStatement' &&
node.expression.type === 'CallExpression' &&
node.expression.callee.type === 'Super';
const isAssignmentExpression = (node, name) => {
if (
node.type !== 'ExpressionStatement' ||
node.expression.type !== 'AssignmentExpression'
) {
return false;
}
const lhs = node.expression.left;
if (!lhs.object || lhs.object.type !== 'ThisExpression') {
return false;
}
return lhs.property.name === name;
};
const isClassProperty = (node, name) => {
if (node.type !== 'ClassProperty' || node.computed) {
return false;
}
const {key} = node;
if (key.type !== 'Identifier') {
return false;
}
return key.name === name;
};
const customErrorDefinition = (context, node) => {
if (!hasValidSuperClass(node)) {
return;
}
if (node.id === null) {
return;
}
const {name} = node.id;
const className = getClassName(name);
if (name !== className) {
context.report({
node: node.id,
message: `Invalid class name, use \`${className}\`.`
});
}
const {body} = node.body;
const constructor = body.find(x => x.kind === 'constructor');
if (!constructor) {
context.report({
node,
message: 'Add a constructor to your error.',
fix: fixer => fixer.insertTextAfterRange([
node.body.range[0],
node.body.range[0] + 1
], getConstructorMethod(name))
});
return;
}
const constructorBodyNode = constructor.value.body;
// Verify the constructor has a body (TypeScript)
if (!constructorBodyNode) {
return;
}
const constructorBody = constructorBodyNode.body;
const superExpression = constructorBody.find(body => isSuperExpression(body));
const messageExpressionIndex = constructorBody.findIndex(x => isAssignmentExpression(x, 'message'));
if (!superExpression) {
context.report({
node: constructorBodyNode,
message: 'Missing call to `super()` in constructor.'
});
} else if (messageExpressionIndex !== -1) {
const expression = constructorBody[messageExpressionIndex];
context.report({
node: superExpression,
message: 'Pass the error message to `super()` instead of setting `this.message`.',
fix: fixer => {
const fixings = [];
if (superExpression.expression.arguments.length === 0) {
const rhs = expression.expression.right;
fixings.push(
fixer.insertTextAfterRange([
superExpression.range[0],
superExpression.range[0] + 6
], rhs.raw || rhs.name)
);
}
fixings.push(
fixer.removeRange([
messageExpressionIndex === 0 ? constructorBodyNode.range[0] : constructorBody[messageExpressionIndex - 1].range[1],
expression.range[1]
])
);
return fixings;
}
});
}
const nameExpression = constructorBody.find(x => isAssignmentExpression(x, 'name'));
if (!nameExpression) {
const nameProperty = node.body.body.find(node => isClassProperty(node, 'name'));
if (!nameProperty || !nameProperty.value || nameProperty.value.value !== name) {
context.report({
node: nameProperty && nameProperty.value ? nameProperty.value : constructorBodyNode,
message: `The \`name\` property should be set to \`${name}\`.`
});
}
} else if (nameExpression.expression.right.value !== name) {
context.report({
node: nameExpression ? nameExpression.expression.right : constructorBodyNode,
message: `The \`name\` property should be set to \`${name}\`.`
});
}
};
const customErrorExport = (context, node) => {
if (!node.left.object || node.left.object.name !== 'exports') {
return;
}
if (!node.left.property) {
return;
}
const exportsName = node.left.property.name;
const maybeError = node.right;
if (maybeError.type !== 'ClassExpression') {
return;
}
if (!hasValidSuperClass(maybeError)) {
return;
}
if (!maybeError.id) {
return;
}
// Assume rule has already fixed the error name
const errorName = maybeError.id.name;
if (exportsName === errorName) {
return;
}
context.report({
node: node.left.property,
messageId: MESSAGE_ID_INVALID_EXPORT,
fix: fixer => fixer.replaceText(node.left.property, errorName)
});
};
const create = context => {
return {
ClassDeclaration: node => customErrorDefinition(context, node),
'AssignmentExpression[right.type="ClassExpression"]': node => customErrorDefinition(context, node.right),
'AssignmentExpression[left.type="MemberExpression"]': node => customErrorExport(context, node)
};
};
module.exports = {
create,
meta: {
type: 'problem',
docs: {
url: getDocumentationUrl(__filename)
},
fixable: 'code',
messages: {
[MESSAGE_ID_INVALID_EXPORT]: 'Exported error name should match error class'
}
}
};
+104
View File
@@ -0,0 +1,104 @@
'use strict';
const getDocumentationUrl = require('./utils/get-documentation-url');
const errorConstructors = new Set([
'Error',
'EvalError',
'InternalError',
'RangeError',
'ReferenceError',
'SyntaxError',
'TypeError',
'URIError'
]);
const isReferenceAssigned = expression => {
if (expression.type === 'AssignmentExpression') {
const assignedVariable = expression.left;
return assignedVariable.type === 'Identifier' && assignedVariable.name;
}
return false;
};
const findIdentifierValues = (identifierNode, context) => {
const scope = context.getScope(identifierNode);
const declarations = scope.set.get(identifierNode.name);
if (declarations === undefined) {
return [];
}
const expressions = declarations.references.map(reference => reference.identifier.parent);
const referenceValues = [];
for (const expression of expressions) {
if (isReferenceAssigned(expression)) {
referenceValues.push(expression.right);
} else if (expression.type === 'VariableDeclarator') {
referenceValues.push(expression.init);
}
}
return referenceValues;
};
const isEmptyMessageString = node => {
return (
node.arguments.length > 0 &&
node.arguments[0].type === 'Literal' &&
!node.arguments[0].value
);
};
const reportError = (expressionNode, context) => {
const error = expressionNode.callee;
if (errorConstructors.has(error.name)) {
if (expressionNode.arguments.length === 0) {
context.report({
node: expressionNode.parent,
message: 'Pass a message to the error constructor.'
});
}
if (isEmptyMessageString(expressionNode)) {
context.report({
node: expressionNode.parent,
message: 'Error message should not be an empty string.'
});
}
}
};
const checkErrorMessage = (node, context) => {
if (node.type === 'Identifier') {
const identifierValues = findIdentifierValues(node, context);
for (const node of identifierValues) {
checkErrorMessage(node, context);
}
} else if (node.type === 'NewExpression' || node.type === 'CallExpression') {
reportError(node, context);
}
};
const create = context => {
const throwStatements = [];
return {
'ThrowStatement'(throwStatement) {
throwStatements.push(throwStatement);
},
'Program:exit'() {
for (const throwStatement of throwStatements) {
checkErrorMessage(throwStatement.argument, context);
}
}
};
};
module.exports = {
create,
meta: {
type: 'problem',
docs: {
url: getDocumentationUrl(__filename)
}
}
};
+59
View File
@@ -0,0 +1,59 @@
'use strict';
const getDocumentationUrl = require('./utils/get-documentation-url');
const replaceTemplateElement = require('./utils/replace-template-element');
const escapeWithLowercase = /(?<=(?:^|[^\\])(?:\\\\)*\\)(?<data>x[\dA-Fa-f]{2}|u[\dA-Fa-f]{4}|u{[\dA-Fa-f]+})/g;
const escapePatternWithLowercase = /(?<=(?:^|[^\\])(?:\\\\)*\\)(?<data>x[\dA-Fa-f]{2}|u[\dA-Fa-f]{4}|u{[\dA-Fa-f]+}|c[a-z])/g;
const message = 'Use uppercase characters for the value of the escape sequence.';
const create = context => {
const check = ({node, original, regex = escapeWithLowercase, fix}) => {
const fixed = original.replace(regex, data => data.slice(0, 1) + data.slice(1).toUpperCase());
if (fixed !== original) {
context.report({
node,
message,
fix: fixer => fix ? fix(fixer, fixed) : fixer.replaceText(node, fixed)
});
}
};
return {
Literal(node) {
if (typeof node.value !== 'string') {
return;
}
check({
node,
original: node.raw
});
},
'Literal[regex]'(node) {
check({
node,
original: node.raw,
regex: escapePatternWithLowercase
});
},
TemplateElement(node) {
check({
node,
original: node.value.raw,
fix: (fixer, fixed) => replaceTemplateElement(fixer, node, fixed)
});
}
};
};
module.exports = {
create,
meta: {
type: 'suggestion',
docs: {
url: getDocumentationUrl(__filename)
},
fixable: 'code'
}
};
+533
View File
@@ -0,0 +1,533 @@
'use strict';
const readPkgUp = require('read-pkg-up');
const semver = require('semver');
const ci = require('ci-info');
const baseRule = require('eslint/lib/rules/no-warning-comments');
const getDocumentationUrl = require('./utils/get-documentation-url');
// `unicorn/` prefix is added to avoid conflicts with core rule
const MESSAGE_ID_AVOID_MULTIPLE_DATES = 'unicorn/avoidMultipleDates';
const MESSAGE_ID_EXPIRED_TODO = 'unicorn/expiredTodo';
const MESSAGE_ID_AVOID_MULTIPLE_PACKAGE_VERSIONS =
'unicorn/avoidMultiplePackageVersions';
const MESSAGE_ID_REACHED_PACKAGE_VERSION = 'unicorn/reachedPackageVersion';
const MESSAGE_ID_HAVE_PACKAGE = 'unicorn/havePackage';
const MESSAGE_ID_DONT_HAVE_PACKAGE = 'unicorn/dontHavePackage';
const MESSAGE_ID_VERSION_MATCHES = 'unicorn/versionMatches';
const MESSAGE_ID_ENGINE_MATCHES = 'unicorn/engineMatches';
const MESSAGE_ID_REMOVE_WHITESPACES = 'unicorn/removeWhitespaces';
const MESSAGE_ID_MISSING_AT_SYMBOL = 'unicorn/missingAtSymbol';
const packageResult = readPkgUp.sync();
const hasPackage = Boolean(packageResult);
const packageJson = hasPackage ? packageResult.packageJson : {};
const packageDependencies = {
...packageJson.dependencies,
...packageJson.devDependencies
};
const DEPENDENCY_INCLUSION_RE = /^[+-]\s*@?\S+\/?\S+/;
const VERSION_COMPARISON_RE = /^(?<name>@?\S\/?\S+)@(?<condition>>|>=)(?<version>\d+(?:\.\d+){0,2}(?:-[\da-z-]+(?:\.[\da-z-]+)*)?(?:\+[\da-z-]+(?:\.[\da-z-]+)*)?)/i;
const PKG_VERSION_RE = /^(?<condition>>|>=)(?<version>\d+(?:\.\d+){0,2}(?:-[\da-z-]+(?:\.[\da-z-]+)*)?(?:\+[\da-z-]+(?:\.[\da-z-]+)*)?)\s*$/;
const ISO8601_DATE = /\d{4}-\d{2}-\d{2}/;
function parseTodoWithArguments(string, {terms}) {
const lowerCaseString = string.toLowerCase();
const lowerCaseTerms = terms.map(term => term.toLowerCase());
const hasTerm = lowerCaseTerms.some(term => lowerCaseString.includes(term));
if (!hasTerm) {
return false;
}
const TODO_ARGUMENT_RE = /\[(?<rawArguments>[^}]+)]/i;
const result = TODO_ARGUMENT_RE.exec(string);
if (!result) {
return false;
}
const {rawArguments} = result.groups;
return rawArguments
.split(',')
.map(argument => parseArgument(argument.trim()))
.reduce((groups, argument) => {
if (!groups[argument.type]) {
groups[argument.type] = [];
}
groups[argument.type].push(argument.value);
return groups;
}, {});
}
function parseArgument(argumentString) {
if (ISO8601_DATE.test(argumentString)) {
return {
type: 'dates',
value: argumentString
};
}
if (hasPackage && DEPENDENCY_INCLUSION_RE.test(argumentString)) {
const condition = argumentString[0] === '+' ? 'in' : 'out';
const name = argumentString.slice(1).trim();
return {
type: 'dependencies',
value: {
name,
condition
}
};
}
if (hasPackage && VERSION_COMPARISON_RE.test(argumentString)) {
const {groups} = VERSION_COMPARISON_RE.exec(argumentString);
const name = groups.name.trim();
const condition = groups.condition.trim();
const version = groups.version.trim();
const hasEngineKeyword = name.indexOf('engine:') === 0;
const isNodeEngine = hasEngineKeyword && name === 'engine:node';
if (hasEngineKeyword && isNodeEngine) {
return {
type: 'engines',
value: {
condition,
version
}
};
}
if (!hasEngineKeyword) {
return {
type: 'dependencies',
value: {
name,
condition,
version
}
};
}
}
if (hasPackage && PKG_VERSION_RE.test(argumentString)) {
const result = PKG_VERSION_RE.exec(argumentString);
const {condition, version} = result.groups;
return {
type: 'packageVersions',
value: {
condition: condition.trim(),
version: version.trim()
}
};
}
// Currently being ignored as integration tests pointed
// some TODO comments have `[random data like this]`
return {
type: 'unknowns',
value: argumentString
};
}
function parseTodoMessage(todoString) {
// @example "TODO [...]: message here"
// @example "TODO [...] message here"
const argumentsEnd = todoString.indexOf(']');
const afterArguments = todoString.slice(argumentsEnd + 1).trim();
// Check if have to skip colon
// @example "TODO [...]: message here"
const dropColon = afterArguments[0] === ':';
if (dropColon) {
return afterArguments.slice(1).trim();
}
return afterArguments;
}
function reachedDate(past) {
const now = new Date().toISOString().slice(0, 10);
return Date.parse(past) < Date.parse(now);
}
function tryToCoerceVersion(rawVersion) {
if (!rawVersion) {
return false;
}
let version = String(rawVersion);
// Remove leading things like `^1.0.0`, `>1.0.0`
const leadingNoises = [
'>=',
'<=',
'>',
'<',
'~',
'^'
];
const foundTrailingNoise = leadingNoises.find(noise => version.startsWith(noise));
if (foundTrailingNoise) {
version = version.slice(foundTrailingNoise.length);
}
// Get only the first member for cases such as `1.0.0 - 2.9999.9999`
const parts = version.split(' ');
if (parts.length > 1) {
version = parts[0];
}
if (semver.valid(version)) {
return version;
}
try {
// Try to semver.parse a perfect match while semver.coerce tries to fix errors
// But coerce can't parse pre-releases.
return semver.parse(version) || semver.coerce(version);
} catch (_) {
return false;
}
}
function semverComparisonForOperator(operator) {
return {
'>': semver.gt,
'>=': semver.gte
}[operator];
}
const create = context => {
const options = {
terms: ['todo', 'fixme', 'xxx'],
ignore: [],
ignoreDatesOnPullRequests: true,
allowWarningComments: true,
...context.options[0]
};
const ignoreRegexes = options.ignore.map(
pattern => pattern instanceof RegExp ? pattern : new RegExp(pattern, 'u')
);
const sourceCode = context.getSourceCode();
const comments = sourceCode.getAllComments();
const unusedComments = comments
.filter(token => token.type !== 'Shebang')
// Block comments come as one.
// Split for situations like this:
// /*
// * TODO [2999-01-01]: Validate this
// * TODO [2999-01-01]: And this
// * TODO [2999-01-01]: Also this
// */
.map(comment =>
comment.value.split('\n').map(line => ({
...comment,
value: line
}))
)
// Flatten
.reduce((accumulator, array) => accumulator.concat(array), [])
.filter(comment => processComment(comment));
// This is highly dependable on ESLint's `no-warning-comments` implementation.
// What we do is patch the parts we know the rule will use, `getAllComments`.
// Since we have priority, we leave only the comments that we didn't use.
const fakeContext = {
...context,
getSourceCode() {
return {
...sourceCode,
getAllComments() {
return options.allowWarningComments ? [] : unusedComments;
}
};
}
};
const rules = baseRule.create(fakeContext);
function processComment(comment) {
if (ignoreRegexes.some(ignore => ignore.test(comment.value))) {
return;
}
const parsed = parseTodoWithArguments(comment.value, options);
if (!parsed) {
return true;
}
// Count if there are valid properties.
// Otherwise, it's a useless TODO and falls back to `no-warning-comments`.
let uses = 0;
const {
packageVersions = [],
dates = [],
dependencies = [],
engines = [],
unknowns = []
} = parsed;
if (dates.length > 1) {
uses++;
context.report({
loc: comment.loc,
messageId: MESSAGE_ID_AVOID_MULTIPLE_DATES,
data: {
expirationDates: dates.join(', '),
message: parseTodoMessage(comment.value)
}
});
} else if (dates.length === 1) {
uses++;
const [date] = dates;
const shouldIgnore = options.ignoreDatesOnPullRequests && ci.isPR;
if (!shouldIgnore && reachedDate(date)) {
context.report({
loc: comment.loc,
messageId: MESSAGE_ID_EXPIRED_TODO,
data: {
expirationDate: date,
message: parseTodoMessage(comment.value)
}
});
}
}
if (packageVersions.length > 1) {
uses++;
context.report({
loc: comment.loc,
messageId: MESSAGE_ID_AVOID_MULTIPLE_PACKAGE_VERSIONS,
data: {
versions: packageVersions
.map(({condition, version}) => `${condition}${version}`)
.join(', '),
message: parseTodoMessage(comment.value)
}
});
} else if (packageVersions.length === 1) {
uses++;
const [{condition, version}] = packageVersions;
const packageVersion = tryToCoerceVersion(packageJson.version);
const decidedPackageVersion = tryToCoerceVersion(version);
const compare = semverComparisonForOperator(condition);
if (packageVersion && compare(packageVersion, decidedPackageVersion)) {
context.report({
loc: comment.loc,
messageId: MESSAGE_ID_REACHED_PACKAGE_VERSION,
data: {
comparison: `${condition}${version}`,
message: parseTodoMessage(comment.value)
}
});
}
}
// Inclusion: 'in', 'out'
// Comparison: '>', '>='
for (const dependency of dependencies) {
uses++;
const targetPackageRawVersion = packageDependencies[dependency.name];
const hasTargetPackage = Boolean(targetPackageRawVersion);
const isInclusion = ['in', 'out'].includes(dependency.condition);
if (isInclusion) {
const [trigger, messageId] =
dependency.condition === 'in' ?
[hasTargetPackage, MESSAGE_ID_HAVE_PACKAGE] :
[!hasTargetPackage, MESSAGE_ID_DONT_HAVE_PACKAGE];
if (trigger) {
context.report({
loc: comment.loc,
messageId,
data: {
package: dependency.name,
message: parseTodoMessage(comment.value)
}
});
}
continue;
}
const todoVersion = tryToCoerceVersion(dependency.version);
const targetPackageVersion = tryToCoerceVersion(targetPackageRawVersion);
if (!hasTargetPackage || !targetPackageVersion) {
// Can't compare `¯\_(ツ)_/¯`
continue;
}
const compare = semverComparisonForOperator(dependency.condition);
if (compare(targetPackageVersion, todoVersion)) {
context.report({
loc: comment.loc,
messageId: MESSAGE_ID_VERSION_MATCHES,
data: {
comparison: `${dependency.name} ${dependency.condition} ${dependency.version}`,
message: parseTodoMessage(comment.value)
}
});
}
}
const packageEngines = packageJson.engines || {};
for (const engine of engines) {
uses++;
const targetPackageRawEngineVersion = packageEngines.node;
const hasTargetEngine = Boolean(targetPackageRawEngineVersion);
if (!hasTargetEngine) {
continue;
}
const todoEngine = tryToCoerceVersion(engine.version);
const targetPackageEngineVersion = tryToCoerceVersion(
targetPackageRawEngineVersion
);
const compare = semverComparisonForOperator(engine.condition);
if (compare(targetPackageEngineVersion, todoEngine)) {
context.report({
loc: comment.loc,
messageId: MESSAGE_ID_ENGINE_MATCHES,
data: {
comparison: `node${engine.condition}${engine.version}`,
message: parseTodoMessage(comment.value)
}
});
}
}
for (const unknown of unknowns) {
// In this case, check if there's just an '@' missing before a '>' or '>='.
const hasAt = unknown.includes('@');
const comparisonIndex = unknown.indexOf('>');
if (!hasAt && comparisonIndex !== -1) {
const testString = `${unknown.slice(
0,
comparisonIndex
)}@${unknown.slice(comparisonIndex)}`;
if (parseArgument(testString).type !== 'unknowns') {
uses++;
context.report({
loc: comment.loc,
messageId: MESSAGE_ID_MISSING_AT_SYMBOL,
data: {
original: unknown,
fix: testString,
message: parseTodoMessage(comment.value)
}
});
continue;
}
}
const withoutWhitespaces = unknown.replace(/ /g, '');
if (parseArgument(withoutWhitespaces).type !== 'unknowns') {
uses++;
context.report({
loc: comment.loc,
messageId: MESSAGE_ID_REMOVE_WHITESPACES,
data: {
original: unknown,
fix: withoutWhitespaces,
message: parseTodoMessage(comment.value)
}
});
continue;
}
}
return uses === 0;
}
return {
Program() {
rules.Program(); // eslint-disable-line new-cap
}
};
};
const schema = [
{
type: 'object',
properties: {
terms: {
type: 'array',
items: {
type: 'string'
}
},
ignore: {
type: 'array',
uniqueItems: true
},
ignoreDatesOnPullRequests: {
type: 'boolean',
default: true
},
allowWarningComments: {
type: 'boolean',
default: false
}
},
additionalProperties: false
}
];
module.exports = {
create,
meta: {
type: 'suggestion',
docs: {
url: getDocumentationUrl(__filename)
},
messages: {
[MESSAGE_ID_AVOID_MULTIPLE_DATES]:
'Avoid using multiple expiration dates in TODO: {{expirationDates}}. {{message}}',
[MESSAGE_ID_EXPIRED_TODO]:
'There is a TODO that is past due date: {{expirationDate}}. {{message}}',
[MESSAGE_ID_REACHED_PACKAGE_VERSION]:
'There is a TODO that is past due package version: {{comparison}}. {{message}}',
[MESSAGE_ID_AVOID_MULTIPLE_PACKAGE_VERSIONS]:
'Avoid using multiple package versions in TODO: {{versions}}. {{message}}',
[MESSAGE_ID_HAVE_PACKAGE]:
'There is a TODO that is deprecated since you installed: {{package}}. {{message}}',
[MESSAGE_ID_DONT_HAVE_PACKAGE]:
'There is a TODO that is deprecated since you uninstalled: {{package}}. {{message}}',
[MESSAGE_ID_VERSION_MATCHES]:
'There is a TODO match for package version: {{comparison}}. {{message}}',
[MESSAGE_ID_ENGINE_MATCHES]:
'There is a TODO match for Node.js version: {{comparison}}. {{message}}',
[MESSAGE_ID_REMOVE_WHITESPACES]:
'Avoid using whitespaces on TODO argument. On \'{{original}}\' use \'{{fix}}\'. {{message}}',
[MESSAGE_ID_MISSING_AT_SYMBOL]:
'Missing \'@\' on TODO argument. On \'{{original}}\' use \'{{fix}}\'. {{message}}',
...baseRule.meta.messages
},
schema
}
};
+173
View File
@@ -0,0 +1,173 @@
'use strict';
const getDocumentationUrl = require('./utils/get-documentation-url');
const operatorTypes = {
gt: ['>'],
gte: ['>='],
ne: ['!==', '!=']
};
function reportError(context, node, message, fixDetails) {
context.report({
node,
message,
fix: fixDetails && (fixer => {
return fixer.replaceText(
node,
`${context.getSourceCode().getText(fixDetails.node)} ${fixDetails.operator} ${fixDetails.value}`
);
})
});
}
function checkZeroType(context, node) {
if (node.operator === '<' && node.right.value === 1) {
reportError(
context,
node,
'Zero `.length` should be compared with `=== 0`.',
{
node: node.left,
operator: '===',
value: 0
}
);
}
}
function checkNonZeroType(context, node, type) {
const {value} = node.right;
const {operator} = node;
switch (type) {
case 'greater-than':
if (
(operatorTypes.gte.includes(operator) && value === 1) ||
(operatorTypes.ne.includes(operator) && value === 0)
) {
reportError(
context,
node,
'Non-zero `.length` should be compared with `> 0`.',
{
node: node.left,
operator: '>',
value: 0
}
);
}
break;
case 'greater-than-or-equal':
if (
(operatorTypes.gt.includes(operator) && value === 0) ||
(operatorTypes.ne.includes(operator) && value === 0)
) {
reportError(
context,
node,
'Non-zero `.length` should be compared with `>= 1`.',
{
node: node.left,
operator: '>=',
value: 1
}
);
}
break;
case 'not-equal':
if (
(operatorTypes.gt.includes(operator) && value === 0) ||
(operatorTypes.gte.includes(operator) && value === 1)
) {
reportError(
context,
node,
'Non-zero `.length` should be compared with `!== 0`.',
{
node: node.left,
operator: '!==',
value: 0
}
);
}
break;
default:
break;
}
}
function checkBinaryExpression(context, node, options) {
if (
node.right.type === 'Literal' &&
node.left.type === 'MemberExpression' &&
node.left.property.type === 'Identifier' &&
node.left.property.name === 'length'
) {
checkZeroType(context, node);
checkNonZeroType(context, node, options['non-zero']);
}
}
function checkExpression(context, node) {
if (node.type === 'LogicalExpression') {
checkExpression(context, node.left);
checkExpression(context, node.right);
return;
}
if (node.type === 'UnaryExpression' && node.operator === '!') {
checkExpression(context, node.argument);
return;
}
if (node.type === 'BinaryExpression') {
checkBinaryExpression(context, node, context.options[0] || {});
return;
}
if (
node.type === 'MemberExpression' &&
node.property.type === 'Identifier' &&
node.property.name === 'length' &&
!node.computed
) {
reportError(context, node, '`length` property should be compared to a value.');
}
}
const create = context => {
return {
IfStatement: node => {
checkExpression(context, node.test);
},
ConditionalExpression: node => {
checkExpression(context, node.test);
}
};
};
const schema = [
{
type: 'object',
properties: {
'non-zero': {
enum: ['not-equal', 'greater-than', 'greater-than-or-equal']
}
}
}
];
module.exports = {
create,
meta: {
type: 'problem',
docs: {
url: getDocumentationUrl(__filename)
},
fixable: 'code',
schema
}
};
+253
View File
@@ -0,0 +1,253 @@
'use strict';
const path = require('path');
const {camelCase, kebabCase, snakeCase, upperFirst} = require('lodash');
const getDocumentationUrl = require('./utils/get-documentation-url');
const cartesianProductSamples = require('./utils/cartesian-product-samples');
const pascalCase = string => upperFirst(camelCase(string));
const numberRegex = /\d+/;
const PLACEHOLDER = '\uFFFF\uFFFF\uFFFF';
const PLACEHOLDER_REGEX = new RegExp(PLACEHOLDER, 'i');
const isIgnoredChar = char => !/^[a-z\d-_$]$/i.test(char);
function ignoreNumbers(fn) {
return string => {
const stack = [];
let execResult = numberRegex.exec(string);
while (execResult) {
stack.push(execResult[0]);
string = string.replace(execResult[0], PLACEHOLDER);
execResult = numberRegex.exec(string);
}
let withCase = fn(string);
while (stack.length > 0) {
withCase = withCase.replace(PLACEHOLDER_REGEX, stack.shift());
}
return withCase;
};
}
const cases = {
camelCase: {
fn: camelCase,
name: 'camel case'
},
kebabCase: {
fn: kebabCase,
name: 'kebab case'
},
snakeCase: {
fn: snakeCase,
name: 'snake case'
},
pascalCase: {
fn: pascalCase,
name: 'pascal case'
}
};
/**
Get the cases specified by the option.
@param {object} options
@returns {string[]} The chosen cases.
*/
function getChosenCases(options) {
if (options.case) {
return [options.case];
}
if (options.cases) {
const cases = Object.keys(options.cases)
.filter(cases => options.cases[cases]);
return cases.length > 0 ? cases : ['kebabCase'];
}
return ['kebabCase'];
}
function validateFilename(words, caseFunctions) {
return words
.filter(({ignored}) => !ignored)
.every(({word}) => caseFunctions.some(fn => fn(word) === word));
}
function fixFilename(words, caseFunctions, {leading, extension}) {
const replacements = words
.map(({word, ignored}) => ignored ? [word] : caseFunctions.map(fn => fn(word)));
const {
samples: combinations
} = cartesianProductSamples(replacements);
return combinations.map(parts => `${leading}${parts.join('')}${extension}`);
}
const leadingUnderscoresRegex = /^(?<leading>_+)(?<tailing>.*)$/;
function splitFilename(filename) {
const result = leadingUnderscoresRegex.exec(filename) || {groups: {}};
const {leading = '', tailing = filename} = result.groups;
const words = [];
let lastWord;
for (const char of tailing) {
const isIgnored = isIgnoredChar(char);
if (lastWord && lastWord.ignored === isIgnored) {
lastWord.word += char;
} else {
lastWord = {
word: char,
ignored: isIgnored
};
words.push(lastWord);
}
}
return {
leading,
words
};
}
/**
Turns `[a, b, c]` into `a, b, or c`.
@param {string[]} words
@returns {string}
*/
function englishishJoinWords(words) {
if (words.length === 1) {
return words[0];
}
if (words.length === 2) {
return `${words[0]} or ${words[1]}`;
}
words = words.slice();
const last = words.pop();
return `${words.join(', ')}, or ${last}`;
}
const create = context => {
const options = context.options[0] || {};
const chosenCases = getChosenCases(options);
const ignore = (options.ignore || []).map(item => {
if (item instanceof RegExp) {
return item;
}
return new RegExp(item, 'u');
});
const chosenCasesFunctions = chosenCases.map(case_ => ignoreNumbers(cases[case_].fn));
const filenameWithExtension = context.getFilename();
if (filenameWithExtension === '<input>' || filenameWithExtension === '<text>') {
return {};
}
return {
Program: node => {
const extension = path.extname(filenameWithExtension);
const filename = path.basename(filenameWithExtension, extension);
const base = filename + extension;
if (base === 'index.js' || ignore.some(regexp => regexp.test(base))) {
return;
}
const {leading, words} = splitFilename(filename);
const isValid = validateFilename(words, chosenCasesFunctions);
if (isValid) {
return;
}
const renamedFilenames = fixFilename(words, chosenCasesFunctions, {
leading,
extension
});
context.report({
node,
messageId: chosenCases.length > 1 ? 'renameToCases' : 'renameToCase',
data: {
chosenCases: englishishJoinWords(chosenCases.map(x => cases[x].name)),
renamedFilenames: englishishJoinWords(renamedFilenames.map(x => `\`${x}\``))
}
});
}
};
};
const schema = [
{
oneOf: [
{
properties: {
case: {
enum: [
'camelCase',
'snakeCase',
'kebabCase',
'pascalCase'
]
},
ignore: {
type: 'array',
uniqueItems: true
}
},
additionalProperties: false
},
{
properties: {
cases: {
properties: {
camelCase: {
type: 'boolean'
},
snakeCase: {
type: 'boolean'
},
kebabCase: {
type: 'boolean'
},
pascalCase: {
type: 'boolean'
}
},
additionalProperties: false
},
ignore: {
type: 'array',
uniqueItems: true
}
},
additionalProperties: false
}
]
}
];
module.exports = {
create,
meta: {
type: 'suggestion',
docs: {
url: getDocumentationUrl(__filename)
},
schema,
messages: {
renameToCase: 'Filename is not in {{chosenCases}}. Rename it to {{renamedFilenames}}.',
renameToCases: 'Filename is not in {{chosenCases}}. Rename it to {{renamedFilenames}}.'
}
}
};
+55
View File
@@ -0,0 +1,55 @@
'use strict';
const getDocumentationUrl = require('./utils/get-documentation-url');
const regexp = /^(?<package>@.*?\/.*?|[./]+?.*?)\/(?:\.|(?:index(?:\.js)?))?$/;
const isImportingIndex = value => regexp.test(value);
const normalize = value => value.replace(regexp, '$<package>');
const importIndex = (context, node, argument) => {
if (argument && isImportingIndex(argument.value)) {
context.report({
node,
message: 'Do not reference the index file directly.',
fix: fixer => fixer.replaceText(argument, `'${normalize(argument.value)}'`)
});
}
};
const create = context => {
const options = context.options[0] || {};
const rules = {
'CallExpression[callee.name="require"]': node => importIndex(context, node, node.arguments[0])
};
if (!options.ignoreImports) {
rules.ImportDeclaration = node => importIndex(context, node, node.source);
}
return rules;
};
const schema = [
{
type: 'object',
properties: {
ignoreImports: {
type: 'boolean',
default: false
}
},
additionalProperties: false
}
];
module.exports = {
create,
meta: {
type: 'suggestion',
docs: {
url: getDocumentationUrl(__filename)
},
schema,
fixable: 'code'
}
};

Some files were not shown because too many files have changed in this diff Show More