Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: validate type comments and generate .d.ts #204

Open
wants to merge 14 commits into
base: main
Choose a base branch
from
1 change: 1 addition & 0 deletions .eslintignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@
!.vitepress
/docs/.vitepress/dist
/docs/.vitepress/cache
/dist
7 changes: 6 additions & 1 deletion .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,12 @@
module.exports = {
root: true,
extends: ["plugin:@eslint-community/mysticatea/es2020"],
parserOptions: {
project: true,
},
rules: {
semi: ["error", "never"],
"semi-spacing": ["error", { before: false, after: true }],

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's something deeply funny to me about an eslint-community project using ESLint for formatting.

"@eslint-community/mysticatea/prettier": "off",
"no-restricted-properties": [
"error",
Expand All @@ -18,7 +23,7 @@ module.exports = {
},
overrides: [
{
files: ["src/**/*.mjs", "test/**/*.mjs"],
files: ["src/**/*.mjs", "test/**/*.mjs", "rollup.config.mjs"],
extends: ["plugin:@eslint-community/mysticatea/+modules"],
rules: {
"init-declarations": "off",
Expand Down
6 changes: 3 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,9 @@ jobs:
- name: ▶️ Run lint script
run: npm run lint

- name: 🏗 Build
run: npm run build

test:
name:
🧪 Test (Node@${{ matrix.node }} - ESLint@${{ matrix.eslint }} - ${{
Expand Down Expand Up @@ -83,9 +86,6 @@ jobs:
- name: 📥 Install ESLint v${{ matrix.eslint }}
run: npm install --save-dev eslint@${{ matrix.eslint }}

- name: 🏗 Build
run: npm run build

- name: ▶️ Run test script
run: npm run test

Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@
/node_modules
/index.*
/test.*
/dist
11 changes: 11 additions & 0 deletions declaration.tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"extends": "./tsconfig",
"files": [],
"exclude": ["tests/**/*.js"],
"compilerOptions": {
"declaration": true,
"emitDeclarationOnly": true,
"noEmit": false,
"outDir": "dist"
}
}
37 changes: 23 additions & 14 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,27 +18,30 @@
"sideEffects": false,
"exports": {
".": {
"import": "./index.mjs",
"require": "./index.js"
},
"./package.json": "./package.json"
"types": "./index.d.ts",
"default": "./index.js"
}
},
"main": "index",
"module": "index.mjs",
"main": "index.js",
"types": "index.d.ts",
"files": [
"index.*"
"index.d.ts",
"index.js"
],
"scripts": {
"prebuild": "npm run -s clean",
"build": "rollup -c",
"clean": "rimraf .nyc_output coverage index.*",
"prebuild": "npm run -s clean ",
"build": "tsc -p declaration.tsconfig.json && rollup -c",
"clean": "rimraf .nyc_output coverage dist index.*",
"coverage": "opener ./coverage/lcov-report/index.html",
"docs:build": "vitepress build docs",
"docs:watch": "vitepress dev docs",
"format": "npm run -s format:prettier -- --write",
"format:prettier": "prettier .",
"format:check": "npm run -s format:prettier -- --check",
"lint": "eslint .",
"lint:eslint": "eslint .",
"lint:tsc": "tsc",

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Naming: I wouldn't call TypeScript a "lint" task. Even if it acts essentially as a linter in this setup, I've found it confusing for newer contributors to see it named as one.

"lint:type-coverage": "type-coverage --detail --strict --at-least 99 --ignore-files 'test/*' --ignore-files 'src/get-static-value.mjs'",
"lint": "run-p lint:*",
"test": "c8 mocha --reporter dot \"test/*.mjs\"",
"preversion": "npm test && npm run -s build",
"postversion": "git push && git push --tags",
Expand All @@ -50,17 +53,23 @@
},
"devDependencies": {
"@eslint-community/eslint-plugin-mysticatea": "^15.5.1",
"@types/eslint": "^8.21.0",
"@types/estree": "^1.0.0",
"@types/mocha": "^10.0.1",
"@types/node": "^18.19.21",
"c8": "^8.0.1",
"dot-prop": "^7.2.0",
"eslint": "^8.50.0",
"mocha": "^9.2.2",
"npm-run-all": "^4.1.5",
"npm-run-all2": "^6.1.2",
"opener": "^1.5.2",
"prettier": "2.8.8",
"rimraf": "^3.0.2",
"rollup": "^2.79.1",
"rollup-plugin-sourcemaps": "^0.6.3",
"rollup": "^4.12.0",
"rollup-plugin-dts": "^6.1.0",
"semver": "^7.5.4",
"type-coverage": "^2.27.1",
"typescript": "^5.3.3",
"vitepress": "^1.0.0-rc.20",
"warun": "^1.0.0"
},
Expand Down
27 changes: 0 additions & 27 deletions rollup.config.js

This file was deleted.

39 changes: 39 additions & 0 deletions rollup.config.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/**
* @author Toru Nagashima
* See LICENSE file in root directory for full license.
*/

import { readFileSync } from "node:fs"
import { URL } from "node:url"

import { dts } from "rollup-plugin-dts"

/** @type {{ dependencies: Record<string, string> }} */
const packageInfo = JSON.parse(
readFileSync(new URL("./package.json", import.meta.url), "utf8"),
)

export default [
{
input: "src/index.mjs",
output: {
exports: "named",
file: `index.js`,
format: "cjs",
sourcemap: true,
},
external: Object.keys(packageInfo.dependencies),
},
{
input: "dist/index.d.mts",
output: [
{
exports: "named",
file: `index.d.ts`,
format: "cjs",
},
],
// type-coverage:ignore-next-line
plugins: [dts()],
},
]
9 changes: 5 additions & 4 deletions src/find-variable.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,19 @@ import { getInnermostScope } from "./get-innermost-scope.mjs"

/**
* Find the variable of a given name.
* @param {Scope} initialScope The scope to start finding.
* @param {string|Node} nameOrNode The variable name to find. If this is a Node object then it should be an Identifier node.
* @returns {Variable|null} The found variable or null.
* @param {import('eslint').Scope.Scope} initialScope The scope to start finding.
* @param {string | import('./types.mjs').Node} nameOrNode The variable name to find. If this is a Node object then it should be an Identifier node.
* @returns {import('eslint').Scope.Variable|null} The found variable or null.
*/
export function findVariable(initialScope, nameOrNode) {
let name = ""
/** @type {import('eslint').Scope.Scope|null} */
let scope = initialScope

if (typeof nameOrNode === "string") {
name = nameOrNode
} else {
name = nameOrNode.name
name = "name" in nameOrNode ? nameOrNode.name : ""
scope = getInnermostScope(scope, nameOrNode)
}

Expand Down
51 changes: 28 additions & 23 deletions src/get-function-head-location.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -2,46 +2,51 @@ import { isArrowToken, isOpeningParenToken } from "./token-predicate.mjs"

/**
* Get the `(` token of the given function node.
* @param {Node} node - The function node to get.
* @param {SourceCode} sourceCode - The source code object to get tokens.
* @returns {Token} `(` token.
* @param {Extract<import('eslint').Rule.Node, { type: 'FunctionDeclaration' | 'FunctionExpression' | 'ArrowFunctionExpression'}>} node - The function node to get.
* @param {import('eslint').SourceCode} sourceCode - The source code object to get tokens.
* @returns {import('eslint').AST.Token | null} `(` token.
*/
function getOpeningParenOfParams(node, sourceCode) {
return node.id
return "id" in node && node.id
? sourceCode.getTokenAfter(node.id, isOpeningParenToken)
: sourceCode.getFirstToken(node, isOpeningParenToken)
}

/**
* Get the location of the given function node for reporting.
* @param {Node} node - The function node to get.
* @param {SourceCode} sourceCode - The source code object to get tokens.
* @returns {string} The location of the function node for reporting.
* @param {Extract<import('eslint').Rule.Node, { type: 'FunctionDeclaration' | 'FunctionExpression' | 'ArrowFunctionExpression'}>} node - The function node to get.
* @param {import('eslint').SourceCode} sourceCode - The source code object to get tokens.
* @returns {import('eslint').AST.SourceLocation|null} The location of the function node for reporting.
*/
export function getFunctionHeadLocation(node, sourceCode) {
const parent = node.parent
let start = null
let end = null
const parent = "parent" in node ? node.parent : undefined

/** @type {import('eslint').AST.SourceLocation["start"]|undefined} */
let start,
/** @type {import('eslint').AST.SourceLocation["end"]|undefined} */
end

if (node.type === "ArrowFunctionExpression") {
const arrowToken = sourceCode.getTokenBefore(node.body, isArrowToken)

start = arrowToken.loc.start
end = arrowToken.loc.end
start = arrowToken?.loc.start
end = arrowToken?.loc.end
} else if (
parent.type === "Property" ||
parent.type === "MethodDefinition" ||
parent.type === "PropertyDefinition"
parent?.type === "Property" ||
parent?.type === "MethodDefinition" ||
parent?.type === "PropertyDefinition"
) {
start = parent.loc.start
end = getOpeningParenOfParams(node, sourceCode).loc.start
start = parent.loc?.start
end = getOpeningParenOfParams(node, sourceCode)?.loc.start
} else {
start = node.loc.start
end = getOpeningParenOfParams(node, sourceCode).loc.start
start = node.loc?.start
end = getOpeningParenOfParams(node, sourceCode)?.loc.start
}

return {
start: { ...start },
end: { ...end },
}
return start && end
? {
start: { ...start },
end: { ...end },
}
: null
}
13 changes: 7 additions & 6 deletions src/get-function-name-with-kind.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,14 @@ import { getPropertyName } from "./get-property-name.mjs"

/**
* Get the name and kind of the given function node.
* @param {ASTNode} node - The function node to get.
* @param {SourceCode} [sourceCode] The source code object to get the code of computed property keys.
* @param {Extract<import('eslint').Rule.Node, { type: 'FunctionDeclaration' | 'FunctionExpression' | 'ArrowFunctionExpression'}>} node - The function node to get.
* @param {import('eslint').SourceCode} [sourceCode] The source code object to get the code of computed property keys.
* @returns {string} The name and kind of the function node.
*/
// eslint-disable-next-line complexity
export function getFunctionNameWithKind(node, sourceCode) {
const parent = node.parent
/** @type {string[]} */
const tokens = []
const isObjectMethod = parent.type === "Property" && parent.value === node
const isClassMethod =
Expand All @@ -25,10 +26,10 @@ export function getFunctionNameWithKind(node, sourceCode) {
tokens.push("private")
}
}
if (node.async) {
if ("async" in node && node.async) {
tokens.push("async")
}
if (node.generator) {
if ("generator" in node && node.generator) {
tokens.push("generator")
}

Expand Down Expand Up @@ -68,8 +69,8 @@ export function getFunctionNameWithKind(node, sourceCode) {
}
}
}
} else if (node.id) {
tokens.push(`'${node.id.name}'`)
} else if ("id" in node && node.id) {
tokens.push(`'${"name" in node.id ? node.id.name : undefined}'`)
} else if (
parent.type === "VariableDeclarator" &&
parent.id &&
Expand Down
15 changes: 10 additions & 5 deletions src/get-innermost-scope.mjs
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
/**
* Get the innermost scope which contains a given location.
* @param {Scope} initialScope The initial scope to search.
* @param {Node} node The location to search.
* @returns {Scope} The innermost scope.
* @param {import('eslint').Scope.Scope} initialScope The initial scope to search.
* @param {import('./types.mjs').Node} node The location to search.
* @returns {import('eslint').Scope.Scope} The innermost scope.
*/
export function getInnermostScope(initialScope, node) {
const location = node.range[0]
const location = node.range ? node.range[0] : undefined

let scope = initialScope
let found = false
Expand All @@ -14,7 +14,12 @@ export function getInnermostScope(initialScope, node) {
for (const childScope of scope.childScopes) {
const range = childScope.block.range

if (range[0] <= location && location < range[1]) {
if (
range &&
location !== undefined &&
range[0] <= location &&
location < range[1]
) {
scope = childScope
found = true
break
Expand Down
Loading