Skip to content

Latest commit

 

History

History
2081 lines (1689 loc) · 46.8 KB

File metadata and controls

2081 lines (1689 loc) · 46.8 KB

ember-object

Usage

npx ember-native-class-codemod ember-object path/of/files/ or/some**/*glob.js

# or

yarn global add ember-native-class-codemod
ember-native-class-codemod ember-object path/of/files/ or/some**/*glob.js

Input / Output


action-invalid-1

Input (action-invalid-1.input.js):

const Foo1 = EmberObject.extend({
  actions: {
    bar() {
      this._super(...arguments);
      this.get('bar')();
    },
  },
});

Output (action-invalid-1.output.js):

/*
Expect error:
	ValidationError: Validation errors for class 'Foo1':
		[actions]: Transform not supported - [bar]: calling the passed action would cause an infinite loop. See https://github.com/scalvert/eslint-plugin-ember-es6-class/pull/2 for more details
*/

const Foo1 = EmberObject.extend({
  actions: {
    bar() {
      this._super(...arguments);
      this.get('bar')();
    },
  },
});

action-invalid-2

Input (action-invalid-2.input.js):

const Foo2 = EmberObject.extend({
  actions: {
    biz() {
      this._super(...arguments);
      get(this, 'biz')();
    },
  },
});

Output (action-invalid-2.output.js):

/*
Expect error:
	ValidationError: Validation errors for class 'Foo2':
		[actions]: Transform not supported - [biz]: calling the passed action would cause an infinite loop. See https://github.com/scalvert/eslint-plugin-ember-es6-class/pull/2 for more details
*/

const Foo2 = EmberObject.extend({
  actions: {
    biz() {
      this._super(...arguments);
      get(this, 'biz')();
    },
  },
});

action-invalid-3

Input (action-invalid-3.input.js):

const Foo3 = EmberObject.extend({
  actions: {
    baz() {
      this._super(...arguments);
      tryInvoke(this, 'baz');
    },
  },
});

Output (action-invalid-3.output.js):

/*
Expect error:
	ValidationError: Validation errors for class 'Foo3':
		[actions]: Transform not supported - [baz]: calling the passed action would cause an infinite loop. See https://github.com/scalvert/eslint-plugin-ember-es6-class/pull/2 for more details
*/

const Foo3 = EmberObject.extend({
  actions: {
    baz() {
      this._super(...arguments);
      tryInvoke(this, 'baz');
    },
  },
});

action-invalid-4

Input (action-invalid-4.input.js):

const Foo4 = EmberObject.extend({
  actions: {
    sendBaz() {
      this._super(...arguments);
      this.send('sendBaz');
    },
  },
});

Output (action-invalid-4.output.js):

/*
Expect error:
	ValidationError: Validation errors for class 'Foo4':
		[actions]: Transform not supported - [sendBaz]: calling the passed action would cause an infinite loop. See https://github.com/scalvert/eslint-plugin-ember-es6-class/pull/2 for more details
*/

const Foo4 = EmberObject.extend({
  actions: {
    sendBaz() {
      this._super(...arguments);
      this.send('sendBaz');
    },
  },
});

action-invalid-5

Input (action-invalid-5.input.js):

const Foo5 = EmberObject.extend({
  actions: {
    thisBaz() {
      this._super(...arguments);
      this.thisBaz();
    },
  },
});

Output (action-invalid-5.output.js):

/*
Expect error:
	ValidationError: Validation errors for class 'Foo5':
		[actions]: Transform not supported - [thisBaz]: calling the passed action would cause an infinite loop. See https://github.com/scalvert/eslint-plugin-ember-es6-class/pull/2 for more details
*/

const Foo5 = EmberObject.extend({
  actions: {
    thisBaz() {
      this._super(...arguments);
      this.thisBaz();
    },
  },
});

async-function-property

Input (async-function-property.input.js):

const Foo = Test.extend({
  myAsyncMethod: async function() {
    await Promise.resolve('hello');
  }
});

Output (async-function-property.output.js):

import classic from 'ember-classic-decorator';

@classic
class Foo extends Test {
  async myAsyncMethod() {
    await Promise.resolve('hello');
  }
}

async-method

Input (async-method.input.js):

const Foo = Test.extend({
  async myAsyncMethod() {
    await Promise.resolve('hello');
  }
});

Output (async-method.output.js):

import classic from 'ember-classic-decorator';

@classic
class Foo extends Test {
  async myAsyncMethod() {
    await Promise.resolve('hello');
  }
}

basic-computed

Input (basic-computed.input.js):

import { computed } from '@ember/object';

var HasComputed = EmberObject.extend({
  isEnabled: computed('a', 'c', function() {
    return false;
  }),
  a: true,
  c: ''
});

Output (basic-computed.output.js):

import classic from 'ember-classic-decorator';
import { computed } from '@ember/object';

@classic
class HasComputed extends EmberObject {
  @computed('a', 'c')
  get isEnabled() {
    return false;
  }

