index.mjs 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242
  1. import * as _acorn from "acorn";
  2. const leftCurlyBrace = "{".charCodeAt(0);
  3. const space = " ".charCodeAt(0);
  4. const keyword = "assert";
  5. const FUNC_STATEMENT = 1, FUNC_HANGING_STATEMENT = 2, FUNC_NULLABLE_ID = 4
  6. export function importAssertions(Parser) {
  7. // Use supplied version acorn version if present, to avoid
  8. // reference mismatches due to different acorn versions. This
  9. // allows this plugin to be used with Rollup which supplies
  10. // its own internal version of acorn and thereby sidesteps
  11. // the package manager.
  12. const acorn = Parser.acorn || _acorn;
  13. const { tokTypes: tt, TokenType } = acorn;
  14. return class extends Parser {
  15. constructor(...args) {
  16. super(...args);
  17. this.assertToken = new TokenType(keyword);
  18. }
  19. _codeAt(i) {
  20. return this.input.charCodeAt(i);
  21. }
  22. _eat(t) {
  23. if (this.type !== t) {
  24. this.unexpected();
  25. }
  26. this.next();
  27. }
  28. readToken(code) {
  29. let i = 0;
  30. for (; i < keyword.length; i++) {
  31. if (this._codeAt(this.pos + i) !== keyword.charCodeAt(i)) {
  32. return super.readToken(code);
  33. }
  34. }
  35. // ensure that the keyword is at the correct location
  36. // ie `assert{...` or `assert {...`
  37. for (;; i++) {
  38. if (this._codeAt(this.pos + i) === leftCurlyBrace) {
  39. // Found '{'
  40. break;
  41. } else if (this._codeAt(this.pos + i) === space) {
  42. // white space is allowed between `assert` and `{`, so continue.
  43. continue;
  44. } else {
  45. return super.readToken(code);
  46. }
  47. }
  48. // If we're inside a dynamic import expression we'll parse
  49. // the `assert` keyword as a standard object property name
  50. // ie `import(""./foo.json", { assert: { type: "json" } })`
  51. if (this.type.label === "{") {
  52. return super.readToken(code);
  53. }
  54. this.pos += keyword.length;
  55. return this.finishToken(this.assertToken);
  56. }
  57. parseDynamicImport(node) {
  58. this.next(); // skip `(`
  59. // Parse node.source.
  60. node.source = this.parseMaybeAssign();
  61. if (this.eat(tt.comma)) {
  62. const obj = this.parseObj(false);
  63. node.arguments = [obj];
  64. }
  65. this._eat(tt.parenR);
  66. return this.finishNode(node, "ImportExpression");
  67. }
  68. // ported from acorn/src/statement.js pp.parseExport
  69. parseExport(node, exports) {
  70. this.next();
  71. // export * from '...'
  72. if (this.eat(tt.star)) {
  73. if (this.options.ecmaVersion >= 11) {
  74. if (this.eatContextual("as")) {
  75. node.exported = this.parseIdent(true);
  76. this.checkExport(exports, node.exported.name, this.lastTokStart);
  77. } else {
  78. node.exported = null;
  79. }
  80. }
  81. this.expectContextual("from");
  82. if (this.type !== tt.string) { this.unexpected(); }
  83. node.source = this.parseExprAtom();
  84. if (this.type === this.assertToken) {
  85. this.next();
  86. const assertions = this.parseImportAssertions();
  87. if (assertions) {
  88. node.assertions = assertions;
  89. }
  90. }
  91. this.semicolon();
  92. return this.finishNode(node, "ExportAllDeclaration")
  93. }
  94. if (this.eat(tt._default)) { // export default ...
  95. this.checkExport(exports, "default", this.lastTokStart);
  96. var isAsync;
  97. if (this.type === tt._function || (isAsync = this.isAsyncFunction())) {
  98. var fNode = this.startNode();
  99. this.next();
  100. if (isAsync) { this.next(); }
  101. node.declaration = this.parseFunction(fNode, FUNC_STATEMENT | FUNC_NULLABLE_ID, false, isAsync);
  102. } else if (this.type === tt._class) {
  103. var cNode = this.startNode();
  104. node.declaration = this.parseClass(cNode, "nullableID");
  105. } else {
  106. node.declaration = this.parseMaybeAssign();
  107. this.semicolon();
  108. }
  109. return this.finishNode(node, "ExportDefaultDeclaration")
  110. }
  111. // export var|const|let|function|class ...
  112. if (this.shouldParseExportStatement()) {
  113. node.declaration = this.parseStatement(null);
  114. if (node.declaration.type === "VariableDeclaration")
  115. { this.checkVariableExport(exports, node.declaration.declarations); }
  116. else
  117. { this.checkExport(exports, node.declaration.id.name, node.declaration.id.start); }
  118. node.specifiers = [];
  119. node.source = null;
  120. } else { // export { x, y as z } [from '...']
  121. node.declaration = null;
  122. node.specifiers = this.parseExportSpecifiers(exports);
  123. if (this.eatContextual("from")) {
  124. if (this.type !== tt.string) { this.unexpected(); }
  125. node.source = this.parseExprAtom();
  126. if (this.type === this.assertToken) {
  127. this.next();
  128. const assertions = this.parseImportAssertions();
  129. if (assertions) {
  130. node.assertions = assertions;
  131. }
  132. }
  133. } else {
  134. for (var i = 0, list = node.specifiers; i < list.length; i += 1) {
  135. // check for keywords used as local names
  136. var spec = list[i];
  137. this.checkUnreserved(spec.local);
  138. // check if export is defined
  139. this.checkLocalExport(spec.local);
  140. }
  141. node.source = null;
  142. }
  143. this.semicolon();
  144. }
  145. return this.finishNode(node, "ExportNamedDeclaration")
  146. }
  147. parseImport(node) {
  148. this.next();
  149. // import '...'
  150. if (this.type === tt.string) {
  151. node.specifiers = [];
  152. node.source = this.parseExprAtom();
  153. } else {
  154. node.specifiers = this.parseImportSpecifiers();
  155. this.expectContextual("from");
  156. node.source =
  157. this.type === tt.string ? this.parseExprAtom() : this.unexpected();
  158. }
  159. if (this.type === this.assertToken) {
  160. this.next();
  161. const assertions = this.parseImportAssertions();
  162. if (assertions) {
  163. node.assertions = assertions;
  164. }
  165. }
  166. this.semicolon();
  167. return this.finishNode(node, "ImportDeclaration");
  168. }
  169. parseImportAssertions() {
  170. this._eat(tt.braceL);
  171. const attrs = this.parseAssertEntries();
  172. this._eat(tt.braceR);
  173. return attrs;
  174. }
  175. parseAssertEntries() {
  176. const attrs = [];
  177. const attrNames = new Set();
  178. do {
  179. if (this.type === tt.braceR) {
  180. break;
  181. }
  182. const node = this.startNode();
  183. // parse AssertionKey : IdentifierName, StringLiteral
  184. let assertionKeyNode;
  185. if (this.type === tt.string) {
  186. assertionKeyNode = this.parseLiteral(this.value);
  187. } else {
  188. assertionKeyNode = this.parseIdent(true);
  189. }
  190. this.next();
  191. node.key = assertionKeyNode;
  192. // check if we already have an entry for an attribute
  193. // if a duplicate entry is found, throw an error
  194. // for now this logic will come into play only when someone declares `type` twice
  195. if (attrNames.has(node.key.name)) {
  196. this.raise(this.pos, "Duplicated key in assertions");
  197. }
  198. attrNames.add(node.key.name);
  199. if (this.type !== tt.string) {
  200. this.raise(
  201. this.pos,
  202. "Only string is supported as an assertion value"
  203. );
  204. }
  205. node.value = this.parseLiteral(this.value);
  206. attrs.push(this.finishNode(node, "ImportAttribute"));
  207. } while (this.eat(tt.comma));
  208. return attrs;
  209. }
  210. };
  211. }