DescriptionFileUtils.js 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197
  1. /*
  2. MIT License http://www.opensource.org/licenses/mit-license.php
  3. Author Tobias Koppers @sokra
  4. */
  5. "use strict";
  6. const forEachBail = require("./forEachBail");
  7. /** @typedef {import("./Resolver")} Resolver */
  8. /** @typedef {import("./Resolver").JsonObject} JsonObject */
  9. /** @typedef {import("./Resolver").JsonValue} JsonValue */
  10. /** @typedef {import("./Resolver").ResolveContext} ResolveContext */
  11. /** @typedef {import("./Resolver").ResolveRequest} ResolveRequest */
  12. /**
  13. * @typedef {object} DescriptionFileInfo
  14. * @property {JsonObject=} content content
  15. * @property {string} path path
  16. * @property {string} directory directory
  17. */
  18. /**
  19. * @callback ErrorFirstCallback
  20. * @param {Error|null=} error
  21. * @param {DescriptionFileInfo=} result
  22. */
  23. /**
  24. * @typedef {object} Result
  25. * @property {string} path path to description file
  26. * @property {string} directory directory of description file
  27. * @property {JsonObject} content content of description file
  28. */
  29. /**
  30. * @param {string} directory directory
  31. * @returns {string|null} parent directory or null
  32. */
  33. function cdUp(directory) {
  34. if (directory === "/") return null;
  35. const i = directory.lastIndexOf("/");
  36. const j = directory.lastIndexOf("\\");
  37. const path = i < 0 ? j : j < 0 ? i : i < j ? j : i;
  38. if (path < 0) return null;
  39. return directory.slice(0, path || 1);
  40. }
  41. /**
  42. * @param {Resolver} resolver resolver
  43. * @param {string} directory directory
  44. * @param {string[]} filenames filenames
  45. * @param {DescriptionFileInfo|undefined} oldInfo oldInfo
  46. * @param {ResolveContext} resolveContext resolveContext
  47. * @param {ErrorFirstCallback} callback callback
  48. */
  49. function loadDescriptionFile(
  50. resolver,
  51. directory,
  52. filenames,
  53. oldInfo,
  54. resolveContext,
  55. callback,
  56. ) {
  57. (function findDescriptionFile() {
  58. if (oldInfo && oldInfo.directory === directory) {
  59. // We already have info for this directory and can reuse it
  60. return callback(null, oldInfo);
  61. }
  62. forEachBail(
  63. filenames,
  64. /**
  65. * @param {string} filename filename
  66. * @param {(err?: null|Error, result?: null|Result) => void} callback callback
  67. * @returns {void}
  68. */
  69. (filename, callback) => {
  70. const descriptionFilePath = resolver.join(directory, filename);
  71. /**
  72. * @param {(null | Error)=} err error
  73. * @param {JsonObject=} resolvedContent content
  74. * @returns {void}
  75. */
  76. function onJson(err, resolvedContent) {
  77. if (err) {
  78. if (resolveContext.log) {
  79. resolveContext.log(
  80. `${descriptionFilePath} (directory description file): ${err}`,
  81. );
  82. } else {
  83. err.message = `${descriptionFilePath} (directory description file): ${err}`;
  84. }
  85. return callback(err);
  86. }
  87. callback(null, {
  88. content: /** @type {JsonObject} */ (resolvedContent),
  89. directory,
  90. path: descriptionFilePath,
  91. });
  92. }
  93. if (resolver.fileSystem.readJson) {
  94. resolver.fileSystem.readJson(descriptionFilePath, (err, content) => {
  95. if (err) {
  96. if (
  97. typeof (/** @type {NodeJS.ErrnoException} */ (err).code) !==
  98. "undefined"
  99. ) {
  100. if (resolveContext.missingDependencies) {
  101. resolveContext.missingDependencies.add(descriptionFilePath);
  102. }
  103. return callback();
  104. }
  105. if (resolveContext.fileDependencies) {
  106. resolveContext.fileDependencies.add(descriptionFilePath);
  107. }
  108. return onJson(err);
  109. }
  110. if (resolveContext.fileDependencies) {
  111. resolveContext.fileDependencies.add(descriptionFilePath);
  112. }
  113. onJson(null, content);
  114. });
  115. } else {
  116. resolver.fileSystem.readFile(descriptionFilePath, (err, content) => {
  117. if (err) {
  118. if (resolveContext.missingDependencies) {
  119. resolveContext.missingDependencies.add(descriptionFilePath);
  120. }
  121. return callback();
  122. }
  123. if (resolveContext.fileDependencies) {
  124. resolveContext.fileDependencies.add(descriptionFilePath);
  125. }
  126. /** @type {JsonObject | undefined} */
  127. let json;
  128. if (content) {
  129. try {
  130. json = JSON.parse(content.toString());
  131. } catch (/** @type {unknown} */ err_) {
  132. return onJson(/** @type {Error} */ (err_));
  133. }
  134. } else {
  135. return onJson(new Error("No content in file"));
  136. }
  137. onJson(null, json);
  138. });
  139. }
  140. },
  141. /**
  142. * @param {(null | Error)=} err error
  143. * @param {(null | Result)=} result result
  144. * @returns {void}
  145. */
  146. (err, result) => {
  147. if (err) return callback(err);
  148. if (result) return callback(null, result);
  149. const dir = cdUp(directory);
  150. if (!dir) {
  151. return callback();
  152. }
  153. directory = dir;
  154. return findDescriptionFile();
  155. },
  156. );
  157. })();
  158. }
  159. /**
  160. * @param {JsonObject} content content
  161. * @param {string|string[]} field field
  162. * @returns {JsonValue | undefined} field data
  163. */
  164. function getField(content, field) {
  165. if (!content) return undefined;
  166. if (Array.isArray(field)) {
  167. /** @type {JsonValue} */
  168. let current = content;
  169. for (let j = 0; j < field.length; j++) {
  170. if (current === null || typeof current !== "object") {
  171. current = null;
  172. break;
  173. }
  174. current = /** @type {JsonObject} */ (current)[field[j]];
  175. }
  176. return current;
  177. }
  178. return content[field];
  179. }
  180. module.exports.loadDescriptionFile = loadDescriptionFile;
  181. module.exports.getField = getField;
  182. module.exports.cdUp = cdUp;