  a = true;
  c = '';
}

basic

Input (basic.input.js):

/**
 * Program comments
 */
const Foo1 = Test.extend(MyMixin, {
  /**
   * Property comments
   */
  prop: 'defaultValue',
  boolProp: true,
  numProp: 123,
  [MY_VAL]: 'val',
  queryParams: {},
  someVal,

  /**
   * Method comments
   */
  method() {
    // do things
  },

  otherMethod: function() {},

  get accessor() {
    return this._value;
  },

  set accessor(value) {
    this._value = value;
  },

  anotherMethod() {
    this._super(...arguments);
  },
});

const Foo2 = EmberObject.extend(MixinA, MixinB);

Output (basic.output.js):

import classic from 'ember-classic-decorator';

/**
 * Program comments
 */
@classic
class Foo1 extends Test.extend(MyMixin) {
  /**
   * Property comments
   */
  prop = 'defaultValue';

  boolProp = true;
  numProp = 123;
  [MY_VAL] = 'val';
  queryParams = {};
  someVal = someVal;

  /**
   * Method comments
   */
  method() {
    // do things
  }

  otherMethod() {}

  get accessor() {
    return this._value;
  }

  set accessor(value) {
    this._value = value;
  }

  anotherMethod() {
    super.anotherMethod(...arguments);
  }
}

@classic
class Foo2 extends EmberObject.extend(MixinA, MixinB) {}

chained-class-definition

Input (chained-class-definition.input.js):

import EmberObject from '@ember/object';

export default EmberObject.extend({}).reopenClass({});

Output (chained-class-definition.output.js):

/*
Expect error:
	ValidationError: Validation errors for class 'ChainedClassDefinition':
		class has chained definition (e.g. EmberObject.extend().reopenClass();
*/

import EmberObject from '@ember/object';

export default EmberObject.extend({}).reopenClass({});

class-fields

Input (class-fields.input.js):

/**
 * Program comments
 */
const Foo = Test.extend({
  /**
   * Method comments
   */
  method() {
    // do things
  },

  otherMethod: function() {},

  get accessor() {
    return this._value;
  },

  set accessor(value) {
    this._value = value;
  },

  anotherMethod() {
    this._super(...arguments);
  },
});

Output (class-fields.output.js):

import classic from 'ember-classic-decorator';

/**
 * Program comments
 */
@classic
class Foo extends Test {
  /**
   * Method comments
   */
  method() {
    // do things
  }

  otherMethod() {}

  get accessor() {
    return this._value;
  }

  set accessor(value) {
    this._value = value;
  }

  anotherMethod() {
    super.anotherMethod(...arguments);
  }
}

class-reopen

Input (class-reopen.input.js):

import EmberObject from '@ember/object';

const Foo = EmberObject.extend({});

Foo.reopenClass({});

export default Foo;

Output (class-reopen.output.js):

import classic from 'ember-classic-decorator';
import EmberObject from '@ember/object';

@classic
class Foo extends EmberObject {}

Foo.reopenClass({});

export default Foo;

decorators-invalid-1

Input (decorators-invalid-1.input.js):

// Do not transform
const Foo1 = EmberObject.extend({
  statefulObject: {},
});

Output (decorators-invalid-1.output.js):

/*
Expect error:
  ValidationError: Validation errors for class 'Foo1':
    [statefulObject]: Transform not supported - value is of type object. For more details: eslint-plugin-ember/avoid-leaking-state-in-ember-objects
*/

// Do not transform
const Foo1 = EmberObject.extend({
  statefulObject: {},
});

decorators-invalid-2

Input (decorators-invalid-2.input.js):

// Do not transform if not a primitive value
const Foo2 = EmberObject.extend({
  macroValue: macro(),
});

Output (decorators-invalid-2.output.js):

/*
Expect error:
  ValidationError: Validation errors for class 'Foo2':
    [macroValue]: Transform not supported - call to 'macro' can not be transformed
*/

// Do not transform if not a primitive value
const Foo2 = EmberObject.extend({
  macroValue: macro(),
});

decorators-invalid-3

Input (decorators-invalid-3.input.js):

import { computed } from '@ember/object';

// Do not transform as a computed property has readOnly and volatile with meta
const Foo3 = EmberObject.extend({
  firstName: '',
  lastName: '',

  fName2: computed('firstName', 'lastName', function() {
    return true;
  })
    .property('baz')
    .readOnly()
    .volatile()
    .meta({ type: 'Property' }),
});

Output (decorators-invalid-3.output.js):

/*
Expect error:
  ValidationError: Validation errors for class 'Foo3':
    [fName2]: Transform not supported - value has modifiers like 'property' or 'meta'
*/

import { computed } from '@ember/object';

// Do not transform as a computed property has readOnly and volatile with meta
const Foo3 = EmberObject.extend({
  firstName: '',
  lastName: '',

  fName2: computed('firstName', 'lastName', function() {
    return true;
  })
    .property('baz')
    .readOnly()
    .volatile()
    .meta({ type: 'Property' }),
});

