diff --git a/.eslintrc b/.eslintrc index c80940f3cbf..82db37fd109 100644 --- a/.eslintrc +++ b/.eslintrc @@ -1,4 +1,5 @@ { + "parser": "@typescript-eslint/parser", "extends": [ "react-app", "react-app/jest", @@ -6,7 +7,8 @@ "plugin:cypress/recommended", "plugin:jsx-a11y/recommended", "prettier", - "plugin:prettier/recommended" + "plugin:prettier/recommended", + "plugin:@typescript-eslint/recommended" ], "globals": { "File": true, @@ -36,8 +38,10 @@ }], "no-nested-ternary": "off", "prettier/prettier": "error", + "no-shadow": "off", "react/react-in-jsx-scope": 0, "react/require-default-props": "off", + "@typescript-eslint/no-empty-function": "off", "react/function-component-definition": [1, { "namedComponents": ["arrow-function"] }], @@ -53,6 +57,36 @@ "^@codingame\/monaco-vscode-api", "^@swagger-api\/apidom-json-pointer" ] - }] - } + }], + "react/jsx-filename-extension": [2, + { + "extensions": [ + ".tsx", + ".jsx" + ] + } + ] + }, + "overrides": [ + { + "files": ["*.ts", "*.tsx"], + "rules": { + "import/extensions": "off", + "import/no-unresolved": "off" + } + }, + { + "files": ["*.js"], + "rules": { + "@typescript-eslint/return-await": "off" + } + }, + { + "files": ["*.d.ts"], + "rules": { + "vars-on-top": "off", + "no-var": "off" + } + } + ] } diff --git a/package-lock.json b/package-lock.json index c3697184415..53859580bc6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -71,6 +71,8 @@ "@testing-library/jest-dom": "^6.6.3", "@testing-library/react": "^16.3.0", "@testing-library/user-event": "^14.6.1", + "@typescript-eslint/eslint-plugin": "^5.62.0", + "@typescript-eslint/parser": "^5.62.0", "copyfiles": "^2.4.1", "cross-env": "^7.0.3", "cypress": "^13.15.2", @@ -91,6 +93,7 @@ "serve": "^14.2.4", "source-map-explorer": "^2.5.3", "start-server-and-test": "^2.0.11", + "typescript-strict-plugin": "^2.4.4", "web-vitals": "^4.2.4" } }, @@ -8444,6 +8447,7 @@ "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.62.0.tgz", "integrity": "sha512-VlJEV0fOQ7BExOsHYAGrgbEiZoi8D+Bl2+f6V2RrXerRSylnp+ZBHmPvaIa8cz0Ajx7WO7Z5RqfgYg7ED1nRhA==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { "@typescript-eslint/scope-manager": "5.62.0", "@typescript-eslint/types": "5.62.0", @@ -9756,6 +9760,68 @@ "node": ">=8" } }, + "node_modules/bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "node_modules/bl/node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "node_modules/bl/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dev": true, + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/bl/node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, "node_modules/blob-util": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/blob-util/-/blob-util-2.0.2.tgz", @@ -10454,6 +10520,19 @@ "node": ">=8" } }, + "node_modules/cli-spinners": { + "version": "2.9.2", + "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.2.tgz", + "integrity": "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/cli-table3": { "version": "0.6.3", "resolved": "https://registry.npmjs.org/cli-table3/-/cli-table3-0.6.3.tgz", @@ -10566,6 +10645,16 @@ "node": ">=12" } }, + "node_modules/clone": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", + "integrity": "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8" + } + }, "node_modules/clsx": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", @@ -12675,6 +12764,19 @@ "node": ">= 10" } }, + "node_modules/defaults": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz", + "integrity": "sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "clone": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/define-data-property": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", @@ -16728,6 +16830,16 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/is-interactive": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz", + "integrity": "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/is-map": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.2.tgz", @@ -22152,6 +22264,30 @@ "node": ">= 0.8.0" } }, + "node_modules/ora": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz", + "integrity": "sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "bl": "^4.1.0", + "chalk": "^4.1.0", + "cli-cursor": "^3.1.0", + "cli-spinners": "^2.5.0", + "is-interactive": "^1.0.0", + "is-unicode-supported": "^0.1.0", + "log-symbols": "^4.1.0", + "strip-ansi": "^6.0.0", + "wcwidth": "^1.0.1" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/ospath": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/ospath/-/ospath-1.2.2.tgz", @@ -28041,6 +28177,7 @@ "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", "dev": true, + "license": "Apache-2.0", "peer": true, "bin": { "tsc": "bin/tsc", @@ -28050,6 +28187,135 @@ "node": ">=4.2.0" } }, + "node_modules/typescript-strict-plugin": { + "version": "2.4.4", + "resolved": "https://registry.npmjs.org/typescript-strict-plugin/-/typescript-strict-plugin-2.4.4.tgz", + "integrity": "sha512-OXcWHQk+pW9gqEL/Mb1eTgj/Yiqk1oHBERr9v4VInPOYN++p+cXejmQK/h/VlUPGD++FXQ8pgiqVMyEtxU4T6A==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^3.0.0", + "execa": "^4.0.0", + "minimatch": "^9.0.3", + "ora": "^5.4.1", + "yargs": "^16.2.0" + }, + "bin": { + "tsc-strict": "dist/cli/tsc-strict/index.js", + "update-strict-comments": "dist/cli/update-strict-comments/index.js" + } + }, + "node_modules/typescript-strict-plugin/node_modules/chalk": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", + "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/typescript-strict-plugin/node_modules/cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "node_modules/typescript-strict-plugin/node_modules/execa": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-4.1.0.tgz", + "integrity": "sha512-j5W0//W7f8UxAn8hXVnwG8tLwdiUy4FJLcSupCg6maBYZDpyBvTApK7KyuI4bKj8KOh1r2YH+6ucuYtJv1bTZA==", + "dev": true, + "license": "MIT", + "dependencies": { + "cross-spawn": "^7.0.0", + "get-stream": "^5.0.0", + "human-signals": "^1.1.1", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.0", + "onetime": "^5.1.0", + "signal-exit": "^3.0.2", + "strip-final-newline": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/typescript-strict-plugin/node_modules/get-stream": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "dev": true, + "license": "MIT", + "dependencies": { + "pump": "^3.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/typescript-strict-plugin/node_modules/human-signals": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-1.1.1.tgz", + "integrity": "sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=8.12.0" + } + }, + "node_modules/typescript-strict-plugin/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/typescript-strict-plugin/node_modules/yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "dev": true, + "license": "MIT", + "dependencies": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/unbox-primitive": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", @@ -28578,6 +28844,16 @@ "minimalistic-assert": "^1.0.0" } }, + "node_modules/wcwidth": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", + "integrity": "sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==", + "dev": true, + "license": "MIT", + "dependencies": { + "defaults": "^1.0.3" + } + }, "node_modules/web-streams-polyfill": { "version": "3.3.3", "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz", diff --git a/package.json b/package.json index a3880e7fd48..e51fb5ebf76 100644 --- a/package.json +++ b/package.json @@ -14,14 +14,17 @@ "module": "./dist/esm/swagger-editor.js", "browser": "./dist/esm/swagger-editor.js", "jsnext:main": "./dist/esm/swagger-editor.js", + "types": "./dist/types/App.d.ts", "exports": { "./package.json": "./package.json", "./swagger-editor.css": "./dist/swagger-editor.css", ".": { - "browser": "./dist/esm/swagger-editor.js" + "browser": "./dist/esm/swagger-editor.js", + "types": "./dist/types/App.d.ts" }, "./plugins/*": { - "browser": "./dist/esm/plugins/*/index.js" + "browser": "./dist/esm/plugins/*/index.js", + "types": "./dist/types/plugins/*/index.d.ts" }, "./presets/*": { "browser": "./dist/esm/presets/*/index.js" @@ -33,13 +36,21 @@ "browser": "./dist/esm/editor.worker.js" } }, + "typesVersions": { + "*": { + "plugins/*": [ + "./dist/types/plugins/*/index.d.ts" + ] + } + }, "scripts": { "start": "cross-env DISABLE_ESLINT_PLUGIN=false ENABLE_PROGRESS_PLUGIN=true react-scripts start", - "build": "npm run build:app && npm run build:bundle:esm && npm run build:bundle:umd", + "build": "npm run build:app && npm run build:bundle:esm && npm run build:bundle:umd && npm run build:definitions", "build:app": "cross-env NODE_OPTIONS=--max_old_space_size=4096 ENABLE_PROGRESS_PLUGIN=false DISABLE_ESLINT_PLUGIN=false react-scripts build", "build:app:serve": "serve -s build -l 3050 -L", "build:bundle:esm": "rimraf ./dist/esm && cross-env DISABLE_ESLINT_PLUGIN=false ENABLE_PROGRESS_PLUGIN=false GENERATE_SOURCEMAP=true BUILD_ESM_BUNDLE=true DIST_PATH=dist/esm react-scripts build-bundle && rimraf ./dist/esm/swagger-editor.css*", "build:bundle:umd": "rimraf ./dist/umd ./dist/swagger-editor.css && cross-env NODE_OPTIONS=--max_old_space_size=4096 DISABLE_ESLINT_PLUGIN=false ENABLE_PROGRESS_PLUGIN=false GENERATE_SOURCEMAP=false BUILD_UMD_BUNDLE=true DIST_PATH=dist/umd react-scripts build-bundle && copyfiles -u 2 ./dist/umd/swagger-editor.css ./dist && rimraf ./dist/umd/swagger-editor.css", + "build:definitions": "tsc -p tsconfig.json", "analyze": "source-map-explorer 'build/static/js/main.6d62e780.js'", "test": "react-scripts test", "cy:dev": "start-server-and-test cy:dev:server http://localhost:3003 cy:dev:open", @@ -50,8 +61,8 @@ "cy:run:chrome": "cross-env BROWSERSLIST_ENV=production cypress run --browser chrome", "cy:run:firefox": "cross-env BROWSERSLIST_ENV=production cypress run --browser firefox", "cy:run:electron": "cross-env BROWSERSLIST_ENV=production cypress run --browser electron", - "lint": "eslint . --ext .jsx,.js", - "lint:fix": "eslint . --ext .jsx,.js --fix", + "lint": "eslint . --ext .jsx,.js,.tsx,.ts", + "lint:fix": "eslint . --ext .jsx,.js,.tsx,.ts --fix", "clean": "rimraf ./build ./dist", "link:apidom": "npm link @swagger-api/apidom-ast @swagger-api/apidom-core @swagger-api/apidom-error @swagger-api/apidom-json-path @swagger-api/apidom-json-pointer @swagger-api/apidom-ls @swagger-api/apidom-ns-api-design-systems @swagger-api/apidom-ns-asyncapi-2 @swagger-api/apidom-ns-json-schema-draft-4 @swagger-api/apidom-ns-json-schema-draft-6 @swagger-api/apidom-ns-json-schema-draft-7 @swagger-api/apidom-ns-openapi-2 @swagger-api/apidom-ns-openapi-3-0 @swagger-api/apidom-ns-openapi-3-1 @swagger-api/apidom-parser-adapter-api-design-systems-json @swagger-api/apidom-parser-adapter-api-design-systems-yaml @swagger-api/apidom-parser-adapter-asyncapi-json-2 @swagger-api/apidom-parser-adapter-asyncapi-yaml-2 @swagger-api/apidom-parser-adapter-json @swagger-api/apidom-parser-adapter-openapi-json-2 @swagger-api/apidom-parser-adapter-openapi-json-3-0 @swagger-api/apidom-parser-adapter-openapi-json-3-1 @swagger-api/apidom-parser-adapter-openapi-yaml-2 @swagger-api/apidom-parser-adapter-openapi-yaml-3-0 @swagger-api/apidom-parser-adapter-openapi-yaml-3-1 @swagger-api/apidom-parser-adapter-yaml-1-2 @swagger-api/apidom-parser @swagger-api/apidom-reference", "link:apidom-ls": "npm link @swagger-api/apidom-ls" @@ -135,6 +146,8 @@ "@testing-library/jest-dom": "^6.6.3", "@testing-library/react": "^16.3.0", "@testing-library/user-event": "^14.6.1", + "@typescript-eslint/eslint-plugin": "^5.62.0", + "@typescript-eslint/parser": "^5.62.0", "copyfiles": "^2.4.1", "cross-env": "^7.0.3", "cypress": "^13.15.2", @@ -155,6 +168,7 @@ "serve": "^14.2.4", "source-map-explorer": "^2.5.3", "start-server-and-test": "^2.0.11", + "typescript-strict-plugin": "^2.4.4", "web-vitals": "^4.2.4" } } diff --git a/src/App.jsx b/src/App.tsx similarity index 81% rename from src/App.jsx rename to src/App.tsx index b08df0c67bf..3181c589d93 100644 --- a/src/App.jsx +++ b/src/App.tsx @@ -1,7 +1,7 @@ +// @ts-strict-ignore import React, { useMemo } from 'react'; -import PropTypes from 'prop-types'; import isPlainObject from 'lodash/isPlainObject.js'; -import SwaggerUI from 'swagger-ui-react'; +import SwaggerUI, { SwaggerUIProps } from 'swagger-ui-react'; import 'swagger-ui-react/swagger-ui.css'; /** * Plugins @@ -30,17 +30,21 @@ import EditorContentFixturesPlugin from 'plugins/editor-content-fixtures/index.j import EditorContentFromFilePlugin from 'plugins/editor-content-from-file/index.js'; import EditorSafeRenderPlugin from 'plugins/editor-safe-render/index.js'; import SwaggerUIAdapterPlugin from 'plugins/swagger-ui-adapter/index.js'; -import PropsChangeWatcherPlugin from 'plugins/props-change-watcher/index.js'; +import PropsChangeWatcherPlugin from 'plugins/props-change-watcher/index'; import UtilPlugin from 'plugins/util/index.js'; /** * Presets */ import TextareaPreset from 'presets/textarea/index.js'; import MonacoPreset from 'presets/monaco/index.js'; +/** + * Types + */ +import { SwaggerEditorType } from 'types/swagger-editor'; import './styles/main.scss'; -const SwaggerEditor = React.memo( +const SwaggerEditor: SwaggerEditorType = React.memo( ({ spec = SwaggerUI.config.defaults.spec, url = SwaggerUI.config.defaults.url, @@ -56,7 +60,7 @@ const SwaggerEditor = React.memo( defaultModelExpandDepth = SwaggerUI.config.defaults.defaultModelExpandDepth, defaultModelsExpandDepth = SwaggerUI.config.defaults.defaultModelsExpandDepth, defaultModelRendering = SwaggerUI.config.defaults.defaultModelRendering, - presets = [SwaggerEditor.presets.default], + presets = SwaggerEditor.presets?.default ? [SwaggerEditor.presets.default] : [], deepLinking = SwaggerUI.config.defaults.deepLinking, showExtensions = true, showCommonExtensions = SwaggerUI.config.defaults.showCommonExtensions, @@ -70,7 +74,7 @@ const SwaggerEditor = React.memo( oauth2RedirectUrl = SwaggerUI.config.defaults.oauth2RedirectUrl, initialState = SwaggerUI.config.defaults.initialState, onComplete = null, - }) => { + }: SwaggerUIProps) => { const { plugin: propsChangeWatcherPlugin, getSystem } = PropsChangeWatcherPlugin.useMountPlugin(); const specStr = useMemo(() => { @@ -124,45 +128,6 @@ const SwaggerEditor = React.memo( } ); -/* eslint-disable react/require-default-props */ -SwaggerEditor.propTypes = { - spec: PropTypes.oneOfType([PropTypes.string, PropTypes.object]), - url: PropTypes.string, - layout: PropTypes.string, - requestInterceptor: PropTypes.func, - responseInterceptor: PropTypes.func, - onComplete: PropTypes.func, - docExpansion: PropTypes.oneOf(['list', 'full', 'none']), - supportedSubmitMethods: PropTypes.arrayOf( - PropTypes.oneOf(['get', 'put', 'post', 'delete', 'options', 'head', 'patch', 'trace']) - ), - queryConfigEnabled: PropTypes.bool, - plugins: PropTypes.oneOfType([ - PropTypes.arrayOf(PropTypes.object), - PropTypes.arrayOf(PropTypes.func), - PropTypes.func, - ]), - displayOperationId: PropTypes.bool, - showMutatedRequest: PropTypes.bool, - defaultModelExpandDepth: PropTypes.number, - defaultModelsExpandDepth: PropTypes.number, - defaultModelRendering: PropTypes.oneOf(['example', 'model']), - presets: PropTypes.arrayOf(PropTypes.func), - deepLinking: PropTypes.bool, - showExtensions: PropTypes.bool, - showCommonExtensions: PropTypes.bool, - filter: PropTypes.oneOfType([PropTypes.string, PropTypes.bool]), - requestSnippetsEnabled: PropTypes.bool, - requestSnippets: PropTypes.shape(), - tryItOutEnabled: PropTypes.bool, - displayRequestDuration: PropTypes.bool, - persistAuthorization: PropTypes.bool, - withCredentials: PropTypes.bool, - oauth2RedirectUrl: PropTypes.string, - initialState: PropTypes.shape(), -}; -/* eslint-enable */ - SwaggerEditor.plugins = { Util: UtilPlugin, Modals: ModalsPlugin, diff --git a/src/index.jsx b/src/index.tsx similarity index 87% rename from src/index.jsx rename to src/index.tsx index 04dd3ff7124..12cf57b9a43 100644 --- a/src/index.jsx +++ b/src/index.tsx @@ -2,9 +2,9 @@ import React from 'react'; import { createRoot } from 'react-dom/client'; import 'swagger-ui-react/swagger-ui.css'; -import SwaggerEditor from './App.jsx'; +import SwaggerEditor from './App'; -const root = createRoot(document.getElementById('swagger-editor')); +const root = createRoot(document.getElementById('swagger-editor') as HTMLElement); if (process.env.REACT_APP_E2E_TESTS) { globalThis.React = React; diff --git a/src/plugins/props-change-watcher/actions.js b/src/plugins/props-change-watcher/actions.js deleted file mode 100644 index 0cf04c07e91..00000000000 --- a/src/plugins/props-change-watcher/actions.js +++ /dev/null @@ -1,7 +0,0 @@ -export const EDITOR_PROP_CHANGED = 'EDITOR_PROP_CHANGED'; - -export const propChanged = (propName, newValue, oldValue) => ({ - type: EDITOR_PROP_CHANGED, - payload: propName, - meta: { newValue, oldValue }, -}); diff --git a/src/plugins/props-change-watcher/actions.ts b/src/plugins/props-change-watcher/actions.ts new file mode 100644 index 00000000000..75159743fde --- /dev/null +++ b/src/plugins/props-change-watcher/actions.ts @@ -0,0 +1,15 @@ +import { Action } from 'types/actions'; + +export const EDITOR_PROP_CHANGED = 'EDITOR_PROP_CHANGED'; + +export type PropChangedAction = ( + propName: string, + newValue: unknown, + oldValue: unknown +) => Action; + +export const propChanged: PropChangedAction = (propName, newValue, oldValue) => ({ + type: EDITOR_PROP_CHANGED, + payload: propName, + meta: { newValue, oldValue }, +}); diff --git a/src/plugins/props-change-watcher/hooks/index.js b/src/plugins/props-change-watcher/hooks/index.js deleted file mode 100644 index d3c4c06d709..00000000000 --- a/src/plugins/props-change-watcher/hooks/index.js +++ /dev/null @@ -1,2 +0,0 @@ -export { default as usePropChange } from './use-prop-change.js'; -export { default as useMountPlugin } from './use-mount-plugin.js'; diff --git a/src/plugins/props-change-watcher/hooks/index.ts b/src/plugins/props-change-watcher/hooks/index.ts new file mode 100644 index 00000000000..c711a812255 --- /dev/null +++ b/src/plugins/props-change-watcher/hooks/index.ts @@ -0,0 +1,2 @@ +export { default as usePropChange } from './use-prop-change'; +export { default as useMountPlugin } from './use-mount-plugin'; diff --git a/src/plugins/props-change-watcher/hooks/use-mount-plugin.js b/src/plugins/props-change-watcher/hooks/use-mount-plugin.ts similarity index 58% rename from src/plugins/props-change-watcher/hooks/use-mount-plugin.js rename to src/plugins/props-change-watcher/hooks/use-mount-plugin.ts index 27621627ae2..b093276c922 100644 --- a/src/plugins/props-change-watcher/hooks/use-mount-plugin.js +++ b/src/plugins/props-change-watcher/hooks/use-mount-plugin.ts @@ -1,12 +1,13 @@ import { useRef } from 'react'; +import { SystemValues, System } from 'types/system'; -import pluginImpl from '../plugin-impl.js'; +import pluginImpl from '../plugin-impl'; const useMountPlugin = () => { - const system = useRef(null); + const system = useRef(null); return { - plugin(sys) { + plugin(sys: System) { system.current = sys.getSystem(); return pluginImpl; }, diff --git a/src/plugins/props-change-watcher/hooks/use-prop-change.js b/src/plugins/props-change-watcher/hooks/use-prop-change.ts similarity index 77% rename from src/plugins/props-change-watcher/hooks/use-prop-change.js rename to src/plugins/props-change-watcher/hooks/use-prop-change.ts index 39cb3f73a51..12376221574 100644 --- a/src/plugins/props-change-watcher/hooks/use-prop-change.js +++ b/src/plugins/props-change-watcher/hooks/use-prop-change.ts @@ -1,6 +1,8 @@ import { useEffect, useRef } from 'react'; -const usePropChange = (prop, callback) => { +type UsePropChangeCallback = (newValue: unknown, oldValue: unknown) => void; + +const usePropChange = (prop: unknown, callback: UsePropChangeCallback) => { const isInitialMount = useRef(true); const previousProp = useRef(prop); diff --git a/src/plugins/props-change-watcher/index.js b/src/plugins/props-change-watcher/index.ts similarity index 73% rename from src/plugins/props-change-watcher/index.js rename to src/plugins/props-change-watcher/index.ts index dbaf98a8a65..949290a53e3 100644 --- a/src/plugins/props-change-watcher/index.js +++ b/src/plugins/props-change-watcher/index.ts @@ -1,5 +1,5 @@ -import pluginImpl from './plugin-impl.js'; -import { usePropChange, useMountPlugin } from './hooks/index.js'; +import pluginImpl from './plugin-impl'; +import { usePropChange, useMountPlugin } from './hooks/index'; /** * This plugin is meant to be utilized via the `useMountPlugin` hook. diff --git a/src/plugins/props-change-watcher/plugin-impl.js b/src/plugins/props-change-watcher/plugin-impl.ts similarity index 71% rename from src/plugins/props-change-watcher/plugin-impl.js rename to src/plugins/props-change-watcher/plugin-impl.ts index 6da41f0fff1..33ff6410764 100644 --- a/src/plugins/props-change-watcher/plugin-impl.js +++ b/src/plugins/props-change-watcher/plugin-impl.ts @@ -1,4 +1,4 @@ -import { propChanged } from './actions.js'; +import { propChanged } from './actions'; export default { statePlugins: { diff --git a/src/reportWebVitals.js b/src/reportWebVitals.ts similarity index 67% rename from src/reportWebVitals.js rename to src/reportWebVitals.ts index 9fb67546c17..007127e2325 100644 --- a/src/reportWebVitals.js +++ b/src/reportWebVitals.ts @@ -1,4 +1,7 @@ -const reportWebVitals = (onPerfEntry) => { +// eslint-disable-next-line import/no-extraneous-dependencies +import { MetricType } from 'web-vitals'; + +const reportWebVitals = (onPerfEntry?: (metric: MetricType) => void) => { if (onPerfEntry && onPerfEntry instanceof Function) { // eslint-disable-next-line import/no-extraneous-dependencies import('web-vitals').then(({ onCLS, onFID, onFCP, onLCP, onTTFB }) => { diff --git a/src/types/actions.ts b/src/types/actions.ts new file mode 100644 index 00000000000..76453ed2ebe --- /dev/null +++ b/src/types/actions.ts @@ -0,0 +1,13 @@ +export type AnyAction = { + type: T; +}; + +export interface Action< + Payload, + Meta extends Record | unknown[] | undefined = undefined, +> extends AnyAction { + type: string; + payload: Payload; + error?: boolean; + meta?: Meta; +} diff --git a/src/types/global.d.ts b/src/types/global.d.ts new file mode 100644 index 00000000000..5d062a79d3d --- /dev/null +++ b/src/types/global.d.ts @@ -0,0 +1,10 @@ +import { Root } from 'react-dom/client'; + +import { SwaggerEditorType } from './swagger-editor'; + +declare global { + var root: Root; + var SwaggerEditor: SwaggerEditorType; +} + +export {}; diff --git a/src/types/index.d.ts b/src/types/index.d.ts new file mode 100644 index 00000000000..ae94af771eb --- /dev/null +++ b/src/types/index.d.ts @@ -0,0 +1,60 @@ +declare module 'swagger-ui-react' { + interface Request { + [k: string]: unknown; + } + interface Response { + [k: string]: unknown; + } + type System = unknown; + + export type PluginGenerator = (system: System) => object; + + export type Plugin = object | PluginGenerator; + + export type Preset = () => unknown; + + export interface SwaggerUIProps { + spec?: object | string; + url?: string; + layout?: string; + onComplete?: null | ((system: System) => void); + requestInterceptor?: (req: Request) => Request | Promise; + responseInterceptor?: (res: Response) => Response | Promise; + docExpansion?: 'list' | 'full' | 'none'; + defaultModelExpandDepth?: number; + defaultModelsExpandDepth?: number; + defaultModelRendering?: 'example' | 'model'; + initialState?: object; + queryConfigEnabled?: boolean; + plugins?: Plugin[]; + supportedSubmitMethods?: Array< + 'get' | 'put' | 'post' | 'delete' | 'options' | 'head' | 'patch' | 'trace' + >; + deepLinking?: boolean; + showMutatedRequest?: boolean; + showExtensions?: boolean; + showCommonExtensions?: boolean; + presets?: Preset[]; + filter?: string | boolean; + requestSnippetsEnabled?: boolean; + requestSnippets?: object; + displayOperationId?: boolean; + tryItOutEnabled?: boolean; + displayRequestDuration?: boolean; + persistAuthorization?: boolean; + withCredentials?: boolean; + oauth2RedirectUrl?: string; + } + + class SwaggerUI extends React.Component { + static config: { + defaults: SwaggerUIProps & { + plugins: []; + }; + }; + + static presets: unknown; + } + + export default SwaggerUI; +} diff --git a/src/types/swagger-editor.ts b/src/types/swagger-editor.ts new file mode 100644 index 00000000000..cedd31606b4 --- /dev/null +++ b/src/types/swagger-editor.ts @@ -0,0 +1,52 @@ +import React from 'react'; +import { Preset, SwaggerUIProps, Plugin } from 'swagger-ui-react'; + +enum presetNames { + textarea = 'textarea', + monaco = 'monaco', + default = 'default', +} + +enum pluginNames { + Util = 'Util', + Modals = 'Modals', + Dialogs = 'Dialogs', + DropdownMenu = 'DropdownMenu', + Dropzone = 'Dropzone', + Versions = 'Versions', + EditorTextarea = 'EditorTextarea', + EditorMonaco = 'EditorMonaco', + EditorMonacoLanguageApiDOM = 'EditorMonacoLanguageApiDOM', + EditorMonacoYamlPaste = 'EditorMonacoYamlPaste', + EditorContentReadOnly = 'EditorContentReadOnly', + EditorContentOrigin = 'EditorContentOrigin', + EditorContentType = 'EditorContentType', + EditorContentPersistence = 'EditorContentPersistence', + EditorContentFixtures = 'EditorContentFixtures', + EditorContentFromFile = 'EditorContentFromFile', + EditorPreview = 'EditorPreview', + EditorPreviewSwaggerUI = 'EditorPreviewSwaggerUI', + EditorPreviewAsyncAPI = 'EditorPreviewAsyncAPI', + EditorPreviewApiDesignSystems = 'EditorPreviewApiDesignSystems', + EditorSafeRender = 'EditorSafeRender', + TopBar = 'TopBar', + SplashScreenPlugin = 'SplashScreenPlugin', + Layout = 'Layout', + SwaggerUIAdapter = 'SwaggerUIAdapter', +} + +interface SwaggerEditorPlugins { + plugins?: { + [key in pluginNames]: Plugin; + }; +} + +interface SwaggerEditorPresets { + presets?: { + [key in presetNames]: Preset; + }; +} + +export type SwaggerEditorType = React.NamedExoticComponent & + SwaggerEditorPresets & + SwaggerEditorPlugins; diff --git a/src/types/system.ts b/src/types/system.ts new file mode 100644 index 00000000000..36e40b14f6e --- /dev/null +++ b/src/types/system.ts @@ -0,0 +1,27 @@ +import { PropChangedAction } from 'plugins/props-change-watcher/actions'; +import { Action } from 'types/actions'; +import { ComponentType } from 'react'; + +export interface SystemValues { + getComponent: ( + componentName: string, + container?: boolean, + config?: object + ) => ComponentType | null; + editorSelectors: { + selectContent: () => string; + selectInferFileNameWithExtensionFromContent: () => string; + }; + editorActions: { + convertContentToJSON: (content: string) => Action; + setContent: (content: string) => Action; + propChanged: PropChangedAction; + }; + EditorContentOrigin: { + Conversion: 'conversion'; + }; +} + +export interface System extends SystemValues { + getSystem: () => SystemValues; +} diff --git a/test/cypress/support/setup-node-events.js b/test/cypress/support/setup-node-events.js index 8d7aba484e7..6c09f55af30 100644 --- a/test/cypress/support/setup-node-events.js +++ b/test/cypress/support/setup-node-events.js @@ -24,6 +24,7 @@ const setupNodeEvents = (on, config) => { console.error('\x1b[31m', 'ERROR:', message, '\x1b[0m'); return null; }, + // eslint-disable-next-line @typescript-eslint/no-unused-vars warn(message) { // default: disabling warnings to reduce ci pollution // console.warn('\x1b[33m', 'WARNING:', message, '\x1b[0m'); diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 00000000000..97726062e72 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,32 @@ +{ + "compilerOptions": { + "target": "esnext", + "module": "esnext", + "moduleResolution": "node", + "baseUrl": "./", + "allowJs": false, + "declaration": true, + "emitDeclarationOnly": true, + "strict": false, + "rootDir": "./src", + "isolatedModules": true, + "outDir": "dist/types", + "sourceMap": true, + "incremental": false, + "esModuleInterop": true, + "allowSyntheticDefaultImports": true, + "paths": { + "plugins/*": ["src/plugins/*"], + "presets/*": ["src/presets/*"], + "types/*": ["src/types/*"] + }, + "plugins": [ + { + "name": "typescript-strict-plugin" + } + ], + "jsx": "react" + }, + "exclude": ["node_modules", "src/reportWebVitals.ts"], + "include": ["src/**/*.ts", "src/**/*.tsx", "src/*.ts", "src/*.tsx"] +}