From 54734e6825468cbe633307537d3340afc14115e3 Mon Sep 17 00:00:00 2001 From: dmitryger Date: Wed, 10 Apr 2024 15:37:11 +0300 Subject: [PATCH] feat: typescript language support --- .gitignore | 3 + src/index.ts | 4 +- src/languages/typescript.ts | 63 +++++++++++++ tests/cpp.test.ts | 3 +- tests/cs.test.ts | 3 +- tests/large.test.ts | 3 +- tests/typescript.test.ts | 182 ++++++++++++++++++++++++++++++++++++ 7 files changed, 257 insertions(+), 4 deletions(-) create mode 100644 src/languages/typescript.ts create mode 100644 tests/typescript.test.ts diff --git a/.gitignore b/.gitignore index 0125458..4abcf35 100644 --- a/.gitignore +++ b/.gitignore @@ -116,3 +116,6 @@ dist .yarn/build-state.yml .yarn/install-state.gz .pnp.* + + +.idea \ No newline at end of file diff --git a/src/index.ts b/src/index.ts index fe94f25..5d72fac 100644 --- a/src/index.ts +++ b/src/index.ts @@ -23,6 +23,7 @@ import { Ruby } from "./languages/ruby"; import { Rust } from "./languages/rust"; import { SQL } from "./languages/sql"; import { YAML } from "./languages/yaml"; +import { Typescript } from "./languages/typescript"; import { nearTop, getPoints } from "./points"; import { convert } from "./shiki"; import { shebangMap } from "./shebang"; @@ -51,7 +52,8 @@ const languages: Record = { Ruby, Rust, SQL, - YAML + YAML, + Typescript }; /** diff --git a/src/languages/typescript.ts b/src/languages/typescript.ts new file mode 100644 index 0000000..807d9cb --- /dev/null +++ b/src/languages/typescript.ts @@ -0,0 +1,63 @@ +import type { LanguagePattern } from "../types"; + + +export const Typescript: LanguagePattern[] = [{ + pattern: /(Readonly<|ReadonlyArray<|Array<|Record<|Pick<|Omit<|Exclude<|Extract<)/, + type: "constant.dictionary" +}, +{ + pattern: /\w+\[]/, + type: "keyword.other" +}, +{ + pattern: /:\s*\w+\s*)?\((\w+\s*\??:\s*(\w+|({(\s*\w+\s*\??:\s*\w+\s*(,)?\s*)*})?)\s*(,)?\s*)*\)\s*:\s*\w+\s*{/, + type: "keyword.function" +}, +{ + pattern: /(\s*\w*\s*)(=)?\s+\w*\s*(<(\w*\s*)*>)?\((\w+\s*\??:\s*(\w+|({(\s*\w+\s*\??:\s*\w+\s*(,)?\s*)*})?)\s*(,)?\s*)*\)\s*:\s*\w+\s*(=>)\s*{/, + type: "keyword.function" +}, +{ + pattern: /(typeof|declare)\s+/, + type: "keyword" +}, +{ + pattern: /\s+as\s+/, + type: "keyword" +}, +// Rust types +{ + pattern: /usize/, + type: "not" +}, +// Kotlin +{ + pattern: /Array/, + type: "not" +} +]; \ No newline at end of file diff --git a/tests/cpp.test.ts b/tests/cpp.test.ts index eec286f..f8b6fe3 100644 --- a/tests/cpp.test.ts +++ b/tests/cpp.test.ts @@ -33,7 +33,8 @@ test("hello world", () => { Rust: 0, SQL: 0, Unknown: 1, - YAML: 0 + YAML: 0, + Typescript: 0, }); assert.equal(code.linesOfCode, 1); }); diff --git a/tests/cs.test.ts b/tests/cs.test.ts index 1c44a9d..b7c6d9f 100644 --- a/tests/cs.test.ts +++ b/tests/cs.test.ts @@ -34,7 +34,8 @@ test("hello world", () => { Rust: -40, SQL: 0, Unknown: 1, - YAML: 0 + YAML: 0, + Typescript: 0 }); assert.equal(code.linesOfCode, 2); }); diff --git a/tests/large.test.ts b/tests/large.test.ts index 460f8c6..bb2911d 100644 --- a/tests/large.test.ts +++ b/tests/large.test.ts @@ -682,7 +682,8 @@ test("large input", () => { Rust: 4, SQL: 22, Unknown: 1, - YAML: 4 + YAML: 4, + Typescript: 2, }); assert.equal(code.linesOfCode, 299); }); diff --git a/tests/typescript.test.ts b/tests/typescript.test.ts new file mode 100644 index 0000000..0d5daca --- /dev/null +++ b/tests/typescript.test.ts @@ -0,0 +1,182 @@ +import { test } from "uvu"; +import * as assert from "uvu/assert"; +import detectLang from "../src/index"; + +test("hello world", () => { + const code = detectLang("console.log(\"Hello world!\" as string);"); + assert.equal(code.language, "Typescript"); +}); + +test("fizz buzz", () => { + const code = detectLang(` + const length: number = 100 + + const numberArrays: number[] = Array.from({length: length}, (_, i: number) => i) + + numberArrays.forEach((item: number): void => { + if (item % 15 === 0) console.log('FizzBuzz' as string) + else if(item % 3 === 0) console.log('Fizz' as string) + else if (item % 5 === 0) console.log('Buzz' as string) + }) + `); + assert.equal(code.language, "Typescript"); +}); + +test("quick sort", () => { + const code = detectLang(` + function heapSort(arr: Array): void { + heapify(arr) + let end: number = arr.length - 1 + while (end > 0) { + [arr[end], arr[0]] = [arr[0], arr[end]] + end-- + siftDown(arr, 0, end) + } +} + +function heapify(arr: Array): void { + let start: number = Math.floor(arr.length/2) - 1 + + while (start >= 0) { + siftDown(arr, start, arr.length - 1) + start-- + } +} + +function siftDown(arr: Array, startPos: number, endPos: number): void { + let rootPos: number = startPos + + while (rootPos * 2 + 1 <= endPos) { + let childPos: number = rootPos * 2 + 1 + if (childPos + 1 <= endPos && arr[childPos] < arr[childPos + 1]) { + childPos++ + } + if (arr[rootPos] < arr[childPos]) { + [arr[rootPos], arr[childPos]] = [arr[childPos], arr[rootPos]] + rootPos = childPos + } else { + return + } + } +} +test('test code', (): void => { + let arr: Array = [12, 11, 15, 10, 9, 1, 2, 3, 13, 14, 4, 5, 6, 7, 8,] + const result: Array = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15] + heapSort(arr) + expect(arr).toStrictEqual(result) +}) +`); + assert.equal(code.language, "Typescript"); +}); + +test("http server", () => { + const code = detectLang(` + const http = require('http'); + +interface Error { + message: string +} + +http.get('http://rosettacode.org', (resp: any): void => { + + let data: string = ''; + + resp.on('data', (chunk: any): void => { + data += chunk; + }); + + resp.on('end', (): void => { + console.log("Data:", data); + }); + +}).on("error", (err: Error): void => { + console.log("Error: " + err.message); +}); + `); + + assert.equal(code.language, "Typescript"); +}); + +test("longest palindrome", () => { + const code = detectLang(` + function isPalindrome(s: string): boolean { + return s === s.split('').reverse().join('') +} + +function longestPalindrome(s: string): string { + if (s.length < 2) + return s + + const unique: Set = new Set(s) + if (unique.size === 1) { + return s + } + + let startPos: number = 0 + let nextPos: number = 1 + + let startCharSubstring: string = s[0] + let longestSubstring: string = s[0] + let curSubString: string = s[0] + s[1] + + + while (startPos !== s.length - 1) { + if (startCharSubstring === s[nextPos]) { + if (isPalindrome(curSubString) && curSubString.length > longestSubstring.length) { + longestSubstring = curSubString + } + } + + if (nextPos === s.length - 1) { + startPos += 1 + nextPos = startPos + 1 + curSubString = s[startPos] + s[nextPos] + startCharSubstring = s[startPos] + } else { + nextPos += 1 + curSubString += s[nextPos] + } + } + + return longestSubstring +} + `); + assert.equal(code.language, "Typescript"); +}); + +test("vue 3 component code", () => { + const code = detectLang(` +enum VARIANT { + 'big'= 'BIG TITLE', + 'small'= 'SMALL TITLE' +} + +type Variants = 'big' | 'small' +interface Props { + title?: string, + isView: string, + variant: Variants +} + +interface Emits { + (e: 'click', item: string): void +} + +const props = defineProps() + +const emit = defineEmits() + +const newTitle = ref('') + +const changeTitle = (): void => { + if (props.variant === 'big') { + newTitle.value = VARIANT.big + } else if (props.variant === 'small') { + newTitle.value = VARIANT.small + } +} + `); + assert.equal(code.language, "Typescript"); +}); + +test.run(); \ No newline at end of file