decorators-invalid-4

Input (decorators-invalid-4.input.js):

// Do not transform as a computed meta has volatile
const Foo4 = EmberObject.extend({
  lName1: add('description', 'lastName').volatile(),
});

Output (decorators-invalid-4.output.js):

/*
Expect error:
  ValidationError: Validation errors for class 'Foo4':
    [lName1]: Transform not supported - call to 'add' can not be transformed
*/

// Do not transform as a computed meta has volatile
const Foo4 = EmberObject.extend({
  lName1: add('description', 'lastName').volatile(),
});

decorators-invalid-5

Input (decorators-invalid-5.input.js):

import { computed } from '@ember/object';

// Do not transform as computed prop has `property`
const Foo5 = EmberObject.extend({
  fName2: computed('firstName', 'lastName', function() {
    return true;
  }).property('baz'),
});

Output (decorators-invalid-5.output.js):

/*
Expect error:
  ValidationError: Validation errors for class 'Foo5':
    [fName2]: Transform not supported - value has modifiers like 'property' or 'meta'
*/

import { computed } from '@ember/object';

// Do not transform as computed prop has `property`
const Foo5 = EmberObject.extend({
  fName2: computed('firstName', 'lastName', function() {
    return true;
  }).property('baz'),
});

decorators-invalid-6

Input (decorators-invalid-6.input.js):

import { computed } from '@ember/object';

// Do not transform as computed prop has `meta`
const Foo6 = EmberObject.extend({
  fName2: computed('firstName', 'lastName', function() {
    return true;
  }).meta({ type: 'Property' }),
});

Output (decorators-invalid-6.output.js):

/*
Expect error:
  ValidationError: Validation errors for class 'Foo6':
    [fName2]: Transform not supported - value has modifiers like 'property' or 'meta'
*/

import { computed } from '@ember/object';

// Do not transform as computed prop has `meta`
const Foo6 = EmberObject.extend({
  fName2: computed('firstName', 'lastName', function() {
    return true;
  }).meta({ type: 'Property' }),
});

decorators-invalid-7

Input (decorators-invalid-7.input.js):

// Do not transform as action name matches lifecycle hook
const Foo7 = EmberObject.extend({
  actions: {
    click() {
      this.set('clicked', true);
    },
  },
});

Output (decorators-invalid-7.output.js):

/*
Expect error:
  ValidationError: Validation errors for class 'Foo7':
    [actions]: Transform not supported - [click]: action name matches one of the lifecycle hooks. Rename and try again. See https://github.com/scalvert/ember-native-class-codemod/issues/34 for more details
*/

// Do not transform as action name matches lifecycle hook
const Foo7 = EmberObject.extend({
  actions: {
    click() {
      this.set('clicked', true);
    },
  },
});

decorators-invalid-8

Input (decorators-invalid-8.input.js):

const Foo8 = EmberObject.extend({
  statefulArray: [],
});

Output (decorators-invalid-8.output.js):

/*
Expect error:
  ValidationError: Validation errors for class 'Foo8':
    [statefulArray]: Transform not supported - value is of type object. For more details: eslint-plugin-ember/avoid-leaking-state-in-ember-objects
*/

const Foo8 = EmberObject.extend({
  statefulArray: [],
});

decorators

Input (decorators.input.js):

import {
  alias,
  gt,
  equal,
  readOnly,
  reads,
  oneWay as enoWay,
  sum as add,
  map as computedMap,
  filter,
} from '@ember/object/computed';
import { get, set, observer as watcher, computed } from '@ember/object';
import { inject as controller } from '@ember/controller';
import { inject as service } from '@ember/service';
import { on } from '@ember/object/evented';
import layout from 'components/templates/foo';
import { someActionUtil } from 'some/action/util';
import NUMERIC_CONSTANT from 'constants/numbers';

const Foo1 = EmberObject.extend({
  tagName: 'div',
  classNames: ['test-class', 'custom-class'],
  a: '',
  b: service('store'),
  myController: controller('abc'),
  observedProp: watcher('xyz', function() {
    return 'observed';
  }),
  observedProp2: watcher('xyz', function() {
    return this._super(...arguments);
  }),
  event: on('click', function() {
    return 'abc';
  }),
  excitingChores: computedMap('chores', function(chore, index) {
    return chore.toUpperCase() + '!';
  }),
  remainingChores: filter('chores', function(chore, index, array) {
    return !chore.done;
  }),

  actions: {
    someActionUtil,
    /**
    Comments
    */
    bar(temp1) {},
    baz() {
      this._super(...arguments);
    },
    biz() {},
  },
});

var comp = EmberObject.extend({
  classNameBindings: ['isEnabled:enabled:disabled', 'a:b:c', 'c:d'],
  isEnabled: computed('a', 'c', function() {
    return false;
  }),
  a: true,
  c: '',
  attributeBindings: ['customHref:href'],

  customHref: 'http://emberjs.com',
});

