index.js 12 KB

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