Skip to content

Parsing errors for private class attributes after upgrading Babel from 7.26.10 to 7.27.1 #17290

@Tobbe

Description

@Tobbe

Sorry for a somewhat lazy bug report. I just wanted to get this out there in case someone has a quick fix or suggestion. I'll keep chipping away at this myself in the meantime

How are you using Babel?

@babel/register

Input code

// Introspects a given model and returns its attributes and figures out what
// other models it belongs to or has many of.

export default class Reflection {
  #hasMany = null
  #belongsTo = null
  #attributes = null

  constructor(model) {
    this.model = model
  }

  get attributes() {
    if (!this.#attributes) {
      this.#parseAttributes()
    }

    return this.#attributes
  }

  get belongsTo() {
    if (!this.#belongsTo) {
      this.#parseBelongsTo()
    }

    return this.#belongsTo
  }

  get hasMany() {
    if (!this.#hasMany) {
      this.#parseHasMany()
    }

    return this.#hasMany
  }

  // Finds the schema for a single model
  #schemaForModel(name = this.model.name) {
    return this.model.schema.models.find((model) => model.name === name)
  }

  #parseHasMany() {
    const selfSchema = this.#schemaForModel()
    this.#hasMany = {}

    selfSchema?.fields?.forEach((field) => {
      if (field.isList) {
        // get other side of relationship to determine foreign key name
        const otherSchema = this.#schemaForModel(field.type)
        const belongsTo = otherSchema.fields.find(
          (field) => field.type === this.model.name,
        )

        this.#hasMany[field.name] = {
          modelName: field.type,
          referenceName: belongsTo.name,
          // a null foreign key denotes an implicit many-to-many relationship
          foreignKey: belongsTo.relationFromFields[0] || null,
          primaryKey: belongsTo.relationToFields[0],
        }
      }
    })
  }

  #parseBelongsTo() {
    const selfSchema = this.#schemaForModel()
    this.#belongsTo = {}

    selfSchema?.fields?.forEach((field) => {
      if (field.relationFromFields?.length) {
        this.#belongsTo[field.name] = {
          modelName: field.type,
          foreignKey: field.relationFromFields[0],
          primaryKey: field.relationToFields[0],
        }
      }
    })
  }

  #parseAttributes() {
    const selfSchema = this.#schemaForModel()
    this.#attributes = {}

    if (!this.#hasMany) {
      this.#parseHasMany()
    }
    if (!this.belongsTo) {
      this.#parseBelongsTo()
    }

    selfSchema?.fields?.forEach((field) => {
      const { name, ...props } = field
      if (
        !Object.keys(this.#hasMany).includes(name) &&
        !Object.keys(this.#belongsTo).includes(name)
      ) {
        this.#attributes[name] = props
      }
    })
  }
}

Configuration file name

babel.config.js

Configuration

const path = require('path')

const packageJSON = require(path.join(__dirname, 'package.json'))

const TARGETS_NODE = '20.10'

// Run `npx browserslist "defaults"` to see a list of target browsers.
const TARGETS_BROWSERS = ['defaults']

// Warning! Recommended to specify used minor core-js version, like corejs: '3.6',
// instead of corejs: '3', since with '3' it will not be injected modules
// which were added in minor core-js releases.
// https://github.com/zloirock/core-js/blob/master/README.md#babelpreset-env
const CORE_JS_VERSION = packageJSON.devDependencies['core-js']
  .split('.')
  .slice(0, 2)
  .join('.') // Produces: 3.12, instead of 3.12.1

// We use the recommended babel configuration for monorepos, which is a base directory
// `babel.config.js` file, but then use a per-project `.babelrc.js` file.
// Learn more: https://babeljs.io/docs/en/config-files#monorepos

