Skip to content

Commit f8f72f4

Browse files
shobhitgbuehler
authored andcommitted
feat: Optional configuration for multiple imports per line and strictly one import per line. (#50)
As per buehler/typescript-hero/issues/379, there should be an option `multiLineWrapMethod`, where a user can choose between: `ONE_IMPORT_PER_LINE` OR `MULTIPLE_IMPORTS_PER_LINE`
1 parent a655610 commit f8f72f4

File tree

5 files changed

+309
-35
lines changed

5 files changed

+309
-35
lines changed

package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
},
3232
"homepage": "https://github.com/TypeScript-Heroes/node-typescript-parser#readme",
3333
"devDependencies": {
34+
"@types/lodash-es": "^4.17.0",
3435
"@types/jest": "^21.1.8",
3536
"@types/mock-fs": "^3.6.30",
3637
"@types/node": "^8.0.57",
@@ -45,7 +46,8 @@
4546
"typedoc": "^0.9.0"
4647
},
4748
"dependencies": {
48-
"lodash": "^4.17.4",
49+
"lodash": "^4.17.5",
50+
"lodash-es": "^4.17.5",
4951
"tslib": "^1.8.1",
5052
"typescript": "^2.6.2"
5153
}

src/code-generators/TypescriptGenerationOptions.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
export enum MultiLineImportRule {
2+
strictlyOneImportPerLine = 'strictlyOneImportPerLine',
3+
oneImportPerLineOnlyAfterThreshold = 'oneImportPerLineOnlyAfterThreshold',
4+
multipleImportsPerLine = 'multipleImportsPerLine',
5+
}
6+
17
/**
28
* Typescript generation options type. Contains all information needed to stringify some objects to typescript.
39
*
@@ -29,6 +35,14 @@ export interface TypescriptGenerationOptions {
2935
*/
3036
spaceBraces: boolean;
3137

38+
/**
39+
* The wrapping methodology to be used for imports.
40+
*
41+
* @type {MultiLineImportRule}
42+
* @memberof TypescriptGenerationOptions
43+
*/
44+
wrapMethod: MultiLineImportRule;
45+
3246
/**
3347
* The threshold where an import is written as multiline.
3448
*
@@ -52,4 +66,12 @@ export interface TypescriptGenerationOptions {
5266
* @memberof TypescriptGenerationOptions
5367
*/
5468
tabSize: number;
69+
70+
/**
71+
* Insert spaces instead of tabs (default: true)
72+
*
73+
* @type {boolean}
74+
* @memberof TypescriptGenerationOptions
75+
*/
76+
insertSpaces: boolean;
5577
}

src/code-generators/typescript-generators/namedImport.ts

Lines changed: 52 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,18 @@
11
import { NamedImport } from '../../imports/NamedImport';
22
import { SymbolSpecifier } from '../../SymbolSpecifier';
33
import { stringTemplate } from '../../utilities/StringTemplate';
4-
import { TypescriptGenerationOptions } from '../TypescriptGenerationOptions';
4+
import { TypescriptGenerationOptions, MultiLineImportRule } from '../TypescriptGenerationOptions';
55
import { generateSymbolSpecifier } from './symbolSpecifier';
66

7-
const importTemplate = stringTemplate`import ${0} from ${1}`;
7+
const oneLinerImportTemplate = stringTemplate`import ${0} from ${1}`;
88