const Foo2 = EmberObject.extend({
  firstName: null,
  lastName: null,

  /**
  Computed fullname
  */
  fullName: computed('firstName', 'lastName', {
    get(key) {
      return this._super(...arguments) && `${this.get('firstName')} ${this.get('lastName')}`;
    },
    set(key, value) {
      let [firstName, lastName] = value.split(/\s+/);
      this.set('firstName', firstName);
      this.set('lastName', lastName);
      return value;
    },
  }).readOnly(),
  /**
  Computed description
  */
  description: computed('fullName', 'age', 'country', function() {
    const desc = this._super(...arguments);
    if (desc) {
      return desc;
    }
    return `${this.get('fullName')}; Age: ${this.get('age')}; Country: ${this.get('country')}`;
  })
    .volatile()
    .readOnly(),

  /**
   * Fname
   */
  fName: alias('firstName'),

  /**
   * Fname1
   */
  fName1: alias('firstName'),

  /**
   * Fname2
   */
  fName2: computed('firstName', 'lastName', function() {
    return true;
  }).readOnly(),

  /**
   * Fname3
   */
  fName3: computed('firstName', 'lastName', function() {
    return true;
  }).volatile(),

  /**
   * Lname
   */
  lName: alias('firstName', 'lastName').readOnly(),

  /**
   * Lname1
   */
  lName1: add('description', 'lastName'),

  /**
   * Lname2
   */
  lName2: readOnly('description'),

  /**
   * Lname3
   */
  lName3: reads('description', 'lastName'),

  /**
   * Lname4
   */
  lName4: enoWay('description', 'lastName'),

  /**
   * Lname5
   */
  lName5: add('description', 'lastName').readOnly(),

  isEqualToLimit: equal('limit', NUMERIC_CONSTANT.LIMIT).readOnly(),

  isGreaterThanLimit: gt('limit', NUMERIC_CONSTANT).readOnly(),
});

const Foo3 = EmberObject.extend({
  layout,
});

Output (decorators.output.js):

import classic from 'ember-classic-decorator';

import {
  attributeBindings,
  classNameBindings,
  classNames,
  layout as templateLayout,
  tagName,
} from '@ember-decorators/component';

import { observes as watcher, on } from '@ember-decorators/object';
import { inject as controller } from '@ember/controller';
import { inject as service } from '@ember/service';

import {
  filter,
  map as computedMap,
  sum as add,
  oneWay as enoWay,
  reads,
  readOnly,
  equal,
  gt,
  alias,
} from '@ember/object/computed';

import { get, set, action, computed } from '@ember/object';
import layout from 'components/templates/foo';
import { someActionUtil } from 'some/action/util';
import NUMERIC_CONSTANT from 'constants/numbers';

@classic
@tagName('div')
@classNames('test-class', 'custom-class')
class Foo1 extends EmberObject {
  a = '';

  @service('store')
  b;

  @controller('abc')
  myController;

  @watcher('xyz')
  observedProp() {
    return 'observed';
  }

  @watcher('xyz')
  observedProp2() {
    return super.observedProp2(...arguments);
  }

  @on('click')
  event() {
    return 'abc';
  }

  @computedMap('chores', function(chore, index) {
    return chore.toUpperCase() + '!';
  })
  excitingChores;

  @filter('chores', function(chore, index, array) {
    return !chore.done;
  })
  remainingChores;

  @action
  someActionUtil() {
    return someActionUtil.call(this, ...arguments);
  }

  /**
  Comments
  */
  @action
  bar(temp1) {}

  @action
  baz() {
    // TODO: This call to super is within an action, and has to refer to the parent
    // class's actions to be safe. This should be refactored to call a normal method
    // on the parent class. If the parent class has not been converted to native
    // classes, it may need to be refactored as well. See
    // https://github.com/scalvert/ember-native-class-codemod/blob/master/README.md
    // for more details.
    super.actions.baz.call(this, ...arguments);
  }

  @action
  biz() {}
}

@classic
@classNameBindings('isEnabled:enabled:disabled', 'a:b:c', 'c:d')
@attributeBindings('customHref:href')
class comp extends EmberObject {
  @computed('a', 'c')
  get isEnabled() {
    return false;
  }

  a = true;
  c = '';
  customHref = 'http://emberjs.com';
}

@classic
class Foo2 extends EmberObject {
  firstName = null;
  lastName = null;

  /**
  Computed fullname
  */
  @(computed('firstName', 'lastName').readOnly())
  get fullName() {
    return super.fullName && `${this.get('firstName')} ${this.get('lastName')}`;
  }

  set fullName(value) {
    let [firstName, lastName] = value.split(/\s+/);
    this.set('firstName', firstName);
    this.set('lastName', lastName);
    return value;
  }

