diff --git a/packages/@ember/-internals/glimmer/lib/helpers/-normalize-class.ts b/packages/@ember/-internals/glimmer/lib/helpers/-normalize-class.ts index 52b2989294b..5603e12c135 100644 --- a/packages/@ember/-internals/glimmer/lib/helpers/-normalize-class.ts +++ b/packages/@ember/-internals/glimmer/lib/helpers/-normalize-class.ts @@ -1,5 +1,5 @@ import { assert } from '@ember/debug'; -import { dasherize } from '@ember/string'; +import { dasherize } from '@ember/-internals/string'; import type { CapturedArguments } from '@glimmer/interfaces'; import { createComputeRef, valueForRef } from '@glimmer/reference'; import { internalHelper } from './internal-helper'; diff --git a/packages/@ember/-internals/glimmer/lib/utils/bindings.ts b/packages/@ember/-internals/glimmer/lib/utils/bindings.ts index 26069564072..aa04023b570 100644 --- a/packages/@ember/-internals/glimmer/lib/utils/bindings.ts +++ b/packages/@ember/-internals/glimmer/lib/utils/bindings.ts @@ -1,6 +1,6 @@ import { get } from '@ember/-internals/metal'; import { assert } from '@ember/debug'; -import { dasherize } from '@ember/string'; +import { dasherize } from '@ember/-internals/string'; import type { ElementOperations } from '@glimmer/interfaces'; import type { Reference } from '@glimmer/reference'; import { diff --git a/packages/@ember/-internals/string/index.ts b/packages/@ember/-internals/string/index.ts new file mode 100644 index 00000000000..e2e2591b6b4 --- /dev/null +++ b/packages/@ember/-internals/string/index.ts @@ -0,0 +1,105 @@ +/* + This module exists to separate the @ember/string methods used + internally in ember-source, from those public methods that are + now deprecated and to be removed. +*/ + +import { Cache } from '@ember/-internals/utils'; + +const STRING_DASHERIZE_REGEXP = /[ _]/g; + +const STRING_DASHERIZE_CACHE = new Cache(1000, (key) => + decamelize(key).replace(STRING_DASHERIZE_REGEXP, '-') +); + +const STRING_CLASSIFY_REGEXP_1 = /^(-|_)+(.)?/; +const STRING_CLASSIFY_REGEXP_2 = /(.)(-|_|\.|\s)+(.)?/g; +const STRING_CLASSIFY_REGEXP_3 = /(^|\/|\.)([a-z])/g; + +const CLASSIFY_CACHE = new Cache(1000, (str) => { + let replace1 = (_match: string, _separator: string, chr: string) => + chr ? `_${chr.toUpperCase()}` : ''; + let replace2 = (_match: string, initialChar: string, _separator: string, chr: string) => + initialChar + (chr ? chr.toUpperCase() : ''); + let parts = str.split('/'); + for (let i = 0; i < parts.length; i++) { + parts[i] = parts[i]!.replace(STRING_CLASSIFY_REGEXP_1, replace1).replace( + STRING_CLASSIFY_REGEXP_2, + replace2 + ); + } + return parts + .join('/') + .replace(STRING_CLASSIFY_REGEXP_3, (match /*, separator, chr */) => match.toUpperCase()); +}); + +const STRING_DECAMELIZE_REGEXP = /([a-z\d])([A-Z])/g; + +const DECAMELIZE_CACHE = new Cache(1000, (str) => + str.replace(STRING_DECAMELIZE_REGEXP, '$1_$2').toLowerCase() +); + +/** + Defines string helper methods used internally in ember-source. + + @class String + @private + */ + +/** + Replaces underscores, spaces, or camelCase with dashes. + + ```javascript + import { dasherize } from '@ember/-internals/string'; + + dasherize('innerHTML'); // 'inner-html' + dasherize('action_name'); // 'action-name' + dasherize('css-class-name'); // 'css-class-name' + dasherize('my favorite items'); // 'my-favorite-items' + dasherize('privateDocs/ownerInvoice'; // 'private-docs/owner-invoice' + ``` + + @method dasherize + @param {String} str The string to dasherize. + @return {String} the dasherized string. + @private + */ +export function dasherize(str: string): string { + return STRING_DASHERIZE_CACHE.get(str); +} + +/** + Returns the UpperCamelCase form of a string. + + ```javascript + import { classify } from '@ember/string'; + + classify('innerHTML'); // 'InnerHTML' + classify('action_name'); // 'ActionName' + classify('css-class-name'); // 'CssClassName' + classify('my favorite items'); // 'MyFavoriteItems' + classify('private-docs/owner-invoice'); // 'PrivateDocs/OwnerInvoice' + ``` + + @method classify + @param {String} str the string to classify + @return {String} the classified string + @private + */ +export function classify(str: string): string { + return CLASSIFY_CACHE.get(str); +} + +/** + Converts a camelized string into all lower case separated by underscores. + + ```javascript + decamelize('innerHTML'); // 'inner_html' + decamelize('action_name'); // 'action_name' + decamelize('css-class-name'); // 'css-class-name' + decamelize('my favorite items'); // 'my favorite items' + ``` + */ +function decamelize(str: string): string { + return DECAMELIZE_CACHE.get(str); +} diff --git a/packages/@ember/-internals/string/tests/classify_test.js b/packages/@ember/-internals/string/tests/classify_test.js new file mode 100644 index 00000000000..a167dbfcbcb --- /dev/null +++ b/packages/@ember/-internals/string/tests/classify_test.js @@ -0,0 +1,65 @@ +/* eslint-disable qunit/no-test-expect-argument */ +import { classify } from '@ember/-internals/string'; +import { moduleFor, AbstractTestCase } from 'internal-test-helpers'; + +function test(assert, given, expected, description) { + assert.deepEqual(classify(given), expected, description); +} + +moduleFor( + 'EmberInternalsString.classify', + class extends AbstractTestCase { + ['@test String classify tests'](assert) { + test(assert, 'my favorite items', 'MyFavoriteItems', 'classify normal string'); + test(assert, 'css-class-name', 'CssClassName', 'classify dasherized string'); + test(assert, 'action_name', 'ActionName', 'classify underscored string'); + test( + assert, + 'privateDocs/ownerInvoice', + 'PrivateDocs/OwnerInvoice', + 'classify namespaced camelized string' + ); + test( + assert, + 'private_docs/owner_invoice', + 'PrivateDocs/OwnerInvoice', + 'classify namespaced underscored string' + ); + test( + assert, + 'private-docs/owner-invoice', + 'PrivateDocs/OwnerInvoice', + 'classify namespaced dasherized string' + ); + test(assert, '-view-registry', '_ViewRegistry', 'classify prefixed dasherized string'); + test( + assert, + 'components/-text-field', + 'Components/_TextField', + 'classify namespaced prefixed dasherized string' + ); + test(assert, '_Foo_Bar', '_FooBar', 'classify underscore-prefixed underscored string'); + test(assert, '_Foo-Bar', '_FooBar', 'classify underscore-prefixed dasherized string'); + test( + assert, + '_foo/_bar', + '_Foo/_Bar', + 'classify underscore-prefixed-namespaced underscore-prefixed string' + ); + test( + assert, + '-foo/_bar', + '_Foo/_Bar', + 'classify dash-prefixed-namespaced underscore-prefixed string' + ); + test( + assert, + '-foo/-bar', + '_Foo/_Bar', + 'classify dash-prefixed-namespaced dash-prefixed string' + ); + test(assert, 'InnerHTML', 'InnerHTML', 'does nothing with classified string'); + test(assert, '_FooBar', '_FooBar', 'does nothing with classified prefixed string'); + } + } +); diff --git a/packages/@ember/-internals/string/tests/dasherize_test.js b/packages/@ember/-internals/string/tests/dasherize_test.js new file mode 100644 index 00000000000..090cb1b429d --- /dev/null +++ b/packages/@ember/-internals/string/tests/dasherize_test.js @@ -0,0 +1,43 @@ +/* eslint-disable qunit/no-test-expect-argument */ +import { dasherize } from '@ember/-internals/string'; +import { moduleFor, AbstractTestCase } from 'internal-test-helpers'; + +function test(assert, given, expected, description) { + assert.deepEqual(dasherize(given), expected, description); +} + +moduleFor( + 'EmberInternalsString.dasherize', + class extends AbstractTestCase { + ['@test String dasherize tests'](assert) { + test(assert, 'my favorite items', 'my-favorite-items', 'dasherize normal string'); + test(assert, 'css-class-name', 'css-class-name', 'does nothing with dasherized string'); + test(assert, 'action_name', 'action-name', 'dasherize underscored string'); + test(assert, 'innerHTML', 'inner-html', 'dasherize camelcased string'); + test( + assert, + 'toString', + 'to-string', + 'dasherize string that is the property name of Object.prototype' + ); + test( + assert, + 'PrivateDocs/OwnerInvoice', + 'private-docs/owner-invoice', + 'dasherize namespaced classified string' + ); + test( + assert, + 'privateDocs/ownerInvoice', + 'private-docs/owner-invoice', + 'dasherize namespaced camelized string' + ); + test( + assert, + 'private_docs/owner_invoice', + 'private-docs/owner-invoice', + 'dasherize namespaced underscored string' + ); + } + } +); diff --git a/packages/@ember/debug/container-debug-adapter.ts b/packages/@ember/debug/container-debug-adapter.ts index 43e490de888..2a6617548b4 100644 --- a/packages/@ember/debug/container-debug-adapter.ts +++ b/packages/@ember/debug/container-debug-adapter.ts @@ -1,4 +1,4 @@ -import { classify, dasherize } from '@ember/string'; +import { classify, dasherize } from '@ember/-internals/string'; import EmberObject from '@ember/object'; import { typeOf } from '@ember/utils'; import type { InternalOwner } from '@ember/-internals/owner'; diff --git a/packages/@ember/debug/data-adapter.ts b/packages/@ember/debug/data-adapter.ts index bc02dc4b5c9..056b69748c8 100644 --- a/packages/@ember/debug/data-adapter.ts +++ b/packages/@ember/debug/data-adapter.ts @@ -2,7 +2,7 @@ import type { InternalOwner } from '@ember/-internals/owner'; import { getOwner } from '@ember/-internals/owner'; import { _backburner, next } from '@ember/runloop'; import { get } from '@ember/object'; -import { dasherize } from '@ember/string'; +import { dasherize } from '@ember/-internals/string'; import Namespace from '@ember/application/namespace'; import type { NativeArray } from '@ember/array'; import EmberObject from '@ember/object'; diff --git a/packages/@ember/object/tests/observable_test.js b/packages/@ember/object/tests/observable_test.js index a000ec06e9e..c23f55dd650 100644 --- a/packages/@ember/object/tests/observable_test.js +++ b/packages/@ember/object/tests/observable_test.js @@ -1,7 +1,6 @@ import { context } from '@ember/-internals/environment'; import { run } from '@ember/runloop'; import { get, computed } from '@ember/object'; -import { w } from '@ember/string'; import EmberObject, { observer } from '@ember/object'; import Observable from '@ember/object/observable'; import { A as emberA } from '@ember/array'; @@ -359,7 +358,7 @@ moduleFor( ['@test getting values should call function return value'](assert) { // get each property twice. Verify return. - let keys = w('computed dependent'); + let keys = ['computed', 'dependent']; keys.forEach(function (key) { assert.equal(object.get(key), key, `Try #1: object.get(${key}) should run function`); @@ -367,15 +366,15 @@ moduleFor( }); // verify each call count. cached should only be called once - w('computedCalls dependentCalls').forEach((key) => { + ['computedCalls', 'dependentCalls'].forEach((key) => { assert.equal(object[key].length, 1, `non-cached property ${key} should be called 1x`); }); } ['@test setting values should call function return value'](assert) { // get each property twice. Verify return. - let keys = w('computed dependent'); - let values = w('value1 value2'); + let keys = ['computed', 'dependent']; + let values = ['value1', 'value2']; keys.forEach((key) => { assert.equal( diff --git a/packages/@ember/string/index.ts b/packages/@ember/string/index.ts index d96d1604eae..7f845c66a95 100644 --- a/packages/@ember/string/index.ts +++ b/packages/@ember/string/index.ts @@ -76,6 +76,7 @@ const DECAMELIZE_CACHE = new Cache(1000, (str) => @class String @public + @deprecated Add the package `@ember/string` to your project to use in place of this module. */ /** @@ -98,8 +99,10 @@ const DECAMELIZE_CACHE = new Cache(1000, (str) => @param {String} str The string to split @return {Array} array containing the split strings @public + @deprecated Add `@ember/string` to your package.json */ export function w(str: string): string[] { + deprecateImportFromInternalString(); return str.split(/\s+/); } @@ -119,8 +122,10 @@ export function w(str: string): string[] { @param {String} str The string to decamelize. @return {String} the decamelized string. @public + @deprecated Add `@ember/string` to your package.json */ export function decamelize(str: string): string { + deprecateImportFromInternalString(); return DECAMELIZE_CACHE.get(str); } @@ -141,8 +146,10 @@ export function decamelize(str: string): string { @param {String} str The string to dasherize. @return {String} the dasherized string. @public + @deprecated Add `@ember/string` to your package.json */ export function dasherize(str: string): string { + deprecateImportFromInternalString(); return STRING_DASHERIZE_CACHE.get(str); } @@ -164,8 +171,10 @@ export function dasherize(str: string): string { @param {String} str The string to camelize. @return {String} the camelized string. @public + @deprecated Add `@ember/string` to your package.json */ export function camelize(str: string): string { + deprecateImportFromInternalString(); return CAMELIZE_CACHE.get(str); } @@ -186,8 +195,10 @@ export function camelize(str: string): string { @param {String} str the string to classify @return {String} the classified string @public + @deprecated Add `@ember/string` to your package.json */ export function classify(str: string): string { + deprecateImportFromInternalString(); return CLASSIFY_CACHE.get(str); } @@ -209,8 +220,10 @@ export function classify(str: string): string { @param {String} str The string to underscore. @return {String} the underscored string. @public + @deprecated Add `@ember/string` to your package.json */ export function underscore(str: string): string { + deprecateImportFromInternalString(); return UNDERSCORE_CACHE.get(str); } @@ -231,11 +244,30 @@ export function underscore(str: string): string { @param {String} str The string to capitalize. @return {String} The capitalized string. @public + @deprecated Add `@ember/string` to your package.json */ export function capitalize(str: string): string { + deprecateImportFromInternalString(); return CAPITALIZE_CACHE.get(str); } +function deprecateImportFromInternalString() { + deprecate( + 'Importing from `@ember/string` without having the `@ember/string` package in your project is deprecated. Please add `@ember/string` to your `package.json', + false, + { + id: 'ember-string.add-package', + for: 'ember-source', + since: { + available: '4.10', + enabled: '4.10', + }, + until: '5.0.0', + url: 'https://deprecations.emberjs.com/v4.x/#toc_ember-string-add-package', + } + ); +} + function deprecateImportFromString( name: string, message = `Importing ${name} from '@ember/string' is deprecated. Please import ${name} from '@ember/template' instead.` diff --git a/packages/@ember/string/tests/camelize_test.js b/packages/@ember/string/tests/camelize_test.js index 1cb053fa798..c579be15ead 100644 --- a/packages/@ember/string/tests/camelize_test.js +++ b/packages/@ember/string/tests/camelize_test.js @@ -3,7 +3,9 @@ import { camelize } from '@ember/string'; import { moduleFor, AbstractTestCase } from 'internal-test-helpers'; function test(assert, given, expected, description) { - assert.deepEqual(camelize(given), expected, description); + expectDeprecation(() => { + assert.deepEqual(camelize(given), expected, description); + }, 'Importing from `@ember/string` without having the `@ember/string` package in your project is deprecated. Please add `@ember/string` to your `package.json'); } moduleFor( diff --git a/packages/@ember/string/tests/capitalize_test.js b/packages/@ember/string/tests/capitalize_test.js index 7b11679722a..4fc1e757bc4 100644 --- a/packages/@ember/string/tests/capitalize_test.js +++ b/packages/@ember/string/tests/capitalize_test.js @@ -3,7 +3,9 @@ import { capitalize } from '@ember/string'; import { moduleFor, AbstractTestCase } from 'internal-test-helpers'; function test(assert, given, expected, description) { - assert.deepEqual(capitalize(given), expected, description); + expectDeprecation(() => { + assert.deepEqual(capitalize(given), expected, description); + }, 'Importing from `@ember/string` without having the `@ember/string` package in your project is deprecated. Please add `@ember/string` to your `package.json'); } moduleFor( diff --git a/packages/@ember/string/tests/classify_test.js b/packages/@ember/string/tests/classify_test.js index 5232ccbf8c5..13dca64b245 100644 --- a/packages/@ember/string/tests/classify_test.js +++ b/packages/@ember/string/tests/classify_test.js @@ -3,7 +3,9 @@ import { classify } from '@ember/string'; import { moduleFor, AbstractTestCase } from 'internal-test-helpers'; function test(assert, given, expected, description) { - assert.deepEqual(classify(given), expected, description); + expectDeprecation(() => { + assert.deepEqual(classify(given), expected, description); + }, 'Importing from `@ember/string` without having the `@ember/string` package in your project is deprecated. Please add `@ember/string` to your `package.json'); } moduleFor( diff --git a/packages/@ember/string/tests/dasherize_test.js b/packages/@ember/string/tests/dasherize_test.js index 9f41db6957b..98fb5a5dcf1 100644 --- a/packages/@ember/string/tests/dasherize_test.js +++ b/packages/@ember/string/tests/dasherize_test.js @@ -3,7 +3,9 @@ import { dasherize } from '@ember/string'; import { moduleFor, AbstractTestCase } from 'internal-test-helpers'; function test(assert, given, expected, description) { - assert.deepEqual(dasherize(given), expected, description); + expectDeprecation(() => { + assert.deepEqual(dasherize(given), expected, description); + }, 'Importing from `@ember/string` without having the `@ember/string` package in your project is deprecated. Please add `@ember/string` to your `package.json'); } moduleFor( diff --git a/packages/@ember/string/tests/decamelize_test.js b/packages/@ember/string/tests/decamelize_test.js index f37af0c66a2..d5620398cd1 100644 --- a/packages/@ember/string/tests/decamelize_test.js +++ b/packages/@ember/string/tests/decamelize_test.js @@ -3,7 +3,9 @@ import { decamelize } from '@ember/string'; import { moduleFor, AbstractTestCase } from 'internal-test-helpers'; function test(assert, given, expected, description) { - assert.deepEqual(decamelize(given), expected, description); + expectDeprecation(() => { + assert.deepEqual(decamelize(given), expected, description); + }, 'Importing from `@ember/string` without having the `@ember/string` package in your project is deprecated. Please add `@ember/string` to your `package.json'); } moduleFor( diff --git a/packages/@ember/string/tests/underscore_test.js b/packages/@ember/string/tests/underscore_test.js index 69a1b83b33b..11ea62ab652 100644 --- a/packages/@ember/string/tests/underscore_test.js +++ b/packages/@ember/string/tests/underscore_test.js @@ -3,7 +3,9 @@ import { underscore } from '@ember/string'; import { moduleFor, AbstractTestCase } from 'internal-test-helpers'; function test(assert, given, expected, description) { - assert.deepEqual(underscore(given), expected, description); + expectDeprecation(() => { + assert.deepEqual(underscore(given), expected, description); + }, 'Importing from `@ember/string` without having the `@ember/string` package in your project is deprecated. Please add `@ember/string` to your `package.json'); } moduleFor( diff --git a/packages/@ember/string/tests/w_test.js b/packages/@ember/string/tests/w_test.js index e9fada90f2a..ae5dc9912bd 100644 --- a/packages/@ember/string/tests/w_test.js +++ b/packages/@ember/string/tests/w_test.js @@ -3,7 +3,9 @@ import { w } from '@ember/string'; import { moduleFor, AbstractTestCase } from 'internal-test-helpers'; function test(assert, given, expected, description) { - assert.deepEqual(w(given), expected, description); + expectDeprecation(() => { + assert.deepEqual(w(given), expected, description); + }, 'Importing from `@ember/string` without having the `@ember/string` package in your project is deprecated. Please add `@ember/string` to your `package.json'); } moduleFor( diff --git a/packages/ember/tests/routing/query_params_test.js b/packages/ember/tests/routing/query_params_test.js index 618e4a4b6e4..3d213fcac5e 100644 --- a/packages/ember/tests/routing/query_params_test.js +++ b/packages/ember/tests/routing/query_params_test.js @@ -1,5 +1,5 @@ import Controller from '@ember/controller'; -import { dasherize } from '@ember/string'; +import { dasherize } from '@ember/-internals/string'; import EmberObject, { get, computed } from '@ember/object'; import { RSVP } from '@ember/-internals/runtime'; import { A as emberA } from '@ember/array'; diff --git a/packages/ember/tests/routing/router_service_test/urlFor_test.js b/packages/ember/tests/routing/router_service_test/urlFor_test.js index 8b091812e58..486709c2c5b 100644 --- a/packages/ember/tests/routing/router_service_test/urlFor_test.js +++ b/packages/ember/tests/routing/router_service_test/urlFor_test.js @@ -1,11 +1,10 @@ import Controller from '@ember/controller'; -import { capitalize } from '@ember/string'; import Route from '@ember/routing/route'; import { get } from '@ember/object'; import { RouterTestCase, moduleFor } from 'internal-test-helpers'; function setupController(app, name) { - let controllerName = `${capitalize(name)}Controller`; + let controllerName = `${name}Controller`; Object.defineProperty(app, controllerName, { get() { @@ -30,7 +29,7 @@ moduleFor( ['@test RouterService#urlFor returns URL for simple route with dynamic segments'](assert) { assert.expect(1); - setupController(this.application, 'dynamic'); + setupController(this.application, 'Dynamic'); let dynamicModel = { id: 1, contents: 'much dynamicism' };