index.js 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295
  1. (function (factory) {
  2. if (typeof module === 'object' && typeof module.exports === 'object') {
  3. var v = factory(require, exports); if (v !== undefined) module.exports = v;
  4. }
  5. else if (typeof define === 'function' && define.amd) {
  6. define(["require", "exports", 'fs', 'glob', 'mkdirp', 'os', 'path', 'bluebird', 'typescript'], factory);
  7. }
  8. })(function (require, exports) {
  9. "use strict";
  10. var fs = require('fs');
  11. var glob = require('glob');
  12. var mkdirp = require('mkdirp');
  13. var os = require('os');
  14. var pathUtil = require('path');
  15. var Promise = require('bluebird');
  16. var ts = require('typescript');
  17. var filenameToMid = (function () {
  18. if (pathUtil.sep === '/') {
  19. return function (filename) {
  20. return filename;
  21. };
  22. }
  23. else {
  24. var separatorExpression_1 = new RegExp(pathUtil.sep.replace('\\', '\\\\'), 'g');
  25. return function (filename) {
  26. return filename.replace(separatorExpression_1, '/');
  27. };
  28. }
  29. })();
  30. /**
  31. * A helper function that takes TypeScript diagnostic errors and returns an error
  32. * object.
  33. * @param diagnostics The array of TypeScript Diagnostic objects
  34. */
  35. function getError(diagnostics) {
  36. var message = 'Declaration generation failed';
  37. diagnostics.forEach(function (diagnostic) {
  38. var position = diagnostic.file.getLineAndCharacterOfPosition(diagnostic.start);
  39. message +=
  40. ("\n" + diagnostic.file.fileName + "(" + (position.line + 1) + "," + (position.character + 1) + "): ") +
  41. ("error TS" + diagnostic.code + ": " + diagnostic.messageText);
  42. });
  43. var error = new Error(message);
  44. error.name = 'EmitterError';
  45. return error;
  46. }
  47. function getFilenames(baseDir, files) {
  48. return files.map(function (filename) {
  49. var resolvedFilename = pathUtil.resolve(filename);
  50. if (resolvedFilename.indexOf(baseDir) === 0) {
  51. return resolvedFilename;
  52. }
  53. return pathUtil.resolve(baseDir, filename);
  54. });
  55. }
  56. function processTree(sourceFile, replacer) {
  57. var code = '';
  58. var cursorPosition = 0;
  59. function skip(node) {
  60. cursorPosition = node.end;
  61. }
  62. function readThrough(node) {
  63. code += sourceFile.text.slice(cursorPosition, node.pos);
  64. cursorPosition = node.pos;
  65. }
  66. function visit(node) {
  67. readThrough(node);
  68. var replacement = replacer(node);
  69. if (replacement != null) {
  70. code += replacement;
  71. skip(node);
  72. }
  73. else {
  74. ts.forEachChild(node, visit);
  75. }
  76. }
  77. visit(sourceFile);
  78. code += sourceFile.text.slice(cursorPosition);
  79. return code;
  80. }
  81. /**
  82. * Load and parse a TSConfig File
  83. * @param options The dts-generator options to load config into
  84. * @param fileName The path to the file
  85. */
  86. function getTSConfig(options, fileName) {
  87. var configText = fs.readFileSync(fileName, { encoding: 'utf8' });
  88. var result = ts.parseConfigFileTextToJson(fileName, configText);
  89. if (result.error) {
  90. throw getError([result.error]);
  91. }
  92. var configObject = result.config;
  93. var configParseResult = ts.parseJsonConfigFileContent(configObject, ts.sys, pathUtil.dirname(fileName));
  94. if (configParseResult.errors && configParseResult.errors.length) {
  95. throw getError(configParseResult.errors);
  96. }
  97. options.target = configParseResult.options.target;
  98. if (configParseResult.options.outDir) {
  99. options.outDir = configParseResult.options.outDir;
  100. }
  101. if (configParseResult.options.moduleResolution) {
  102. options.moduleResolution = configParseResult.options.moduleResolution;
  103. }
  104. if (configParseResult.options.rootDir) {
  105. options.rootDir = configParseResult.options.rootDir;
  106. }
  107. options.files = configParseResult.fileNames;
  108. return options;
  109. }
  110. function isNodeKindImportDeclaration(value) {
  111. return value && value.kind === ts.SyntaxKind.ImportDeclaration;
  112. }
  113. function isNodeKindExternalModuleReference(value) {
  114. return value && value.kind === ts.SyntaxKind.ExternalModuleReference;
  115. }
  116. function isNodeKindStringLiteral(value) {
  117. return value && value.kind === ts.SyntaxKind.StringLiteral;
  118. }
  119. function isNodeKindExportDeclaration(value) {
  120. return value && value.kind === ts.SyntaxKind.ExportDeclaration;
  121. }
  122. function isNodeKindExportAssignment(value) {
  123. return value && value.kind === ts.SyntaxKind.ExportAssignment;
  124. }
  125. function generate(options) {
  126. var noop = function (message) {
  127. var optionalParams = [];
  128. for (var _i = 1; _i < arguments.length; _i++) {
  129. optionalParams[_i - 1] = arguments[_i];
  130. }
  131. };
  132. var sendMessage = options.sendMessage || noop;
  133. var verboseMessage = options.verbose ? sendMessage : noop;
  134. /* following tsc behaviour, if a project is speicified, or if no files are specified then
  135. * attempt to load tsconfig.json */
  136. if (options.project || !options.files || options.files.length === 0) {
  137. verboseMessage("project = \"" + (options.project || options.baseDir) + "\"");
  138. var tsconfigFilename = pathUtil.join(options.project || options.baseDir, 'tsconfig.json');
  139. if (fs.existsSync(tsconfigFilename)) {
  140. verboseMessage(" parsing \"" + tsconfigFilename + "\"");
  141. getTSConfig(options, tsconfigFilename);
  142. }
  143. else {
  144. sendMessage("No \"tsconfig.json\" found at \"" + tsconfigFilename + "\"!");
  145. return new Promise(function (resolve, reject) {
  146. reject(new SyntaxError('Unable to resolve configuration.'));
  147. });
  148. }
  149. }
  150. var baseDir = pathUtil.resolve(options.rootDir || options.project || options.baseDir);
  151. verboseMessage("baseDir = \"" + baseDir + "\"");
  152. var eol = options.eol || os.EOL;
  153. var nonEmptyLineStart = new RegExp(eol + '(?!' + eol + '|$)', 'g');
  154. var indent = options.indent === undefined ? '\t' : options.indent;
  155. var target = typeof options.target !== 'undefined' ? options.target : ts.ScriptTarget.Latest;
  156. verboseMessage("taget = " + target);
  157. var compilerOptions = {
  158. declaration: true,
  159. module: ts.ModuleKind.CommonJS,
  160. target: target
  161. };
  162. if (options.outDir) {
  163. verboseMessage("outDir = " + options.outDir);
  164. compilerOptions.outDir = options.outDir;
  165. }
  166. if (options.moduleResolution) {
  167. verboseMessage("moduleResolution = " + options.moduleResolution);
  168. compilerOptions.moduleResolution = options.moduleResolution;
  169. }
  170. var filenames = getFilenames(baseDir, options.files);
  171. verboseMessage('filenames:');
  172. filenames.forEach(function (name) { verboseMessage(' ' + name); });
  173. var excludesMap = {};
  174. options.exclude = options.exclude || ['node_modules/**/*.d.ts'];
  175. options.exclude && options.exclude.forEach(function (filename) {
  176. glob.sync(filename).forEach(function (globFileName) {
  177. excludesMap[filenameToMid(pathUtil.resolve(baseDir, globFileName))] = true;
  178. });
  179. });
  180. if (options.exclude) {
  181. verboseMessage('exclude:');
  182. options.exclude.forEach(function (name) { verboseMessage(' ' + name); });
  183. }
  184. mkdirp.sync(pathUtil.dirname(options.out));
  185. /* node.js typings are missing the optional mode in createWriteStream options and therefore
  186. * in TS 1.6 the strict object literal checking is throwing, therefore a hammer to the nut */
  187. var output = fs.createWriteStream(options.out, { mode: parseInt('644', 8) });
  188. var host = ts.createCompilerHost(compilerOptions);
  189. var program = ts.createProgram(filenames, compilerOptions, host);
  190. function writeFile(filename, data, writeByteOrderMark) {
  191. // Compiler is emitting the non-declaration file, which we do not care about
  192. if (filename.slice(-5) !== '.d.ts') {
  193. return;
  194. }
  195. writeDeclaration(ts.createSourceFile(filename, data, target, true));
  196. }
  197. return new Promise(function (resolve, reject) {
  198. output.on('close', function () { resolve(undefined); });
  199. output.on('error', reject);
  200. if (options.externs) {
  201. options.externs.forEach(function (path) {
  202. sendMessage("Writing external dependency " + path);
  203. output.write(("/// <reference path=\"" + path + "\" />") + eol);
  204. });
  205. }
  206. sendMessage('processing:');
  207. var mainExportDeclaration = false;
  208. var mainExportAssignment = false;
  209. program.getSourceFiles().some(function (sourceFile) {
  210. // Source file is a default library, or other dependency from another project, that should not be included in
  211. // our bundled output
  212. if (pathUtil.normalize(sourceFile.fileName).indexOf(baseDir) !== 0) {
  213. return;
  214. }
  215. if (excludesMap[filenameToMid(pathUtil.normalize(sourceFile.fileName))]) {
  216. return;
  217. }
  218. sendMessage(" " + sourceFile.fileName);
  219. // Source file is already a declaration file so should does not need to be pre-processed by the emitter
  220. if (sourceFile.fileName.slice(-5) === '.d.ts') {
  221. writeDeclaration(sourceFile);
  222. return;
  223. }
  224. // We can optionally output the main module if there's something to export.
  225. if (options.main && options.main === (options.name + filenameToMid(sourceFile.fileName.slice(baseDir.length, -3)))) {
  226. ts.forEachChild(sourceFile, function (node) {
  227. mainExportDeclaration = mainExportDeclaration || isNodeKindExportDeclaration(node);
  228. mainExportAssignment = mainExportAssignment || isNodeKindExportAssignment(node);
  229. });
  230. }
  231. var emitOutput = program.emit(sourceFile, writeFile);
  232. if (emitOutput.emitSkipped || emitOutput.diagnostics.length > 0) {
  233. reject(getError(emitOutput.diagnostics
  234. .concat(program.getSemanticDiagnostics(sourceFile))
  235. .concat(program.getSyntacticDiagnostics(sourceFile))
  236. .concat(program.getDeclarationDiagnostics(sourceFile))));
  237. return true;
  238. }
  239. });
  240. if (options.main && options.name) {
  241. output.write(("declare module '" + options.name + "' {") + eol + indent);
  242. if (compilerOptions.target >= ts.ScriptTarget.ES6) {
  243. if (mainExportAssignment) {
  244. output.write(("export {default} from '" + options.main + "';") + eol + indent);
  245. }
  246. if (mainExportDeclaration) {
  247. output.write(("export * from '" + options.main + "';") + eol);
  248. }
  249. }
  250. else {
  251. output.write(("import main = require('" + options.main + "');") + eol + indent);
  252. output.write('export = main;' + eol);
  253. }
  254. output.write('}' + eol);
  255. sendMessage("Aliased main module " + options.name + " to " + options.main);
  256. }
  257. sendMessage("output to \"" + options.out + "\"");
  258. output.end();
  259. });
  260. function writeDeclaration(declarationFile) {
  261. var filename = declarationFile.fileName;
  262. var sourceModuleId = options.name ? options.name + filenameToMid(filename.slice(baseDir.length, -5)) : filenameToMid(filename.slice(baseDir.length + 1, -5));
  263. /* For some reason, SourceFile.externalModuleIndicator is missing from 1.6+, so having
  264. * to use a sledgehammer on the nut */
  265. if (declarationFile.externalModuleIndicator) {
  266. output.write('declare module \'' + sourceModuleId + '\' {' + eol + indent);
  267. var content = processTree(declarationFile, function (node) {
  268. if (isNodeKindExternalModuleReference(node)) {
  269. var expression = node.expression;
  270. if (expression.text.charAt(0) === '.') {
  271. return ' require(\'' + filenameToMid(pathUtil.join(pathUtil.dirname(sourceModuleId), expression.text)) + '\')';
  272. }
  273. }
  274. else if (node.kind === ts.SyntaxKind.DeclareKeyword) {
  275. return '';
  276. }
  277. else if (isNodeKindStringLiteral(node) && node.parent &&
  278. (isNodeKindExportDeclaration(node.parent) || isNodeKindImportDeclaration(node.parent))) {
  279. var text = node.text;
  280. if (text.charAt(0) === '.') {
  281. return " '" + filenameToMid(pathUtil.join(pathUtil.dirname(sourceModuleId), text)) + "'";
  282. }
  283. }
  284. });
  285. output.write(content.replace(nonEmptyLineStart, '$&' + indent));
  286. output.write(eol + '}' + eol);
  287. }
  288. else {
  289. output.write(declarationFile.text);
  290. }
  291. }
  292. }
  293. Object.defineProperty(exports, "__esModule", { value: true });
  294. exports.default = generate;
  295. });