  /**
  Computed description
  */
  get description() {
    const desc = super.description;
    if (desc) {
      return desc;
    }
    return `${this.get('fullName')}; Age: ${this.get('age')}; Country: ${this.get('country')}`;
  }

  /**
   * Fname
   */
  @alias('firstName')
  fName;

  /**
   * Fname1
   */
  @alias('firstName')
  fName1;

  /**
   * Fname2
   */
  @(computed('firstName', 'lastName').readOnly())
  get fName2() {
    return true;
  }

  /**
   * Fname3
   */
  @(computed('firstName', 'lastName').volatile())
  get fName3() {
    return true;
  }

  /**
   * Lname
   */
  @(alias('firstName', 'lastName').readOnly())
  lName;

  /**
   * Lname1
   */
  @add('description', 'lastName')
  lName1;

  /**
   * Lname2
   */
  @readOnly('description')
  lName2;

  /**
   * Lname3
   */
  @reads('description', 'lastName')
  lName3;

  /**
   * Lname4
   */
  @enoWay('description', 'lastName')
  lName4;

  /**
   * Lname5
   */
  @(add('description', 'lastName').readOnly())
  lName5;

  @(equal('limit', NUMERIC_CONSTANT.LIMIT).readOnly())
  isEqualToLimit;

  @(gt('limit', NUMERIC_CONSTANT).readOnly())
  isGreaterThanLimit;
}

@classic
@templateLayout(layout)
class Foo3 extends EmberObject {}

default-export

Input (default-export.input.js):

export default EmberObject.extend({});

Output (default-export.output.js):

import classic from 'ember-classic-decorator';
@classic
export default class DefaultExport extends EmberObject {}

double-quotes

Input (double-quotes.input.js):

/**
 * Program comments
 */
const Foo1 = Test.extend(MyMixin, {
  /**
   * Property comments
   */
  prop: "defaultValue",
  boolProp: true,
  numProp: 123,
  [MY_VAL]: "val",
  queryParams: {},
  someVal,

  /**
   * Method comments
   */
  method() {
    // do things
  },

  otherMethod: function() {},

  get accessor() {
    return this._value;
  },

  set accessor(value) {
    this._value = value;
  },

  anotherMethod() {
    this._super(...arguments);
  },
});

const Foo2 = EmberObject.extend(MixinA, MixinB);

Output (double-quotes.output.js):

import classic from "ember-classic-decorator";

/**
 * Program comments
 */
@classic
class Foo1 extends Test.extend(MyMixin) {
  /**
   * Property comments
   */
  prop = "defaultValue";

  boolProp = true;
  numProp = 123;
  [MY_VAL] = "val";
  queryParams = {};
  someVal = someVal;

  /**
   * Method comments
   */
  method() {
    // do things
  }

  otherMethod() {}

  get accessor() {
    return this._value;
  }

  set accessor(value) {
    this._value = value;
  }

  anotherMethod() {
    super.anotherMethod(...arguments);
  }
}

@classic
class Foo2 extends EmberObject.extend(MixinA, MixinB) {}

ember-concurrency

Input (ember-concurrency.input.js):

import Component from '@ember/component';
import { task } from 'ember-concurrency';

export default Component.extend({
  fetchAlerts: task(function*() {
    let alerts = yield this.store.query('alert', {
      filter: { id: this.get('alert.id') }
    });
    return alerts.sortBy('createdAt').reverse();
  }).drop(),
});

Output (ember-concurrency.output.js):

import classic from 'ember-classic-decorator';
import Component from '@ember/component';
import { task } from 'ember-concurrency';

@classic
export default class EmberConcurrency extends Component {
  @(task(function*() {
    let alerts = yield this.store.query('alert', {
      filter: { id: this.get('alert.id') }
    });
    return alerts.sortBy('createdAt').reverse();
  }).drop())
  fetchAlerts;
}

frozen

Input (frozen.input.js):

/**
 * Program comments
 */
const Foo = Test.extend({
  frozen: Object.freeze(['name'])
});

Output (frozen.output.js):

import classic from 'ember-classic-decorator';

/**
 * Program comments
 */
@classic
class Foo extends Test {
 frozen = Object.freeze(['name']);
}

generator-method

Input (generator-method.input.js):

const Foo = Test.extend({
  *gen() {
    yield 'hello';
  }
});

Output (generator-method.output.js):

import classic from 'ember-classic-decorator';

@classic
class Foo extends Test {
  *gen() {
    yield 'hello';
  }
}

generator-property

Input (generator-property.input.js):

const Foo = Test.extend({
  gen: function*() {
    yield 'hello';
  }
});

Output (generator-property.output.js):

import classic from 'ember-classic-decorator';

@classic
class Foo extends Test {
  *gen() {
    yield 'hello';
  }
}

import

Input (import.input.js):

import Service from '@ember/service';
import Controller from '@ember/controller';
import Evented, { on } from '@ember/object/evented';

const ser = Service.extend({});
const ctrl = Controller.extend({});
const evt = Service.extend(Evented, {
  e: on('click', function() {
    return 'e';
  }),
});

