diff --git a/addon/addon/index.js b/addon/addon/index.js index 0292db14..f1b43dfc 100644 --- a/addon/addon/index.js +++ b/addon/addon/index.js @@ -3,7 +3,7 @@ import Ember from 'ember'; import { assert, deprecate, warn } from '@ember/debug'; import EmberObject from '@ember/object'; -import { dasherize, classify, underscore } from '@ember/string'; +import { dasherize, classify, underscore } from './string'; import { DEBUG } from '@glimmer/env'; import classFactory from './utils/class-factory'; diff --git a/addon/addon/string/cache.js b/addon/addon/string/cache.js new file mode 100644 index 00000000..f55ba78f --- /dev/null +++ b/addon/addon/string/cache.js @@ -0,0 +1,36 @@ +export default class Cache { + constructor(limit, func, store) { + this.limit = limit; + this.func = func; + this.store = store; + this.size = 0; + this.misses = 0; + this.hits = 0; + this.store = store || new Map(); + } + get(key) { + let value = this.store.get(key); + if (this.store.has(key)) { + this.hits++; + return this.store.get(key); + } + else { + this.misses++; + value = this.set(key, this.func(key)); + } + return value; + } + set(key, value) { + if (this.limit > this.size) { + this.size++; + this.store.set(key, value); + } + return value; + } + purge() { + this.store.clear(); + this.size = 0; + this.hits = 0; + this.misses = 0; + } +} \ No newline at end of file diff --git a/addon/addon/string/index.js b/addon/addon/string/index.js new file mode 100644 index 00000000..afc4d3d5 --- /dev/null +++ b/addon/addon/string/index.js @@ -0,0 +1,122 @@ +/* eslint-disable no-useless-escape */ +import Cache from './cache'; +let STRINGS = {}; +export function setStrings(strings) { + STRINGS = strings; +} +export function getStrings() { + return STRINGS; +} +export function getString(name) { + return STRINGS[name]; +} +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) => { + const replace1 = (_match, _separator, chr) => chr ? `_${chr.toUpperCase()}` : ''; + const replace2 = (_match, initialChar, _separator, chr) => initialChar + (chr ? chr.toUpperCase() : ''); + const 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_UNDERSCORE_REGEXP_1 = /([a-z\d])([A-Z]+)/g; +const STRING_UNDERSCORE_REGEXP_2 = /\-|\s+/g; +const UNDERSCORE_CACHE = new Cache(1000, (str) => str + .replace(STRING_UNDERSCORE_REGEXP_1, '$1_$2') + .replace(STRING_UNDERSCORE_REGEXP_2, '_') + .toLowerCase()); +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()); +/** + Converts a camelized string into all lower case separated by underscores. + + ```javascript + import { decamelize } from '@ember/string'; + + decamelize('innerHTML'); // 'inner_html' + decamelize('action_name'); // 'action_name' + decamelize('css-class-name'); // 'css-class-name' + decamelize('my favorite items'); // 'my favorite items' + ``` + + @method decamelize + @param {String} str The string to decamelize. + @return {String} the decamelized string. + @public +*/ +export function decamelize(str) { + return DECAMELIZE_CACHE.get(str); +} +/** + Replaces underscores, spaces, or camelCase with dashes. + + ```javascript + import { dasherize } from '@ember/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. + @public +*/ +export function dasherize(str) { + 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 + @public +*/ +export function classify(str) { + return CLASSIFY_CACHE.get(str); +} +/** + More general than decamelize. Returns the lower\_case\_and\_underscored + form of a string. + + ```javascript + import { underscore } from '@ember/string'; + + underscore('innerHTML'); // 'inner_html' + underscore('action_name'); // 'action_name' + underscore('css-class-name'); // 'css_class_name' + underscore('my favorite items'); // 'my_favorite_items' + underscore('privateDocs/ownerInvoice'); // 'private_docs/owner_invoice' + ``` + + @method underscore + @param {String} str The string to underscore. + @return {String} the underscored string. + @public +*/ +export function underscore(str) { + return UNDERSCORE_CACHE.get(str); +} \ No newline at end of file diff --git a/addon/package.json b/addon/package.json index 5200f5c3..c63a489c 100644 --- a/addon/package.json +++ b/addon/package.json @@ -27,7 +27,6 @@ }, "devDependencies": {}, "peerDependencies": { - "@ember/string": "^3.0.1", "ember-source": "^4.8.3 || >= 5.0.0" }, "peerDependenciesMeta": { diff --git a/package-lock.json b/package-lock.json index 3d5a4b52..a8d41123 100644 --- a/package-lock.json +++ b/package-lock.json @@ -26,7 +26,7 @@ }, "addon": { "name": "ember-resolver", - "version": "10.0.0", + "version": "11.0.0-beta.0", "license": "MIT", "dependencies": { "ember-cli-babel": "^7.26.11" @@ -36,8 +36,7 @@ "node": "14.* || 16.* || >= 18" }, "peerDependencies": { - "@ember/string": "^3.0.1", - "ember-source": "^4.8.3" + "ember-source": "^4.8.3 || >= 5.0.0" }, "peerDependenciesMeta": { "ember-source": { @@ -1773,17 +1772,6 @@ "node": ">=8" } }, - "node_modules/@ember/string": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@ember/string/-/string-3.0.1.tgz", - "integrity": "sha512-ntnmXS+upOWVXE+rVw2l03DjdMnaGdWbYVUxUBuPJqnIGZu2XFRsoXc7E6mOw62s8i1Xh1RgTuFHN41QGIolEQ==", - "dependencies": { - "ember-cli-babel": "^7.26.6" - }, - "engines": { - "node": "12.* || 14.* || >= 16" - } - }, "node_modules/@ember/test-helpers": { "version": "2.9.3", "resolved": "https://registry.npmjs.org/@ember/test-helpers/-/test-helpers-2.9.3.tgz", @@ -23616,11 +23604,10 @@ } }, "test-app": { - "version": "10.0.0", + "version": "11.0.0-beta.0", "license": "MIT", "devDependencies": { "@ember/optional-features": "^2.0.0", - "@ember/string": "^3.0.1", "@ember/test-helpers": "^2.8.1", "babel-eslint": "^10.1.0", "broccoli-asset-rev": "^3.0.0", @@ -23633,7 +23620,7 @@ "ember-disable-prototype-extensions": "^1.1.3", "ember-load-initializers": "^2.1.2", "ember-qunit": "^6.2.0", - "ember-resolver": "10.0.0", + "ember-resolver": "11.0.0-beta.0", "ember-source": "~4.9.3", "ember-source-channel-url": "^3.0.0", "ember-try": "^2.0.0", @@ -24835,14 +24822,6 @@ } } }, - "@ember/string": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@ember/string/-/string-3.0.1.tgz", - "integrity": "sha512-ntnmXS+upOWVXE+rVw2l03DjdMnaGdWbYVUxUBuPJqnIGZu2XFRsoXc7E6mOw62s8i1Xh1RgTuFHN41QGIolEQ==", - "requires": { - "ember-cli-babel": "^7.26.6" - } - }, "@ember/test-helpers": { "version": "2.9.3", "resolved": "https://registry.npmjs.org/@ember/test-helpers/-/test-helpers-2.9.3.tgz", @@ -40380,7 +40359,6 @@ "version": "file:test-app", "requires": { "@ember/optional-features": "^2.0.0", - "@ember/string": "^3.0.1", "@ember/test-helpers": "^2.8.1", "babel-eslint": "^10.1.0", "broccoli-asset-rev": "^3.0.0", @@ -40393,7 +40371,7 @@ "ember-disable-prototype-extensions": "^1.1.3", "ember-load-initializers": "^2.1.2", "ember-qunit": "^6.2.0", - "ember-resolver": "10.0.0", + "ember-resolver": "11.0.0-beta.0", "ember-source": "~4.9.3", "ember-source-channel-url": "^3.0.0", "ember-try": "^2.0.0", diff --git a/test-app/package.json b/test-app/package.json index 994d0ec2..c4e2c361 100644 --- a/test-app/package.json +++ b/test-app/package.json @@ -29,7 +29,6 @@ "dependencies": {}, "devDependencies": { "@ember/optional-features": "^2.0.0", - "@ember/string": "^3.0.1", "@ember/test-helpers": "^2.8.1", "babel-eslint": "^10.1.0", "broccoli-asset-rev": "^3.0.0", diff --git a/test-app/tests/helpers/create-test-function.js b/test-app/tests/helpers/create-test-function.js new file mode 100644 index 00000000..43dfd8fc --- /dev/null +++ b/test-app/tests/helpers/create-test-function.js @@ -0,0 +1,9 @@ +import { test } from 'qunit'; + +export default function (fn) { + return function (given, expected, description) { + test(description, function (assert) { + assert.deepEqual(fn(given), expected); + }); + }; +} diff --git a/test-app/tests/unit/classify_test.js b/test-app/tests/unit/classify_test.js new file mode 100644 index 00000000..69d61d29 --- /dev/null +++ b/test-app/tests/unit/classify_test.js @@ -0,0 +1,51 @@ +import { module } from 'qunit'; +import { classify } from 'ember-resolver/string/index'; +import createTestFunction from '../helpers/create-test-function'; + +module('classify'); + +const test = createTestFunction(classify); + +test('my favorite items', 'MyFavoriteItems', 'classify normal string'); +test('css-class-name', 'CssClassName', 'classify dasherized string'); +test('action_name', 'ActionName', 'classify underscored string'); +test( + 'privateDocs/ownerInvoice', + 'PrivateDocs/OwnerInvoice', + 'classify namespaced camelized string' +); +test( + 'private_docs/owner_invoice', + 'PrivateDocs/OwnerInvoice', + 'classify namespaced underscored string' +); +test( + 'private-docs/owner-invoice', + 'PrivateDocs/OwnerInvoice', + 'classify namespaced dasherized string' +); +test('-view-registry', '_ViewRegistry', 'classify prefixed dasherized string'); +test( + 'components/-text-field', + 'Components/_TextField', + 'classify namespaced prefixed dasherized string' +); +test('_Foo_Bar', '_FooBar', 'classify underscore-prefixed underscored string'); +test('_Foo-Bar', '_FooBar', 'classify underscore-prefixed dasherized string'); +test( + '_foo/_bar', + '_Foo/_Bar', + 'classify underscore-prefixed-namespaced underscore-prefixed string' +); +test( + '-foo/_bar', + '_Foo/_Bar', + 'classify dash-prefixed-namespaced underscore-prefixed string' +); +test( + '-foo/-bar', + '_Foo/_Bar', + 'classify dash-prefixed-namespaced dash-prefixed string' +); +test('InnerHTML', 'InnerHTML', 'does nothing with classified string'); +test('_FooBar', '_FooBar', 'does nothing with classified prefixed string'); diff --git a/test-app/tests/unit/dasherize_test.js b/test-app/tests/unit/dasherize_test.js new file mode 100644 index 00000000..37aab09b --- /dev/null +++ b/test-app/tests/unit/dasherize_test.js @@ -0,0 +1,32 @@ +import { module } from 'qunit'; +import { dasherize } from 'ember-resolver/string/index'; +import createTestFunction from '../helpers/create-test-function'; + +module('dasherize'); + +const test = createTestFunction(dasherize); + +test('my favorite items', 'my-favorite-items', 'dasherize normal string'); +test('css-class-name', 'css-class-name', 'does nothing with dasherized string'); +test('action_name', 'action-name', 'dasherize underscored string'); +test('innerHTML', 'inner-html', 'dasherize camelcased string'); +test( + 'toString', + 'to-string', + 'dasherize string that is the property name of Object.prototype' +); +test( + 'PrivateDocs/OwnerInvoice', + 'private-docs/owner-invoice', + 'dasherize namespaced classified string' +); +test( + 'privateDocs/ownerInvoice', + 'private-docs/owner-invoice', + 'dasherize namespaced camelized string' +); +test( + 'private_docs/owner_invoice', + 'private-docs/owner-invoice', + 'dasherize namespaced underscored string' +); diff --git a/test-app/tests/unit/decamelize_test.js b/test-app/tests/unit/decamelize_test.js new file mode 100644 index 00000000..4d18aa9e --- /dev/null +++ b/test-app/tests/unit/decamelize_test.js @@ -0,0 +1,31 @@ +import { module } from 'qunit'; +import { decamelize } from 'ember-resolver/string/index'; +import createTestFunction from '../helpers/create-test-function'; + +module('decamelize'); + +const test = createTestFunction(decamelize); + +test( + 'my favorite items', + 'my favorite items', + 'does nothing with normal string' +); +test('css-class-name', 'css-class-name', 'does nothing with dasherized string'); +test('action_name', 'action_name', 'does nothing with underscored string'); +test( + 'innerHTML', + 'inner_html', + 'converts a camelized string into all lower case separated by underscores.' +); +test('size160Url', 'size160_url', 'decamelizes strings with numbers'); +test( + 'PrivateDocs/OwnerInvoice', + 'private_docs/owner_invoice', + 'decamelize namespaced classified string' +); +test( + 'privateDocs/ownerInvoice', + 'private_docs/owner_invoice', + 'decamelize namespaced camelized string' +); diff --git a/test-app/tests/unit/underscore_test.js b/test-app/tests/unit/underscore_test.js new file mode 100644 index 00000000..e9c655fb --- /dev/null +++ b/test-app/tests/unit/underscore_test.js @@ -0,0 +1,27 @@ +import { module } from 'qunit'; +import { underscore } from 'ember-resolver/string/index'; +import createTestFunction from '../helpers/create-test-function'; + +module('underscore'); + +const test = createTestFunction(underscore); + +test('my favorite items', 'my_favorite_items', 'with normal string'); +test('css-class-name', 'css_class_name', 'with dasherized string'); +test('action_name', 'action_name', 'does nothing with underscored string'); +test('innerHTML', 'inner_html', 'with camelcased string'); +test( + 'PrivateDocs/OwnerInvoice', + 'private_docs/owner_invoice', + 'underscore namespaced classified string' +); +test( + 'privateDocs/ownerInvoice', + 'private_docs/owner_invoice', + 'underscore namespaced camelized string' +); +test( + 'private-docs/owner-invoice', + 'private_docs/owner_invoice', + 'underscore namespaced dasherized string' +);