From 3e192d3ac3a3d5c4470f42ee4b1d8deca2b8331b Mon Sep 17 00:00:00 2001 From: kokororin Date: Sat, 29 Apr 2023 15:35:50 +0800 Subject: [PATCH] refactor: code --- .eslintrc.cjs | 2 + scripts/generate.ts | 142 ++++++++------- src/ITransformationItem.ts | 4 - src/PHPFmt.ts | 255 +++++++++++++-------------- src/PHPFmtError.ts | 13 ++ src/PHPFmtProvider.ts | 95 +++++----- src/Transformations.ts | 16 +- src/Widget.ts | 2 +- src/extension.ts | 4 +- src/{IPHPFmtConfig.ts => types.d.ts} | 7 +- test/extension.test.ts | 4 +- tsconfig.json | 19 +- 12 files changed, 281 insertions(+), 282 deletions(-) delete mode 100644 src/ITransformationItem.ts create mode 100644 src/PHPFmtError.ts rename src/{IPHPFmtConfig.ts => types.d.ts} (76%) diff --git a/.eslintrc.cjs b/.eslintrc.cjs index 81fa997..5ff8656 100644 --- a/.eslintrc.cjs +++ b/.eslintrc.cjs @@ -18,7 +18,9 @@ module.exports = { 'plugin:prettier/recommended' ], rules: { + 'no-sync': 'error', 'prefer-promise-reject-errors': 'off', + 'n/prefer-promises/fs': 'error', '@typescript-eslint/no-floating-promises': [ 'error', { diff --git a/scripts/generate.ts b/scripts/generate.ts index ff76701..630019d 100644 --- a/scripts/generate.ts +++ b/scripts/generate.ts @@ -1,85 +1,91 @@ import path from 'path'; import fs from 'fs'; import os from 'os'; -import Transformations from '../src/Transformations'; +import pjson from 'pjson'; +import { Transformations } from '../src/Transformations'; -// eslint-disable-next-line @typescript-eslint/no-var-requires -const pkg: any = require('pjson'); +const pkg = pjson as any; const readmePath: string = path.join(__dirname, '/../README.md'); -const configuration: any = pkg.contributes.configuration; +const configuration = pkg.contributes.configuration; -let config: string = - '| Key | Type | Description | Default |' + - os.EOL + - '| -------- | ----------- | ----------- | ----------- |' + - os.EOL; +void (async () => { + try { + let config: string = + '| Key | Type | Description | Default |' + + os.EOL + + '| -------- | ----------- | ----------- | ----------- |' + + os.EOL; -for (const configKey of Object.keys(configuration.properties)) { - const configValue = configuration.properties[configKey]; - config += `| ${configKey} | `; + for (const configKey of Object.keys(configuration.properties)) { + const configValue = configuration.properties[configKey]; + config += `| ${configKey} | `; - if (typeof configValue.type === 'string') { - config += `\`${configValue.type}\``; - } else if (Array.isArray(configValue.type)) { - config += `\`${configValue.type.join(' \\| ')}\``; - } - config += ` | ${configValue.description}`; + if (typeof configValue.type === 'string') { + config += `\`${configValue.type}\``; + } else if (Array.isArray(configValue.type)) { + config += `\`${configValue.type.join(' \\| ')}\``; + } + config += ` | ${configValue.description}`; - if (typeof configValue.default === 'string') { - config += ` | "${configValue.default}"`; - } else if (typeof configValue.default === 'number') { - config += ` | ${configValue.default}`; - } else if ( - Array.isArray(configValue.default) || - typeof configValue.default === 'boolean' - ) { - config += ` | ${JSON.stringify(configValue.default)}`; - } else { - throw new Error('uncovered type'); - } + if (typeof configValue.default === 'string') { + config += ` | "${configValue.default}"`; + } else if (typeof configValue.default === 'number') { + config += ` | ${configValue.default}`; + } else if ( + Array.isArray(configValue.default) || + typeof configValue.default === 'boolean' + ) { + config += ` | ${JSON.stringify(configValue.default)}`; + } else { + throw new Error('uncovered type'); + } - config += ' | ' + os.EOL; -} + config += ' | ' + os.EOL; + } -let readmeContent = fs.readFileSync(readmePath).toString(); -readmeContent = readmeContent.replace( - /([\s\S]*)/, - () => { - return ( - '' + - os.EOL + - config + - os.EOL + - '' + let readmeContent = String(await fs.promises.readFile(readmePath)); + readmeContent = readmeContent.replace( + /([\s\S]*)/, + () => { + return ( + '' + + os.EOL + + config + + os.EOL + + '' + ); + } ); - } -); -readmeContent = readmeContent.replace( - /([\s\S]*)/, - () => { - return ( - '' + - os.EOL + - '| Key | Description |' + - os.EOL + - '| -------- | ----------- |' + - os.EOL + - new Transformations('php') - .getTransformations() - .map(item => { - let row = `| ${item.key} | `; - row += item.description; - row += ' |'; - return row; - }) - .join(os.EOL) + - os.EOL + - '' + readmeContent = readmeContent.replace( + /([\s\S]*)/, + () => { + return ( + '' + + os.EOL + + '| Key | Description |' + + os.EOL + + '| -------- | ----------- |' + + os.EOL + + new Transformations('php') + .getTransformations() + .map(item => { + let row = `| ${item.key} | `; + row += item.description; + row += ' |'; + return row; + }) + .join(os.EOL) + + os.EOL + + '' + ); + } ); - } -); -fs.writeFileSync(readmePath, readmeContent); + await fs.promises.writeFile(readmePath, readmeContent); + } catch (err) { + console.error(err); + } +})(); diff --git a/src/ITransformationItem.ts b/src/ITransformationItem.ts deleted file mode 100644 index d678ee1..0000000 --- a/src/ITransformationItem.ts +++ /dev/null @@ -1,4 +0,0 @@ -export default interface ITransformationItem { - key: string; - description: string; -} diff --git a/src/PHPFmt.ts b/src/PHPFmt.ts index 7310290..5403322 100644 --- a/src/PHPFmt.ts +++ b/src/PHPFmt.ts @@ -10,12 +10,13 @@ import { execSync } from 'child_process'; import detectIndent from 'detect-indent'; import findUp from 'find-up'; import phpfmt from 'use-phpfmt'; -import type IPHPFmtConfig from './IPHPFmtConfig'; -import Widget from './Widget'; +import type { PHPFmtConfig } from './types'; +import { Widget } from './Widget'; +import { PHPFmtError, PHPFmtIgnoreError } from './PHPFmtError'; -class PHPFmt { +export class PHPFmt { private readonly widget: Widget; - private config: IPHPFmtConfig = {} as any; + private config: PHPFmtConfig = {} as any; private readonly args: string[] = []; public constructor() { @@ -45,7 +46,7 @@ class PHPFmt { } if (!this.config.detect_indent) { - const spaces: number | boolean = this.config.indent_with_space; + const spaces = this.config.indent_with_space; if (spaces === true) { this.args.push('--indent_with_space'); } else if (spaces > 0) { @@ -61,12 +62,12 @@ class PHPFmt { this.args.push('--visibility_order'); } - const passes: string[] = this.config.passes; + const passes = this.config.passes; if (passes.length > 0) { this.args.push(`--passes=${passes.join(',')}`); } - const exclude: string[] = this.config.exclude; + const exclude = this.config.exclude; if (exclude.length > 0) { this.args.push(`--exclude=${exclude.join(',')}`); } @@ -88,163 +89,143 @@ class PHPFmt { return this.widget; } - public getConfig(): IPHPFmtConfig { + public getConfig(): PHPFmtConfig { return this.config; } private getArgs(fileName: string): string[] { - const args: string[] = this.args.slice(0); + const args = this.args.slice(0); args.push(`"${fileName}"`); return args; } public async format(text: string): Promise { - return await new Promise((resolve, reject) => { - if (this.config.detect_indent) { - const indentInfo = detectIndent(text); - if (!indentInfo.type) { - // fallback to default - this.args.push('--indent_with_space'); - } else if (indentInfo.type === 'space') { - this.args.push(`--indent_with_space=${indentInfo.amount}`); - } - } else { - if (this.config.indent_with_space !== 4 && this.config.psr2) { - reject( - new Error( - 'phpfmt: For PSR2, code MUST use 4 spaces for indenting, not tabs.' - ) - ); - return; - } + if (this.config.detect_indent) { + const indentInfo = detectIndent(text); + if (!indentInfo.type) { + // fallback to default + this.args.push('--indent_with_space'); + } else if (indentInfo.type === 'space') { + this.args.push(`--indent_with_space=${indentInfo.amount}`); } - - let fileName: string | undefined; - let iniPath: string | undefined; - const execOptions = { cwd: '' }; - if (Window.activeTextEditor != null) { - fileName = Window.activeTextEditor.document.fileName; - execOptions.cwd = path.dirname(fileName); - - const workspaceFolders: WorkspaceFolder[] | undefined = - Workspace.workspaceFolders; - if (workspaceFolders != null) { - iniPath = findUp.sync('.phpfmt.ini', { - cwd: execOptions.cwd - }); - const origIniPath = iniPath; - - for (const workspaceFolder of workspaceFolders) { - if (origIniPath?.startsWith(workspaceFolder.uri.fsPath)) { - break; - } else { - iniPath = undefined; - } - } - } + } else { + if (this.config.indent_with_space !== 4 && this.config.psr2) { + throw new PHPFmtError( + 'For PSR2, code MUST use 4 spaces for indenting, not tabs.' + ); } + } - try { - const stdout: Buffer = execSync( - `${this.config.php_bin} -r "echo PHP_VERSION_ID;"`, - execOptions - ); - if ( - Number(stdout.toString()) < 50600 && - Number(stdout.toString()) > 80000 - ) { - reject(new Error('phpfmt: PHP version < 5.6 or > 8.0')); - return; - } - } catch (err) { - reject( - new Error(`phpfmt: php_bin "${this.config.php_bin}" is invalid`) + let fileName: string | undefined; + let iniPath: string | undefined; + const execOptions = { cwd: '' }; + if (Window.activeTextEditor != null) { + fileName = Window.activeTextEditor.document.fileName; + execOptions.cwd = path.dirname(fileName); + + const workspaceFolders: WorkspaceFolder[] | undefined = + Workspace.workspaceFolders; + if (workspaceFolders != null) { + iniPath = await findUp('.phpfmt.ini', { + cwd: execOptions.cwd + }); + const origIniPath = iniPath; + + const workspaceFolder = workspaceFolders.find(folder => + iniPath?.startsWith(folder.uri.fsPath) ); - return; + iniPath = workspaceFolder != null ? origIniPath : undefined; + } + } + + try { + const stdout = execSync( + `${this.config.php_bin} -r "echo PHP_VERSION_ID;"`, + execOptions + ); + if (Number(stdout.toString()) < 70000) { + throw new PHPFmtError('PHP version < 7 is not supported'); } + } catch (err) { + throw new PHPFmtError(`php_bin "${this.config.php_bin}" is invalid`); + } - const tmpDir: string = os.tmpdir(); - - let tmpRandomFileName: string = `${tmpDir}/temp-${Math.random() - .toString(36) - .replace(/[^a-z]+/g, '') - .substr(0, 10)}`; - - if (fileName) { - const basename = path.basename(fileName); - const ignore: string[] = this.config.ignore; - if (ignore.length > 0) { - for (const ignoreItem of ignore) { - if (basename.match(ignoreItem) != null) { - this.widget.addToOutput( - `Ignored file ${basename} by match of ${ignoreItem}` - ); - reject(); - return; - } + const tmpDir = os.tmpdir(); + + let tmpRandomFileName = `${tmpDir}/temp-${Math.random() + .toString(36) + .replace(/[^a-z]+/g, '') + .substring(0, 10)}`; + + if (fileName) { + const basename = path.basename(fileName); + const ignore = this.config.ignore; + if (ignore.length > 0) { + for (const ignoreItem of ignore) { + if (basename.match(ignoreItem) != null) { + this.widget.addToOutput( + `Ignored file ${basename} by match of ${ignoreItem}` + ); + throw new PHPFmtIgnoreError(); } } - tmpRandomFileName += `-${basename}`; - } else { - tmpRandomFileName += '.php'; } + tmpRandomFileName += `-${basename}`; + } else { + tmpRandomFileName += '.php'; + } - const tmpFileName: string = path.normalize(tmpRandomFileName); + const tmpFileName = path.normalize(tmpRandomFileName); - try { - fs.writeFileSync(tmpFileName, text); - } catch (err) { - this.widget.addToOutput(err.message); - reject(new Error(`phpfmt: Cannot create tmp file in "${tmpDir}"`)); - return; - } + try { + await fs.promises.writeFile(tmpFileName, text); + } catch (err) { + this.widget.addToOutput(err.message); + throw new PHPFmtError(`Cannot create tmp file in "${tmpDir}"`); + } - // test whether the php file has syntax error - try { - execSync(`${this.config.php_bin} -l ${tmpFileName}`, execOptions); - } catch (err) { - this.widget.addToOutput(err.message); - Window.setStatusBarMessage( - 'phpfmt: Format failed - syntax errors found', - 4500 - ); - reject(); - return; - } + // test whether the php file has syntax error + try { + execSync(`${this.config.php_bin} -l ${tmpFileName}`, execOptions); + } catch (err) { + this.widget.addToOutput(err.message); + Window.setStatusBarMessage( + 'phpfmt: Format failed - syntax errors found', + 4500 + ); + throw new PHPFmtIgnoreError(); + } - const args: string[] = this.getArgs(tmpFileName); - args.unshift(`"${phpfmt.pharPath}"`); + const args = this.getArgs(tmpFileName); + args.unshift(`"${phpfmt.pharPath}"`); - let formatCmd: string; - if (!iniPath) { - formatCmd = `${this.config.php_bin} ${args.join(' ')}`; - } else { - formatCmd = `${this.config.php_bin} ${ - args[0] - } --config=${iniPath} ${args.pop()}`; - } + let formatCmd: string; + if (!iniPath) { + formatCmd = `${this.config.php_bin} ${args.join(' ')}`; + } else { + formatCmd = `${this.config.php_bin} ${ + args[0] + } --config=${iniPath} ${args.pop()}`; + } - this.widget.addToOutput(formatCmd); + this.widget.addToOutput(formatCmd); - try { - execSync(formatCmd, execOptions); - } catch (err) { - this.widget.addToOutput(err.message).show(); - reject(new Error('phpfmt: Execute phpfmt failed')); - return; - } + try { + execSync(formatCmd, execOptions); + } catch (err) { + this.widget.addToOutput(err.message).show(); + throw new PHPFmtError('Execute phpfmt failed'); + } - const formatted: string = fs.readFileSync(tmpFileName, 'utf-8'); - try { - fs.unlinkSync(tmpFileName); - } catch (err) {} + const formatted = await fs.promises.readFile(tmpFileName, 'utf-8'); + try { + await fs.promises.unlink(tmpFileName); + } catch (err) {} - if (formatted.length > 0) { - resolve(formatted); - } else { - reject(); - } - }); + if (formatted.length > 0) { + return formatted; + } + throw new PHPFmtIgnoreError(); } } diff --git a/src/PHPFmtError.ts b/src/PHPFmtError.ts new file mode 100644 index 0000000..24e16ac --- /dev/null +++ b/src/PHPFmtError.ts @@ -0,0 +1,13 @@ +export class PHPFmtError extends Error { + public constructor(message: string) { + super(`phpfmt: ${message}`); + + Object.setPrototypeOf(this, new.target.prototype); + } +} + +export class PHPFmtIgnoreError extends PHPFmtError { + public constructor() { + super(PHPFmtIgnoreError.name); + } +} diff --git a/src/PHPFmtProvider.ts b/src/PHPFmtProvider.ts index 21b247c..f8b6e86 100644 --- a/src/PHPFmtProvider.ts +++ b/src/PHPFmtProvider.ts @@ -10,10 +10,12 @@ import { type DocumentSelector, type QuickPickItem } from 'vscode'; -import type PHPFmt from './PHPFmt'; -import type Widget from './Widget'; -import Transformations from './Transformations'; -import type ITransformationItem from './ITransformationItem'; +import pkg from 'pjson'; +import type { PHPFmt } from './PHPFmt'; +import type { Widget } from './Widget'; +import { Transformations } from './Transformations'; +import type { TransformationItem } from './types'; +import { PHPFmtIgnoreError } from './PHPFmtError'; export default class PHPFmtProvider { private readonly phpfmt: PHPFmt; @@ -27,6 +29,7 @@ export default class PHPFmtProvider { { language: 'php', scheme: 'file' }, { language: 'php', scheme: 'untitled' } ]; + this.phpfmt.getWidget().addToOutput(`Extension Version: ${pkg.version}`); } public onDidChangeConfiguration(): Disposable { @@ -49,10 +52,10 @@ export default class PHPFmtProvider { this.phpfmt.getConfig().php_bin ); - const transformationItems: ITransformationItem[] = + const transformationItems: TransformationItem[] = transformations.getTransformations(); - const items: QuickPickItem[] = new Array(); + const items: QuickPickItem[] = []; for (const item of transformationItems) { items.push({ label: item.key, @@ -77,31 +80,22 @@ export default class PHPFmtProvider { this.documentSelector, { provideDocumentFormattingEdits: async document => { - return await new Promise((resolve, reject) => { - const originalText: string = document.getText(); + try { + const originalText = document.getText(); const lastLine = document.lineAt(document.lineCount - 1); - const range: Range = new Range( - new Position(0, 0), - lastLine.range.end - ); + const range = new Range(new Position(0, 0), lastLine.range.end); - this.phpfmt - .format(originalText) - .then((text: string) => { - if (text !== originalText) { - resolve([new TextEdit(range, text)]); - } else { - reject(); - } - }) - .catch(err => { - if (err instanceof Error) { - void Window.showErrorMessage(err.message); - this.widget.addToOutput(err.message); - } - reject(); - }); - }); + const text = await this.phpfmt.format(originalText); + if (text !== originalText) { + return [new TextEdit(range, text)]; + } + } catch (err) { + if (!(err instanceof PHPFmtIgnoreError) && err instanceof Error) { + void Window.showErrorMessage(err.message); + this.widget.addToOutput(err.message); + } + } + return []; } } ); @@ -112,39 +106,32 @@ export default class PHPFmtProvider { this.documentSelector, { provideDocumentRangeFormattingEdits: async (document, range) => { - return await new Promise((resolve, reject) => { - let originalText: string = document.getText(range); + try { + let originalText = document.getText(range); if (originalText.replace(/\s+/g, '').length === 0) { - reject(); - return; + return []; } - let hasModified: boolean = false; + let hasModified = false; if (originalText.search(/^\s*<\?php/i) === -1) { originalText = ` { - if (hasModified) { - text = text.replace(/^<\?php\r?\n/, ''); - } - if (text !== originalText) { - resolve([new TextEdit(range, text)]); - } else { - reject(); - } - }) - .catch(err => { - if (err instanceof Error) { - void Window.showErrorMessage(err.message); - this.widget.addToOutput(err.message); - } - reject(); - }); - }); + let text = await this.phpfmt.format(originalText); + if (hasModified) { + text = text.replace(/^<\?php\r?\n/, ''); + } + if (text !== originalText) { + return [new TextEdit(range, text)]; + } + } catch (err) { + if (!(err instanceof PHPFmtIgnoreError) && err instanceof Error) { + void Window.showErrorMessage(err.message); + this.widget.addToOutput(err.message); + } + } + return []; } } ); diff --git a/src/Transformations.ts b/src/Transformations.ts index 6d492f8..58753ea 100644 --- a/src/Transformations.ts +++ b/src/Transformations.ts @@ -1,9 +1,9 @@ import os from 'os'; import { execSync } from 'child_process'; import phpfmt from 'use-phpfmt'; -import type ITransformationItem from './ITransformationItem'; +import type { TransformationItem } from './types'; -class Transformations { +export class Transformations { private readonly phpBin: string; public constructor(phpBin: string) { @@ -14,8 +14,8 @@ class Transformations { return `${this.phpBin} "${phpfmt.pharPath}"`; } - public getTransformations(): ITransformationItem[] { - const output: string = execSync(`${this.baseCmd} --list-simple`).toString(); + public getTransformations(): TransformationItem[] { + const output = execSync(`${this.baseCmd} --list-simple`).toString(); return output .trim() @@ -33,13 +33,11 @@ class Transformations { }); } - public getExample(transformationItem: ITransformationItem): string { - const output: string = execSync( + public getExample(transformationItem: TransformationItem): string { + const output = execSync( `${this.baseCmd} --help-pass ${transformationItem.key}` ).toString(); - return output.toString().trim(); + return output.trim(); } } - -export default Transformations; diff --git a/src/Widget.ts b/src/Widget.ts index dd2edfe..f0f6b2c 100644 --- a/src/Widget.ts +++ b/src/Widget.ts @@ -6,7 +6,7 @@ import { type TextEditor } from 'vscode'; -export default class Widget { +export class Widget { private readonly outputChannel: OutputChannel; private readonly statusBarItem: StatusBarItem; private static instance: Widget; diff --git a/src/extension.ts b/src/extension.ts index e3d1004..70b3657 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -1,9 +1,9 @@ import { type ExtensionContext } from 'vscode'; -import PHPFmt from './PHPFmt'; +import { PHPFmt } from './PHPFmt'; import PHPFmtProvider from './PHPFmtProvider'; export function activate(context: ExtensionContext): void { - const provider: PHPFmtProvider = new PHPFmtProvider(new PHPFmt()); + const provider = new PHPFmtProvider(new PHPFmt()); context.subscriptions.push( provider.onDidChangeConfiguration(), diff --git a/src/IPHPFmtConfig.ts b/src/types.d.ts similarity index 76% rename from src/IPHPFmtConfig.ts rename to src/types.d.ts index 41e51c5..022c89e 100644 --- a/src/IPHPFmtConfig.ts +++ b/src/types.d.ts @@ -1,4 +1,4 @@ -export default interface IPHPFmtConfig { +export interface PHPFmtConfig { php_bin: string; detect_indent: boolean; psr1: boolean; @@ -15,3 +15,8 @@ export default interface IPHPFmtConfig { cakephp: boolean; custom_arguments: string; } + +export interface TransformationItem { + key: string; + description: string; +} diff --git a/test/extension.test.ts b/test/extension.test.ts index cfd10f8..105cbee 100644 --- a/test/extension.test.ts +++ b/test/extension.test.ts @@ -9,9 +9,9 @@ import { type Extension } from 'vscode'; import phpfmt from 'use-phpfmt'; +import pjson from 'pjson'; -// eslint-disable-next-line @typescript-eslint/no-var-requires -const pkg: any = require('pjson'); +const pkg = pjson as any; suite('PHPFmt Test', () => { const extension = Extensions.getExtension( diff --git a/tsconfig.json b/tsconfig.json index f2ec650..92affe9 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,18 +1,29 @@ { "compilerOptions": { + "target": "es2020", "module": "commonjs", - "target": "es6", + "lib": [ + "es2021" + ], "outDir": "out", "rootDir": ".", "strict": true, + "importHelpers": true, + "moduleResolution": "node", "noUnusedLocals": true, "noUnusedParameters": true, "forceConsistentCasingInFileNames": true, "removeComments": true, - "lib": ["es6"], "sourceMap": true, "esModuleInterop": true, - "useUnknownInCatchVariables": false + "allowSyntheticDefaultImports": true, + "useUnknownInCatchVariables": false, }, - "exclude": ["node_modules", ".vscode-test", "testProject", "scripts"] + "exclude": [ + "node_modules", + ".vscode-test", + "testProject", + "scripts", + "test" + ] }