export { ser, ctrl, evt };

Output (import.output.js):

import classic from 'ember-classic-decorator';
import { on } from '@ember-decorators/object';
import Service from '@ember/service';
import Controller from '@ember/controller';
import Evented from '@ember/object/evented';

@classic
class ser extends Service {}

@classic
class ctrl extends Controller {}

@classic
class evt extends Service.extend(Evented) {
  @on('click')
  e() {
    return 'e';
  }
}

export { ser, ctrl, evt };

injecting-service

Input (injecting-service.input.js):

import Service, { service as injectService } from '@ember/service';

export default Service.extend({
  something: injectService(),
  otherThing: injectService('some-thing'),
});

Output (injecting-service.output.js):

import classic from 'ember-classic-decorator';
import Service, { service as injectService } from '@ember/service';

@classic
export default class InjectingService extends Service {
  @injectService()
  something;

  @injectService('some-thing')
  otherThing;
}

logical-expression

Input (logical-expression.input.js):

/**
 * Program comments
 */
const Foo = Test.extend({
  location: ENV.locationType || 'history'
});

Output (logical-expression.output.js):

import classic from 'ember-classic-decorator';

/**
 * Program comments
 */
@classic
class Foo extends Test {
 location = ENV.locationType || 'history';
}

mixin

Input (mixin.input.js):

const HasMixin = Test.extend(MyMixin, {});

Output (mixin.output.js):

import classic from 'ember-classic-decorator';

@classic
class HasMixin extends Test.extend(MyMixin) {}

object-literal-with-action-hash-and-decorator

Input (object-literal-with-action-hash-and-decorator.input.js):

import EmberObject, { action, set } from '@ember/object';

const Foo = EmberObject.extend({
  @action
  toggleShowing() {
    set(this, 'isShowing', !this.isShowing);
  },

  actions: {
    toggleSnowing() {
      set(this, 'isSnowing', !this.isSnowing);
    }
  }
});

Output (object-literal-with-action-hash-and-decorator.output.js):

import classic from 'ember-classic-decorator';
import EmberObject, { action, set } from '@ember/object';

@classic
class Foo extends EmberObject {
  @action
  toggleShowing() {
    set(this, 'isShowing', !this.isShowing);
  }

  @action
  toggleSnowing() {
    set(this, 'isSnowing', !this.isSnowing);
  }
}

object-literal-with-decorators-invalid-1

Input (object-literal-with-decorators-invalid-1.input.js):

// Do not transform if not a primitive value
const Foo1 = EmberObject.extend({
  @tracked computedMacro: customMacro(),
});

Output (object-literal-with-decorators-invalid-1.output.js):

/*
Expect error:
  ValidationError: Validation errors for class 'Foo1':
    [computedMacro]: Transform not supported - can only transform object literal decorators on methods or properties with literal values (string, number, boolean, null, undefined)
    [computedMacro]: Transform not supported - call to 'customMacro' can not be transformed
*/

// Do not transform if not a primitive value
const Foo1 = EmberObject.extend({
  @tracked computedMacro: customMacro(),
});

object-literal-with-decorators-invalid-2

Input (object-literal-with-decorators-invalid-2.input.js):

// Do not transform if not on allowlist
const Foo2 = EmberObject.extend({
  @banned prop: '',
});

Output (object-literal-with-decorators-invalid-2.output.js):

/*
Expect error:
  ValidationError: Validation errors for class 'Foo2':
    [prop]: Transform not supported - decorator '@banned' not included in ALLOWED_OBJECT_LITERAL_DECORATORS or option '--objectLiteralDecorators'
*/

// Do not transform if not on allowlist
const Foo2 = EmberObject.extend({
  @banned prop: '',
});

object-literal-with-decorators-invalid-3

Input (object-literal-with-decorators-invalid-3.input.js):

// Do not transform array
const Foo3 = EmberObject.extend({
  @tracked arr: [1, 2, 3],
});

Output (object-literal-with-decorators-invalid-3.output.js):

/*
Expect error:
  ValidationError: Validation errors for class 'Foo3':
    [arr]: Transform not supported - value is of type object. For more details: eslint-plugin-ember/avoid-leaking-state-in-ember-objects
*/

// Do not transform array
const Foo3 = EmberObject.extend({
  @tracked arr: [1, 2, 3],
});

object-literal-with-decorators-invalid-4

Input (object-literal-with-decorators-invalid-4.input.js):

// Do not transform function expression if not on allowlist
const Foo4 = EmberObject.extend({
  @userAdded methodish: () => {},
});

Output (object-literal-with-decorators-invalid-4.output.js):

/*
Expect error:
  ValidationError: Validation errors for class 'Foo4':
    [methodish]: Transform not supported - decorator '@userAdded' not included in ALLOWED_OBJECT_LITERAL_DECORATORS or option '--objectLiteralDecorators'
*/

