features.js 6.15 KB
Newer Older
Rosanny Sihombing's avatar
Rosanny Sihombing committed
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
"use strict";

Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.FEATURES = void 0;
exports.enableFeature = enableFeature;
exports.isLoose = isLoose;
exports.shouldTransform = shouldTransform;

var _decorators = require("./decorators");

const FEATURES = Object.freeze({
  fields: 1 << 1,
  privateMethods: 1 << 2,
  decorators: 1 << 3,
  privateIn: 1 << 4,
  staticBlocks: 1 << 5
});
exports.FEATURES = FEATURES;
const featuresSameLoose = new Map([[FEATURES.fields, "@babel/plugin-proposal-class-properties"], [FEATURES.privateMethods, "@babel/plugin-proposal-private-methods"], [FEATURES.privateIn, "@babel/plugin-proposal-private-property-in-object"]]);
const featuresKey = "@babel/plugin-class-features/featuresKey";
const looseKey = "@babel/plugin-class-features/looseKey";
const looseLowPriorityKey = "@babel/plugin-class-features/looseLowPriorityKey/#__internal__@babel/preset-env__please-overwrite-loose-instead-of-throwing";

function enableFeature(file, feature, loose) {
  if (!hasFeature(file, feature) || canIgnoreLoose(file, feature)) {
    file.set(featuresKey, file.get(featuresKey) | feature);

    if (loose === "#__internal__@babel/preset-env__prefer-true-but-false-is-ok-if-it-prevents-an-error") {
      setLoose(file, feature, true);
      file.set(looseLowPriorityKey, file.get(looseLowPriorityKey) | feature);
    } else if (loose === "#__internal__@babel/preset-env__prefer-false-but-true-is-ok-if-it-prevents-an-error") {
      setLoose(file, feature, false);
      file.set(looseLowPriorityKey, file.get(looseLowPriorityKey) | feature);
    } else {
      setLoose(file, feature, loose);
    }
  }

  let resolvedLoose;
  let higherPriorityPluginName;

  for (const [mask, name] of featuresSameLoose) {
    if (!hasFeature(file, mask)) continue;
    const loose = isLoose(file, mask);

    if (canIgnoreLoose(file, mask)) {
      continue;
    } else if (resolvedLoose === !loose) {
      throw new Error("'loose' mode configuration must be the same for @babel/plugin-proposal-class-properties, " + "@babel/plugin-proposal-private-methods and " + "@babel/plugin-proposal-private-property-in-object (when they are enabled).");
    } else {
      resolvedLoose = loose;
      higherPriorityPluginName = name;
    }
  }

  if (resolvedLoose !== undefined) {
    for (const [mask, name] of featuresSameLoose) {
      if (hasFeature(file, mask) && isLoose(file, mask) !== resolvedLoose) {
        setLoose(file, mask, resolvedLoose);
        console.warn(`Though the "loose" option was set to "${!resolvedLoose}" in your @babel/preset-env ` + `config, it will not be used for ${name} since the "loose" mode option was set to ` + `"${resolvedLoose}" for ${higherPriorityPluginName}.\nThe "loose" option must be the ` + `same for @babel/plugin-proposal-class-properties, @babel/plugin-proposal-private-methods ` + `and @babel/plugin-proposal-private-property-in-object (when they are enabled): you can ` + `silence this warning by explicitly adding\n` + `\t["${name}", { "loose": ${resolvedLoose} }]\n` + `to the "plugins" section of your Babel config.`);
      }
    }
  }
}

function hasFeature(file, feature) {
  return !!(file.get(featuresKey) & feature);
}

function isLoose(file, feature) {
  return !!(file.get(looseKey) & feature);
}

function setLoose(file, feature, loose) {
  if (loose) file.set(looseKey, file.get(looseKey) | feature);else file.set(looseKey, file.get(looseKey) & ~feature);
  file.set(looseLowPriorityKey, file.get(looseLowPriorityKey) & ~feature);
}

function canIgnoreLoose(file, feature) {
  return !!(file.get(looseLowPriorityKey) & feature);
}

function shouldTransform(path, file) {
  let decoratorPath = null;
  let publicFieldPath = null;
  let privateFieldPath = null;
  let privateMethodPath = null;
  let staticBlockPath = null;

  if ((0, _decorators.hasOwnDecorators)(path.node)) {
    decoratorPath = path.get("decorators.0");
  }

  for (const el of path.get("body.body")) {
    if (!decoratorPath && (0, _decorators.hasOwnDecorators)(el.node)) {
      decoratorPath = el.get("decorators.0");
    }

    if (!publicFieldPath && el.isClassProperty()) {
      publicFieldPath = el;
    }

    if (!privateFieldPath && el.isClassPrivateProperty()) {
      privateFieldPath = el;
    }

    if (!privateMethodPath && el.isClassPrivateMethod != null && el.isClassPrivateMethod()) {
      privateMethodPath = el;
    }

    if (!staticBlockPath && el.isStaticBlock != null && el.isStaticBlock()) {
      staticBlockPath = el;
    }
  }

  if (decoratorPath && privateFieldPath) {
    throw privateFieldPath.buildCodeFrameError("Private fields in decorated classes are not supported yet.");
  }

  if (decoratorPath && privateMethodPath) {
    throw privateMethodPath.buildCodeFrameError("Private methods in decorated classes are not supported yet.");
  }

  if (decoratorPath && !hasFeature(file, FEATURES.decorators)) {
    throw path.buildCodeFrameError("Decorators are not enabled." + "\nIf you are using " + '["@babel/plugin-proposal-decorators", { "version": "legacy" }], ' + 'make sure it comes *before* "@babel/plugin-proposal-class-properties" ' + "and enable loose mode, like so:\n" + '\t["@babel/plugin-proposal-decorators", { "version": "legacy" }]\n' + '\t["@babel/plugin-proposal-class-properties", { "loose": true }]');
  }

  if (privateMethodPath && !hasFeature(file, FEATURES.privateMethods)) {
    throw privateMethodPath.buildCodeFrameError("Class private methods are not enabled. " + "Please add `@babel/plugin-proposal-private-methods` to your configuration.");
  }

  if ((publicFieldPath || privateFieldPath) && !hasFeature(file, FEATURES.fields) && !hasFeature(file, FEATURES.privateMethods)) {
    throw path.buildCodeFrameError("Class fields are not enabled. " + "Please add `@babel/plugin-proposal-class-properties` to your configuration.");
  }

  if (staticBlockPath && !hasFeature(file, FEATURES.staticBlocks)) {
    throw path.buildCodeFrameError("Static class blocks are not enabled. " + "Please add `@babel/plugin-proposal-class-static-block` to your configuration.");
  }

  if (decoratorPath || privateMethodPath || staticBlockPath) {
    return true;
  }

  if ((publicFieldPath || privateFieldPath) && hasFeature(file, FEATURES.fields)) {
    return true;
  }

  return false;
}