9-
const multiLineImport = stringTemplate`import ${3}{
9+
const multiLineImportTemplate = stringTemplate`import ${3}{
1010
${0}${1}
1111
} from ${2}`;
1212

13+
const defaultAliasOnlyMultiLineImportTemplate = stringTemplate`import ${0}
14+
from ${1}`;
15+
1316
/**
1417
* Sort function for symbol specifiers. Does sort after the specifiers name (to lowercase).
1518
*
@@ -45,34 +48,61 @@ export function generateNamedImport(
4548
stringQuoteStyle,
4649
spaceBraces,
4750
tabSize,
51+
wrapMethod,
4852
multiLineWrapThreshold,
4953
multiLineTrailingComma,
54+
insertSpaces = true,
5055
}: TypescriptGenerationOptions,
5156
): string {
52-
const space = spaceBraces ? ' ' : '';
5357
const lib = `${stringQuoteStyle}${imp.libraryName}${stringQuoteStyle}${eol}`;
54-
55-
const specifiers = imp.specifiers.sort(specifierSort).map(o => generateSymbolSpecifier(o)).join(', ');
56-
let importSpecifiers = `${space}${specifiers}${space}`;
57-
if (importSpecifiers.trim().length === 0) {
58-
importSpecifiers = ' ';
58+
// const specifiers = imp.specifiers.sort(specifierSort).map(o => generateSymbolSpecifier(o)).join(', ');
59+
const oneLinerImportStatement = oneLinerImportTemplate(getImportSpecifiers(imp, spaceBraces), lib);
60+
if (oneLinerImportStatement.length <= multiLineWrapThreshold &&
61+
(wrapMethod !== MultiLineImportRule.strictlyOneImportPerLine ||
62+
imp.specifiers.length <= 1)) {
63+
return oneLinerImportStatement;
5964
}
60-
61-
const importString = importTemplate(
62-
getImportSpecifiers(imp, spaceBraces),
63-
lib,
64-
);
65-
66-
if (importString.length > multiLineWrapThreshold) {
67-
const spacings = Array(tabSize + 1).join(' ');
68-
return multiLineImport(
69-
imp.specifiers.sort(specifierSort).map(o => `${spacings}${generateSymbolSpecifier(o)}`).join(',\n'),
70-
multiLineTrailingComma ? ',' : '',
71-
`${stringQuoteStyle}${imp.libraryName}${stringQuoteStyle}${eol}`,
65+
const defaultAliasOnly: boolean = imp.specifiers.length === 0;
66+
if (defaultAliasOnly) {
67+
return defaultAliasOnlyMultiLineImportTemplate(
7268
imp.defaultAlias ? `${imp.defaultAlias}, ` : '',
69+
`${stringQuoteStyle}${imp.libraryName}${stringQuoteStyle}${eol}`,
7370
);
7471
}
75-
return importString;
72+
73+
const sortedImportSpecifiers: SymbolSpecifier[] = imp.specifiers.sort(specifierSort);
74+
let importSpecifierStrings: string = '';
75+
const indent = insertSpaces ? Array(tabSize + 1).join(' ') : '\t';
76+
if (wrapMethod === MultiLineImportRule.strictlyOneImportPerLine ||
77+
wrapMethod === MultiLineImportRule.oneImportPerLineOnlyAfterThreshold) {
78+
importSpecifierStrings = sortedImportSpecifiers.map(o => `${indent}${generateSymbolSpecifier(o)}`).join(',\n');
79+
} else if (wrapMethod === MultiLineImportRule.multipleImportsPerLine) {
80+
importSpecifierStrings = sortedImportSpecifiers.reduce(
81+
(acc, curr) => {
82+
const symbolSpecifier: string = generateSymbolSpecifier(curr);
83+
// const dist: number = acc.out.length - acc.lastWrapOffset + symbolSpecifier.length;
84+
const importLines = acc.out.split('\n');
85+
const lastImportLine = importLines[importLines.length - 1];
86+
const dist: number = lastImportLine.length + `, `.length + symbolSpecifier.length;
87+
const needsWrap: boolean = dist >= multiLineWrapThreshold;
88+
return {
89+
out: acc.out + (needsWrap ? `,\n${indent}` : (acc.out.length ? `, ` : `${indent}`)) +
90+
symbolSpecifier,
91+
lastWrapOffset: acc.lastWrapOffset + (needsWrap ? dist : 0),
92+
};
93+
},
94+
{
95+
out: '',
96+
lastWrapOffset: 0,
97+
},
98+
).out;
99+
}
100+
return multiLineImportTemplate(
101+
importSpecifierStrings,
102+
multiLineTrailingComma ? ',' : '',
103+
`${stringQuoteStyle}${imp.libraryName}${stringQuoteStyle}${eol}`,
104+
imp.defaultAlias ? `${imp.defaultAlias}, ` : '',
105+
);
76106
}
77107

78108
function getImportSpecifiers(namedImport: NamedImport, spaceBraces: boolean): string {

test/code-generators/TypescriptCodeGenerator.spec.ts

Lines changed: 47 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { TypescriptCodeGenerator } from '../../src/code-generators/TypescriptCodeGenerator';
2-
import { TypescriptGenerationOptions } from '../../src/code-generators/TypescriptGenerationOptions';
2+
import { TypescriptGenerationOptions, MultiLineImportRule } from '../../src/code-generators/TypescriptGenerationOptions';
33
import { ClassDeclaration } from '../../src/declarations';
44
import { GetterDeclaration, SetterDeclaration } from '../../src/declarations/AccessorDeclaration';
55
import { DeclarationVisibility } from '../../src/declarations/DeclarationVisibility';
@@ -37,6 +37,9 @@ multiLineNamedImport.specifiers = [
3737
new SymbolSpecifier('spec13'),
3838
new SymbolSpecifier('spec14'),
3939
new SymbolSpecifier('spec15'),
40+
new SymbolSpecifier('spec16'),
41+
new SymbolSpecifier('spec17'),
42+
new SymbolSpecifier('spec18'),
4043
];
4144

4245
const defaultImport = new NamedImport('defaultImport');
@@ -54,18 +57,42 @@ describe('TypescriptCodeGenerator', () => {
5457
const defaultOptions: TypescriptGenerationOptions = {
5558
eol: ';',
5659
multiLineTrailingComma: true,
60+
wrapMethod: MultiLineImportRule.oneImportPerLineOnlyAfterThreshold,
5761
multiLineWrapThreshold: 125,
5862
spaceBraces: true,
5963
stringQuoteStyle: `'`,
6064
tabSize: 4,
65+
insertSpaces: true,
6166
};
62-
const impOptions: TypescriptGenerationOptions = {
67+
const impOptions_oneImportPerLineOnlyAfterThreshold: TypescriptGenerationOptions = {
6368
eol: ';',
6469
multiLineTrailingComma: true,
70+
wrapMethod: MultiLineImportRule.oneImportPerLineOnlyAfterThreshold,
6571
multiLineWrapThreshold: 125,
6672
spaceBraces: true,
6773
stringQuoteStyle: `"`,
6874
tabSize: 2,
75+
insertSpaces: true,
76+
};
77+
const impOptions_strictlyOneImportPerLine: TypescriptGenerationOptions = {
78+
eol: ';',
79+
multiLineTrailingComma: true,
80+
wrapMethod: MultiLineImportRule.strictlyOneImportPerLine,
81+
multiLineWrapThreshold: 125,
82+
spaceBraces: true,
83+
stringQuoteStyle: `"`,
84+
tabSize: 2,
85+
insertSpaces: true,
86+
};
87+
const impOptions_multipleImportsPerLine: TypescriptGenerationOptions = {
88+
eol: ';',
89+
multiLineTrailingComma: true,
90+
wrapMethod: MultiLineImportRule.multipleImportsPerLine,
91+
multiLineWrapThreshold: 125,
92+
spaceBraces: true,
93+
stringQuoteStyle: `"`,
94+
tabSize: 2,
95+
insertSpaces: true,
6996
};
7097
const imports = [
7198
new ExternalModuleImport('externalModuleLib', 'externalAlias'),
@@ -134,11 +161,28 @@ describe('TypescriptCodeGenerator', () => {
134161
});
135162

136163
it(`should generate the correct code for ${imp.constructor.name} with double quote`, () => {
137-
const generator = new TypescriptCodeGenerator(impOptions);
164+
const generator = new TypescriptCodeGenerator(defaultOptions);
165+
166+
expect(generator.generate(imp)).toMatchSnapshot();
167+
});
168+
169+
it(`should generate the correct code for ${imp.constructor.name} with double quote`, () => {
170+
const generator = new TypescriptCodeGenerator(impOptions_oneImportPerLineOnlyAfterThreshold);
171+
172+
expect(generator.generate(imp)).toMatchSnapshot();
173+
});
174+
175+
it(`should generate the correct code for ${imp.constructor.name} with double quote`, () => {
176+
const generator = new TypescriptCodeGenerator(impOptions_strictlyOneImportPerLine);
138177

139178
expect(generator.generate(imp)).toMatchSnapshot();
140179
});
141180

181+
it(`should generate multiple imports per line for ${imp.constructor.name} with single quote`, () => {
182+
const generator = new TypescriptCodeGenerator(impOptions_multipleImportsPerLine);
183+
184+
expect(generator.generate(imp)).toMatchSnapshot();
185+
});
142186
}
143187

144188
it('should throw on non generatable element', () => {

0 commit comments

Comments
 (0)