diff --git a/package.json b/package.json index b45dbb1..d78835c 100644 --- a/package.json +++ b/package.json @@ -31,6 +31,7 @@ }, "homepage": "https://github.com/TypeScript-Heroes/node-typescript-parser#readme", "devDependencies": { + "@types/lodash-es": "^4.17.0", "@types/jest": "^21.1.8", "@types/mock-fs": "^3.6.30", "@types/node": "^8.0.57", @@ -45,7 +46,8 @@ "typedoc": "^0.9.0" }, "dependencies": { - "lodash": "^4.17.4", + "lodash": "^4.17.5", + "lodash-es": "^4.17.5", "tslib": "^1.8.1", "typescript": "^2.6.2" } diff --git a/src/code-generators/TypescriptGenerationOptions.ts b/src/code-generators/TypescriptGenerationOptions.ts index c58db01..800268e 100644 --- a/src/code-generators/TypescriptGenerationOptions.ts +++ b/src/code-generators/TypescriptGenerationOptions.ts @@ -1,3 +1,9 @@ +export enum MultiLineImportRule { + strictlyOneImportPerLine = 'strictlyOneImportPerLine', + oneImportPerLineOnlyAfterThreshold = 'oneImportPerLineOnlyAfterThreshold', + multipleImportsPerLine = 'multipleImportsPerLine', +} + /** * Typescript generation options type. Contains all information needed to stringify some objects to typescript. * @@ -29,6 +35,14 @@ export interface TypescriptGenerationOptions { */ spaceBraces: boolean; + /** + * The wrapping methodology to be used for imports. + * + * @type {MultiLineImportRule} + * @memberof TypescriptGenerationOptions + */ + wrapMethod: MultiLineImportRule; + /** * The threshold where an import is written as multiline. * @@ -52,4 +66,12 @@ export interface TypescriptGenerationOptions { * @memberof TypescriptGenerationOptions */ tabSize: number; + + /** + * Insert spaces instead of tabs (default: true) + * + * @type {boolean} + * @memberof TypescriptGenerationOptions + */ + insertSpaces: boolean; } diff --git a/src/code-generators/typescript-generators/namedImport.ts b/src/code-generators/typescript-generators/namedImport.ts index 155a240..988babd 100644 --- a/src/code-generators/typescript-generators/namedImport.ts +++ b/src/code-generators/typescript-generators/namedImport.ts @@ -1,15 +1,18 @@ import { NamedImport } from '../../imports/NamedImport'; import { SymbolSpecifier } from '../../SymbolSpecifier'; import { stringTemplate } from '../../utilities/StringTemplate'; -import { TypescriptGenerationOptions } from '../TypescriptGenerationOptions'; +import { TypescriptGenerationOptions, MultiLineImportRule } from '../TypescriptGenerationOptions'; import { generateSymbolSpecifier } from './symbolSpecifier'; -const importTemplate = stringTemplate`import ${0} from ${1}`; +const oneLinerImportTemplate = stringTemplate`import ${0} from ${1}`; -const multiLineImport = stringTemplate`import ${3}{ +const multiLineImportTemplate = stringTemplate`import ${3}{ ${0}${1} } from ${2}`; +const defaultAliasOnlyMultiLineImportTemplate = stringTemplate`import ${0} +from ${1}`; + /** * Sort function for symbol specifiers. Does sort after the specifiers name (to lowercase). * @@ -45,34 +48,61 @@ export function generateNamedImport( stringQuoteStyle, spaceBraces, tabSize, + wrapMethod, multiLineWrapThreshold, multiLineTrailingComma, + insertSpaces = true, }: TypescriptGenerationOptions, ): string { - const space = spaceBraces ? ' ' : ''; const lib = `${stringQuoteStyle}${imp.libraryName}${stringQuoteStyle}${eol}`; - - const specifiers = imp.specifiers.sort(specifierSort).map(o => generateSymbolSpecifier(o)).join(', '); - let importSpecifiers = `${space}${specifiers}${space}`; - if (importSpecifiers.trim().length === 0) { - importSpecifiers = ' '; + // const specifiers = imp.specifiers.sort(specifierSort).map(o => generateSymbolSpecifier(o)).join(', '); + const oneLinerImportStatement = oneLinerImportTemplate(getImportSpecifiers(imp, spaceBraces), lib); + if (oneLinerImportStatement.length <= multiLineWrapThreshold && + (wrapMethod !== MultiLineImportRule.strictlyOneImportPerLine || + imp.specifiers.length <= 1)) { + return oneLinerImportStatement; } - - const importString = importTemplate( - getImportSpecifiers(imp, spaceBraces), - lib, - ); - - if (importString.length > multiLineWrapThreshold) { - const spacings = Array(tabSize + 1).join(' '); - return multiLineImport( - imp.specifiers.sort(specifierSort).map(o => `${spacings}${generateSymbolSpecifier(o)}`).join(',\n'), - multiLineTrailingComma ? ',' : '', - `${stringQuoteStyle}${imp.libraryName}${stringQuoteStyle}${eol}`, + const defaultAliasOnly: boolean = imp.specifiers.length === 0; + if (defaultAliasOnly) { + return defaultAliasOnlyMultiLineImportTemplate( imp.defaultAlias ? `${imp.defaultAlias}, ` : '', + `${stringQuoteStyle}${imp.libraryName}${stringQuoteStyle}${eol}`, ); } - return importString; + + const sortedImportSpecifiers: SymbolSpecifier[] = imp.specifiers.sort(specifierSort); + let importSpecifierStrings: string = ''; + const indent = insertSpaces ? Array(tabSize + 1).join(' ') : '\t'; + if (wrapMethod === MultiLineImportRule.strictlyOneImportPerLine || + wrapMethod === MultiLineImportRule.oneImportPerLineOnlyAfterThreshold) { + importSpecifierStrings = sortedImportSpecifiers.map(o => `${indent}${generateSymbolSpecifier(o)}`).join(',\n'); + } else if (wrapMethod === MultiLineImportRule.multipleImportsPerLine) { + importSpecifierStrings = sortedImportSpecifiers.reduce( + (acc, curr) => { + const symbolSpecifier: string = generateSymbolSpecifier(curr); + // const dist: number = acc.out.length - acc.lastWrapOffset + symbolSpecifier.length; + const importLines = acc.out.split('\n'); + const lastImportLine = importLines[importLines.length - 1]; + const dist: number = lastImportLine.length + `, `.length + symbolSpecifier.length; + const needsWrap: boolean = dist >= multiLineWrapThreshold; + return { + out: acc.out + (needsWrap ? `,\n${indent}` : (acc.out.length ? `, ` : `${indent}`)) + + symbolSpecifier, + lastWrapOffset: acc.lastWrapOffset + (needsWrap ? dist : 0), + }; + }, + { + out: '', + lastWrapOffset: 0, + }, + ).out; + } + return multiLineImportTemplate( + importSpecifierStrings, + multiLineTrailingComma ? ',' : '', + `${stringQuoteStyle}${imp.libraryName}${stringQuoteStyle}${eol}`, + imp.defaultAlias ? `${imp.defaultAlias}, ` : '', + ); } function getImportSpecifiers(namedImport: NamedImport, spaceBraces: boolean): string { diff --git a/test/code-generators/TypescriptCodeGenerator.spec.ts b/test/code-generators/TypescriptCodeGenerator.spec.ts index 8bf5904..4f57319 100644 --- a/test/code-generators/TypescriptCodeGenerator.spec.ts +++ b/test/code-generators/TypescriptCodeGenerator.spec.ts @@ -1,5 +1,5 @@ import { TypescriptCodeGenerator } from '../../src/code-generators/TypescriptCodeGenerator'; -import { TypescriptGenerationOptions } from '../../src/code-generators/TypescriptGenerationOptions'; +import { TypescriptGenerationOptions, MultiLineImportRule } from '../../src/code-generators/TypescriptGenerationOptions'; import { ClassDeclaration } from '../../src/declarations'; import { GetterDeclaration, SetterDeclaration } from '../../src/declarations/AccessorDeclaration'; import { DeclarationVisibility } from '../../src/declarations/DeclarationVisibility'; @@ -37,6 +37,9 @@ multiLineNamedImport.specifiers = [ new SymbolSpecifier('spec13'), new SymbolSpecifier('spec14'), new SymbolSpecifier('spec15'), + new SymbolSpecifier('spec16'), + new SymbolSpecifier('spec17'), + new SymbolSpecifier('spec18'), ]; const defaultImport = new NamedImport('defaultImport'); @@ -54,18 +57,42 @@ describe('TypescriptCodeGenerator', () => { const defaultOptions: TypescriptGenerationOptions = { eol: ';', multiLineTrailingComma: true, + wrapMethod: MultiLineImportRule.oneImportPerLineOnlyAfterThreshold, multiLineWrapThreshold: 125, spaceBraces: true, stringQuoteStyle: `'`, tabSize: 4, + insertSpaces: true, }; - const impOptions: TypescriptGenerationOptions = { + const impOptions_oneImportPerLineOnlyAfterThreshold: TypescriptGenerationOptions = { eol: ';', multiLineTrailingComma: true, + wrapMethod: MultiLineImportRule.oneImportPerLineOnlyAfterThreshold, multiLineWrapThreshold: 125, spaceBraces: true, stringQuoteStyle: `"`, tabSize: 2, + insertSpaces: true, + }; + const impOptions_strictlyOneImportPerLine: TypescriptGenerationOptions = { + eol: ';', + multiLineTrailingComma: true, + wrapMethod: MultiLineImportRule.strictlyOneImportPerLine, + multiLineWrapThreshold: 125, + spaceBraces: true, + stringQuoteStyle: `"`, + tabSize: 2, + insertSpaces: true, + }; + const impOptions_multipleImportsPerLine: TypescriptGenerationOptions = { + eol: ';', + multiLineTrailingComma: true, + wrapMethod: MultiLineImportRule.multipleImportsPerLine, + multiLineWrapThreshold: 125, + spaceBraces: true, + stringQuoteStyle: `"`, + tabSize: 2, + insertSpaces: true, }; const imports = [ new ExternalModuleImport('externalModuleLib', 'externalAlias'), @@ -134,11 +161,28 @@ describe('TypescriptCodeGenerator', () => { }); it(`should generate the correct code for ${imp.constructor.name} with double quote`, () => { - const generator = new TypescriptCodeGenerator(impOptions); + const generator = new TypescriptCodeGenerator(defaultOptions); + + expect(generator.generate(imp)).toMatchSnapshot(); + }); + + it(`should generate the correct code for ${imp.constructor.name} with double quote`, () => { + const generator = new TypescriptCodeGenerator(impOptions_oneImportPerLineOnlyAfterThreshold); + + expect(generator.generate(imp)).toMatchSnapshot(); + }); + + it(`should generate the correct code for ${imp.constructor.name} with double quote`, () => { + const generator = new TypescriptCodeGenerator(impOptions_strictlyOneImportPerLine); expect(generator.generate(imp)).toMatchSnapshot(); }); + it(`should generate multiple imports per line for ${imp.constructor.name} with single quote`, () => { + const generator = new TypescriptCodeGenerator(impOptions_multipleImportsPerLine); + + expect(generator.generate(imp)).toMatchSnapshot(); + }); } it('should throw on non generatable element', () => { diff --git a/test/code-generators/__snapshots__/TypescriptCodeGenerator.spec.ts.snap b/test/code-generators/__snapshots__/TypescriptCodeGenerator.spec.ts.snap index 30013e0..e9e69d5 100644 --- a/test/code-generators/__snapshots__/TypescriptCodeGenerator.spec.ts.snap +++ b/test/code-generators/__snapshots__/TypescriptCodeGenerator.spec.ts.snap @@ -1,8 +1,40 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP +exports[`TypescriptCodeGenerator should generate multiple imports per line for ExternalModuleImport with single quote 1`] = `"import externalAlias = require(\\"externalModuleLib\\");"`; + +exports[`TypescriptCodeGenerator should generate multiple imports per line for NamedImport with single quote 1`] = `"import { spec1, spec2 as alias2 } from \\"namedLib\\";"`; + +exports[`TypescriptCodeGenerator should generate multiple imports per line for NamedImport with single quote 2`] = ` +"import { + spec1, spec10, spec11, spec12, spec13, spec14, spec15, spec16, spec17, spec18, spec2, spec3, spec4, spec5, spec6, spec7, + spec8, spec9, +} from \\"multiLineNamedLib\\";" +`; + +exports[`TypescriptCodeGenerator should generate multiple imports per line for NamedImport with single quote 3`] = `"import { } from \\"emptyImport\\";"`; + +exports[`TypescriptCodeGenerator should generate multiple imports per line for NamedImport with single quote 4`] = `"import Default from \\"defaultImport\\";"`; + +exports[`TypescriptCodeGenerator should generate multiple imports per line for NamedImport with single quote 5`] = `"import Default, { spec1, spec2 as alias2 } from \\"defaultWithNamedImport\\";"`; + +exports[`TypescriptCodeGenerator should generate multiple imports per line for NamedImport with single quote 6`] = ` +"import Default, { + spec1, spec10, spec11, spec12, spec13, spec14, spec15, spec16, spec17, spec18, spec2, spec3, spec4, spec5, spec6, spec7, + spec8, spec9, +} from \\"defaultWithNamedMultilineImport\\";" +`; + +exports[`TypescriptCodeGenerator should generate multiple imports per line for NamespaceImport with single quote 1`] = `"import * as namespaceAlias from \\"namespaceLib\\";"`; + +exports[`TypescriptCodeGenerator should generate multiple imports per line for StringImport with single quote 1`] = `"import \\"stringLib\\";"`; + exports[`TypescriptCodeGenerator should generate the correct code for ExternalModuleImport 1`] = `"import externalAlias = require('externalModuleLib');"`; -exports[`TypescriptCodeGenerator should generate the correct code for ExternalModuleImport with double quote 1`] = `"import externalAlias = require(\\"externalModuleLib\\");"`; +exports[`TypescriptCodeGenerator should generate the correct code for ExternalModuleImport with double quote 1`] = `"import externalAlias = require('externalModuleLib');"`; + +exports[`TypescriptCodeGenerator should generate the correct code for ExternalModuleImport with double quote 2`] = `"import externalAlias = require(\\"externalModuleLib\\");"`; + +exports[`TypescriptCodeGenerator should generate the correct code for ExternalModuleImport with double quote 3`] = `"import externalAlias = require(\\"externalModuleLib\\");"`; exports[`TypescriptCodeGenerator should generate the correct code for ExternalModuleImport with single quote 1`] = `"import externalAlias = require('externalModuleLib');"`; @@ -107,6 +139,9 @@ exports[`TypescriptCodeGenerator should generate the correct code for NamedImpor spec13, spec14, spec15, + spec16, + spec17, + spec18, spec2, spec3, spec4, @@ -133,6 +168,9 @@ exports[`TypescriptCodeGenerator should generate the correct code for NamedImpor spec13, spec14, spec15, + spec16, + spec17, + spec18, spec2, spec3, spec4, @@ -144,9 +182,41 @@ exports[`TypescriptCodeGenerator should generate the correct code for NamedImpor } from 'defaultWithNamedMultilineImport';" `; -exports[`TypescriptCodeGenerator should generate the correct code for NamedImport with double quote 1`] = `"import { spec1, spec2 as alias2 } from \\"namedLib\\";"`; +exports[`TypescriptCodeGenerator should generate the correct code for NamedImport with double quote 1`] = `"import { spec1, spec2 as alias2 } from 'namedLib';"`; -exports[`TypescriptCodeGenerator should generate the correct code for NamedImport with double quote 2`] = ` +exports[`TypescriptCodeGenerator should generate the correct code for NamedImport with double quote 2`] = `"import { spec1, spec2 as alias2 } from \\"namedLib\\";"`; + +exports[`TypescriptCodeGenerator should generate the correct code for NamedImport with double quote 3`] = ` +"import { + spec1, + spec2 as alias2, +} from \\"namedLib\\";" +`; + +exports[`TypescriptCodeGenerator should generate the correct code for NamedImport with double quote 4`] = ` +"import { + spec1, + spec10, + spec11, + spec12, + spec13, + spec14, + spec15, + spec16, + spec17, + spec18, + spec2, + spec3, + spec4, + spec5, + spec6, + spec7, + spec8, + spec9, +} from 'multiLineNamedLib';" +`; + +exports[`TypescriptCodeGenerator should generate the correct code for NamedImport with double quote 5`] = ` "import { spec1, spec10, @@ -155,6 +225,9 @@ exports[`TypescriptCodeGenerator should generate the correct code for NamedImpor spec13, spec14, spec15, + spec16, + spec17, + spec18, spec2, spec3, spec4, @@ -166,13 +239,99 @@ exports[`TypescriptCodeGenerator should generate the correct code for NamedImpor } from \\"multiLineNamedLib\\";" `; -exports[`TypescriptCodeGenerator should generate the correct code for NamedImport with double quote 3`] = `"import { } from \\"emptyImport\\";"`; +exports[`TypescriptCodeGenerator should generate the correct code for NamedImport with double quote 6`] = ` +"import { + spec1, + spec10, + spec11, + spec12, + spec13, + spec14, + spec15, + spec16, + spec17, + spec18, + spec2, + spec3, + spec4, + spec5, + spec6, + spec7, + spec8, + spec9, +} from \\"multiLineNamedLib\\";" +`; -exports[`TypescriptCodeGenerator should generate the correct code for NamedImport with double quote 4`] = `"import Default from \\"defaultImport\\";"`; +exports[`TypescriptCodeGenerator should generate the correct code for NamedImport with double quote 7`] = `"import { } from 'emptyImport';"`; -exports[`TypescriptCodeGenerator should generate the correct code for NamedImport with double quote 5`] = `"import Default, { spec1, spec2 as alias2 } from \\"defaultWithNamedImport\\";"`; +exports[`TypescriptCodeGenerator should generate the correct code for NamedImport with double quote 8`] = `"import { } from \\"emptyImport\\";"`; -exports[`TypescriptCodeGenerator should generate the correct code for NamedImport with double quote 6`] = ` +exports[`TypescriptCodeGenerator should generate the correct code for NamedImport with double quote 9`] = `"import { } from \\"emptyImport\\";"`; + +exports[`TypescriptCodeGenerator should generate the correct code for NamedImport with double quote 10`] = `"import Default from 'defaultImport';"`; + +exports[`TypescriptCodeGenerator should generate the correct code for NamedImport with double quote 11`] = `"import Default from \\"defaultImport\\";"`; + +exports[`TypescriptCodeGenerator should generate the correct code for NamedImport with double quote 12`] = `"import Default from \\"defaultImport\\";"`; + +exports[`TypescriptCodeGenerator should generate the correct code for NamedImport with double quote 13`] = `"import Default, { spec1, spec2 as alias2 } from 'defaultWithNamedImport';"`; + +exports[`TypescriptCodeGenerator should generate the correct code for NamedImport with double quote 14`] = `"import Default, { spec1, spec2 as alias2 } from \\"defaultWithNamedImport\\";"`; + +exports[`TypescriptCodeGenerator should generate the correct code for NamedImport with double quote 15`] = ` +"import Default, { + spec1, + spec2 as alias2, +} from \\"defaultWithNamedImport\\";" +`; + +exports[`TypescriptCodeGenerator should generate the correct code for NamedImport with double quote 16`] = ` +"import Default, { + spec1, + spec10, + spec11, + spec12, + spec13, + spec14, + spec15, + spec16, + spec17, + spec18, + spec2, + spec3, + spec4, + spec5, + spec6, + spec7, + spec8, + spec9, +} from 'defaultWithNamedMultilineImport';" +`; + +exports[`TypescriptCodeGenerator should generate the correct code for NamedImport with double quote 17`] = ` +"import Default, { + spec1, + spec10, + spec11, + spec12, + spec13, + spec14, + spec15, + spec16, + spec17, + spec18, + spec2, + spec3, + spec4, + spec5, + spec6, + spec7, + spec8, + spec9, +} from \\"defaultWithNamedMultilineImport\\";" +`; + +exports[`TypescriptCodeGenerator should generate the correct code for NamedImport with double quote 18`] = ` "import Default, { spec1, spec10, @@ -181,6 +340,9 @@ exports[`TypescriptCodeGenerator should generate the correct code for NamedImpor spec13, spec14, spec15, + spec16, + spec17, + spec18, spec2, spec3, spec4, @@ -203,6 +365,9 @@ exports[`TypescriptCodeGenerator should generate the correct code for NamedImpor spec13, spec14, spec15, + spec16, + spec17, + spec18, spec2, spec3, spec4, @@ -229,6 +394,9 @@ exports[`TypescriptCodeGenerator should generate the correct code for NamedImpor spec13, spec14, spec15, + spec16, + spec17, + spec18, spec2, spec3, spec4, @@ -242,7 +410,11 @@ exports[`TypescriptCodeGenerator should generate the correct code for NamedImpor exports[`TypescriptCodeGenerator should generate the correct code for NamespaceImport 1`] = `"import * as namespaceAlias from 'namespaceLib';"`; -exports[`TypescriptCodeGenerator should generate the correct code for NamespaceImport with double quote 1`] = `"import * as namespaceAlias from \\"namespaceLib\\";"`; +exports[`TypescriptCodeGenerator should generate the correct code for NamespaceImport with double quote 1`] = `"import * as namespaceAlias from 'namespaceLib';"`; + +exports[`TypescriptCodeGenerator should generate the correct code for NamespaceImport with double quote 2`] = `"import * as namespaceAlias from \\"namespaceLib\\";"`; + +exports[`TypescriptCodeGenerator should generate the correct code for NamespaceImport with double quote 3`] = `"import * as namespaceAlias from \\"namespaceLib\\";"`; exports[`TypescriptCodeGenerator should generate the correct code for NamespaceImport with single quote 1`] = `"import * as namespaceAlias from 'namespaceLib';"`; @@ -315,7 +487,11 @@ exports[`TypescriptCodeGenerator should generate the correct code for SetterDecl exports[`TypescriptCodeGenerator should generate the correct code for StringImport 1`] = `"import 'stringLib';"`; -exports[`TypescriptCodeGenerator should generate the correct code for StringImport with double quote 1`] = `"import \\"stringLib\\";"`; +exports[`TypescriptCodeGenerator should generate the correct code for StringImport with double quote 1`] = `"import 'stringLib';"`; + +exports[`TypescriptCodeGenerator should generate the correct code for StringImport with double quote 2`] = `"import \\"stringLib\\";"`; + +exports[`TypescriptCodeGenerator should generate the correct code for StringImport with double quote 3`] = `"import \\"stringLib\\";"`; exports[`TypescriptCodeGenerator should generate the correct code for StringImport with single quote 1`] = `"import 'stringLib';"`;