From b65a6de6f37e5b0ab69d43ef5310ad0b8499b03e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EB=AF=BC=EC=83=81?= Date: Thu, 20 Apr 2023 13:10:25 +0900 Subject: [PATCH 1/4] feat: initial setup for i18n support --- src/extension.ts | 3 +- src/format/formatDiagnostic.ts | 9 +++- src/format/formatDiagnosticMessage.ts | 59 ++++++++++++++++----------- src/format/i18n/en.ts | 19 +++++++++ src/format/i18n/ko.ts | 19 +++++++++ src/format/i18n/locales.ts | 17 ++++++++ 6 files changed, 99 insertions(+), 27 deletions(-) create mode 100644 src/format/i18n/en.ts create mode 100644 src/format/i18n/ko.ts create mode 100644 src/format/i18n/locales.ts diff --git a/src/extension.ts b/src/extension.ts index aec5c6c..057fc97 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -4,6 +4,7 @@ import { MarkdownString, Range, window, + env, } from "vscode"; import { createConverter } from "vscode-languageclient/lib/common/codeConverter"; import { formatDiagnostic } from "./format/formatDiagnostic"; @@ -41,7 +42,7 @@ export function activate(context: ExtensionContext) { // formatDiagnostic converts message based on LSP Diagnostic type, not VSCode Diagnostic type, so it can be used in other IDEs. // Here we convert VSCode Diagnostic to LSP Diagnostic to make formatDiagnostic recognize it. const markdownString = new MarkdownString( - formatDiagnostic(converter.asDiagnostic(diagnostic), prettify) + formatDiagnostic(converter.asDiagnostic(diagnostic), prettify, env.language) ); markdownString.isTrusted = true; diff --git a/src/format/formatDiagnostic.ts b/src/format/formatDiagnostic.ts index f92648f..c5d102f 100644 --- a/src/format/formatDiagnostic.ts +++ b/src/format/formatDiagnostic.ts @@ -4,14 +4,19 @@ import { d } from "../utils"; import { embedSymbolLinks } from "./embedSymbolLinks"; import { formatDiagnosticMessage } from "./formatDiagnosticMessage"; import { identSentences } from "./identSentences"; +import { getFormatRegexes } from './i18n/locales'; -export function formatDiagnostic(diagnostic: Diagnostic, format: (type: string) => string) { +export function formatDiagnostic(diagnostic: Diagnostic, format: (type: string) => string, locale:string) { const newDiagnostic = embedSymbolLinks(diagnostic); return d/*html*/ ` ${title(newDiagnostic)} - ${formatDiagnosticMessage(identSentences(newDiagnostic.message), format)} + ${formatDiagnosticMessage( + identSentences(newDiagnostic.message), + format, + getFormatRegexes(locale) + )} `; } diff --git a/src/format/formatDiagnosticMessage.ts b/src/format/formatDiagnosticMessage.ts index c3e187e..80bf883 100644 --- a/src/format/formatDiagnosticMessage.ts +++ b/src/format/formatDiagnosticMessage.ts @@ -21,30 +21,45 @@ const formatTypeOrModuleBlock = ( format ); +export type FormatDiagnosticMessageRules = + | "DeclareModuleSnippet" + | "MissingPropsError" + | "TypePairs" + | "TypeAnnotationOptions" + | "Overloaded" + | "SimpleStrings" + | "Types" + | "ReversedTypes" + | "SimpleTypesRest" + | "TypescriptKeywords" + | "ReturnValues" + | "RegularCodeBlocks"; + export const formatDiagnosticMessage = ( message: string, - format: (type: string) => string + format: (type: string) => string, + regexes: Record ) => message // format declare module snippet .replaceAll( - /'(declare module )'(.*)';'/g, + regexes['DeclareModuleSnippet'], (_: string, p1: string, p2: string) => formatTypeScriptBlock(_, `${p1} "${p2}"`) ) // format missing props error .replaceAll( - /(is missing the following properties from type )'(.*)': (.+?)(?=and|$)/g, + regexes['MissingPropsError'], (_, pre, type, post) => `${pre}${formatTypeBlock("", type, format)}: ` + .join('')}` ) // Format type pairs .replaceAll( - /(types) '(.*?)' and '(.*?)'[\.]?/gi, + regexes['TypePairs'], (_: string, p1: string, p2: string, p3: string) => `${formatTypeBlock(p1, p2, format)} and ${formatTypeBlock( "", @@ -54,7 +69,7 @@ export const formatDiagnosticMessage = ( ) // Format type annotation options .replaceAll( - /type annotation must be '(.*?)' or '(.*?)'[\.]?/gi, + regexes['TypeAnnotationOptions'], (_: string, p1: string, p2: string, p3: string) => `${formatTypeBlock(p1, p2, format)} or ${formatTypeBlock( "", @@ -62,41 +77,37 @@ export const formatDiagnosticMessage = ( format )}` ) + // Format Overloaded .replaceAll( - /(Overload \d of \d), '(.*?)', /gi, - (_, p1: string, p2: string) => `${p1}${formatTypeBlock("", p2, format)}` + regexes['Overloaded'], + (_, p1: string, p2: string) => `${p1}${formatTypeBlock('', p2, format)}` ) // format simple strings - .replaceAll(/^'"[^"]*"'$/g, formatTypeScriptBlock) + .replaceAll(regexes['SimpleStrings'], formatTypeScriptBlock) // Format types - .replaceAll( - /(type|type alias|interface|module|file|file name|method's) '(.*?)'(?=[\s.])/gi, - (_, p1: string, p2: string) => formatTypeOrModuleBlock(_, p1, p2, format) + .replaceAll(regexes['Types'], (_, p1: string, p2: string) => + formatTypeOrModuleBlock(_, p1, p2, format) ) // Format reversed types .replaceAll( - /(.*)'([^>]*)' (type|interface|return type|file|module)/gi, + regexes['ReversedTypes'], (_: string, p1: string, p2: string, p3: string) => - `${p1}${formatTypeOrModuleBlock(_, "", p2, format)} ${p3}` + `${p1}${formatTypeOrModuleBlock(_, '', p2, format)} ${p3}` ) // Format simple types that didn't captured before - .replaceAll( - /'((void|null|undefined|any|boolean|string|number|bigint|symbol)(\[\])?)'/g, - formatSimpleTypeBlock - ) + .replaceAll(regexes['SimpleTypesRest'], formatSimpleTypeBlock) // Format some typescript key words .replaceAll( - /'(import|export|require|in|continue|break|let|false|true|const|new|throw|await|for await|[0-9]+)( ?.*?)'/g, + regexes['TypescriptKeywords'], (_: string, p1: string, p2: string) => formatTypeScriptBlock(_, `${p1}${p2}`) ) // Format return values .replaceAll( - /(return|operator) '(.*?)'/gi, - (_, p1: string, p2: string) => `${p1} ${formatTypeScriptBlock("", p2)}` + regexes['ReturnValues'], + (_, p1: string, p2: string) => `${p1} ${formatTypeScriptBlock('', p2)}` ) // Format regular code blocks - .replaceAll( - /'((?:(?!:\s*}).)*?)' (?!\s*:)/g, - (_: string, p1: string) => `${unstyledCodeBlock(p1)} ` + .replaceAll(regexes['RegularCodeBlocks'], (_: string, p1: string) => + `${unstyledCodeBlock(p1)} ` ); diff --git a/src/format/i18n/en.ts b/src/format/i18n/en.ts new file mode 100644 index 0000000..f43247b --- /dev/null +++ b/src/format/i18n/en.ts @@ -0,0 +1,19 @@ +import { FormatDiagnosticMessageRules } from '../formatDiagnosticMessage'; + +export const en: Record = { + DeclareModuleSnippet: /'(declare module )'(.*)';'/g, + MissingPropsError: + /(is missing the following properties from type .*: )(.+?)(?=and|$)/g, + TypePairs: /(types) '(.*?)' and '(.*?)'[\.]?/gi, + TypeAnnotationOptions: /type annotation must be '(.*?)' or '(.*?)'[\.]?/gi, + Overloaded: /(Overload \d of \d), '(.*?)', /gi, + SimpleStrings: /^'"[^"]*"'$/g, + Types: /(.*)'([^>]*)' (type|interface|return type|file|module)/gi, + ReversedTypes: /(.*)'([^>]*)' (type|interface|return type|file|module)/gi, + SimpleTypesRest: + /'((void|null|undefined|any|boolean|string|number|bigint|symbol)(\[\])?)'/g, + TypescriptKeywords: + /'(import|export|require|in|continue|break|let|false|true|const|new|throw|await|for await|[0-9]+)( ?.*?)'/g, + ReturnValues: /(return|operator) '(.*?)'/gi, + RegularCodeBlocks: /'(.*?)'/g +}; diff --git a/src/format/i18n/ko.ts b/src/format/i18n/ko.ts new file mode 100644 index 0000000..83fc367 --- /dev/null +++ b/src/format/i18n/ko.ts @@ -0,0 +1,19 @@ +import { FormatDiagnosticMessageRules } from '../formatDiagnosticMessage'; + +export const ko: Record = { + DeclareModuleSnippet: /'(declare module )'(.*)';'/g, + MissingPropsError: + /(is missing the following properties from type .*: )(.+?)(?=and|$)/g, + TypePairs: /(types) '(.*?)' and '(.*?)'[\.]?/gi, + TypeAnnotationOptions: /type annotation must be '(.*?)' or '(.*?)'[\.]?/gi, + Overloaded: /(Overload \d of \d), '(.*?)', /gi, + SimpleStrings: /^'"[^"]*"'$/g, + Types: /(.*)'([^>]*)' (type|interface|return type|file|module)/gi, + ReversedTypes: /(.*)'([^>]*)' (type|interface|return type|file|module)/gi, + SimpleTypesRest: + /'((void|null|undefined|any|boolean|string|number|bigint|symbol)(\[\])?)'/g, + TypescriptKeywords: + /'(import|export|require|in|continue|break|let|false|true|const|new|throw|await|for await|[0-9]+)( ?.*?)'/g, + ReturnValues: /(return|operator) '(.*?)'/gi, + RegularCodeBlocks: /'(.*?)'/g +}; diff --git a/src/format/i18n/locales.ts b/src/format/i18n/locales.ts new file mode 100644 index 0000000..2dcf74c --- /dev/null +++ b/src/format/i18n/locales.ts @@ -0,0 +1,17 @@ +import { en } from './en'; +import { ko } from './ko'; + +const supportedLocales = ['en', 'ko'] as const; +export type SupportedLocales = typeof supportedLocales[number]; + + + +export function getFormatRegexes(locale: string) { + switch (locale) { + case 'ko': + return ko; + case 'en': + default: + return en; + } +} \ No newline at end of file From e8249d8b12a3dd5f1fc997f6e3e95993e9ba864e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EB=AF=BC=EC=83=81?= Date: Thu, 20 Apr 2023 15:43:22 +0900 Subject: [PATCH 2/4] feat: get locale from vscode env --- src/extension.ts | 6 +++--- src/format/formatDiagnostic.ts | 7 +++---- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/src/extension.ts b/src/extension.ts index 057fc97..f67d3db 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -13,10 +13,12 @@ import { hoverProvider } from "./provider/hoverProvider"; import { registerSelectedTextHoverProvider } from "./provider/selectedTextHoverProvider"; import { uriStore } from "./provider/uriStore"; import { has } from "./utils"; +import { getFormatRegexes } from './format/i18n/locales'; export function activate(context: ExtensionContext) { const registeredLanguages = new Set(); const converter = createConverter(); + const formatLocaleRegexes = getFormatRegexes(env.language); registerSelectedTextHoverProvider(context); @@ -41,9 +43,7 @@ export function activate(context: ExtensionContext) { .forEach(async (diagnostic) => { // formatDiagnostic converts message based on LSP Diagnostic type, not VSCode Diagnostic type, so it can be used in other IDEs. // Here we convert VSCode Diagnostic to LSP Diagnostic to make formatDiagnostic recognize it. - const markdownString = new MarkdownString( - formatDiagnostic(converter.asDiagnostic(diagnostic), prettify, env.language) - ); + const markdownString = new MarkdownString(formatDiagnostic(converter.asDiagnostic(diagnostic), prettify, formatLocaleRegexes)); markdownString.isTrusted = true; markdownString.supportHtml = true; diff --git a/src/format/formatDiagnostic.ts b/src/format/formatDiagnostic.ts index c5d102f..5e3d29d 100644 --- a/src/format/formatDiagnostic.ts +++ b/src/format/formatDiagnostic.ts @@ -2,11 +2,10 @@ import { Diagnostic } from "vscode-languageserver-types"; import { title } from "../components"; import { d } from "../utils"; import { embedSymbolLinks } from "./embedSymbolLinks"; -import { formatDiagnosticMessage } from "./formatDiagnosticMessage"; +import { FormatDiagnosticMessageRules, formatDiagnosticMessage } from "./formatDiagnosticMessage"; import { identSentences } from "./identSentences"; -import { getFormatRegexes } from './i18n/locales'; -export function formatDiagnostic(diagnostic: Diagnostic, format: (type: string) => string, locale:string) { +export function formatDiagnostic(diagnostic: Diagnostic, format: (type: string) => string, formatRegexes:Record) { const newDiagnostic = embedSymbolLinks(diagnostic); return d/*html*/ ` @@ -15,7 +14,7 @@ export function formatDiagnostic(diagnostic: Diagnostic, format: (type: string) ${formatDiagnosticMessage( identSentences(newDiagnostic.message), format, - getFormatRegexes(locale) + formatRegexes )} `; From e45c857fb99e50c5dfa1bca04102b7325a6289cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EB=AF=BC=EC=83=81?= Date: Wed, 26 Apr 2023 02:24:58 +0900 Subject: [PATCH 3/4] feat: initial setup for i18n --- src/extension.ts | 75 +++++++++++++------------ src/format/formatDiagnosticMessage.ts | 55 ++++++++++--------- src/format/i18n/en.ts | 70 +++++++++++++++++++++++- src/format/i18n/ko.ts | 70 +++++++++++++++++++++++- src/format/i18n/locales.ts | 24 +++++--- src/test/suite/errorMessageMocks.ts | 20 ++++--- src/test/suite/extension.test.ts | 79 +++++++++++++-------------- src/test/suite/types.ts | 14 +++++ 8 files changed, 282 insertions(+), 125 deletions(-) create mode 100644 src/test/suite/types.ts diff --git a/src/extension.ts b/src/extension.ts index f67d3db..9383cf5 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -4,75 +4,82 @@ import { MarkdownString, Range, window, - env, -} from "vscode"; -import { createConverter } from "vscode-languageclient/lib/common/codeConverter"; -import { formatDiagnostic } from "./format/formatDiagnostic"; -import { prettify } from "./format/prettify"; -import { hoverProvider } from "./provider/hoverProvider"; -import { registerSelectedTextHoverProvider } from "./provider/selectedTextHoverProvider"; -import { uriStore } from "./provider/uriStore"; -import { has } from "./utils"; -import { getFormatRegexes } from './format/i18n/locales'; + env +} from 'vscode' +import * as vscode from 'vscode' +import { createConverter } from 'vscode-languageclient/lib/common/codeConverter' +import { formatDiagnostic } from './format/formatDiagnostic' +import { prettify } from './format/prettify' +import { hoverProvider } from './provider/hoverProvider' +import { registerSelectedTextHoverProvider } from './provider/selectedTextHoverProvider' +import { uriStore } from './provider/uriStore' +import { has } from './utils' +import { getLocale } from './format/i18n/locales' export function activate(context: ExtensionContext) { - const registeredLanguages = new Set(); - const converter = createConverter(); - const formatLocaleRegexes = getFormatRegexes(env.language); + const registeredLanguages = new Set() + const converter = createConverter() + const { regexes } = getLocale(env.language) - registerSelectedTextHoverProvider(context); + registerSelectedTextHoverProvider(context) context.subscriptions.push( languages.onDidChangeDiagnostics(async (e) => { e.uris.forEach((uri) => { - const diagnostics = languages.getDiagnostics(uri); + const diagnostics = languages.getDiagnostics(uri) const items: { - range: Range; - contents: MarkdownString[]; - }[] = []; + range: Range + contents: MarkdownString[] + }[] = [] - let hasTsDiagnostic = false; + let hasTsDiagnostic = false diagnostics .filter((diagnostic) => diagnostic.source - ? has(["ts", "deno-ts", "js"], diagnostic.source) + ? has(['ts', 'deno-ts', 'js'], diagnostic.source) : false ) .forEach(async (diagnostic) => { // formatDiagnostic converts message based on LSP Diagnostic type, not VSCode Diagnostic type, so it can be used in other IDEs. // Here we convert VSCode Diagnostic to LSP Diagnostic to make formatDiagnostic recognize it. - const markdownString = new MarkdownString(formatDiagnostic(converter.asDiagnostic(diagnostic), prettify, formatLocaleRegexes)); + const markdownString = new MarkdownString( + formatDiagnostic( + converter.asDiagnostic(diagnostic), + prettify, + regexes + ) + ) - markdownString.isTrusted = true; - markdownString.supportHtml = true; + markdownString.isTrusted = true + markdownString.supportHtml = true items.push({ range: diagnostic.range, - contents: [markdownString], - }); - hasTsDiagnostic = true; - }); - uriStore[uri.path] = items; + contents: [markdownString] + }) + hasTsDiagnostic = true + }) + uriStore[uri.path] = items if (hasTsDiagnostic) { const editor = window.visibleTextEditors.find( (editor) => editor.document.uri.toString() === uri.toString() - ); + ) if (editor && !registeredLanguages.has(editor.document.languageId)) { - registeredLanguages.add(editor.document.languageId); + registeredLanguages.add(editor.document.languageId) context.subscriptions.push( languages.registerHoverProvider( { - language: editor.document.languageId, + language: editor.document.languageId }, hoverProvider ) - ); + ) } } - }); + }) }) - ); + ) } diff --git a/src/format/formatDiagnosticMessage.ts b/src/format/formatDiagnosticMessage.ts index 80bf883..63c48a6 100644 --- a/src/format/formatDiagnosticMessage.ts +++ b/src/format/formatDiagnosticMessage.ts @@ -1,11 +1,11 @@ -import { inlineCodeBlock, unstyledCodeBlock } from "../components"; -import { formatTypeBlock } from "./formatTypeBlock"; +import { inlineCodeBlock, unstyledCodeBlock } from '../components' +import { formatTypeBlock } from './formatTypeBlock' const formatTypeScriptBlock = (_: string, code: string) => - inlineCodeBlock(code, "typescript"); + inlineCodeBlock(code, 'typescript') const formatSimpleTypeBlock = (_: string, code: string) => - inlineCodeBlock(code, "type"); + inlineCodeBlock(code, 'type') const formatTypeOrModuleBlock = ( _: string, @@ -15,25 +15,25 @@ const formatTypeOrModuleBlock = ( ) => formatTypeBlock( prefix, - ["module", "file", "file name"].includes(prefix.toLowerCase()) + ['module', 'file', 'file name'].includes(prefix.toLowerCase()) ? `"${code}"` : code, format - ); + ) -export type FormatDiagnosticMessageRules = - | "DeclareModuleSnippet" - | "MissingPropsError" - | "TypePairs" - | "TypeAnnotationOptions" - | "Overloaded" - | "SimpleStrings" - | "Types" - | "ReversedTypes" - | "SimpleTypesRest" - | "TypescriptKeywords" - | "ReturnValues" - | "RegularCodeBlocks"; +export type FormatDiagnosticMessageRules = + | 'DeclareModuleSnippet' + | 'MissingPropsError' + | 'TypePairs' + | 'TypeAnnotationOptions' + | 'Overloaded' + | 'SimpleStrings' + | 'Types' + | 'ReversedTypes' + | 'SimpleTypesRest' + | 'TypescriptKeywords' + | 'ReturnValues' + | 'RegularCodeBlocks' export const formatDiagnosticMessage = ( message: string, @@ -51,8 +51,8 @@ export const formatDiagnosticMessage = ( .replaceAll( regexes['MissingPropsError'], (_, pre, type, post) => - `${pre}${formatTypeBlock("", type, format)}:
    ${post - .split(", ") + `${pre}${formatTypeBlock('', type, format)}:
      ${post + .split(', ') .filter(Boolean) .map((prop: string) => `
    • ${prop}
    • `) .join('')}
    ` @@ -62,7 +62,7 @@ export const formatDiagnosticMessage = ( regexes['TypePairs'], (_: string, p1: string, p2: string, p3: string) => `${formatTypeBlock(p1, p2, format)} and ${formatTypeBlock( - "", + '', p3, format )}` @@ -72,7 +72,7 @@ export const formatDiagnosticMessage = ( regexes['TypeAnnotationOptions'], (_: string, p1: string, p2: string, p3: string) => `${formatTypeBlock(p1, p2, format)} or ${formatTypeBlock( - "", + '', p3, format )}` @@ -86,7 +86,7 @@ export const formatDiagnosticMessage = ( .replaceAll(regexes['SimpleStrings'], formatTypeScriptBlock) // Format types .replaceAll(regexes['Types'], (_, p1: string, p2: string) => - formatTypeOrModuleBlock(_, p1, p2, format) + formatTypeOrModuleBlock(_, p1, p2, format) ) // Format reversed types .replaceAll( @@ -108,6 +108,7 @@ export const formatDiagnosticMessage = ( (_, p1: string, p2: string) => `${p1} ${formatTypeScriptBlock('', p2)}` ) // Format regular code blocks - .replaceAll(regexes['RegularCodeBlocks'], (_: string, p1: string) => - `${unstyledCodeBlock(p1)} ` - ); + .replaceAll( + regexes['RegularCodeBlocks'], + (_: string, p1: string) => `${unstyledCodeBlock(p1)} ` + ) diff --git a/src/format/i18n/en.ts b/src/format/i18n/en.ts index f43247b..b017956 100644 --- a/src/format/i18n/en.ts +++ b/src/format/i18n/en.ts @@ -1,6 +1,10 @@ -import { FormatDiagnosticMessageRules } from '../formatDiagnosticMessage'; +import { inlineCodeBlock } from '../../components' +import { ErrorMessageMocks, TestSuites } from '../../test/suite/types' +import { d } from '../../utils' +import { FormatDiagnosticMessageRules } from '../formatDiagnosticMessage' +import { Locale } from './locales' -export const en: Record = { +const regexes: Record = { DeclareModuleSnippet: /'(declare module )'(.*)';'/g, MissingPropsError: /(is missing the following properties from type .*: )(.+?)(?=and|$)/g, @@ -16,4 +20,64 @@ export const en: Record = { /'(import|export|require|in|continue|break|let|false|true|const|new|throw|await|for await|[0-9]+)( ?.*?)'/g, ReturnValues: /(return|operator) '(.*?)'/gi, RegularCodeBlocks: /'(.*?)'/g -}; +} + +const testCase: Record = { + 'Test of adding missing parentheses': 'Hello, {world! [This] is a (test.)}', + + 'Special characters in object keys': + 'Type ' + + inlineCodeBlock('string', 'type') + + ' is not assignable to type ' + + inlineCodeBlock(`{ "abc*bc": string }`, 'type') + + '.', + + "Special method's word in the error": + 'Type ' + + inlineCodeBlock(`{ person: { "first-name": string } }`, 'type') + + ' is not assignable to type ' + + inlineCodeBlock('string', 'type') + + '.', + + 'Formatting type with params destructuring should succeed': '' +} + +const errorMessageMocks: Record = { + errorWithSpecialCharsInObjectKeys: d` +Type 'string' is not assignable to type '{ 'abc*bc': string; }'. +`, + errorWithDashInObjectKeys: d` +Type '{ person: { 'first-name': string; }; }' is not assignable to type 'string'. +`, + errorWithMethodsWordInIt: d` +The 'this' context of type 'ElementHandle' is not assignable to method's 'this' of type 'ElementHandle'. + Type 'Node' is missing the following properties from type 'Element': attributes, classList, className, clientHeight, and 114 more. +`, + errorWithParamsDestructuring: d` +Argument of type '{ $ref: null; ref: (ref: any) => any; columns: ({ label: string; prop: string; } | { label: string; formatter: ({ ip_type }: any) => any; } | { actions: { label: string; disabled: ({ contract_id }: any) => boolean; handler({ contract_id }: any): void; }[]; })[]; ... 4 more ...; load(): Promise<...>; }' is not assignable to parameter of type 'VTableConfig'. + Property 'data' is missing in type '{ $ref: null; ref: (ref: any) => any; columns: ({ label: string; prop: string; } | { label: string; formatter: ({ ip_type }: any) => any; } | { actions: { label: string; disabled: ({ contract_id }: any) => boolean; handler({ contract_id }: any): void; }[]; })[]; ... 4 more ...; load(): Promise<...>; }' but required in type 'VTableConfig'. +`, + + errorWithLongType: d` +Property 'isFlying' is missing in type '{ animal: { __typename?: "Animal" | undefined; id: string; name: string; age: number; isAlived: boolean; ... 8 more ...; attributes: { ...; } | ... 3 more ... | { ...; }; }; }' but required in type '{ animal: { __typename?: "Animal" | undefined; id: string; name: string; age: number; isAlived: boolean; isFlying: boolean; ... 8 more ...; attributes: { ...; } | ... 3 more ... | { ...; }; }; }'. +`, + + errorWithTruncatedType2: d` +Type '{ '!top': string[]; 'xsl:declaration': { attrs: { 'default-collation': null; 'exclude-result-prefixes': null; 'extension-element-prefixes': null; 'use-when': null; 'xpath-default-namespace': null; }; }; 'xsl:instruction': { ...; }; ... 49 more ...; 'xsl:literal-result-element': {}; }' is missing the following properties from type 'GraphQLSchema': description, extensions, astNode, extensionASTNodes, and 21 more. +`, + + errorWithSimpleIndentations: d` +Type '(newIds: number[]) => void' is not assignable to type '(selectedId: string[]) => void'. + Types of parameters 'newIds' and 'selectedId' are incompatible. + Type 'string[]' is not assignable to type 'number[]'. + Type 'string' is not assignable to type 'number'. +` +} + +const en: Locale = { + regexes, + testCase, + errorMessageMocks +} + +export default en diff --git a/src/format/i18n/ko.ts b/src/format/i18n/ko.ts index 83fc367..9e757ae 100644 --- a/src/format/i18n/ko.ts +++ b/src/format/i18n/ko.ts @@ -1,6 +1,10 @@ -import { FormatDiagnosticMessageRules } from '../formatDiagnosticMessage'; +import { inlineCodeBlock } from '../../components' +import { ErrorMessageMocks, TestSuites } from '../../test/suite/types' +import { d } from '../../utils' +import { FormatDiagnosticMessageRules } from '../formatDiagnosticMessage' +import { Locale } from './locales' -export const ko: Record = { +const regexes: Record = { DeclareModuleSnippet: /'(declare module )'(.*)';'/g, MissingPropsError: /(is missing the following properties from type .*: )(.+?)(?=and|$)/g, @@ -16,4 +20,64 @@ export const ko: Record = { /'(import|export|require|in|continue|break|let|false|true|const|new|throw|await|for await|[0-9]+)( ?.*?)'/g, ReturnValues: /(return|operator) '(.*?)'/gi, RegularCodeBlocks: /'(.*?)'/g -}; +} + +const testCase: Record = { + 'Test of adding missing parentheses': 'Hello, {world! [This] is a (test.)}', + + 'Special characters in object keys': + 'Type ' + + inlineCodeBlock('string', 'type') + + ' is not assignable to type ' + + inlineCodeBlock(`{ "abc*bc": string }`, 'type') + + '.', + + "Special method's word in the error": + 'Type ' + + inlineCodeBlock(`{ person: { "first-name": string } }`, 'type') + + ' is not assignable to type ' + + inlineCodeBlock('string', 'type') + + '.', + + 'Formatting type with params destructuring should succeed': '' +} + +const errorMessageMocks: Record = { + errorWithSpecialCharsInObjectKeys: d` +'string' 형식은 '{ 'abc*bc': string; }' 형식에 할당할 수 없습니다. +`, + errorWithDashInObjectKeys: d` +'{ person: { 'first-name': string; }; }' 형식은 'string' 형식에 할당할 수 없습니다. +`, + errorWithMethodsWordInIt: d` +The 'this' context of type 'ElementHandle' is not assignable to method's 'this' of type 'ElementHandle'. + Type 'Node' is missing the following properties from type 'Element': attributes, classList, className, clientHeight, and 114 more. +`, + errorWithParamsDestructuring: d` +Argument of type '{ $ref: null; ref: (ref: any) => any; columns: ({ label: string; prop: string; } | { label: string; formatter: ({ ip_type }: any) => any; } | { actions: { label: string; disabled: ({ contract_id }: any) => boolean; handler({ contract_id }: any): void; }[]; })[]; ... 4 more ...; load(): Promise<...>; }' is not assignable to parameter of type 'VTableConfig'. + Property 'data' is missing in type '{ $ref: null; ref: (ref: any) => any; columns: ({ label: string; prop: string; } | { label: string; formatter: ({ ip_type }: any) => any; } | { actions: { label: string; disabled: ({ contract_id }: any) => boolean; handler({ contract_id }: any): void; }[]; })[]; ... 4 more ...; load(): Promise<...>; }' but required in type 'VTableConfig'. +`, + + errorWithLongType: d` +Property 'isFlying' is missing in type '{ animal: { __typename?: "Animal" | undefined; id: string; name: string; age: number; isAlived: boolean; ... 8 more ...; attributes: { ...; } | ... 3 more ... | { ...; }; }; }' but required in type '{ animal: { __typename?: "Animal" | undefined; id: string; name: string; age: number; isAlived: boolean; isFlying: boolean; ... 8 more ...; attributes: { ...; } | ... 3 more ... | { ...; }; }; }'. +`, + + errorWithTruncatedType2: d` +Type '{ '!top': string[]; 'xsl:declaration': { attrs: { 'default-collation': null; 'exclude-result-prefixes': null; 'extension-element-prefixes': null; 'use-when': null; 'xpath-default-namespace': null; }; }; 'xsl:instruction': { ...; }; ... 49 more ...; 'xsl:literal-result-element': {}; }' is missing the following properties from type 'GraphQLSchema': description, extensions, astNode, extensionASTNodes, and 21 more. +`, + + errorWithSimpleIndentations: d` +Type '(newIds: number[]) => void' is not assignable to type '(selectedId: string[]) => void'. + Types of parameters 'newIds' and 'selectedId' are incompatible. + Type 'string[]' is not assignable to type 'number[]'. + Type 'string' is not assignable to type 'number'. +` +} + +const ko: Locale = { + regexes, + testCase, + errorMessageMocks +} + +export default ko diff --git a/src/format/i18n/locales.ts b/src/format/i18n/locales.ts index 2dcf74c..b4e1bdd 100644 --- a/src/format/i18n/locales.ts +++ b/src/format/i18n/locales.ts @@ -1,17 +1,23 @@ -import { en } from './en'; -import { ko } from './ko'; +import { ErrorMessageMocks, TestSuites } from '../../test/suite/types' +import { FormatDiagnosticMessageRules } from '../formatDiagnosticMessage' +import en from './en' +import ko from './ko' -const supportedLocales = ['en', 'ko'] as const; -export type SupportedLocales = typeof supportedLocales[number]; +const supportedLocales = ['en', 'ko'] as const +export type SupportedLocales = (typeof supportedLocales)[number] +export type Locale = { + regexes: Record + testCase: Record + errorMessageMocks: Record +} - -export function getFormatRegexes(locale: string) { +export function getLocale(locale: string) { switch (locale) { case 'ko': - return ko; + return ko case 'en': default: - return en; + return en } -} \ No newline at end of file +} diff --git a/src/test/suite/errorMessageMocks.ts b/src/test/suite/errorMessageMocks.ts index 6ea4d41..e769bfe 100644 --- a/src/test/suite/errorMessageMocks.ts +++ b/src/test/suite/errorMessageMocks.ts @@ -1,4 +1,4 @@ -import { d } from "../../utils"; +import { d } from '../../utils' /** * This file contains mocks of error messages, only some of them @@ -6,13 +6,15 @@ import { d } from "../../utils"; * the formatting visually, you can try to select them on debug and check the hover. */ +const x: { 'abc*bc': string } = 'string' + export const errorWithSpecialCharsInObjectKeys = d` Type 'string' is not assignable to type '{ 'abc*bc': string; }'. -`; +` export const errorWithDashInObjectKeys = d` Type '{ person: { 'first-name': string; }; }' is not assignable to type 'string'. -`; +` /** * Formatting error from this issue: https://github.com/yoavbls/pretty-ts-errors/issues/20 @@ -20,26 +22,26 @@ Type '{ person: { 'first-name': string; }; }' is not assignable to type 'string' export const errorWithMethodsWordInIt = d` The 'this' context of type 'ElementHandle' is not assignable to method's 'this' of type 'ElementHandle'. Type 'Node' is missing the following properties from type 'Element': attributes, classList, className, clientHeight, and 114 more. -`; +` const errorWithParamsDestructuring = d` Argument of type '{ $ref: null; ref: (ref: any) => any; columns: ({ label: string; prop: string; } | { label: string; formatter: ({ ip_type }: any) => any; } | { actions: { label: string; disabled: ({ contract_id }: any) => boolean; handler({ contract_id }: any): void; }[]; })[]; ... 4 more ...; load(): Promise<...>; }' is not assignable to parameter of type 'VTableConfig'. Property 'data' is missing in type '{ $ref: null; ref: (ref: any) => any; columns: ({ label: string; prop: string; } | { label: string; formatter: ({ ip_type }: any) => any; } | { actions: { label: string; disabled: ({ contract_id }: any) => boolean; handler({ contract_id }: any): void; }[]; })[]; ... 4 more ...; load(): Promise<...>; }' but required in type 'VTableConfig'. -`; +` const errorWithLongType = d` Property 'isFlying' is missing in type '{ animal: { __typename?: "Animal" | undefined; id: string; name: string; age: number; isAlived: boolean; ... 8 more ...; attributes: { ...; } | ... 3 more ... | { ...; }; }; }' but required in type '{ animal: { __typename?: "Animal" | undefined; id: string; name: string; age: number; isAlived: boolean; isFlying: boolean; ... 8 more ...; attributes: { ...; } | ... 3 more ... | { ...; }; }; }'. -`; +` const errorWithTruncatedType2 = d` Type '{ '!top': string[]; 'xsl:declaration': { attrs: { 'default-collation': null; 'exclude-result-prefixes': null; 'extension-element-prefixes': null; 'use-when': null; 'xpath-default-namespace': null; }; }; 'xsl:instruction': { ...; }; ... 49 more ...; 'xsl:literal-result-element': {}; }' is missing the following properties from type 'GraphQLSchema': description, extensions, astNode, extensionASTNodes, and 21 more. -`; +` const errorWithSimpleIndentations = d` Type '(newIds: number[]) => void' is not assignable to type '(selectedId: string[]) => void'. Types of parameters 'newIds' and 'selectedId' are incompatible. Type 'string[]' is not assignable to type 'number[]'. Type 'string' is not assignable to type 'number'. -`; +` -("Property 'user' is missing in type '{ person: { username: string; email: string; }; }' but required in type '{ user: { name: string; email: `${string}@${string}.${string}`; age: number; }; }'."); +;("Property 'user' is missing in type '{ person: { username: string; email: string; }; }' but required in type '{ user: { name: string; email: `${string}@${string}.${string}`; age: number; }; }'.") diff --git a/src/test/suite/extension.test.ts b/src/test/suite/extension.test.ts index 7c6f389..be13803 100644 --- a/src/test/suite/extension.test.ts +++ b/src/test/suite/extension.test.ts @@ -1,57 +1,56 @@ -import * as assert from "assert"; +import * as assert from 'assert' // You can import and use all API from the 'vscode' module // as well as import your extension to test it -import * as vscode from "vscode"; -import { inlineCodeBlock } from "../../components"; -import { addMissingParentheses } from "../../format/addMissingParentheses"; -import { formatDiagnosticMessage } from "../../format/formatDiagnosticMessage"; -import { prettifyType } from "../../format/formatTypeBlock"; -import { prettify } from "../../format/prettify"; -import { d } from "../../utils"; -import { - errorWithDashInObjectKeys, - errorWithSpecialCharsInObjectKeys, -} from "./errorMessageMocks"; +import * as vscode from 'vscode' +import { addMissingParentheses } from '../../format/addMissingParentheses' +import { formatDiagnosticMessage } from '../../format/formatDiagnosticMessage' +import { prettifyType } from '../../format/formatTypeBlock' +import { prettify } from '../../format/prettify' +import { d } from '../../utils' +import { getLocale } from '../../format/i18n/locales' -suite("Extension Test Suite", () => { - vscode.window.showInformationMessage("Start all tests."); +suite('Extension Test Suite', () => { + vscode.window.showInformationMessage('Start all tests.') + const { regexes, testCase, errorMessageMocks } = getLocale( + vscode.env.language + ) - test("Test of adding missing parentheses", () => { + test('Test of adding missing parentheses', () => { assert.strictEqual( - addMissingParentheses("Hello, {world! [This] is a (test."), - "Hello, {world! [This] is a (test.)}" - ); - }); + addMissingParentheses('Hello, {world! [This] is a (test.'), + testCase['Test of adding missing parentheses'] + ) + }) - test("Special characters in object keys", () => { + test('Special characters in object keys', () => { assert.strictEqual( - formatDiagnosticMessage(errorWithSpecialCharsInObjectKeys, prettify), - "Type " + - inlineCodeBlock("string", "type") + - " is not assignable to type " + - inlineCodeBlock(`{ "abc*bc": string }`, "type") + - "." - ); - }); + formatDiagnosticMessage( + errorMessageMocks['errorWithSpecialCharsInObjectKeys'], + prettify, + regexes + ), + testCase['Special characters in object keys'] + ) + }) test("Special method's word in the error", () => { assert.strictEqual( - formatDiagnosticMessage(errorWithDashInObjectKeys, prettify), - "Type " + - inlineCodeBlock(`{ person: { "first-name": string } }`, "type") + - " is not assignable to type " + - inlineCodeBlock("string", "type") + - "." - ); - }); + formatDiagnosticMessage( + errorMessageMocks['errorWithDashInObjectKeys'], + prettify, + regexes + ), + testCase["Special method's word in the error"] + ) + }) - test("Formatting type with params destructuring should succeed", () => { + test('Formatting type with params destructuring should succeed', () => { prettifyType( d` { $ref: null; ref: (ref: any) => any; columns: ({ label: string; prop: string; } | { label: string; formatter: ({ ip_type }: any) => any; } | { actions: { label: string; disabled: ({ contract_id }: any) => boolean; handler({ contract_id }: any): void; }[]; })[]; ... 4 more ...; load(): Promise<...>; } `, prettify, { throwOnError: true } - ); - }); -}); + ) + }) +}) diff --git a/src/test/suite/types.ts b/src/test/suite/types.ts new file mode 100644 index 0000000..5d89fdb --- /dev/null +++ b/src/test/suite/types.ts @@ -0,0 +1,14 @@ +export type ErrorMessageMocks = + | 'errorWithSpecialCharsInObjectKeys' + | 'errorWithDashInObjectKeys' + | 'errorWithMethodsWordInIt' + | 'errorWithParamsDestructuring' + | 'errorWithLongType' + | 'errorWithTruncatedType2' + | 'errorWithSimpleIndentations' + +export type TestSuites = + | 'Test of adding missing parentheses' + | 'Special characters in object keys' + | "Special method's word in the error" + | 'Formatting type with params destructuring should succeed' From 15c59b19de6d4cb9988d4617d76001bcdb955b3b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EB=AF=BC=EC=83=81?= Date: Wed, 26 Apr 2023 02:37:22 +0900 Subject: [PATCH 4/4] fix: test configuration --- src/extension.ts | 2 +- src/provider/selectedTextHoverProvider.ts | 59 +++++++++++---------- src/test/suite/errorMessageMocks.ts | 47 ----------------- src/test/suite/index.ts | 62 +++++++++++------------ 4 files changed, 64 insertions(+), 106 deletions(-) delete mode 100644 src/test/suite/errorMessageMocks.ts diff --git a/src/extension.ts b/src/extension.ts index 9383cf5..e646a8b 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -21,7 +21,7 @@ export function activate(context: ExtensionContext) { const converter = createConverter() const { regexes } = getLocale(env.language) - registerSelectedTextHoverProvider(context) + registerSelectedTextHoverProvider(context, regexes) context.subscriptions.push( languages.onDidChangeDiagnostics(async (e) => { diff --git a/src/provider/selectedTextHoverProvider.ts b/src/provider/selectedTextHoverProvider.ts index 59b2d6d..d611428 100644 --- a/src/provider/selectedTextHoverProvider.ts +++ b/src/provider/selectedTextHoverProvider.ts @@ -1,34 +1,38 @@ -import { ExtensionContext, MarkdownString, languages, window } from "vscode"; -import { createConverter } from "vscode-languageclient/lib/common/codeConverter"; -import { miniLine } from "../components"; -import { formatDiagnostic } from "../format/formatDiagnostic"; -import { prettify } from "../format/prettify"; -import { d } from "../utils"; +import { ExtensionContext, MarkdownString, languages, window } from 'vscode' +import { createConverter } from 'vscode-languageclient/lib/common/codeConverter' +import { miniLine } from '../components' +import { formatDiagnostic } from '../format/formatDiagnostic' +import { prettify } from '../format/prettify' +import { d } from '../utils' +import { FormatDiagnosticMessageRules } from '../format/formatDiagnosticMessage' -const isDebugMode = () => process.env.VSCODE_DEBUG_MODE === "true"; +const isDebugMode = () => process.env.VSCODE_DEBUG_MODE === 'true' /** * Register an hover provider in debug only. * It format selected text and help test things visually easier. */ -export function registerSelectedTextHoverProvider(context: ExtensionContext) { - const converter = createConverter(); +export function registerSelectedTextHoverProvider( + context: ExtensionContext, + regexes: Record +) { + const converter = createConverter() if (!isDebugMode()) { - return; + return } context.subscriptions.push( languages.registerHoverProvider( { - language: "typescript", - pattern: "**/test/**/*.ts", + language: 'typescript', + pattern: '**/test/**/*.ts' }, { provideHover(document, position) { - const editor = window.activeTextEditor; - const range = document.getWordRangeAtPosition(position); - const message = document.getText(editor!.selection); + const editor = window.activeTextEditor + const range = document.getWordRangeAtPosition(position) + const message = document.getText(editor!.selection) const contents = range && message @@ -40,25 +44,26 @@ export function registerSelectedTextHoverProvider(context: ExtensionContext) { message, range, severity: 0, - source: "ts", - code: 1337, + source: 'ts', + code: 1337 }), - prettify + prettify, + regexes ) - ), + ) ] - : []; + : [] - contents[0].isTrusted = true; - contents[0].supportHtml = true; + contents[0].isTrusted = true + contents[0].supportHtml = true return { - contents, - }; - }, + contents + } + } } ) - ); + ) } const debugHoverHeader = d/*html*/ ` @@ -69,4 +74,4 @@ const debugHoverHeader = d/*html*/ `

    ${miniLine} -`; +` diff --git a/src/test/suite/errorMessageMocks.ts b/src/test/suite/errorMessageMocks.ts deleted file mode 100644 index e769bfe..0000000 --- a/src/test/suite/errorMessageMocks.ts +++ /dev/null @@ -1,47 +0,0 @@ -import { d } from '../../utils' - -/** - * This file contains mocks of error messages, only some of them - * are used in tests but all of them can be used to test and debug - * the formatting visually, you can try to select them on debug and check the hover. - */ - -const x: { 'abc*bc': string } = 'string' - -export const errorWithSpecialCharsInObjectKeys = d` -Type 'string' is not assignable to type '{ 'abc*bc': string; }'. -` - -export const errorWithDashInObjectKeys = d` -Type '{ person: { 'first-name': string; }; }' is not assignable to type 'string'. -` - -/** - * Formatting error from this issue: https://github.com/yoavbls/pretty-ts-errors/issues/20 - */ -export const errorWithMethodsWordInIt = d` -The 'this' context of type 'ElementHandle' is not assignable to method's 'this' of type 'ElementHandle'. - Type 'Node' is missing the following properties from type 'Element': attributes, classList, className, clientHeight, and 114 more. -` - -const errorWithParamsDestructuring = d` -Argument of type '{ $ref: null; ref: (ref: any) => any; columns: ({ label: string; prop: string; } | { label: string; formatter: ({ ip_type }: any) => any; } | { actions: { label: string; disabled: ({ contract_id }: any) => boolean; handler({ contract_id }: any): void; }[]; })[]; ... 4 more ...; load(): Promise<...>; }' is not assignable to parameter of type 'VTableConfig'. - Property 'data' is missing in type '{ $ref: null; ref: (ref: any) => any; columns: ({ label: string; prop: string; } | { label: string; formatter: ({ ip_type }: any) => any; } | { actions: { label: string; disabled: ({ contract_id }: any) => boolean; handler({ contract_id }: any): void; }[]; })[]; ... 4 more ...; load(): Promise<...>; }' but required in type 'VTableConfig'. -` - -const errorWithLongType = d` -Property 'isFlying' is missing in type '{ animal: { __typename?: "Animal" | undefined; id: string; name: string; age: number; isAlived: boolean; ... 8 more ...; attributes: { ...; } | ... 3 more ... | { ...; }; }; }' but required in type '{ animal: { __typename?: "Animal" | undefined; id: string; name: string; age: number; isAlived: boolean; isFlying: boolean; ... 8 more ...; attributes: { ...; } | ... 3 more ... | { ...; }; }; }'. -` - -const errorWithTruncatedType2 = d` -Type '{ '!top': string[]; 'xsl:declaration': { attrs: { 'default-collation': null; 'exclude-result-prefixes': null; 'extension-element-prefixes': null; 'use-when': null; 'xpath-default-namespace': null; }; }; 'xsl:instruction': { ...; }; ... 49 more ...; 'xsl:literal-result-element': {}; }' is missing the following properties from type 'GraphQLSchema': description, extensions, astNode, extensionASTNodes, and 21 more. -` - -const errorWithSimpleIndentations = d` -Type '(newIds: number[]) => void' is not assignable to type '(selectedId: string[]) => void'. - Types of parameters 'newIds' and 'selectedId' are incompatible. - Type 'string[]' is not assignable to type 'number[]'. - Type 'string' is not assignable to type 'number'. -` - -;("Property 'user' is missing in type '{ person: { username: string; email: string; }; }' but required in type '{ user: { name: string; email: `${string}@${string}.${string}`; age: number; }; }'.") diff --git a/src/test/suite/index.ts b/src/test/suite/index.ts index 7029e38..c98d20c 100644 --- a/src/test/suite/index.ts +++ b/src/test/suite/index.ts @@ -1,38 +1,38 @@ -import * as path from 'path'; -import * as Mocha from 'mocha'; -import * as glob from 'glob'; +import * as path from 'path' +import * as Mocha from 'mocha' +import * as glob from 'glob' export function run(): Promise { - // Create the mocha test - const mocha = new Mocha({ - ui: 'tdd', - color: true - }); + // Create the mocha test + const mocha = new Mocha({ + ui: 'tdd', + color: true + }) - const testsRoot = path.resolve(__dirname, '..'); + const testsRoot = path.resolve(__dirname, '..') - return new Promise((c, e) => { - glob('**/**.test.js', { cwd: testsRoot }, (err, files) => { - if (err) { - return e(err); - } + return new Promise((c, e) => { + glob('**/**.test.js', { cwd: testsRoot }, (err, files) => { + if (err) { + return e(err) + } - // Add files to the test suite - files.forEach(f => mocha.addFile(path.resolve(testsRoot, f))); + // Add files to the test suite + files.forEach((f) => mocha.addFile(path.resolve(testsRoot, f))) - try { - // Run the mocha test - mocha.run(failures => { - if (failures > 0) { - e(new Error(`${failures} tests failed.`)); - } else { - c(); - } - }); - } catch (err) { - console.error(err); - e(err); - } - }); - }); + try { + // Run the mocha test + mocha.run((failures) => { + if (failures > 0) { + e(new Error(`${failures} tests failed.`)) + } else { + c() + } + }) + } catch (err) { + console.error(err) + e(err) + } + }) + }) }