// Do not transform function expression if not on allowlist
const Foo4 = EmberObject.extend({
  @userAdded methodish: () => {},
});

object-literal-with-decorators

Input (object-literal-with-decorators.input.js):

import EmberObject, { action, set, computed } from '@ember/object';
import { dependentKeyCompat } from '@ember/object/compat';
import { alias } from '@ember/object/computed';
import { tracked } from '@glimmer/tracking';
import { attribute, className } from '@ember-decorators/component';
import { observes, on } from '@ember-decorators/object';

const Foo = EmberObject.extend({

  // @ember/object

  @action
  toggleShowing() {
    set(this, 'isShowing', !this.isShowing);
  },

  @computed('firstName', 'lastName')
  fullName() {
    return `${this.firstName} ${this.lastName}`;
  },

  // @ember/object/compat

  @dependentKeyCompat
  fullName2: function() {
    return `${this.firstName} ${this.lastName}`;
  },

  // @ember/object/computed

  @alias('foo') hasAlias: undefined,
  @and('foo', 'bar') hasAnd: undefined,
  @bool('foo') hasBool: undefined,
  @collect('foo', 'bar') hasCollect: undefined,
  @deprecatingAlias('foo') hasDeprecatingAlias: undefined,
  @empty('foo') hasEmpty: undefined,
  @equal('foo', 'bar') hasEqual: undefined,
  @filterBy('foo', 'bar') hasFilterBy: undefined,
  @gt('foo', 'bar') hasGt: undefined,
  @gte('foo', 'bar') hasGte: undefined,
  @intersect('foo', 'bar') hasIntersect: undefined,
  @lt('foo', 'bar') hasLt: undefined,
  @lte('foo', 'bar') hasLte: undefined,
  @mapBy('foo', 'bar') hasMapBy: undefined,
  @match('foo', /bar/) hasMatch: undefined,
  @max('foo', 'bar') hasMax: undefined,
  @min('foo', 'bar') hasMin: undefined,
  @none('foo') hasNone: undefined,
  @not('foo') hasNot: undefined,
  @notEmpty('foo') hasNotEmpty: undefined,
  @oneWay('foo') hasOneWay: undefined,
  @or('foo', 'bar') hasOr: undefined,
  @readOnly('foo') hasReadOnly: undefined,
  @reads('foo') hasReads: undefined,
  @setDiff('foo', 'bar') hasSetDiff: undefined,
  @sum('foo', 'bar') hasSum: undefined,
  @union('foo', 'bar') hasUnion: undefined,
  @uniq('foo') hasUniq: undefined,
  @uniqBy('foo', 'bar') hasUniqBy: undefined,

  @filter('foo', function(foo, index, array) { return false })
  hasFilter: undefined,

  @map('foo', function(foo, index, array) { return 'bar' })
  hasMap: undefined,

  @sort('foo', function(a, b) {
    if (a.priority > b.priority) {
      return 1;
    } else if (a.priority < b.priority) {
      return -1;
    }

    return 0;
  })
  hasSort: undefined,

  // @glimmer/tracking

  @tracked count: 0,

  // @ember-decorators/component

  @attribute id: '1',

  @className('active', 'inactive')
  isActive: true,

  // @ember-decorators/object

  @observes('value')
  valueObserver() {
    // Executes whenever the "value" property changes
  },

  @on('barEvent')
  bar() {
    // Executes whenever barEvent is emitted
  },

  @userAdded
  yolo() {
    // methods always pass through decorators, even if not on allow-list
  }
});

Output (object-literal-with-decorators.output.js):

import classic from 'ember-classic-decorator';
import { alias } from '@ember/object/computed';
import EmberObject, { action, set, computed } from '@ember/object';
import { dependentKeyCompat } from '@ember/object/compat';
import { tracked } from '@glimmer/tracking';
import { attribute, className } from '@ember-decorators/component';
import { observes, on } from '@ember-decorators/object';

@classic
class Foo extends EmberObject {
  // @ember/object

  @action
  toggleShowing() {
    set(this, 'isShowing', !this.isShowing);
  }

  @computed('firstName', 'lastName')
  fullName() {
    return `${this.firstName} ${this.lastName}`;
  }

  // @ember/object/compat

  @dependentKeyCompat
  fullName2() {
    return `${this.firstName} ${this.lastName}`;
  }

  // @ember/object/computed

  @alias('foo')
  hasAlias;

  @and('foo', 'bar')
  hasAnd;

  @bool('foo')
  hasBool;

  @collect('foo', 'bar')
  hasCollect;

  @deprecatingAlias('foo')
  hasDeprecatingAlias;

  @empty('foo')
  hasEmpty;

  @equal('foo', 'bar')
  hasEqual;

  @filterBy('foo', 'bar')
  hasFilterBy;

  @gt('foo', 'bar')
  hasGt;

  @gte('foo', 'bar')
  hasGte;

  @intersect('foo', 'bar')
  hasIntersect;

  @lt('foo', 'bar')
  hasLt;

