UNPKG

12.2 kBPlain TextView Raw
1import * as fs from 'fs-extra';
2import * as _ from 'lodash';
3import * as path from 'path';
4import { ts } from 'ts-morph';
5
6import { LinkParser } from './link-parser';
7
8import { logger } from './logger';
9
10import { AngularLifecycleHooks } from './angular-lifecycles-hooks';
11import { kindToType } from './kind-to-type';
12import { JsdocParserUtil } from './jsdoc-parser.util';
13
14const getCurrentDirectory = ts.sys.getCurrentDirectory;
15const useCaseSensitiveFileNames = ts.sys.useCaseSensitiveFileNames;
16const newLine = ts.sys.newLine;
17const { marked } = require('marked');
18
19export function getNewLine(): string {
20 return newLine;
21}
22
23export function cleanNameWithoutSpaceAndToLowerCase(name: string): string {
24 return name.toLowerCase().replace(/ /g, '-');
25}
26
27export function getCanonicalFileName(fileName: string): string {
28 return useCaseSensitiveFileNames ? fileName : fileName.toLowerCase();
29}
30
31export const formatDiagnosticsHost: ts.FormatDiagnosticsHost = {
32 getCurrentDirectory,
33 getCanonicalFileName,
34 getNewLine
35};
36
37export function markedtags(tags: Array<any>) {
38 const jsdocParserUtil = new JsdocParserUtil();
39 let mtags = tags;
40 _.forEach(mtags, tag => {
41 const rawComment = jsdocParserUtil.parseJSDocNode(tag);
42 tag.comment = marked(LinkParser.resolveLinks(rawComment));
43 });
44 return mtags;
45}
46
47export function mergeTagsAndArgs(args: Array<any>, jsdoctags?: Array<any>): Array<any> {
48 let margs = _.cloneDeep(args);
49 _.forEach(margs, arg => {
50 arg.tagName = {
51 text: 'param'
52 };
53 if (jsdoctags) {
54 _.forEach(jsdoctags, jsdoctag => {
55 if (jsdoctag.name && jsdoctag.name.text === arg.name) {
56 arg.tagName = jsdoctag.tagName;
57 arg.name = jsdoctag.name;
58 arg.comment = jsdoctag.comment;
59 arg.typeExpression = jsdoctag.typeExpression;
60 }
61 });
62 }
63 });
64 // Add example & returns & private
65 if (jsdoctags) {
66 _.forEach(jsdoctags, jsdoctag => {
67 if (
68 jsdoctag.tagName &&
69 (jsdoctag.tagName.text === 'example' || jsdoctag.tagName.text === 'private')
70 ) {
71 margs.push({
72 tagName: jsdoctag.tagName,
73 comment: jsdoctag.comment
74 });
75 }
76 if (
77 jsdoctag.tagName &&
78 (jsdoctag.tagName.text === 'returns' || jsdoctag.tagName.text === 'return')
79 ) {
80 let ret = {
81 tagName: jsdoctag.tagName,
82 comment: jsdoctag.comment
83 };
84 if (jsdoctag.typeExpression && jsdoctag.typeExpression.type) {
85 ret.returnType = kindToType(jsdoctag.typeExpression.type.kind);
86 }
87 margs.push(ret);
88 }
89 });
90 }
91 return margs;
92}
93
94export function readConfig(configFile: string): any {
95 let result = ts.readConfigFile(configFile, ts.sys.readFile);
96 if (result.error) {
97 let message = ts.formatDiagnostics([result.error], formatDiagnosticsHost);
98 throw new Error(message);
99 }
100 return result.config;
101}
102
103export function stripBom(source: string): string {
104 if (source.charCodeAt(0) === 0xfeff) {
105 return source.slice(1);
106 }
107 return source;
108}
109
110export function hasBom(source: string): boolean {
111 return source.charCodeAt(0) === 0xfeff;
112}
113
114export function handlePath(files: Array<string>, cwd: string): Array<string> {
115 let _files = files;
116 let i = 0;
117 let len = files.length;
118
119 for (i; i < len; i++) {
120 if (files[i].indexOf(cwd) === -1) {
121 files[i] = path.resolve(cwd + path.sep + files[i]);
122 }
123 }
124
125 return _files;
126}
127
128export function cleanLifecycleHooksFromMethods(methods: Array<any>): Array<any> {
129 let result = [];
130 if (typeof methods !== 'undefined') {
131 let i = 0;
132 let len = methods.length;
133 for (i; i < len; i++) {
134 if (!(methods[i].name in AngularLifecycleHooks)) {
135 result.push(methods[i]);
136 }
137 }
138 }
139 return result;
140}
141
142export function cleanSourcesForWatch(list) {
143 return list.filter(element => {
144 if (fs.existsSync(process.cwd() + path.sep + element)) {
145 return element;
146 }
147 });
148}
149
150export function getNamesCompareFn(name?) {
151 /**
152 * Copyright https://github.com/ng-bootstrap/ng-bootstrap
153 */
154 name = name || 'name';
155 const t = (a, b) => {
156 if (a[name]) {
157 return a[name].localeCompare(b[name]);
158 } else {
159 return 0;
160 }
161 };
162 return t;
163}
164
165export function isIgnore(member): boolean {
166 if (member.jsDoc) {
167 for (const doc of member.jsDoc) {
168 if (doc.tags) {
169 for (const tag of doc.tags) {
170 if (tag.tagName.text.indexOf('ignore') > -1) {
171 return true;
172 }
173 }
174 }
175 }
176 }
177 return false;
178}
179
180// https://tc39.github.io/ecma262/#sec-array.prototype.includes
181if (!Array.prototype.includes) {
182 Object.defineProperty(Array.prototype, 'includes', {
183 value: function (searchElement, fromIndex) {
184 if (this == null) {
185 throw new TypeError('"this" is null or not defined');
186 }
187
188 // 1. Let O be ? ToObject(this value).
189 let o = Object(this);
190
191 // 2. Let len be ? ToLength(? Get(O, "length")).
192 let len = o.length >>> 0;
193
194 // 3. If len is 0, return false.
195 if (len === 0) {
196 return false;
197 }
198
199 // 4. Let n be ? ToInteger(fromIndex).
200 // (If fromIndex is undefined, this step produces the value 0.)
201 let n = fromIndex | 0;
202
203 // 5. If n ≥ 0, then
204 // a. Let k be n.
205 // 6. Else n < 0,
206 // a. Let k be len + n.
207 // b. If k < 0, let k be 0.
208 let k = Math.max(n >= 0 ? n : len - Math.abs(n), 0);
209
210 function sameValueZero(x, y) {
211 return (
212 x === y ||
213 (typeof x === 'number' && typeof y === 'number' && isNaN(x) && isNaN(y))
214 );
215 }
216
217 // 7. Repeat, while k < len
218 while (k < len) {
219 // a. Let elementK be the result of ? Get(O, ! ToString(k)).
220 // b. If SameValueZero(searchElement, elementK) is true, return true.
221 if (sameValueZero(o[k], searchElement)) {
222 return true;
223 }
224 // c. Increase k by 1.
225 k++;
226 }
227
228 // 8. Return false
229 return false;
230 }
231 });
232}
233
234export function findMainSourceFolder(files: string[]) {
235 let mainFolder = '';
236 let mainFolderCount = 0;
237 let rawFolders = files.map(filepath => {
238 let shortPath = filepath.replace(process.cwd() + path.sep, '');
239 return path.dirname(shortPath);
240 });
241 let folders = {};
242 rawFolders = _.uniq(rawFolders);
243
244 for (let i = 0; i < rawFolders.length; i++) {
245 let sep = rawFolders[i].split(path.sep);
246 sep.forEach(folder => {
247 if (folders[folder]) {
248 folders[folder] += 1;
249 } else {
250 folders[folder] = 1;
251 }
252 });
253 }
254 for (let f in folders) {
255 if (folders[f] > mainFolderCount) {
256 mainFolderCount = folders[f];
257 mainFolder = f;
258 }
259 }
260 return mainFolder;
261}
262
263// Create a compilerHost object to allow the compiler to read and write files
264export function compilerHost(transpileOptions: any): ts.CompilerHost {
265 const inputFileName =
266 transpileOptions.fileName || (transpileOptions.jsx ? 'module.tsx' : 'module.ts');
267
268 const toReturn: ts.CompilerHost = {
269 getSourceFile: (fileName: string) => {
270 if (fileName.lastIndexOf('.ts') !== -1 || fileName.lastIndexOf('.js') !== -1) {
271 if (fileName === 'lib.d.ts') {
272 return undefined;
273 }
274 if (fileName.substr(-5) === '.d.ts') {
275 return undefined;
276 }
277
278 if (path.isAbsolute(fileName) === false) {
279 fileName = path.join(transpileOptions.tsconfigDirectory, fileName);
280 }
281 if (!fs.existsSync(fileName)) {
282 return undefined;
283 }
284
285 let libSource = '';
286
287 try {
288 libSource = fs.readFileSync(fileName).toString();
289
290 if (hasBom(libSource)) {
291 libSource = stripBom(libSource);
292 }
293 } catch (e) {
294 logger.debug(e, fileName);
295 }
296
297 return ts.createSourceFile(fileName, libSource, transpileOptions.target, false);
298 }
299 return undefined;
300 },
301 writeFile: (name, text) => {},
302 getDefaultLibFileName: () => 'lib.d.ts',
303 useCaseSensitiveFileNames: () => false,
304 getCanonicalFileName: fileName => fileName,
305 getCurrentDirectory: () => '',
306 getNewLine: () => '\n',
307 fileExists: (fileName): boolean => fileName === inputFileName,
308 readFile: () => '',
309 directoryExists: () => true,
310 getDirectories: () => []
311 };
312
313 return toReturn;
314}
315
316export function detectIndent(str, count): string {
317 let stripIndent = (stripedString: string) => {
318 const match = stripedString.match(/^[ \t]*(?=\S)/gm);
319
320 if (!match) {
321 return stripedString;
322 }
323
324 // TODO: use spread operator when targeting Node.js 6
325 const indent = Math.min.apply(
326 Math,
327 match.map(x => x.length)
328 ); // eslint-disable-line
329 const re = new RegExp(`^[ \\t]{${indent}}`, 'gm');
330
331 return indent > 0 ? stripedString.replace(re, '') : stripedString;
332 };
333
334 let repeating = (n, repeatString) => {
335 repeatString = repeatString === undefined ? ' ' : repeatString;
336
337 if (typeof repeatString !== 'string') {
338 throw new TypeError(
339 `Expected \`input\` to be a \`string\`, got \`${typeof repeatString}\``
340 );
341 }
342
343 if (n < 0) {
344 throw new TypeError(`Expected \`count\` to be a positive finite number, got \`${n}\``);
345 }
346
347 let ret = '';
348
349 do {
350 if (n & 1) {
351 ret += repeatString;
352 }
353
354 repeatString += repeatString;
355 } while ((n >>= 1));
356
357 return ret;
358 };
359
360 let indentString = (indentedString, indentCount) => {
361 let indent = ' ';
362 indentCount = indentCount === undefined ? 1 : indentCount;
363
364 if (typeof indentedString !== 'string') {
365 throw new TypeError(
366 `Expected \`input\` to be a \`string\`, got \`${typeof indentedString}\``
367 );
368 }
369
370 if (typeof indentCount !== 'number') {
371 throw new TypeError(
372 `Expected \`count\` to be a \`number\`, got \`${typeof indentCount}\``
373 );
374 }
375
376 if (typeof indent !== 'string') {
377 throw new TypeError(`Expected \`indent\` to be a \`string\`, got \`${typeof indent}\``);
378 }
379
380 if (indentCount === 0) {
381 return indentedString;
382 }
383
384 indent = indentCount > 1 ? repeating(indentCount, indent) : indent;
385
386 return indentedString.replace(/^(?!\s*$)/gm, indent);
387 };
388
389 return indentString(stripIndent(str), count || 0);
390}
391
392const IGNORED_DIRECTORIES = ['.git', 'node_modules'];
393
394export function ignoreDirectory(dir: string): boolean {
395 let base = path.basename(dir);
396
397 if (IGNORED_DIRECTORIES.includes(base)) return true;
398
399 try {
400 fs.accessSync(dir, fs.constants.W_OK);
401 } catch (err) {
402 logger.warn('Ignoring inaccessible folder', dir);
403 return true;
404 }
405
406 return false;
407}