plugin.cjs 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125
  1. /**
  2. * @param {{ defer?: boolean, source?: boolean }} options
  3. * @param {typeof import("acorn").Parser} Parser
  4. * @param {typeof import("acorn").tokTypes} acorn
  5. */
  6. exports.plugin = function acornImportPhase(options, Parser, tt) {
  7. return class extends Parser {
  8. parseImport(node) {
  9. this._phase = null;
  10. const result = super.parseImport(node);
  11. if (this._phase) {
  12. node.phase = this._phase;
  13. }
  14. return result;
  15. }
  16. parseImportSpecifiers() {
  17. let phase =
  18. options.defer !== false && this.isContextual("defer")
  19. ? "defer"
  20. : options.source !== false && this.isContextual("source")
  21. ? "source"
  22. : null;
  23. if (!phase) return super.parseImportSpecifiers();
  24. const phaseId = this.parseIdent();
  25. if (this.isContextual("from") || this.type === tt.comma) {
  26. const defaultSpecifier = this.startNodeAt(phaseId.start, phaseId.loc && phaseId.loc.start);
  27. defaultSpecifier.local = phaseId;
  28. this.checkLValSimple(phaseId, /* BIND_LEXICAL */ 2);
  29. const nodes = [this.finishNode(defaultSpecifier, "ImportDefaultSpecifier")];
  30. if (this.eat(tt.comma)) {
  31. if (this.type !== tt.star && this.type !== tt.braceL) {
  32. this.unexpected();
  33. }
  34. nodes.push(...super.parseImportSpecifiers());
  35. }
  36. return nodes;
  37. }
  38. this._phase = phase;
  39. if (phase === "defer") {
  40. if (this.type !== tt.star) {
  41. this.raiseRecoverable(
  42. phaseId.start,
  43. "'import defer' can only be used with namespace imports ('import defer * as identifierName from ...')."
  44. );
  45. }
  46. } else if (phase === "source") {
  47. if (this.type !== tt.name) {
  48. this.raiseRecoverable(
  49. phaseId.start,
  50. "'import source' can only be used with direct identifier specifier imports."
  51. );
  52. }
  53. }
  54. const specifiers = super.parseImportSpecifiers();
  55. if (phase === "source" && specifiers.some(s => s.type !== "ImportDefaultSpecifier")) {
  56. this.raiseRecoverable(
  57. phaseId.start,
  58. `'import source' can only be used with direct identifier specifier imports ('import source identifierName from ...').`
  59. );
  60. }
  61. return specifiers;
  62. }
  63. parseExprImport(forNew) {
  64. const node = super.parseExprImport(forNew);
  65. if (node.type === "MetaProperty" && (node.property.name === "defer" || node.property.name === "source")) {
  66. if (this.type === tt.parenL) {
  67. const dynImport = this.parseDynamicImport(this.startNodeAt(node.start, node.loc && node.loc.start));
  68. dynImport.phase = node.property.name;
  69. return dynImport;
  70. } else {
  71. this.raiseRecoverable(
  72. node.start,
  73. `'import.${node.property.name}' can only be used in a dynamic import.`
  74. );
  75. }
  76. }
  77. return node;
  78. }
  79. parseImportMeta(node) {
  80. this.next();
  81. var containsEsc = this.containsEsc;
  82. node.property = this.parseIdent(true);
  83. const { name } = node.property;
  84. if (name !== "meta" && name !== "defer" && name !== "source") {
  85. this.raiseRecoverable(
  86. node.property.start,
  87. "The only valid meta property for import is 'import.meta'"
  88. );
  89. }
  90. if (containsEsc) {
  91. this.raiseRecoverable(
  92. node.start,
  93. `'import.${name}' must not contain escaped characters`
  94. );
  95. }
  96. if (
  97. name === "meta" &&
  98. this.options.sourceType !== "module" &&
  99. !this.options.allowImportExportEverywhere
  100. ) {
  101. this.raiseRecoverable(
  102. node.start,
  103. "Cannot use 'import.meta' outside a module"
  104. );
  105. }
  106. return this.finishNode(node, "MetaProperty");
  107. }
  108. };
  109. };