/** @type {import('@babel/core').TransformOptions} */
module.exports = {
  presets: [
    [
      '@babel/preset-env',
      {
        targets: { node: TARGETS_NODE },
        useBuiltIns: 'usage',
        corejs: {
          version: CORE_JS_VERSION,
          // List of supported proposals: https://github.com/zloirock/core-js/blob/master/docs/2019-03-19-core-js-3-babel-and-a-look-into-the-future.md#ecmascript-proposals
          proposals: true,
        },
        exclude: [
          'es.error.cause',
          process.env.NODE_ENV !== 'test' && 'proposal-dynamic-import',
        ].filter(Boolean),
      },
    ],
    ['@babel/preset-react', { runtime: 'automatic' }],
    /**
     *  TODO(pc): w/ '@babel/plugin-transform-typescript' in plugins now, is '@babel/typescript' preset still needed?
     *
     * - Plugins run before Presets.
     * - Plugin ordering is first to last.
     * - Preset ordering is reversed (last to first).
     *
     * https://babeljs.io/docs/en/plugins/#plugin-ordering
     */
    '@babel/typescript',
  ],
  plugins: [
    /**
     * NOTE
     * Needed for react@18
     *
     * ```
     * ✖  @redmix/router:build
     *  SyntaxError: /code/redwood/packages/router/src/location.tsx: TypeScript 'declare' fields must first be transformed by @babel/plugin-transform-typescript.
     *  If you have already enabled that plugin (or '@babel/preset-typescript'), make sure that it runs before any plugin related to additional class features:
     *   - @babel/plugin-proposal-class-properties
     *   - @babel/plugin-proposal-private-methods
     *   - @babel/plugin-proposal-decorators
     *    25 |   // When prerendering, there might be more than one level of location
     *    26 |   // providers. Use the values from the one above.
     *  > 27 |   declare context: React.ContextType<typeof LocationContext>
     *       |   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
     *    28 |   HISTORY_LISTENER_ID: string | undefined = undefined
     *    29 |
     *    30 |   state = {
     * ```
     */
    [
      '@babel/plugin-transform-typescript',
      {
        allowDeclareFields: true,
        /** needed in order build `packages/web/dist/entry/index.js` */
        isTSX: true,
        allExtensions: true,
      },
    ],
    /**
     * NOTE
     * Experimental decorators are used in `@redmix/structure`.
     * https://github.com/tc39/proposal-decorators
     **/
    ['@babel/plugin-proposal-decorators', { legacy: true }],
    // The "loose" option must be the same for all three of these plugins.
    ['@babel/plugin-transform-class-properties', { loose: true }],
    ['@babel/plugin-transform-private-methods', { loose: true }],
    ['@babel/plugin-transform-private-property-in-object', { loose: true }],
    [
      '@babel/plugin-transform-runtime',
      {
        // https://babeljs.io/docs/en/babel-plugin-transform-runtime/#core-js-aliasing
        // Setting the version here also requires `@babel/runtime-corejs3`
        corejs: { version: 3, proposals: true },
        // https://babeljs.io/docs/en/babel-plugin-transform-runtime/#version
        // Transform-runtime assumes that @babel/runtime@7.0.0 is installed.
        // Specifying the version can result in a smaller bundle size.
        version: packageJSON.devDependencies['@babel/runtime-corejs3'],
      },
    ],
    '@babel/plugin-syntax-import-attributes',
  ],
  overrides: [
    // ** WEB PACKAGES **
    {
      test: [
        './packages/auth/',
        './packages/router',
        './packages/forms/',
        './packages/web/',
      ],
      presets: [
        [
          '@babel/preset-env',
          {
            targets: {
              browsers: TARGETS_BROWSERS,
            },
          },
        ],
      ],
      plugins: [
        [
          'babel-plugin-auto-import',
          {
            declarations: [
              {
                // import { React } from 'react'
                default: 'React',
                path: 'react',
              },
            ],
          },
        ],
        // normally provided through preset-env detecting TARGET_BROWSER
        // but webpack 4 has an issue with this
        // see https://github.com/PaulLeCam/react-leaflet/issues/883
        ['@babel/plugin-transform-nullish-coalescing-operator'],
      ],
    },
  ],
  // Ignore test directories when we're not testing
  // Note: No matter what you try to do here, babel will still include
  // snapshot files in the dist output.
  // See https://github.com/babel/babel/issues/11394
  ignore:
    process.env.NODE_ENV === 'test'
      ? []
      : [/\.test\.(js|ts)/, '**/__tests__', '**/__mocks__', '**/__snapshots__'],
}

Current and expected behavior

Current
Parsing error: Private name #parseAttributes is not defined.

Expected
No errors

Environment

npx envinfo --preset babel output for working environment:

  System:
    OS: macOS 15.2
  Binaries:
    Node: 20.19.1 - /usr/local/bin/node
    Yarn: 4.6.0 - /usr/local/bin/yarn
    npm: 10.8.2 - /usr/local/bin/npm
    bun: 1.1.6 - /opt/homebrew/bin/bun
  Monorepos:
    Yarn Workspaces: 4.6.0
    Lerna: 8.1.9
  npmPackages:
    @babel/cli: 7.26.4 => 7.26.4
    @babel/core: ^7.26.10 => 7.26.10
    @babel/generator: 7.26.10 => 7.26.10
    @babel/node: 7.26.0 => 7.26.0
    @babel/plugin-proposal-decorators: 7.25.9 => 7.25.9
    @babel/plugin-transform-class-properties: 7.25.9 => 7.25.9
    @babel/plugin-transform-nullish-coalescing-operator: 7.26.6 => 7.26.6
    @babel/plugin-transform-private-methods: 7.25.9 => 7.25.9
    @babel/plugin-transform-private-property-in-object: 7.25.9 => 7.25.9
    @babel/plugin-transform-react-jsx: 7.25.9 => 7.25.9
    @babel/plugin-transform-runtime: 7.26.10 => 7.26.10
    @babel/preset-env: 7.26.9 => 7.26.9
    @babel/preset-react: 7.26.3 => 7.26.3
    @babel/preset-typescript: 7.26.0 => 7.26.0
    @babel/runtime-corejs3: 7.26.10 => 7.26.10
    babel-jest: ^29.7.0 => 29.7.0
    babel-plugin-auto-import: 1.1.0 => 1.1.0
    babel-plugin-remove-code: 0.0.6 => 0.0.6
    eslint: 8.57.1 => 8.57.1
    jest: 29.7.0 => 29.7.0
    lerna: 8.1.9 => 8.1.9

Possible solution

No response

Additional context

You can see the failing CI run here https://github.com/redmix-run/redmix/actions/runs/14852653931/job/41699066415?pr=75

Metadata

Metadata

Assignees

No one assigned

    Labels

    i: regressionoutdatedA closed issue/PR that is archived due to age. Recommended to make a new issue

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions