@nejs/basic-extensions 2.2.1 → 2.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -58,9 +58,9 @@
58
58
  "test": "jest"
59
59
  },
60
60
  "type": "module",
61
- "version": "2.2.1",
61
+ "version": "2.3.0",
62
62
  "dependencies": {
63
63
  "@nejs/extension": "^2.7.1"
64
64
  },
65
- "browser": "dist/@nejs/basic-extensions.bundle.2.2.0.js"
65
+ "browser": "dist/@nejs/basic-extensions.bundle.2.2.1.js"
66
66
  }
package/src/index.js CHANGED
@@ -3,7 +3,7 @@ import { ObjectExtensions, ObjectPrototypeExtensions } from './objectextensions.
3
3
  import { MapPrototypeExtensions } from './mapextensions.js'
4
4
  import { SetPrototypeExtensions } from './setextensions.js'
5
5
  import { ReflectExtensions } from './reflectextensions.js'
6
- import { StringExtensions } from './stringextensions.js'
6
+ import { StringExtensions, StringPrototypeExtensions } from './stringextensions.js'
7
7
  import { SymbolExtensions } from './symbolextensions.js'
8
8
  import { ArrayPrototypeExtensions } from './arrayextensions.js'
9
9
  import { DescriptorExtensions, Descriptor } from './newClasses/descriptor.js'
@@ -32,6 +32,7 @@ const StaticPatches = [
32
32
 
33
33
  const InstancePatches = [
34
34
  [Object.prototype, ObjectPrototypeExtensions, Object.name],
35
+ [String.prototype, StringPrototypeExtensions, String.name],
35
36
  [Function.prototype, FunctionPrototypeExtensions, Function.name],
36
37
  [Array.prototype, ArrayPrototypeExtensions, Array.name],
37
38
  [Map.prototype, MapPrototypeExtensions, Map.name],
@@ -137,9 +138,9 @@ export const all = (() => {
137
138
  const instancePatchReducer = (accumulator, [_, patch, ownerName]) => {
138
139
  if (!accumulator?.[ownerName])
139
140
  accumulator[ownerName] = {};
140
-
141
+
141
142
  if (!accumulator[ownerName]?.prototype)
142
- accumulator[ownerName].prototype = {};
143
+ accumulator[ownerName].prototype = {};
143
144
 
144
145
  [...patch].reduce(entriesReducer, accumulator[ownerName].prototype)
145
146
  return accumulator
@@ -151,13 +152,13 @@ export const all = (() => {
151
152
  .flatMap(extension => [...extension])
152
153
  .reduce(entriesReducer, dest.classes)
153
154
  )
154
-
155
+
155
156
  for (const [key, entry] of GlobalFunctionsAndProps) {
156
157
  const descriptor = new Descriptor(entry.descriptor, entry.owner)
157
158
  Object.defineProperty(dest.global, key, descriptor.toObject(true))
158
159
  }
159
160
 
160
- return dest
161
+ return dest
161
162
  })()
162
163
 
163
164
  const results = {
@@ -1,5 +1,7 @@
1
1
  import { Patch } from '@nejs/extension';
2
2
 
3
+ const parenthesisPair = ['(', ')'];
4
+
3
5
  /**
4
6
  * `StringExtensions` is a patch for the JavaScript built-in `String` class. It
5
7
  * adds utility methods to the `String` class without modifying the global namespace
@@ -22,4 +24,141 @@ export const StringExtensions = new Patch(String, {
22
24
  }
23
25
  return false
24
26
  },
27
+
28
+ /**
29
+ * A getter property that returns a pair of parentheses as an array.
30
+ * This property can be used when operations require a clear distinction
31
+ * between the opening and closing parentheses, such as parsing or
32
+ * matching balanced expressions in strings.
33
+ *
34
+ * @returns {[string, string]} An array containing a pair of strings: the
35
+ * opening parenthesis '(' as the first element, and the closing parenthesis
36
+ * ')' as the second element.
37
+ */
38
+ get parenthesisPair() {
39
+ return ['(', ')'];
40
+ },
41
+
42
+ /**
43
+ * A getter property that returns a pair of square brackets as an array.
44
+ * This property is particularly useful for operations that require a clear
45
+ * distinction between the opening and closing square brackets, such as
46
+ * parsing arrays in strings or matching balanced expressions within
47
+ * square brackets.
48
+ *
49
+ * @returns {[string, string]} An array containing a pair of strings: the
50
+ * opening square bracket '[' as the first element, and the closing square
51
+ * bracket ']' as the second element.
52
+ */
53
+ get squareBracketsPair() {
54
+ return ['[', ']'];
55
+ },
56
+
57
+ /**
58
+ * A getter property that returns a pair of curly brackets as an array.
59
+ * This property is particularly useful for operations that require a clear
60
+ * distinction between the opening and closing curly brackets, such as
61
+ * parsing objects in strings or matching balanced expressions within
62
+ * curly brackets. The returned array consists of the opening curly bracket
63
+ * '{' as the first element, and the closing curly bracket '}' as the
64
+ * second element.
65
+ *
66
+ * @returns {[string, string]} An array containing a pair of strings: the
67
+ * opening curly bracket '{' as the first element, and the closing curly
68
+ * bracket '}' as the second element.
69
+ */
70
+ get curlyBracketsPair() {
71
+ return ['{', '}'];
72
+ },
25
73
  });
74
+
75
+ /**
76
+ * `StringPrototypeExtensions` provides a set of utility methods that are
77
+ * added to the `String` prototype. This allows all string instances to
78
+ * access new functionality directly, enhancing their capabilities beyond
79
+ * the standard `String` class methods. These extensions are applied using
80
+ * the `Patch` class from '@nejs/extension', ensuring that they do not
81
+ * interfere with the global namespace or existing properties.
82
+ *
83
+ * The extensions include methods for extracting substrings based on
84
+ * specific tokens, checking the presence of certain patterns, and more,
85
+ * making string manipulation tasks more convenient and expressive.
86
+ */
87
+ export const StringPrototypeExtensions = new Patch(String.prototype, {
88
+ /**
89
+ * Extracts a substring from the current string, starting at a given offset
90
+ * and bounded by specified opening and closing tokens. This method is
91
+ * particularly useful for parsing nested structures or quoted strings,
92
+ * where the level of nesting or the presence of escape characters must
93
+ * be considered.
94
+ *
95
+ * @param {number} offset The position in the string from which to start the
96
+ * search for the substring.
97
+ * @param {[string, string]} tokens An array containing two strings: the
98
+ * opening and closing tokens that define the boundaries of the substring
99
+ * to be extracted.
100
+ * @returns {Object} An object with two properties: `extracted`, the
101
+ * extracted substring, and `newOffset`, the position in the original
102
+ * string immediately after the end of the extracted substring. If no
103
+ * substring is found, `extracted` is `null` and `newOffset` is the same
104
+ * as the input offset.
105
+ */
106
+ extractSubstring(offset = 0, tokens = parenthesisPair) {
107
+ let [openToken, closeToken] = tokens;
108
+ let depth = 0;
109
+ let start = -1;
110
+ let end = -1;
111
+ let leadingToken = '';
112
+ let firstToken = 0;
113
+
114
+ for (let i = offset; i < this.length; i++) {
115
+ const char = this[i];
116
+
117
+ if (char === openToken) {
118
+ depth++;
119
+ if (start === -1)
120
+ start = i;
121
+ }
122
+ else if (char === closeToken) {
123
+ depth--;
124
+ if (depth === 0) {
125
+ end = i;
126
+ break;
127
+ }
128
+ }
129
+ }
130
+
131
+ let lRange = [
132
+ Math.max(0, start - 100),
133
+ start
134
+ ];
135
+ let leading = [...this.substring(lRange[0], lRange[1])].reverse().join('')
136
+ let reversedLeadingToken;
137
+
138
+ try {
139
+ reversedLeadingToken = /([^ \,\"\'\`]+)/.exec(leading)[1] ?? '';
140
+ leadingToken = [...reversedLeadingToken].reverse().join('');
141
+ }
142
+ catch(ignored) { }
143
+
144
+ if (start !== -1 && end !== -1) {
145
+ const sliceRange = [start, end + 1];
146
+ const extracted = this.slice(sliceRange[0], sliceRange[1]);
147
+
148
+ return {
149
+ extracted,
150
+ range: [start, end],
151
+ newOffset: end + 1,
152
+ leadingToken,
153
+ };
154
+ }
155
+ else {
156
+ return {
157
+ extracted: null,
158
+ range: [start, end],
159
+ newOffset: offset,
160
+ leadingToken,
161
+ };
162
+ }
163
+ },
164
+ })
@@ -0,0 +1,54 @@
1
+ const { Patches } = require('../dist/cjs/index.js')
2
+ const ArrayPrototypeExtensions = Patches.get(Array.prototype)
3
+
4
+ ArrayPrototypeExtensions.apply();
5
+
6
+ describe('ArrayPrototypeExtensions', () => {
7
+ describe('contains method', () => {
8
+ it('should return true if the array contains the specified element', () => {
9
+ const arr = [1, 2, 3];
10
+ expect(arr.contains(2)).toBeTruthy();
11
+ });
12
+
13
+ it('should return false if the array does not contain the specified element', () => {
14
+ const arr = [1, 2, 3];
15
+ expect(arr.contains(4)).toBeFalsy();
16
+ });
17
+ });
18
+
19
+ describe('findEntry method', () => {
20
+ it('should return the first matching [index, value] entry', () => {
21
+ const arr = ['a', 'b', 'c'];
22
+ expect(arr.findEntry(x => x === 'b')).toEqual([1, 'b']);
23
+ });
24
+
25
+ it('should return undefined if no match is found', () => {
26
+ const arr = ['a', 'b', 'c'];
27
+ expect(arr.findEntry(x => x === 'd')).toBeUndefined();
28
+ });
29
+ });
30
+
31
+ describe('first getter', () => {
32
+ it('should return the first element of the array', () => {
33
+ const arr = [1, 2, 3];
34
+ expect(arr.first).toBe(1);
35
+ });
36
+
37
+ it('should return undefined if the array is empty', () => {
38
+ const arr = [];
39
+ expect(arr.first).toBeUndefined();
40
+ });
41
+ });
42
+
43
+ describe('last getter', () => {
44
+ it('should return the last element of the array', () => {
45
+ const arr = [1, 2, 3];
46
+ expect(arr.last).toBe(3);
47
+ });
48
+
49
+ it('should return undefined if the array is empty', () => {
50
+ const arr = [];
51
+ expect(arr.last).toBeUndefined();
52
+ });
53
+ });
54
+ });
@@ -0,0 +1,60 @@
1
+ const { Patches } = require('../dist/cjs/index.js')
2
+ const StringExtensions = Patches.get(String)
3
+ const StringPrototypeExtensions = Patches.get(String.prototype)
4
+
5
+ describe('StringExtensions', () => {
6
+ beforeAll(() => {
7
+ // Apply the StringExtensions patch
8
+ StringExtensions.apply();
9
+ });
10
+
11
+ test('isString should correctly identify strings', () => {
12
+ expect(String.isString('hello')).toBe(true);
13
+ expect(String.isString(new String('hello'))).toBe(true);
14
+ expect(String.isString(123)).toBe(false);
15
+ expect(String.isString(null)).toBe(false);
16
+ expect(String.isString(undefined)).toBe(false);
17
+ expect(String.isString({})).toBe(false);
18
+ });
19
+
20
+ test('parenthesisPair should return correct pair', () => {
21
+ expect(String.parenthesisPair).toEqual(['(', ')']);
22
+ });
23
+
24
+ test('squareBracketsPair should return correct pair', () => {
25
+ expect(String.squareBracketsPair).toEqual(['[', ']']);
26
+ });
27
+
28
+ test('curlyBracketsPair should return correct pair', () => {
29
+ expect(String.curlyBracketsPair).toEqual(['{', '}']);
30
+ });
31
+ });
32
+
33
+ describe('StringPrototypeExtensions', () => {
34
+ beforeAll(() => {
35
+ // Apply the StringPrototypeExtensions patch
36
+ StringPrototypeExtensions.apply();
37
+ });
38
+
39
+ test('extractSubstring should correctly extract substrings', () => {
40
+ const testString = "This is a test (with a substring) and some more text.";
41
+ const result = testString.extractSubstring(0, ['(', ')']);
42
+ expect(result).toEqual({
43
+ extracted: "(with a substring)",
44
+ range: [15, 32],
45
+ newOffset: 33,
46
+ leadingToken: 'test',
47
+ });
48
+ });
49
+
50
+ test('extractSubstring with no matching tokens should return null', () => {
51
+ const testString = "No parentheses here.";
52
+ const result = testString.extractSubstring();
53
+ expect(result).toEqual({
54
+ extracted: null,
55
+ range: [-1, -1],
56
+ newOffset: 0,
57
+ leadingToken: '',
58
+ });
59
+ });
60
+ });