  @lte('foo', 'bar')
  hasLte;

  @mapBy('foo', 'bar')
  hasMapBy;

  @match('foo', /bar/)
  hasMatch;

  @max('foo', 'bar')
  hasMax;

  @min('foo', 'bar')
  hasMin;

  @none('foo')
  hasNone;

  @not('foo')
  hasNot;

  @notEmpty('foo')
  hasNotEmpty;

  @oneWay('foo')
  hasOneWay;

  @or('foo', 'bar')
  hasOr;

  @readOnly('foo')
  hasReadOnly;

  @reads('foo')
  hasReads;

  @setDiff('foo', 'bar')
  hasSetDiff;

  @sum('foo', 'bar')
  hasSum;

  @union('foo', 'bar')
  hasUnion;

  @uniq('foo')
  hasUniq;

  @uniqBy('foo', 'bar')
  hasUniqBy;

  @filter('foo', function(foo, index, array) { return false })
  hasFilter;

  @map('foo', function(foo, index, array) { return 'bar' })
  hasMap;

  @sort('foo', function(a, b) {
    if (a.priority > b.priority) {
      return 1;
    } else if (a.priority < b.priority) {
      return -1;
    }

    return 0;
  })
  hasSort;

  // @glimmer/tracking

  @tracked
  count = 0;

  // @ember-decorators/component

  @attribute
  id = '1';

  @className('active', 'inactive')
  isActive = true;

  // @ember-decorators/object

  @observes('value')
  valueObserver() {
    // Executes whenever the "value" property changes
  }

  @on('barEvent')
  bar() {
    // Executes whenever barEvent is emitted
  }

  @userAdded
  yolo() {
    // methods always pass through decorators, even if not on allow-list
  }
}

runtime

Input (runtime.input.js):

import Runtime from 'common/runtime';
import { alias } from '@ember/object/computed';
import { computed } from '@ember/object';
import { service } from '@ember/service';

/**
 * Program comments
 */
export default Runtime.extend(MyMixin, {
  /**
   * Property comments
   */
  prop: 'defaultValue',
  boolProp: true,
  numProp: 123,
  [MY_VAL]: 'val',
  queryParams: {},

  error: service(),
  errorService: service('error'),

  unobservedProp: null,
  offProp: null,

  numPlusOne: computed('numProp', function() {
    return this.get('numProp') + 1;
  }),

  numPlusPlus: alias('numPlusOne'),

  computedMacro: customMacro(),

  anotherMacro: customMacroWithInput({
    foo: 123,
    bar: 'baz'
  }),

  /**
   * Method comments
   */
  method() {
    // do things
  },

  otherMethod: function() {},

  get accessor() {
    return this._value;
  },

  set accessor(value) {
    this._value = value;
  },

  anotherMethod() {
    this._super(...arguments);
  },

  overriddenMethod() {
    this._super(...arguments);
  },

  actions: {
    actionMethod() {
      this._super(...arguments) && this.boolProp;
    },

    overriddenActionMethod() {
      this._super(...arguments) && this.boolProp;
    },
  },
});

Output (runtime.output.js):

import classic from 'ember-classic-decorator';
import { off, unobserves } from '@ember-decorators/object';
import { action, computed } from '@ember/object';
import { alias } from '@ember/object/computed';
import Runtime from 'common/runtime';
import { service } from '@ember/service';

/**
 * Program comments
 */
@classic
export default class _Runtime extends Runtime.extend(MyMixin) {
  /**
   * Property comments
   */
  prop = 'defaultValue';

  boolProp = true;
  numProp = 123;
  [MY_VAL] = 'val';
  queryParams = {};

  @service
  error;

  @service('error')
  errorService;

  @unobserves('prop3', 'prop4')
  unobservedProp;

  @off('prop1', 'prop2')
  offProp;

  @computed('numProp')
  get numPlusOne() {
    return this.get('numProp') + 1;
  }

  @alias('numPlusOne')
  numPlusPlus;

  @customMacro()
  computedMacro;

  @customMacroWithInput({
    foo: 123,
    bar: 'baz'
  })
  anotherMacro;

  /**
   * Method comments
   */
  method() {
    // do things
  }

  otherMethod() {}

  get accessor() {
    return this._value;
  }

  set accessor(value) {
    this._value = value;
  }

  anotherMethod() {
    undefined;
  }

  overriddenMethod() {
    super.overriddenMethod(...arguments);
  }

  @action
  actionMethod() {
    undefined && this.boolProp;
  }

  @action
  overriddenActionMethod() {
    // TODO: This call to super is within an action, and has to refer to the parent
    // class's actions to be safe. This should be refactored to call a normal method
    // on the parent class. If the parent class has not been converted to native
    // classes, it may need to be refactored as well. See
    // https://github.com/scalvert/ember-native-class-codemod/blob/master/README.md
    // for more details.
    super.actions.overriddenActionMethod.call(this, ...arguments) && this.boolProp;
  }
}