From a77fcf0743d09b361e7b0693bfee7aca492c19a7 Mon Sep 17 00:00:00 2001 From: Armano Date: Wed, 29 Mar 2023 17:29:46 +0200 Subject: [PATCH 01/12] chore(website): [playground] allow to choose file extensions --- .../src/components/OptionsSelector.tsx | 13 +- .../website/src/components/Playground.tsx | 4 +- packages/website/src/components/config.ts | 12 ++ .../website/src/components/config/utils.ts | 4 - .../src/components/editor/LoadedEditor.tsx | 41 ++-- .../website/src/components/editor/config.ts | 4 +- .../components/editor/useSandboxServices.ts | 12 +- .../src/components/hooks/useHashState.ts | 177 ++++++++---------- .../src/components/linter/WebLinter.ts | 26 +-- .../website/src/components/linter/config.ts | 2 +- packages/website/src/components/types.ts | 6 +- 11 files changed, 147 insertions(+), 154 deletions(-) diff --git a/packages/website/src/components/OptionsSelector.tsx b/packages/website/src/components/OptionsSelector.tsx index 3e3305d886da..62f11ec17b6e 100644 --- a/packages/website/src/components/OptionsSelector.tsx +++ b/packages/website/src/components/OptionsSelector.tsx @@ -7,7 +7,7 @@ import IconExternalLink from '@theme/Icon/ExternalLink'; import React, { useCallback } from 'react'; import { useClipboard } from '../hooks/useClipboard'; -import Checkbox from './inputs/Checkbox'; +import { fileTypes } from './config'; import Dropdown from './inputs/Dropdown'; import Tooltip from './inputs/Tooltip'; import ActionLabel from './layout/ActionLabel'; @@ -74,11 +74,12 @@ function OptionsSelectorContent({ {process.env.TS_ESLINT_VERSION} - - setState({ jsx: e })} + + setState({ fileType })} + options={fileTypes} /> diff --git a/packages/website/src/components/Playground.tsx b/packages/website/src/components/Playground.tsx index d193a6625c8a..02f44fbd7978 100644 --- a/packages/website/src/components/Playground.tsx +++ b/packages/website/src/components/Playground.tsx @@ -50,7 +50,7 @@ function rangeReducer( } function Playground(): JSX.Element { const [state, setState] = useHashState({ - jsx: false, + fileType: 'ts', showAST: false, sourceType: 'module', code: '', @@ -146,7 +146,7 @@ function Playground(): JSX.Element { = ({ code, tsconfig, eslintrc, decoration, - jsx, + fileType, onEsASTChange, onScopeChange, onTsASTChange, @@ -84,11 +97,11 @@ export const LoadedEditor: React.FC = ({ ]); useEffect(() => { - const newPath = jsx ? '/input.tsx' : '/input.ts'; + const newPath = `/input.${fileType}`; if (tabs.code.uri.path !== newPath) { const newModel = sandboxInstance.monaco.editor.createModel( tabs.code.getValue(), - 'typescript', + determineLanguage(newPath), sandboxInstance.monaco.Uri.file(newPath), ); newModel.updateOptions({ tabSize: 2, insertSpaces: true }); @@ -98,22 +111,15 @@ export const LoadedEditor: React.FC = ({ tabs.code.dispose(); tabs.code = newModel; } - }, [ - jsx, - sandboxInstance.editor, - sandboxInstance.monaco.Uri, - sandboxInstance.monaco.editor, - tabs, - ]); + }, [fileType, sandboxInstance.editor, sandboxInstance.monaco, tabs]); useEffect(() => { const config = createCompilerOptions( - jsx, parseTSConfig(tsconfig).compilerOptions, ); webLinter.updateCompilerOptions(config); sandboxInstance.setCompilerSettings(config); - }, [jsx, sandboxInstance, tsconfig, webLinter]); + }, [sandboxInstance, tsconfig, webLinter]); useEffect(() => { webLinter.updateRules(parseESLintRC(eslintrc).rules); @@ -128,10 +134,10 @@ export const LoadedEditor: React.FC = ({ const lintEditor = debounce(() => { console.info('[Editor] linting triggered'); - webLinter.updateParserOptions(jsx, sourceType); + webLinter.updateParserOptions(sourceType); try { - const messages = webLinter.lint(code); + const messages = webLinter.lint(code, tabs.code.uri.path); const markers = parseLintResults(messages, codeActions, ruleId => sandboxInstance.monaco.Uri.parse( @@ -162,7 +168,7 @@ export const LoadedEditor: React.FC = ({ lintEditor(); }, [ code, - jsx, + fileType, tsconfig, eslintrc, sourceType, @@ -237,7 +243,10 @@ export const LoadedEditor: React.FC = ({ run(editor) { const editorModel = editor.getModel(); if (editorModel) { - const fixed = webLinter.fix(editor.getValue()); + const fixed = webLinter.fix( + editor.getValue(), + editorModel.uri.path, + ); if (fixed.fixed) { editorModel.pushEditOperations( null, diff --git a/packages/website/src/components/editor/config.ts b/packages/website/src/components/editor/config.ts index aee01fb11f07..1882238595ce 100644 --- a/packages/website/src/components/editor/config.ts +++ b/packages/website/src/components/editor/config.ts @@ -4,7 +4,6 @@ import type Monaco from 'monaco-editor'; import { getTypescriptOptions } from '../config/utils'; export function createCompilerOptions( - jsx = false, tsConfig: Record = {}, ): Monaco.languages.typescript.CompilerOptions { const config = window.ts.convertCompilerOptionsFromJson( @@ -12,8 +11,9 @@ export function createCompilerOptions( // ts and monaco has different type as monaco types are not changing base on ts version target: 'esnext', module: 'esnext', + jsx: 'preserve', ...tsConfig, - jsx: jsx ? 'preserve' : undefined, + allowJs: true, lib: Array.isArray(tsConfig.lib) ? tsConfig.lib : undefined, moduleResolution: undefined, plugins: undefined, diff --git a/packages/website/src/components/editor/useSandboxServices.ts b/packages/website/src/components/editor/useSandboxServices.ts index 3007856e54ac..6bcc9d01b885 100644 --- a/packages/website/src/components/editor/useSandboxServices.ts +++ b/packages/website/src/components/editor/useSandboxServices.ts @@ -33,22 +33,14 @@ export const useSandboxServices = ( ): Error | SandboxServices | undefined => { const { onLoaded } = props; const [services, setServices] = useState(); - const [loadedTs, setLoadedTs] = useState(props.ts); const { colorMode } = useColorMode(); - useEffect(() => { - if (props.ts !== loadedTs) { - window.location.reload(); - } - }, [props.ts, loadedTs]); - useEffect(() => { let sandboxInstance: SandboxInstance | undefined; - setLoadedTs(props.ts); sandboxSingleton(props.ts) .then(async ({ main, sandboxFactory, lintUtils }) => { - const compilerOptions = createCompilerOptions(props.jsx); + const compilerOptions = createCompilerOptions(); const sandboxConfig: Partial = { text: props.code, @@ -128,7 +120,7 @@ export const useSandboxServices = ( }; // colorMode and jsx can't be reactive here because we don't want to force a recreation // updating of colorMode and jsx is handled in LoadedEditor - }, [props.ts, onLoaded]); + }, []); return services; }; diff --git a/packages/website/src/components/hooks/useHashState.ts b/packages/website/src/components/hooks/useHashState.ts index d8a7a44b5ce9..15a7880c78d4 100644 --- a/packages/website/src/components/hooks/useHashState.ts +++ b/packages/website/src/components/hooks/useHashState.ts @@ -1,31 +1,42 @@ -import { toJsonConfig } from '@site/src/components/config/utils'; import * as lz from 'lz-string'; -import { useCallback, useEffect, useState } from 'react'; +import { useCallback, useState } from 'react'; +import { toJson } from '../config/utils'; import { hasOwnProperty } from '../lib/has-own-property'; -import { shallowEqual } from '../lib/shallowEqual'; import type { ConfigModel } from '../types'; -function writeQueryParam(value: string): string { - return lz.compressToEncodedURIComponent(value); +function writeQueryParam(value: string | null): string { + return (value && lz.compressToEncodedURIComponent(value)) ?? ''; } function readQueryParam(value: string | null, fallback: string): string { - return value - ? lz.decompressFromEncodedURIComponent(value) ?? fallback - : fallback; + return (value && lz.decompressFromEncodedURIComponent(value)) ?? fallback; } -function readShowAST(value: string | null): 'ts' | 'scope' | 'es' | boolean { +function readShowAST(value: string | null): ConfigModel['showAST'] { switch (value) { case 'es': - return 'es'; case 'ts': - return 'ts'; case 'scope': - return 'scope'; + return value; } - return Boolean(value); + return value ? 'es' : false; +} + +function readFileType(value: string | null): ConfigModel['fileType'] { + switch (value) { + case 'ts': + case 'tsx': + case 'd.ts': + case 'js': + case 'jsx': + return value; + } + return 'ts'; +} + +function toJsonConfig(cfg: unknown, prop: string): string { + return toJson({ [prop]: cfg }); } function readLegacyParam( @@ -40,7 +51,7 @@ function readLegacyParam( return undefined; } -const parseStateFromUrl = (hash: string): ConfigModel | undefined => { +const parseStateFromUrl = (hash: string): Partial | undefined => { if (!hash) { return; } @@ -65,20 +76,22 @@ const parseStateFromUrl = (hash: string): ConfigModel | undefined => { ); } + let fileType = readFileType(searchParams.get('fileType')); + if (searchParams.get('jsx') === 'true') { + fileType = 'tsx'; + } + + const code = searchParams.has('code') + ? readQueryParam(searchParams.get('code'), '') + : ''; + return { - // @ts-expect-error: process.env.TS_VERSION - ts: (searchParams.get('ts') ?? process.env.TS_VERSION).trim(), - jsx: searchParams.has('jsx'), - showAST: - searchParams.has('showAST') && readShowAST(searchParams.get('showAST')), + ts: searchParams.get('ts') ?? undefined, + showAST: readShowAST(searchParams.get('showAST')), sourceType: - searchParams.has('sourceType') && - searchParams.get('sourceType') === 'script' - ? 'script' - : 'module', - code: searchParams.has('code') - ? readQueryParam(searchParams.get('code'), '') - : '', + searchParams.get('sourceType') === 'script' ? 'script' : 'module', + code, + fileType, eslintrc: eslintrc ?? '', tsconfig: tsconfig ?? '', }; @@ -88,28 +101,27 @@ const parseStateFromUrl = (hash: string): ConfigModel | undefined => { return undefined; }; -const writeStateToUrl = (newState: ConfigModel): string => { +const writeStateToUrl = (newState: ConfigModel): string | undefined => { try { - return Object.entries({ - ts: newState.ts.trim(), - jsx: newState.jsx, - sourceType: newState.sourceType, - showAST: newState.showAST, - code: newState.code ? writeQueryParam(newState.code) : undefined, - eslintrc: newState.eslintrc - ? writeQueryParam(newState.eslintrc) - : undefined, - tsconfig: newState.tsconfig - ? writeQueryParam(newState.tsconfig) - : undefined, - }) - .filter(item => item[1]) - .map(item => `${encodeURIComponent(item[0])}=${item[1]}`) - .join('&'); + const searchParams = new URLSearchParams(); + searchParams.set('ts', newState.ts.trim()); + if (newState.sourceType === 'script') { + searchParams.set('sourceType', newState.sourceType); + } + if (newState.showAST) { + searchParams.set('showAST', newState.showAST); + } + if (newState.fileType && newState.fileType !== 'ts') { + searchParams.set('fileType', newState.fileType); + } + searchParams.set('code', writeQueryParam(newState.code)); + searchParams.set('eslintrc', writeQueryParam(newState.eslintrc)); + searchParams.set('tsconfig', writeQueryParam(newState.tsconfig)); + return searchParams.toString(); } catch (e) { console.warn(e); } - return ''; + return undefined; }; const retrieveStateFromLocalStorage = (): Partial | undefined => { @@ -131,17 +143,15 @@ const retrieveStateFromLocalStorage = (): Partial | undefined => { state.ts = ts; } } - if (hasOwnProperty('jsx', config)) { - const jsx = config.jsx; - if (typeof jsx === 'boolean') { - state.jsx = jsx; + if (hasOwnProperty('fileType', config)) { + const fileType = config.fileType; + if (fileType === 'true') { + state.fileType = readFileType(fileType); } } if (hasOwnProperty('showAST', config)) { const showAST = config.showAST; - if (typeof showAST === 'boolean') { - state.showAST = showAST; - } else if (typeof showAST === 'string') { + if (typeof showAST === 'string') { state.showAST = readShowAST(showAST); } } @@ -156,7 +166,7 @@ const retrieveStateFromLocalStorage = (): Partial | undefined => { const writeStateToLocalStorage = (newState: ConfigModel): void => { const config: Partial = { ts: newState.ts, - jsx: newState.jsx, + sourceType: newState.sourceType, showAST: newState.showAST, }; window.localStorage.setItem('config', JSON.stringify(config)); @@ -165,66 +175,33 @@ const writeStateToLocalStorage = (newState: ConfigModel): void => { function useHashState( initialState: ConfigModel, ): [ConfigModel, (cfg: Partial) => void] { - const [hash, setHash] = useState(window.location.hash.slice(1)); const [state, setState] = useState(() => ({ ...initialState, ...retrieveStateFromLocalStorage(), ...parseStateFromUrl(window.location.hash.slice(1)), })); - const [tmpState, setTmpState] = useState>(() => ({ - ...initialState, - ...retrieveStateFromLocalStorage(), - ...parseStateFromUrl(window.location.hash.slice(1)), - })); - useEffect(() => { - const newHash = window.location.hash.slice(1); - if (newHash !== hash) { - const newState = parseStateFromUrl(newHash); - if (newState) { - setState(newState); - setTmpState(newState); - } - } - }, [hash]); + const updateState = useCallback((cfg: Partial) => { + console.info('[State] updating config diff', cfg); - useEffect(() => { - const newState = { ...state, ...tmpState }; - if (!shallowEqual(newState, state)) { - writeStateToLocalStorage(newState); + setState(oldState => { + const newState = { ...oldState, ...cfg }; const newHash = writeStateToUrl(newState); - setState(newState); - setHash(newHash); - - if (window.location.hash.slice(1) !== newHash) { - window.history.pushState( - undefined, - document.title, - `${window.location.pathname}#${newHash}`, - ); - } - } - }, [tmpState, state]); - const onHashChange = (): void => { - const newHash = window.location.hash; - console.info('[State] hash change detected', newHash); - setHash(newHash); - }; - - useEffect(() => { - window.addEventListener('popstate', onHashChange); - return (): void => { - window.removeEventListener('popstate', onHashChange); - }; - }, []); + writeStateToLocalStorage(newState); + const url = `${window.location.pathname}#${newHash}`; - const _setState = useCallback((cfg: Partial) => { - console.info('[State] updating config diff', cfg); - setTmpState(cfg); + if (cfg.ts) { + window.location.href = url; + window.location.reload(); + } else { + window.history.replaceState(undefined, document.title, url); + } + return newState; + }); }, []); - return [state, _setState]; + return [state, updateState]; } export default useHashState; diff --git a/packages/website/src/components/linter/WebLinter.ts b/packages/website/src/components/linter/WebLinter.ts index e8e2e7e3aa79..ac7452affbe4 100644 --- a/packages/website/src/components/linter/WebLinter.ts +++ b/packages/website/src/components/linter/WebLinter.ts @@ -34,9 +34,10 @@ export class WebLinter { private compilerOptions: CompilerOptions; private readonly parserOptions: ParserOptions = { ecmaFeatures: { - jsx: false, + jsx: true, globalReturn: false, }, + comment: true, ecmaVersion: 'latest', project: ['./tsconfig.json'], sourceType: 'module', @@ -83,20 +84,24 @@ export class WebLinter { }; } - lint(code: string): TSESLint.Linter.LintMessage[] { - return this.linter.verify(code, this.eslintConfig); + lint(code: string, filename: string): TSESLint.Linter.LintMessage[] { + return this.linter.verify(code, this.eslintConfig, { + filename: filename, + }); } - fix(code: string): TSESLint.Linter.FixReport { - return this.linter.verifyAndFix(code, this.eslintConfig, { fix: true }); + fix(code: string, filename: string): TSESLint.Linter.FixReport { + return this.linter.verifyAndFix(code, this.eslintConfig, { + filename: filename, + fix: true, + }); } updateRules(rules: TSESLint.Linter.RulesRecord): void { this.rules = rules; } - updateParserOptions(jsx?: boolean, sourceType?: TSESLint.SourceType): void { - this.parserOptions.ecmaFeatures!.jsx = jsx ?? false; + updateParserOptions(sourceType?: TSESLint.SourceType): void { this.parserOptions.sourceType = sourceType ?? 'module'; } @@ -108,14 +113,13 @@ export class WebLinter { code: string, eslintOptions: ParserOptions = {}, ): TSESLint.Linter.ESLintParseResult { - const isJsx = eslintOptions?.ecmaFeatures?.jsx ?? false; - const fileName = isJsx ? '/demo.tsx' : '/demo.ts'; + const fileName = eslintOptions.filePath ?? '/input.ts'; this.storedAST = undefined; this.storedTsAST = undefined; this.storedScope = undefined; - this.host.writeFile(fileName, code, false); + this.host.writeFile(fileName, code || '\n', false); const program = window.ts.createProgram({ rootNames: [fileName], @@ -127,7 +131,7 @@ export class WebLinter { const { estree: ast, astMaps } = this.lintUtils.astConverter( tsAst, - { ...parseSettings, code, codeFullText: code, jsx: isJsx }, + { ...parseSettings, code, codeFullText: code }, true, ); diff --git a/packages/website/src/components/linter/config.ts b/packages/website/src/components/linter/config.ts index e528d202e643..0f63afad7d53 100644 --- a/packages/website/src/components/linter/config.ts +++ b/packages/website/src/components/linter/config.ts @@ -13,7 +13,7 @@ export const parseSettings: ParseSettings = { EXPERIMENTAL_useSourceOfProjectReferenceRedirect: false, extraFileExtensions: [], filePath: '', - jsx: false, + jsx: true, loc: true, log: console.log, preserveNodeMaps: true, diff --git a/packages/website/src/components/types.ts b/packages/website/src/components/types.ts index b63d9bca307b..a92af1bf5f89 100644 --- a/packages/website/src/components/types.ts +++ b/packages/website/src/components/types.ts @@ -1,5 +1,7 @@ import type { TSESLint } from '@typescript-eslint/utils'; +import type { detailTabs, fileTypes } from './config'; + export type CompilerFlags = Record; export type SourceType = TSESLint.SourceType; @@ -15,13 +17,13 @@ export interface RuleDetails { export type TabType = 'code' | 'tsconfig' | 'eslintrc'; export interface ConfigModel { - jsx?: boolean; + fileType?: (typeof fileTypes)[number]; sourceType?: SourceType; eslintrc: string; tsconfig: string; code: string; ts: string; - showAST?: boolean | 'ts' | 'es' | 'scope'; + showAST?: (typeof detailTabs)[number]['value']; } export interface SelectedPosition { From 75611fbc8cabf97106b18717b74513a76fc45619 Mon Sep 17 00:00:00 2001 From: Armano Date: Wed, 29 Mar 2023 17:46:27 +0200 Subject: [PATCH 02/12] chore(website): use config fileTypes in readFileType --- packages/website/src/components/config.ts | 16 +++++++++------- .../src/components/hooks/useHashState.ts | 16 ++++++---------- packages/website/src/components/types.ts | 19 +++++++++++++++---- 3 files changed, 30 insertions(+), 21 deletions(-) diff --git a/packages/website/src/components/config.ts b/packages/website/src/components/config.ts index 8784fd617f46..c73405dfadd8 100644 --- a/packages/website/src/components/config.ts +++ b/packages/website/src/components/config.ts @@ -1,11 +1,13 @@ -export const detailTabs = [ - { value: false as const, label: 'Errors' }, - { value: 'es' as const, label: 'ESTree' }, - { value: 'ts' as const, label: 'TypeScript' }, - { value: 'scope' as const, label: 'Scope' }, +import type { ConfigFileType, ConfigShowAst } from './types'; + +export const detailTabs: { value: ConfigShowAst; label: string }[] = [ + { value: false, label: 'Errors' }, + { value: 'es', label: 'ESTree' }, + { value: 'ts', label: 'TypeScript' }, + { value: 'scope', label: 'Scope' }, ]; -export const fileTypes = [ +export const fileTypes: ConfigFileType[] = [ 'ts', 'tsx', 'js', @@ -15,4 +17,4 @@ export const fileTypes = [ 'mjs', 'cts', 'mts', -] as const; +]; diff --git a/packages/website/src/components/hooks/useHashState.ts b/packages/website/src/components/hooks/useHashState.ts index 15a7880c78d4..b521c8ad6574 100644 --- a/packages/website/src/components/hooks/useHashState.ts +++ b/packages/website/src/components/hooks/useHashState.ts @@ -1,9 +1,10 @@ import * as lz from 'lz-string'; import { useCallback, useState } from 'react'; +import { fileTypes } from '../config'; import { toJson } from '../config/utils'; import { hasOwnProperty } from '../lib/has-own-property'; -import type { ConfigModel } from '../types'; +import type { ConfigFileType, ConfigModel, ConfigShowAst } from '../types'; function writeQueryParam(value: string | null): string { return (value && lz.compressToEncodedURIComponent(value)) ?? ''; @@ -13,7 +14,7 @@ function readQueryParam(value: string | null, fallback: string): string { return (value && lz.decompressFromEncodedURIComponent(value)) ?? fallback; } -function readShowAST(value: string | null): ConfigModel['showAST'] { +function readShowAST(value: string | null): ConfigShowAst { switch (value) { case 'es': case 'ts': @@ -23,14 +24,9 @@ function readShowAST(value: string | null): ConfigModel['showAST'] { return value ? 'es' : false; } -function readFileType(value: string | null): ConfigModel['fileType'] { - switch (value) { - case 'ts': - case 'tsx': - case 'd.ts': - case 'js': - case 'jsx': - return value; +function readFileType(value: string | null): ConfigFileType { + if (value && (fileTypes as string[]).includes(value)) { + return value as ConfigFileType; } return 'ts'; } diff --git a/packages/website/src/components/types.ts b/packages/website/src/components/types.ts index a92af1bf5f89..2140a167cc24 100644 --- a/packages/website/src/components/types.ts +++ b/packages/website/src/components/types.ts @@ -1,7 +1,5 @@ import type { TSESLint } from '@typescript-eslint/utils'; -import type { detailTabs, fileTypes } from './config'; - export type CompilerFlags = Record; export type SourceType = TSESLint.SourceType; @@ -16,14 +14,27 @@ export interface RuleDetails { export type TabType = 'code' | 'tsconfig' | 'eslintrc'; +export type ConfigFileType = + | 'ts' + | 'tsx' + | 'js' + | 'jsx' + | 'd.ts' + | 'cjs' + | 'mjs' + | 'cts' + | 'mts'; + +export type ConfigShowAst = false | 'es' | 'ts' | 'scope'; + export interface ConfigModel { - fileType?: (typeof fileTypes)[number]; + fileType?: ConfigFileType; sourceType?: SourceType; eslintrc: string; tsconfig: string; code: string; ts: string; - showAST?: (typeof detailTabs)[number]['value']; + showAST?: ConfigShowAst; } export interface SelectedPosition { From 6e4acd250c592ef976c33c02e8a19c0a0899e509 Mon Sep 17 00:00:00 2001 From: Armano Date: Thu, 30 Mar 2023 03:52:06 +0200 Subject: [PATCH 03/12] fix: remove unnecessary determineLanguage function --- .../src/components/editor/LoadedEditor.tsx | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) diff --git a/packages/website/src/components/editor/LoadedEditor.tsx b/packages/website/src/components/editor/LoadedEditor.tsx index 2c98cc7ccaa4..55cbb32af8d8 100644 --- a/packages/website/src/components/editor/LoadedEditor.tsx +++ b/packages/website/src/components/editor/LoadedEditor.tsx @@ -28,19 +28,6 @@ export interface LoadedEditorProps extends CommonEditorProps { readonly webLinter: WebLinter; } -export function determineLanguage(file: string): string { - if (/\.[mc]?ts(x)?$/.test(file)) { - return 'typescript'; - } - if (/\.[mc]?js(x)?$/.test(file)) { - return 'javascript'; - } - if (/\.(json|eslintrc)$/.test(file)) { - return 'json'; - } - return 'plaintext'; -} - export const LoadedEditor: React.FC = ({ code, tsconfig, @@ -101,7 +88,7 @@ export const LoadedEditor: React.FC = ({ if (tabs.code.uri.path !== newPath) { const newModel = sandboxInstance.monaco.editor.createModel( tabs.code.getValue(), - determineLanguage(newPath), + undefined, sandboxInstance.monaco.Uri.file(newPath), ); newModel.updateOptions({ tabSize: 2, insertSpaces: true }); From bec4560125deada3edbf790b4fa70d6b4d5af16d Mon Sep 17 00:00:00 2001 From: Armano Date: Thu, 30 Mar 2023 03:55:08 +0200 Subject: [PATCH 04/12] fix: remove unnecessary fallback --- packages/website/src/components/OptionsSelector.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/website/src/components/OptionsSelector.tsx b/packages/website/src/components/OptionsSelector.tsx index 62f11ec17b6e..f7e2f88f75e7 100644 --- a/packages/website/src/components/OptionsSelector.tsx +++ b/packages/website/src/components/OptionsSelector.tsx @@ -77,7 +77,7 @@ function OptionsSelectorContent({ setState({ fileType })} options={fileTypes} /> From e3d57f5ebdf24d296203027cc601affd506dc63f Mon Sep 17 00:00:00 2001 From: Armano Date: Thu, 30 Mar 2023 03:58:22 +0200 Subject: [PATCH 05/12] fix: always include extension in generated url --- packages/website/src/components/hooks/useHashState.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/website/src/components/hooks/useHashState.ts b/packages/website/src/components/hooks/useHashState.ts index b521c8ad6574..f053470be3c6 100644 --- a/packages/website/src/components/hooks/useHashState.ts +++ b/packages/website/src/components/hooks/useHashState.ts @@ -107,7 +107,7 @@ const writeStateToUrl = (newState: ConfigModel): string | undefined => { if (newState.showAST) { searchParams.set('showAST', newState.showAST); } - if (newState.fileType && newState.fileType !== 'ts') { + if (newState.fileType) { searchParams.set('fileType', newState.fileType); } searchParams.set('code', writeQueryParam(newState.code)); From 03be3c822ad43c404a2a062fe0720fa8c8fd7a06 Mon Sep 17 00:00:00 2001 From: Armano Date: Thu, 30 Mar 2023 03:59:43 +0200 Subject: [PATCH 06/12] fix: change default fileType to tsx --- packages/website/src/components/Playground.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/website/src/components/Playground.tsx b/packages/website/src/components/Playground.tsx index 02f44fbd7978..92f4ccd76f6d 100644 --- a/packages/website/src/components/Playground.tsx +++ b/packages/website/src/components/Playground.tsx @@ -50,7 +50,7 @@ function rangeReducer( } function Playground(): JSX.Element { const [state, setState] = useHashState({ - fileType: 'ts', + fileType: 'tsx', showAST: false, sourceType: 'module', code: '', From b8b54a49ea15780d76c70f6245c8c16a96a87658 Mon Sep 17 00:00:00 2001 From: Armano Date: Thu, 30 Mar 2023 04:01:24 +0200 Subject: [PATCH 07/12] fix: save fileType to localStorage --- packages/website/src/components/hooks/useHashState.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/website/src/components/hooks/useHashState.ts b/packages/website/src/components/hooks/useHashState.ts index f053470be3c6..06a26bc0db64 100644 --- a/packages/website/src/components/hooks/useHashState.ts +++ b/packages/website/src/components/hooks/useHashState.ts @@ -162,6 +162,7 @@ const retrieveStateFromLocalStorage = (): Partial | undefined => { const writeStateToLocalStorage = (newState: ConfigModel): void => { const config: Partial = { ts: newState.ts, + fileType: newState.fileType, sourceType: newState.sourceType, showAST: newState.showAST, }; From d0c57ac106b7985b1622277c05ef7c89bbe315ba Mon Sep 17 00:00:00 2001 From: Armano Date: Thu, 30 Mar 2023 04:22:58 +0200 Subject: [PATCH 08/12] fix: use `ts.Extension` as type --- packages/website/src/components/config.ts | 18 +++++++++--------- .../src/components/editor/LoadedEditor.tsx | 2 +- .../src/components/hooks/useHashState.ts | 4 ++-- packages/website/src/components/types.ts | 12 ++---------- 4 files changed, 14 insertions(+), 22 deletions(-) diff --git a/packages/website/src/components/config.ts b/packages/website/src/components/config.ts index c73405dfadd8..ea28870acc67 100644 --- a/packages/website/src/components/config.ts +++ b/packages/website/src/components/config.ts @@ -8,13 +8,13 @@ export const detailTabs: { value: ConfigShowAst; label: string }[] = [ ]; export const fileTypes: ConfigFileType[] = [ - 'ts', - 'tsx', - 'js', - 'jsx', - 'd.ts', - 'cjs', - 'mjs', - 'cts', - 'mts', + '.ts', + '.tsx', + '.js', + '.jsx', + '.d.ts', + '.cjs', + '.mjs', + '.cts', + '.mts', ]; diff --git a/packages/website/src/components/editor/LoadedEditor.tsx b/packages/website/src/components/editor/LoadedEditor.tsx index 55cbb32af8d8..5fc1e857305d 100644 --- a/packages/website/src/components/editor/LoadedEditor.tsx +++ b/packages/website/src/components/editor/LoadedEditor.tsx @@ -84,7 +84,7 @@ export const LoadedEditor: React.FC = ({ ]); useEffect(() => { - const newPath = `/input.${fileType}`; + const newPath = `/input${fileType}`; if (tabs.code.uri.path !== newPath) { const newModel = sandboxInstance.monaco.editor.createModel( tabs.code.getValue(), diff --git a/packages/website/src/components/hooks/useHashState.ts b/packages/website/src/components/hooks/useHashState.ts index 06a26bc0db64..f6421a5afbfd 100644 --- a/packages/website/src/components/hooks/useHashState.ts +++ b/packages/website/src/components/hooks/useHashState.ts @@ -28,7 +28,7 @@ function readFileType(value: string | null): ConfigFileType { if (value && (fileTypes as string[]).includes(value)) { return value as ConfigFileType; } - return 'ts'; + return '.ts'; } function toJsonConfig(cfg: unknown, prop: string): string { @@ -74,7 +74,7 @@ const parseStateFromUrl = (hash: string): Partial | undefined => { let fileType = readFileType(searchParams.get('fileType')); if (searchParams.get('jsx') === 'true') { - fileType = 'tsx'; + fileType = '.tsx'; } const code = searchParams.has('code') diff --git a/packages/website/src/components/types.ts b/packages/website/src/components/types.ts index 2140a167cc24..2881810e66f2 100644 --- a/packages/website/src/components/types.ts +++ b/packages/website/src/components/types.ts @@ -1,4 +1,5 @@ import type { TSESLint } from '@typescript-eslint/utils'; +import type * as ts from 'typescript'; export type CompilerFlags = Record; @@ -14,16 +15,7 @@ export interface RuleDetails { export type TabType = 'code' | 'tsconfig' | 'eslintrc'; -export type ConfigFileType = - | 'ts' - | 'tsx' - | 'js' - | 'jsx' - | 'd.ts' - | 'cjs' - | 'mjs' - | 'cts' - | 'mts'; +export type ConfigFileType = `${ts.Extension}`; export type ConfigShowAst = false | 'es' | 'ts' | 'scope'; From bf04456167d254f23466fe54353c2f31c3c27469 Mon Sep 17 00:00:00 2001 From: Armano Date: Thu, 30 Mar 2023 04:40:22 +0200 Subject: [PATCH 09/12] chore: change default fileType to '.tsx' --- packages/website/src/components/Playground.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/website/src/components/Playground.tsx b/packages/website/src/components/Playground.tsx index 92f4ccd76f6d..961e0731e02a 100644 --- a/packages/website/src/components/Playground.tsx +++ b/packages/website/src/components/Playground.tsx @@ -50,7 +50,7 @@ function rangeReducer( } function Playground(): JSX.Element { const [state, setState] = useHashState({ - fileType: 'tsx', + fileType: '.tsx', showAST: false, sourceType: 'module', code: '', From 0746aa08cdff8d4afce88b401c6c9322017ebc00 Mon Sep 17 00:00:00 2001 From: Armano Date: Thu, 30 Mar 2023 06:07:19 +0200 Subject: [PATCH 10/12] chore(website): [playground] use react-router instead of window.history --- .../src/components/hooks/useHashState.ts | 42 ++++++++++--------- 1 file changed, 23 insertions(+), 19 deletions(-) diff --git a/packages/website/src/components/hooks/useHashState.ts b/packages/website/src/components/hooks/useHashState.ts index f6421a5afbfd..bbca735a2223 100644 --- a/packages/website/src/components/hooks/useHashState.ts +++ b/packages/website/src/components/hooks/useHashState.ts @@ -1,3 +1,4 @@ +import { useHistory } from '@docusaurus/router'; import * as lz from 'lz-string'; import { useCallback, useState } from 'react'; @@ -172,31 +173,34 @@ const writeStateToLocalStorage = (newState: ConfigModel): void => { function useHashState( initialState: ConfigModel, ): [ConfigModel, (cfg: Partial) => void] { + const history = useHistory(); const [state, setState] = useState(() => ({ ...initialState, ...retrieveStateFromLocalStorage(), ...parseStateFromUrl(window.location.hash.slice(1)), })); - const updateState = useCallback((cfg: Partial) => { - console.info('[State] updating config diff', cfg); - - setState(oldState => { - const newState = { ...oldState, ...cfg }; - const newHash = writeStateToUrl(newState); - - writeStateToLocalStorage(newState); - const url = `${window.location.pathname}#${newHash}`; - - if (cfg.ts) { - window.location.href = url; - window.location.reload(); - } else { - window.history.replaceState(undefined, document.title, url); - } - return newState; - }); - }, []); + const updateState = useCallback( + (cfg: Partial) => { + console.info('[State] updating config diff', cfg); + setState(oldState => { + const newState = { ...oldState, ...cfg }; + + writeStateToLocalStorage(newState); + + history.replace({ + ...history.location, + hash: writeStateToUrl(newState), + }); + + if (cfg.ts) { + window.location.reload(); + } + return newState; + }); + }, + [setState, history], + ); return [state, updateState]; } From e317691ad182285133d947e6f38f86614d84a4ca Mon Sep 17 00:00:00 2001 From: Armano Date: Sun, 2 Apr 2023 21:03:36 +0200 Subject: [PATCH 11/12] fix: apply changes from code reivew --- packages/website/src/components/hooks/useHashState.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/website/src/components/hooks/useHashState.ts b/packages/website/src/components/hooks/useHashState.ts index bbca735a2223..0ef02e2dbb8b 100644 --- a/packages/website/src/components/hooks/useHashState.ts +++ b/packages/website/src/components/hooks/useHashState.ts @@ -73,10 +73,10 @@ const parseStateFromUrl = (hash: string): Partial | undefined => { ); } - let fileType = readFileType(searchParams.get('fileType')); - if (searchParams.get('jsx') === 'true') { - fileType = '.tsx'; - } + const fileType = + searchParams.get('jsx') === 'true' + ? '.tsx' + : readFileType(searchParams.get('fileType')); const code = searchParams.has('code') ? readQueryParam(searchParams.get('code'), '') From c5d359bc79af720b779a9ded65f2b6e415780bd6 Mon Sep 17 00:00:00 2001 From: Armano Date: Sun, 2 Apr 2023 21:08:18 +0200 Subject: [PATCH 12/12] fix: correct issue after merge --- packages/website/src/components/hooks/useHashState.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/website/src/components/hooks/useHashState.ts b/packages/website/src/components/hooks/useHashState.ts index 0ef02e2dbb8b..2c80d7a3ab23 100644 --- a/packages/website/src/components/hooks/useHashState.ts +++ b/packages/website/src/components/hooks/useHashState.ts @@ -83,7 +83,7 @@ const parseStateFromUrl = (hash: string): Partial | undefined => { : ''; return { - ts: searchParams.get('ts') ?? undefined, + ts: searchParams.get('ts') ?? process.env.TS_VERSION!, showAST: readShowAST(searchParams.get('showAST')), sourceType: searchParams.get('sourceType') === 'script' ? 'script' : 'module',