From d40b1530c770e8a538f00e7ddd88f5f6bb3ec42c Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Wed, 1 May 2024 14:35:58 -0700 Subject: [PATCH] Replace typescript-formatter (#211810) The typescript-formatter package isn't being maintained (lots of old PRs) and has broken in the most recent version of TS Long term we may explore switching to another formatter, but for now I've hook up the basic formatter logic for our hygiene scripts --- build/hygiene.js | 49 +++++++++--------------- build/lib/formatter.js | 76 ++++++++++++++++++++++++++++++++++++++ build/lib/formatter.ts | 84 ++++++++++++++++++++++++++++++++++++++++++ package.json | 1 - yarn.lock | 51 ++----------------------- 5 files changed, 180 insertions(+), 81 deletions(-) create mode 100644 build/lib/formatter.js create mode 100644 build/lib/formatter.ts diff --git a/build/hygiene.js b/build/hygiene.js index 2b5bc151303..bc64a11f8e9 100644 --- a/build/hygiene.js +++ b/build/hygiene.js @@ -23,7 +23,7 @@ const copyrightHeaderLines = [ function hygiene(some, linting = true) { const gulpeslint = require('gulp-eslint'); const gulpstylelint = require('./stylelint'); - const tsfmt = require('typescript-formatter'); + const formatter = require('./lib/formatter'); let errorCount = 0; @@ -111,38 +111,23 @@ function hygiene(some, linting = true) { }); const formatting = es.map(function (file, cb) { - tsfmt - .processString(file.path, file.contents.toString('utf8'), { - verify: false, - tsfmt: true, - // verbose: true, - // keep checkJS happy - editorconfig: undefined, - replace: undefined, - tsconfig: undefined, - tsconfigFile: undefined, - tsfmtFile: undefined, - vscode: undefined, - vscodeFile: undefined, - }) - .then( - (result) => { - const original = result.src.replace(/\r\n/gm, '\n'); - const formatted = result.dest.replace(/\r\n/gm, '\n'); + try { + const rawInput = file.contents.toString('utf8'); + const rawOutput = formatter.format(file.path, rawInput); - if (original !== formatted) { - console.error( - `File not formatted. Run the 'Format Document' command to fix it:`, - file.relative - ); - errorCount++; - } - cb(null, file); - }, - (err) => { - cb(err); - } - ); + const original = rawInput.replace(/\r\n/gm, '\n'); + const formatted = rawOutput.replace(/\r\n/gm, '\n'); + if (original !== formatted) { + console.error( + `File not formatted. Run the 'Format Document' command to fix it:`, + file.relative + ); + errorCount++; + } + cb(null, file); + } catch (err) { + cb(err); + } }); let input; diff --git a/build/lib/formatter.js b/build/lib/formatter.js new file mode 100644 index 00000000000..29f265c8289 --- /dev/null +++ b/build/lib/formatter.js @@ -0,0 +1,76 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.format = format; +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +const fs = require("fs"); +const path = require("path"); +const ts = require("typescript"); +class LanguageServiceHost { + files = {}; + addFile(fileName, text) { + this.files[fileName] = ts.ScriptSnapshot.fromString(text); + } + fileExists(path) { + return !!this.files[path]; + } + readFile(path) { + return this.files[path]?.getText(0, this.files[path].getLength()); + } + // for ts.LanguageServiceHost + getCompilationSettings = () => ts.getDefaultCompilerOptions(); + getScriptFileNames = () => Object.keys(this.files); + getScriptVersion = (_fileName) => '0'; + getScriptSnapshot = (fileName) => this.files[fileName]; + getCurrentDirectory = () => process.cwd(); + getDefaultLibFileName = (options) => ts.getDefaultLibFilePath(options); +} +const defaults = { + baseIndentSize: 0, + indentSize: 4, + tabSize: 4, + indentStyle: ts.IndentStyle.Smart, + newLineCharacter: '\r\n', + convertTabsToSpaces: false, + insertSpaceAfterCommaDelimiter: true, + insertSpaceAfterSemicolonInForStatements: true, + insertSpaceBeforeAndAfterBinaryOperators: true, + insertSpaceAfterConstructor: false, + insertSpaceAfterKeywordsInControlFlowStatements: true, + insertSpaceAfterFunctionKeywordForAnonymousFunctions: false, + insertSpaceAfterOpeningAndBeforeClosingNonemptyParenthesis: false, + insertSpaceAfterOpeningAndBeforeClosingNonemptyBrackets: false, + insertSpaceAfterOpeningAndBeforeClosingNonemptyBraces: true, + insertSpaceAfterOpeningAndBeforeClosingTemplateStringBraces: false, + insertSpaceAfterOpeningAndBeforeClosingJsxExpressionBraces: false, + insertSpaceAfterTypeAssertion: false, + insertSpaceBeforeFunctionParenthesis: false, + placeOpenBraceOnNewLineForFunctions: false, + placeOpenBraceOnNewLineForControlBlocks: false, + insertSpaceBeforeTypeAnnotation: false, +}; +const getOverrides = (() => { + let value; + return () => { + value ??= JSON.parse(fs.readFileSync(path.join(__dirname, '..', '..', 'tsfmt.json'), 'utf8')); + return value; + }; +})(); +function format(fileName, text) { + const host = new LanguageServiceHost(); + host.addFile(fileName, text); + const languageService = ts.createLanguageService(host); + const edits = languageService.getFormattingEditsForDocument(fileName, { ...defaults, ...getOverrides() }); + edits + .sort((a, b) => a.span.start - b.span.start) + .reverse() + .forEach(edit => { + const head = text.slice(0, edit.span.start); + const tail = text.slice(edit.span.start + edit.span.length); + text = `${head}${edit.newText}${tail}`; + }); + return text; +} +//# sourceMappingURL=formatter.js.map \ No newline at end of file diff --git a/build/lib/formatter.ts b/build/lib/formatter.ts new file mode 100644 index 00000000000..0d9035b3d87 --- /dev/null +++ b/build/lib/formatter.ts @@ -0,0 +1,84 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +import * as fs from 'fs'; +import * as path from 'path'; +import * as ts from 'typescript'; + + +class LanguageServiceHost implements ts.LanguageServiceHost { + files: ts.MapLike = {}; + addFile(fileName: string, text: string) { + this.files[fileName] = ts.ScriptSnapshot.fromString(text); + } + + fileExists(path: string): boolean { + return !!this.files[path]; + } + + readFile(path: string): string | undefined { + return this.files[path]?.getText(0, this.files[path]!.getLength()); + } + + // for ts.LanguageServiceHost + + getCompilationSettings = () => ts.getDefaultCompilerOptions(); + getScriptFileNames = () => Object.keys(this.files); + getScriptVersion = (_fileName: string) => '0'; + getScriptSnapshot = (fileName: string) => this.files[fileName]; + getCurrentDirectory = () => process.cwd(); + getDefaultLibFileName = (options: ts.CompilerOptions) => ts.getDefaultLibFilePath(options); +} + +const defaults: ts.FormatCodeSettings = { + baseIndentSize: 0, + indentSize: 4, + tabSize: 4, + indentStyle: ts.IndentStyle.Smart, + newLineCharacter: '\r\n', + convertTabsToSpaces: false, + insertSpaceAfterCommaDelimiter: true, + insertSpaceAfterSemicolonInForStatements: true, + insertSpaceBeforeAndAfterBinaryOperators: true, + insertSpaceAfterConstructor: false, + insertSpaceAfterKeywordsInControlFlowStatements: true, + insertSpaceAfterFunctionKeywordForAnonymousFunctions: false, + insertSpaceAfterOpeningAndBeforeClosingNonemptyParenthesis: false, + insertSpaceAfterOpeningAndBeforeClosingNonemptyBrackets: false, + insertSpaceAfterOpeningAndBeforeClosingNonemptyBraces: true, + insertSpaceAfterOpeningAndBeforeClosingTemplateStringBraces: false, + insertSpaceAfterOpeningAndBeforeClosingJsxExpressionBraces: false, + insertSpaceAfterTypeAssertion: false, + insertSpaceBeforeFunctionParenthesis: false, + placeOpenBraceOnNewLineForFunctions: false, + placeOpenBraceOnNewLineForControlBlocks: false, + insertSpaceBeforeTypeAnnotation: false, +}; + +const getOverrides = (() => { + let value: ts.FormatCodeSettings | undefined; + return () => { + value ??= JSON.parse(fs.readFileSync(path.join(__dirname, '..', '..', 'tsfmt.json'), 'utf8')); + return value; + }; +})(); + +export function format(fileName: string, text: string) { + + const host = new LanguageServiceHost(); + host.addFile(fileName, text); + + const languageService = ts.createLanguageService(host); + const edits = languageService.getFormattingEditsForDocument(fileName, { ...defaults, ...getOverrides() }); + edits + .sort((a, b) => a.span.start - b.span.start) + .reverse() + .forEach(edit => { + const head = text.slice(0, edit.span.start); + const tail = text.slice(edit.span.start + edit.span.length); + text = `${head}${edit.newText}${tail}`; + }); + + return text; +} diff --git a/package.json b/package.json index ce9dd822377..dcde9cd66e9 100644 --- a/package.json +++ b/package.json @@ -208,7 +208,6 @@ "ts-node": "^10.9.1", "tsec": "0.2.7", "typescript": "^5.5.0-dev.20240408", - "typescript-formatter": "7.1.0", "util": "^0.12.4", "vscode-nls-dev": "^3.3.1", "webpack": "^5.91.0", diff --git a/yarn.lock b/yarn.lock index a9ab6181dd6..0079751ce13 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1021,13 +1021,6 @@ resolved "https://registry.yarnpkg.com/@types/color-name/-/color-name-1.1.1.tgz#1c1261bbeaa10a8055bbc5d8ab84b7b2afc846a0" integrity sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ== -"@types/commander@^2.11.0": - version "2.12.2" - resolved "https://registry.yarnpkg.com/@types/commander/-/commander-2.12.2.tgz#183041a23842d4281478fa5d23c5ca78e6fd08ae" - integrity sha512-0QEFiR8ljcHp9bAbWxecjVRuAMr16ivPiGOw6KFQBVrVd0RQIcM3xKdRisH2EDWgVWujiYtHwhSkSUoAAGzH7Q== - dependencies: - commander "*" - "@types/cookie@^0.3.3": version "0.3.3" resolved "https://registry.yarnpkg.com/@types/cookie/-/cookie-0.3.3.tgz#85bc74ba782fb7aa3a514d11767832b0e3bc6803" @@ -1214,7 +1207,7 @@ dependencies: "@types/node" "*" -"@types/semver@^5.4.0", "@types/semver@^5.5.0": +"@types/semver@^5.5.0": version "5.5.0" resolved "https://registry.yarnpkg.com/@types/semver/-/semver-5.5.0.tgz#146c2a29ee7d3bae4bf2fcb274636e264c813c45" integrity sha512-41qEJgBH/TWgo5NFSvBCJ1qkoi3Q6ONSF2avrHq1LVEZfYpdHmj0y9SuTK+u9ZhG1sYQKBL1AWXKyLWP4RaUoQ== @@ -3090,11 +3083,6 @@ command-line-args@^5.2.1: lodash.camelcase "^4.3.0" typical "^4.0.0" -commander@*: - version "8.3.0" - resolved "https://registry.yarnpkg.com/commander/-/commander-8.3.0.tgz#4837ea1b2da67b9c616a67afbb0fafee567bca66" - integrity sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww== - commander@2.11.x: version "2.11.0" resolved "https://registry.yarnpkg.com/commander/-/commander-2.11.0.tgz#157152fd1e7a6c8d98a5b715cf376df928004563" @@ -3105,7 +3093,7 @@ commander@^10.0.1: resolved "https://registry.yarnpkg.com/commander/-/commander-10.0.1.tgz#881ee46b4f77d1c1dccc5823433aa39b022cbe06" integrity sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug== -commander@^2.11.0, commander@^2.19.0, commander@^2.20.0: +commander@^2.19.0, commander@^2.20.0: version "2.20.3" resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== @@ -3125,11 +3113,6 @@ commander@^9.4.0: resolved "https://registry.yarnpkg.com/commander/-/commander-9.5.0.tgz#bc08d1eb5cedf7ccb797a96199d41c7bc3e60d30" integrity sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ== -commandpost@^1.0.0: - version "1.2.1" - resolved "https://registry.yarnpkg.com/commandpost/-/commandpost-1.2.1.tgz#2e9c4c7508b9dc704afefaa91cab92ee6054cc68" - integrity sha512-V1wzc+DTFsO96te2W/U+fKNRSOWtOwXhkkZH2WRLLbucrY+YrDNsRr4vtfSf83MUZVF3E6B4nwT30fqaTpzipQ== - comment-parser@1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/comment-parser/-/comment-parser-1.4.0.tgz#0f8c560f59698193854f12884c20c0e39a26d32c" @@ -3757,18 +3740,6 @@ eastasianwidth@^0.2.0: resolved "https://registry.yarnpkg.com/eastasianwidth/-/eastasianwidth-0.2.0.tgz#696ce2ec0aa0e6ea93a397ffcf24aa7840c827cb" integrity sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA== -editorconfig@^0.15.0: - version "0.15.0" - resolved "https://registry.yarnpkg.com/editorconfig/-/editorconfig-0.15.0.tgz#b6dd4a0b6b9e76ce48e066bdc15381aebb8804fd" - integrity sha512-j7JBoj/bpNzvoTQylfRZSc85MlLNKWQiq5y6gwKhmqD2h1eZ+tH4AXbkhEJD468gjDna/XMx2YtSkCxBRX9OGg== - dependencies: - "@types/commander" "^2.11.0" - "@types/semver" "^5.4.0" - commander "^2.11.0" - lru-cache "^4.1.1" - semver "^5.4.1" - sigmund "^1.0.1" - editorconfig@^0.15.2: version "0.15.2" resolved "https://registry.yarnpkg.com/editorconfig/-/editorconfig-0.15.2.tgz#047be983abb9ab3c2eefe5199cb2b7c5689f0702" @@ -6571,14 +6542,6 @@ lowercase-keys@^2.0.0: resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-2.0.0.tgz#2603e78b7b4b0006cbca2fbcc8a3202558ac9479" integrity sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA== -lru-cache@^4.1.1: - version "4.1.2" - resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-4.1.2.tgz#45234b2e6e2f2b33da125624c4664929a0224c3f" - integrity sha512-wgeVXhrDwAWnIF/yZARsFnMBtdFXOg1b8RIrhilp+0iDYN4mdQcNZElDZ0e4B64BhaxeQ5zN7PMyvu7we1kPeQ== - dependencies: - pseudomap "^1.0.2" - yallist "^2.1.2" - lru-cache@^4.1.3: version "4.1.5" resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-4.1.5.tgz#8bbe50ea85bed59bc9e33dcab8235ee9bcf443cd" @@ -8750,7 +8713,7 @@ semver-greatest-satisfied-range@^1.1.0: dependencies: sver-compat "^1.5.0" -"semver@2 || 3 || 4 || 5", semver@^5.4.1, semver@^5.5.0, semver@^5.5.1, semver@^5.6.0: +"semver@2 || 3 || 4 || 5", semver@^5.5.0, semver@^5.5.1, semver@^5.6.0: version "5.7.2" resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.2.tgz#48d55db737c3287cd4835e17fa13feace1c41ef8" integrity sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g== @@ -9926,14 +9889,6 @@ typedarray@^0.0.6: resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c= -typescript-formatter@7.1.0: - version "7.1.0" - resolved "https://registry.yarnpkg.com/typescript-formatter/-/typescript-formatter-7.1.0.tgz#dd1b5547de211065221f765263e15f18c84c66b8" - integrity sha512-XgPUSZ3beF7Xx2ZIEngIonWpDTS0XzWqV0vjtcm6nOPONug4WFXQYjbvulCzY2T0+knceZn5CFQjVUShNkIdLA== - dependencies: - commandpost "^1.0.0" - editorconfig "^0.15.0" - typescript@^2.6.2: version "2.6.2" resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.6.2.tgz#3c5b6fd7f6de0914269027f03c0946758f7673a4"