diff --git a/package.json b/package.json index 1498faf8..10d17e30 100644 --- a/package.json +++ b/package.json @@ -12,13 +12,14 @@ "dev": "tsc --watch", "copy-languages-json": "cp src/languages.json dist/src/languages.json", "compile": "tsc", - "build": "npm-run-all compile copy-languages-json update-type-defs", + "build": "npm-run-all compile copy-languages-json update-generated", "clean": "rimraf dist/", "prepublishOnly": "npm run build", "docs": "typedoc --out docs --module commonjs --readme README.md --name Unibeautify src", "lint": "tslint '{src,test,script}/**/*.ts'", "lint-fix": "tslint --fix '{src,test,script}/**/*.ts'", "update-type-defs": "node dist/scripts/update-type-defs.js dist/src/beautifier.d.ts", + "update-generated": "node dist/scripts/update-generated.js src/generated.ts", "update-languages": "node dist/scripts/update-languages.js ./src/languages.json" }, "repository": { diff --git a/scripts/update-generated.ts b/scripts/update-generated.ts new file mode 100644 index 00000000..c9f27514 --- /dev/null +++ b/scripts/update-generated.ts @@ -0,0 +1,106 @@ +// tslint:disable:newspaper-order no-shadowed-variable +import { Languages } from "../src/languages"; +import { Options } from "../src/options"; +import { readFileSync, writeFileSync } from "fs"; +import { resolve } from "path"; +// import { replaceInterface, replaceType } from "./utils"; + +const [, , dest] = process.argv; + +if (!dest) { + console.error("Missing required arguments: destination."); + process.exit(1); +} + +const destPath = resolve(process.cwd(), dest); +const originalContents = readFileSync(destPath).toString(); + +let newContents: string = originalContents; +newContents = updateBeautifierLanguageOptions(newContents); +newContents = updateBeautifierOptionName(newContents); +newContents = updateOptionValues(newContents); +newContents = updateBeautifierLanguageOptionComplex(newContents); + +writeFileSync(destPath, newContents); +console.log(`Done writing to ${destPath}!`); + +function updateBeautifierLanguageOptions(originalContents: string): string { + const fields: string[] = Languages.map( + language => ` "${language.name}"?: BeautifierLanguageOptions;` + ); + const interfaceName = "BeautifierOptions"; + const newInterfaceBody: string = ` "_"?: BeautifierLanguageOptions;\n${fields.join( + "\n" + )}`; + return replaceInterface(originalContents, interfaceName, newInterfaceBody); +} + +function updateBeautifierOptionName(originalContents: string): string { + const optionNames = Object.keys(Options); + const enums = optionNames.map(name => `"${name}"`); + const newTypeBody = enums.join(" | "); + return replaceType(originalContents, "BeautifierOptionName", newTypeBody); +} + +function updateOptionValues(originalContents: string): string { + const optionNames = Object.keys(Options); + const fields: string[] = optionNames.map(optionName => { + const option = Options[optionName]; + const fieldType: string = typescriptTypeForOptionType(option.type); + return ` "${optionName}"?: ${fieldType};`; + }); + const interfaceName = "OptionValues"; + const newInterfaceBody: string = fields.join("\n"); + return replaceInterface(originalContents, interfaceName, newInterfaceBody); +} + +function updateBeautifierLanguageOptionComplex( + originalContents: string +): string { + const interfaceName = "BeautifierLanguageOptionComplex"; + const optionNames = Object.keys(Options); + const fields: string[] = optionNames.map(optionName => { + const option = Options[optionName]; + const valueType: string = typescriptTypeForOptionType(option.type); + const fieldType: string = `true | ((${optionName}: ${valueType}) => any) | BeautifierLanguageOption`; + return ` "${optionName}"?: ${fieldType};`; + }); + const newInterfaceBody: string = fields.join("\n"); + const newInterfaceContents: string = `export interface ${interfaceName} { +${newInterfaceBody} +}`; + return originalContents + "\n" + newInterfaceContents + "\n"; +} + +function typescriptTypeForOptionType(optionType: string): string { + switch (optionType) { + case "array": + return "any[]"; + case "integer": + return "number"; + default: + return optionType; + } +} + +function replaceInterface( + originalContents: string, + interfaceName: string, + newInterfaceBody: string +): string { + return [originalContents, ( + `export interface ${interfaceName} { +${newInterfaceBody} +}` + )].join("\n"); +} + +function replaceType( + originalContents: string, + typeName: string, + newTypeBody: string +): string { + return [originalContents, ( + `export declare type ${typeName} = ${newTypeBody};` + )].join("\n"); +} diff --git a/src/DependencyManager/DependencyManager.ts b/src/DependencyManager/DependencyManager.ts index 123d894e..7ca00191 100644 --- a/src/DependencyManager/DependencyManager.ts +++ b/src/DependencyManager/DependencyManager.ts @@ -5,6 +5,7 @@ import { DependencyDefinition, DependencyOptions, } from "./Dependency"; +import { BeautifierOptionName } from "../generated"; export class DependencyManager { private static registry: DependencyRegistry = {}; @@ -84,6 +85,7 @@ export interface LanguageDependencyOptions { [dependencyName: string]: DependencyOptions; } +/* export interface DependencyRegistry { [beautifierName: string]: { [dependencyName: string]: { @@ -91,3 +93,21 @@ export interface DependencyRegistry { }; }; } +*/ + +export interface DependenciesForBeautifierRegistry { + [dependencyName: string]: { + [optionsKey: string]: Dependency; + // [optionName in BeautifierOptionName]: Dependency; + }; +} + +export interface DependencyRegistry { + [beautifierName: string]: DependenciesForBeautifierRegistry; +} + +/* +export interface DependencyRegistry { + [beautifierName in EnabledBeautifiers]: DependenciesForBeautifierRegistry; +} +*/ diff --git a/src/beautifier.ts b/src/beautifier.ts index 5628c4b1..3e3c0fae 100644 --- a/src/beautifier.ts +++ b/src/beautifier.ts @@ -7,25 +7,90 @@ import { DependencyDefinition, DependencyManager, Badge, + DependencyRegistry, + DependenciesForBeautifierRegistry, } from "./DependencyManager"; import { zipObject, unique } from "./utils"; +import { BeautifierOptionName } from "./generated"; +// tslint:disable-next-line:export-name +export { BeautifierOptionName }; /** New name to rename the option (key) to. Name of an option to configure for a beautifier. */ -export type BeautifierOptionName = string; +// export type BeautifierOptionName = string; + +// tslint:disable-next-line:max-line-length +// export type BeautifierOptionName = "align_assignments" | "arrow_parens" | "brace_style" | "break_chained_methods" | "comma_first" | "end_of_line" | "end_with_comma" | "end_with_newline" | "end_with_semicolon" | "force_indentation" | "identifier_case" | "indent_chained_methods" | "indent_char" | "indent_comments" | "indent_inner_html" | "indent_level" | "indent_scripts" | "indent_size" | "indent_style" | "indent_with_tabs" | "jslint_happy" | "jsx_brackets" | "keep_array_indentation" | "keyword_case" | "max_preserve_newlines" | "multiline_ternary" | "newline_before_tags" | "newline_between_rules" | "no_leading_zero" | "object_curly_spacing" | "pragma_insert" | "pragma_require" | "preserve_newlines" | "quotes" | "remove_trailing_whitespace" | "selector_separator_newline" | "space_after_anon_function" | "space_before_conditional" | "space_in_empty_paren" | "space_in_paren" | "typesafe_equality_operators" | "unescape_strings" | "unformatted" | "unindent_chained_methods" | "wrap_attributes" | "wrap_attributes_indent_size" | "wrap_line_length" | "wrap_prose"; + /** Function to process the given options and return a final option value. */ -export type BeautifierOptionTransformFunction = (options: OptionValues) => any; +// export type BeautifierOptionTransformFunction = (options: OptionValues) => any; +// export type BeautifierOptionTransformFunction = (options: LanguageSpecificOptionValues) => any; + +// export type BeautifierOptionTransformFunction< +// OptionNames extends keyof LanguageSpecificOptionValues +// > = (options: Pick) => any; + +export type BeautifierOptionTransformFunction = (options: Partial) => any; + +// export type BeautifierOptionTransformFunction< +// OptionNames extends BeautifierOptionName +// > = (options: Pick) => any; + /** Option that transforms one or more required options into a single value. */ + +/* +export type BeautifyOptionTransform< + SelectedBeautifierOptionNames extends BeautifierOptionName, + T extends SelectedBeautifierOptionNames[] +> = [ + T, + BeautifierOptionTransformFunction +]; +*/ + +/* +export type BeautifyOptionTransform< + SelectedBeautifierOptionNames extends BeautifierOptionName +> = [ + SelectedBeautifierOptionNames[], + BeautifierOptionTransformFunction +]; +*/ + export type BeautifyOptionTransform = [ BeautifierOptionName[], - BeautifierOptionTransformFunction + BeautifierOptionTransformFunction, ]; + +/* +export type BeautifyOptionTransform = T extends [ + (infer SelectedBeautifierOptionNames extends BeautifierOptionName)[], + any +] ? ([ + SelectedBeautifierOptionNames[], + BeautifierOptionTransformFunction +]) : never; +*/ +/* +type BeautifyOptionTransform = T extends { a: infer U, b: infer U } ? U : never; + +type Foo = T extends { a: infer U, b: infer U } ? U : never; + +type Foo = T extends { a: U, b: U } ? U : never; +*/ + +// const complexTransform: BeautifyOptionTransform<"indent_with_tabs" | "indent_size"> = [ +// const complexTransform: BeautifyOptionTransform = [ +// ["indent_with_tabs", "indent_size"], +// (optionValues) => optionValues.indent_with_tabs, +// ]; + /** Option that transforms a single option value with the same name. */ @@ -96,12 +161,48 @@ export interface BeautifierBeautifyData { beautifierConfig?: ResolvedConfig; } -export interface LanguageOptionValues { - [languageName: string]: OptionValues; +export type LanguageSpecificOptionValues = { + [optionName in BeautifierOptionName]?: any; +}; + +export interface BeautifierSpecificOptionValues { + prefer_beautifier_config?: boolean | string; +} + +export interface BeautifierOptionValues { + [beautifierName: string]: BeautifierSpecificOptionValues; } -export interface OptionValues { - [optionName: string]: any; +/* +export type OptionValues = { + beautifiers?: string[]; +} & LanguageSpecificOptionValues & BeautifierOptionValues & DependencyRegistry; +*/ + +export type OptionValues = LanguageSpecificOptionValues & { + beautifiers?: string[]; +} & { + // [beautifierName: string]: BeautifierSpecificOptionValues & DependenciesForBeautifierRegistry | string[] | undefined; + [beautifierName: string]: BeautifierSpecificOptionValues & DependenciesForBeautifierRegistry; +}; + +/* +const test: OptionValues = { + beautifiers: ["test"], +}; +*/ + +/* +export type OptionValues = LanguageSpecificOptionValues + & BeautifierOptionValues + & DependencyRegistry + & { + beautifiers?: EnabledBeautifiers[]; + }; +*/ + +export interface LanguageOptionValues { + [languageName: string]: OptionValues; } /** @@ -565,7 +666,7 @@ export class Unibeautify { return {}; } else if (typeof beautifierOptions === "object") { return Object.keys(beautifierOptions).reduce( - (acc: OptionValues, key: string) => { + (acc: OptionValues, key: BeautifierOptionName) => { const option = beautifierOptions[key]; if (typeof option === "string") { return { diff --git a/src/generated.ts b/src/generated.ts new file mode 100644 index 00000000..043d4937 --- /dev/null +++ b/src/generated.ts @@ -0,0 +1,49 @@ +export type BeautifierOptionName = + | "align_assignments" + | "arrow_parens" + | "brace_style" + | "break_chained_methods" + | "comma_first" + | "end_of_line" + | "end_with_comma" + | "end_with_newline" + | "end_with_semicolon" + | "force_indentation" + | "identifier_case" + | "indent_chained_methods" + | "indent_char" + | "indent_comments" + | "indent_inner_html" + | "indent_level" + | "indent_scripts" + | "indent_size" + | "indent_style" + | "indent_with_tabs" + | "jslint_happy" + | "jsx_brackets" + | "keep_array_indentation" + | "keyword_case" + | "max_preserve_newlines" + | "multiline_ternary" + | "newline_before_tags" + | "newline_between_rules" + | "no_leading_zero" + | "object_curly_spacing" + | "pragma_insert" + | "pragma_require" + | "preserve_newlines" + | "quotes" + | "remove_trailing_whitespace" + | "selector_separator_newline" + | "space_after_anon_function" + | "space_before_conditional" + | "space_in_empty_paren" + | "space_in_paren" + | "typesafe_equality_operators" + | "unescape_strings" + | "unformatted" + | "unindent_chained_methods" + | "wrap_attributes" + | "wrap_attributes_indent_size" + | "wrap_line_length" + | "wrap_prose"; diff --git a/test/beautifier/beautifier.spec.ts b/test/beautifier/beautifier.spec.ts index 91d3356a..c419b840 100644 --- a/test/beautifier/beautifier.spec.ts +++ b/test/beautifier/beautifier.spec.ts @@ -178,23 +178,25 @@ test("should successfully transform option values for beautifier", () => { name: "TestBeautify", options: { [lang1.name]: { - basicTransform: num => num + 1, + basicTransform: (num: number) => num + 1, complexTransform: [ + // ["indent_char", "indent_size", "indent_with_tabs"], + // (optionValues) => optionValues.indent_char + optionValues.indent_size, ["value1", "basicTransform"], - optionValues => optionValues.value1 + optionValues.basicTransform, + (optionValues: any) => optionValues.value1 + optionValues.basicTransform, ], isUndefined: undefined, renamed1: "value1", value1: true, value2: false, willBeReplaced: "value1", - }, + } as any, [lang2.name]: true, }, }; unibeautify.loadBeautifier(beautifier); - const options = { + const options: any = { basicTransform: 2, value1: 123, }; diff --git a/test/beautifier/config.spec.ts b/test/beautifier/config.spec.ts index 6b59739d..2f5c99e0 100644 --- a/test/beautifier/config.spec.ts +++ b/test/beautifier/config.spec.ts @@ -5,6 +5,7 @@ import { DependencyDefinition, DependencyType, DependencyManager, + BeautifierSpecificOptionValues, } from "../../src/"; beforeEach(() => { diff --git a/test/beautifier/options.spec.ts b/test/beautifier/options.spec.ts index 3cf2e57e..8af8da86 100644 --- a/test/beautifier/options.spec.ts +++ b/test/beautifier/options.spec.ts @@ -1,4 +1,4 @@ -import { Unibeautify, Language, Beautifier, OptionsRegistry } from "../../src/"; +import { Unibeautify, Language, Beautifier, OptionsRegistry, BeautifierOptionName } from "../../src/"; test("should get all loaded options", () => { const unibeautify = new Unibeautify(); @@ -28,7 +28,7 @@ test("should get all loaded options", () => { test("should get languages with a loaded beautifier supporting the given option", () => { const unibeautify = new Unibeautify(); - const optionName = "op1"; + const optionName: BeautifierOptionName = "op1" as any; const options1: OptionsRegistry = { [optionName]: { default: false, @@ -77,7 +77,7 @@ test("should get languages with a loaded beautifier supporting the given option" test("should get beautifiers with a loaded language supporting the given option", () => { const unibeautify = new Unibeautify(); - const optionName = "op1"; + const optionName: BeautifierOptionName = "op1" as any; const options1: OptionsRegistry = { [optionName]: { default: false, @@ -139,8 +139,8 @@ test("should get beautifiers with a loaded language supporting the given option" test("should correctly determine whether beautifier supports option for a language", () => { const unibeautify = new Unibeautify(); - const optionName = "op1"; - const undefinedOptionName = "isUndefined"; + const optionName: BeautifierOptionName = "op1" as any; + const undefinedOptionName: BeautifierOptionName = "isUndefined" as any; const options1: OptionsRegistry = { [optionName]: { default: false, @@ -232,9 +232,9 @@ test("should correctly determine whether beautifier supports option for a langua test("should get options supported for a language", () => { const unibeautify = new Unibeautify(); - const optionName1 = "op1"; - const optionName2 = "op2"; - const optionName3 = "op3"; + const optionName1: BeautifierOptionName = "op1" as any; + const optionName2: BeautifierOptionName = "op2" as any; + const optionName3: BeautifierOptionName = "op3" as any; const options1: OptionsRegistry = { [optionName1]: { default: false, @@ -314,7 +314,7 @@ test("should get options supported for a language", () => { test("should get options supported by a beautifier for a language", () => { const unibeautify = new Unibeautify(); - const optionName = "op1"; + const optionName: BeautifierOptionName = "op1" as any; const options1: OptionsRegistry = { [optionName]: { default: false, @@ -406,7 +406,7 @@ test( "tion name", () => { const unibeautify = new Unibeautify(); - const optionName = "op1"; + const optionName: BeautifierOptionName = "op1" as any; const options1: OptionsRegistry = { [optionName]: { default: true,