rsnext/packages/eslint-plugin-next/lib/utils/url.js
Houssein Djirdeh e783b0a2e8
Adds ESLint with default rule-set (#23702)
This PR re-includes ESLint with some notable changes, namely a guided setup similar to how TypeScript is instantiated in a Next.js application.

To add ESLint to a project, developers will have to create an `.eslintrc` file in the root of their project or add an empty `eslintConfig` object to their `package.json` file.

```js
touch .eslintrc
```

Then running `next build` will show instructions to install the required packages needed:

<img width="862" alt="Screen Shot 2021-04-19 at 7 38 27 PM" src="https://user-images.githubusercontent.com/12476932/115316182-dfd51b00-a146-11eb-830c-90bad20ed151.png">

Once installed and `next build` is run again, `.eslintrc` will be automatically configured to include the default config:

```json
{
  "extends": "next"
}
```

In addition to this change:

- The feature is now under the experimental flag and requires opt-in. After testing and feedback, it will be switched to the top-level namespace and turned on by default.
- A new ESLint shareable configuration package is included that can be extended in any application with `{ extends: 'next' }`
  - This default config extends recommended rule sets from [`eslint-plugin-react`](https://www.npmjs.com/package/eslint-plugin-react), [`eslint-plugin-react-hooks`](https://www.npmjs.com/package/eslint-plugin-react-hooks), and [`eslint-plugin-next`](https://www.npmjs.com/package/@next/eslint-plugin-next)
- All rules in [`eslint-plugin-next`](https://www.npmjs.com/package/@next/eslint-plugin-next) have been modified to include actionable links that show more information to help resolve each issue
2021-04-30 11:09:07 +00:00

96 lines
2.2 KiB
JavaScript

const fs = require('fs')
const path = require('path')
/**
* Checks if the source is a directory.
* @param {string} source
*/
function isDirectory(source) {
return fs.lstatSync(source).isDirectory()
}
/**
* Checks if the source is a directory.
* @param {string} source
*/
function isSymlink(source) {
return fs.lstatSync(source).isSymbolicLink()
}
/**
* Gets the possible URLs from a directory.
* @param {string} urlprefix
* @param {string} directory
*/
function getUrlFromPagesDirectory(urlPrefix, directory) {
return parseUrlForPages(urlPrefix, directory).map(
// Since the URLs are normalized we add `^` and `$` to the RegExp to make sure they match exactly.
(url) => new RegExp(`^${normalizeURL(url)}$`)
)
}
/**
* Recursively parse directory for page URLs.
* @param {string} urlprefix
* @param {string} directory
*/
function parseUrlForPages(urlprefix, directory) {
const files = fs.readdirSync(directory)
const res = []
files.forEach((fname) => {
if (/(\.(j|t)sx?)$/.test(fname)) {
fname = fname.replace(/\[.*\]/g, '.*')
if (/^index(\.(j|t)sx?)$/.test(fname)) {
res.push(`${urlprefix}${fname.replace(/^index(\.(j|t)sx?)$/, '')}`)
}
res.push(`${urlprefix}${fname.replace(/(\.(j|t)sx?)$/, '')}`)
} else {
const dirPath = path.join(directory, fname)
if (isDirectory(dirPath) && !isSymlink(dirPath)) {
res.push(...parseUrlForPages(urlprefix + fname + '/', dirPath))
}
}
})
return res
}
/**
* Takes a URL and does the following things.
* - Replaces `index.html` with `/`
* - Makes sure all URLs are have a trailing `/`
* - Removes query string
* @param {string} url
*/
function normalizeURL(url) {
if (!url) {
return
}
url = url.split('?')[0]
url = url.split('#')[0]
url = url = url.replace(/(\/index\.html)$/, '/')
// Empty URLs should not be trailed with `/`, e.g. `#heading`
if (url === '') {
return url
}
url = url.endsWith('/') ? url : url + '/'
return url
}
function execOnce(fn) {
let used = false
let result
return (...args) => {
if (!used) {
used = true
result = fn(...args)
}
return result
}
}
module.exports = {
getUrlFromPagesDirectory,
normalizeURL,
execOnce,
}