Browse Source

fix: environment namespace duplication

e22m4u 8 months ago
parent
commit
b2633e2261
3 changed files with 140 additions and 97 deletions
  1. 17 14
      dist/cjs/index.cjs
  2. 29 23
      src/create-debugger.js
  3. 94 60
      src/create-debugger.spec.js

+ 17 - 14
dist/cjs/index.cjs

@@ -190,18 +190,21 @@ function createDebugger(namespaceOrOptions = void 0, ...namespaceSegments) {
       namespaceOrOptions
     );
   }
-  const state = isNonArrayObject(namespaceOrOptions) ? namespaceOrOptions : {};
+  const withCustomState = isNonArrayObject(namespaceOrOptions);
+  const state = withCustomState ? namespaceOrOptions : {};
   state.nsSegments = Array.isArray(state.nsSegments) ? state.nsSegments : [];
   state.pattern = typeof state.pattern === "string" ? state.pattern : "";
   state.hash = typeof state.hash === "string" ? state.hash : "";
   state.offsetSize = typeof state.offsetSize === "number" ? state.offsetSize : 0;
   state.offsetStep = typeof state.offsetStep === "string" ? state.offsetStep : "   ";
   state.delimiter = state.delimiter && typeof state.delimiter === "string" ? state.delimiter : ":";
-  if (typeof process !== "undefined" && process.env && process.env["DEBUGGER_NAMESPACE"]) {
-    state.nsSegments.push(process.env.DEBUGGER_NAMESPACE);
+  if (!withCustomState) {
+    if (typeof process !== "undefined" && process.env && process.env["DEBUGGER_NAMESPACE"]) {
+      state.nsSegments.push(process.env.DEBUGGER_NAMESPACE);
+    }
+    if (typeof namespaceOrOptions === "string")
+      state.nsSegments.push(namespaceOrOptions);
   }
-  if (typeof namespaceOrOptions === "string")
-    state.nsSegments.push(namespaceOrOptions);
   namespaceSegments.forEach((segment) => {
     if (!segment || typeof segment !== "string")
       throw new import_js_format.Errorf(
@@ -256,38 +259,38 @@ function createDebugger(namespaceOrOptions = void 0, ...namespaceSegments) {
   }
   __name(debugFn, "debugFn");
   debugFn.withNs = function(namespace, ...args) {
-    const stCopy = JSON.parse(JSON.stringify(state));
+    const stateCopy = JSON.parse(JSON.stringify(state));
     [namespace, ...args].forEach((ns) => {
       if (!ns || typeof ns !== "string")
         throw new import_js_format.Errorf(
           "Debugger namespace must be a non-empty String, but %v given.",
           ns
         );
-      stCopy.nsSegments.push(ns);
+      stateCopy.nsSegments.push(ns);
     });
-    return createDebugger(stCopy);
+    return createDebugger(stateCopy);
   };
   debugFn.withHash = function(hashLength = 4) {
-    const stCopy = JSON.parse(JSON.stringify(state));
+    const stateCopy = JSON.parse(JSON.stringify(state));
     if (!hashLength || typeof hashLength !== "number" || hashLength < 1) {
       throw new import_js_format.Errorf(
         "Debugger hash must be a positive Number, but %v given.",
         hashLength
       );
     }
-    stCopy.hash = generateRandomHex(hashLength);
-    return createDebugger(stCopy);
+    stateCopy.hash = generateRandomHex(hashLength);
+    return createDebugger(stateCopy);
   };
   debugFn.withOffset = function(offsetSize) {
-    const stCopy = JSON.parse(JSON.stringify(state));
+    const stateCopy = JSON.parse(JSON.stringify(state));
     if (!offsetSize || typeof offsetSize !== "number" || offsetSize < 1) {
       throw new import_js_format.Errorf(
         "Debugger offset must be a positive Number, but %v given.",
         offsetSize
       );
     }
-    stCopy.offsetSize = offsetSize;
-    return createDebugger(stCopy);
+    stateCopy.offsetSize = offsetSize;
+    return createDebugger(stateCopy);
   };
   return debugFn;
 }

+ 29 - 23
src/create-debugger.js

@@ -135,7 +135,8 @@ export function createDebugger(
   }
   // формирование состояния отладчика
   // для хранения текущих настроек
-  const state = isNonArrayObject(namespaceOrOptions) ? namespaceOrOptions : {};
+  const withCustomState = isNonArrayObject(namespaceOrOptions);
+  const state = withCustomState ? namespaceOrOptions : {};
   state.nsSegments = Array.isArray(state.nsSegments) ? state.nsSegments : [];
   state.pattern = typeof state.pattern === 'string' ? state.pattern : '';
   state.hash = typeof state.hash === 'string' ? state.hash : '';
@@ -147,20 +148,25 @@ export function createDebugger(
     state.delimiter && typeof state.delimiter === 'string'
       ? state.delimiter
       : ':';
-  // если переменная окружения содержит пространство
-  // имен, то значение переменной добавляется
-  // в общий список
-  if (
-    typeof process !== 'undefined' &&
-    process.env &&
-    process.env['DEBUGGER_NAMESPACE']
-  ) {
-    state.nsSegments.push(process.env.DEBUGGER_NAMESPACE);
+  // если первым аргументом не является объект состояния,
+  // то дополнительно проверяется наличие сегмента пространства
+  // имен в переменной окружения, и сегмент из первого аргумента
+  if (!withCustomState) {
+    // если переменная окружения содержит пространство
+    // имен, то значение переменной добавляется
+    // в общий список
+    if (
+      typeof process !== 'undefined' &&
+      process.env &&
+      process.env['DEBUGGER_NAMESPACE']
+    ) {
+      state.nsSegments.push(process.env.DEBUGGER_NAMESPACE);
+    }
+    // если первый аргумент содержит значение,
+    // то оно используется как пространство имен
+    if (typeof namespaceOrOptions === 'string')
+      state.nsSegments.push(namespaceOrOptions);
   }
-  // если первый аргумент содержит значение,
-  // то оно используется как пространство имен
-  if (typeof namespaceOrOptions === 'string')
-    state.nsSegments.push(namespaceOrOptions);
   // проверка типа дополнительных сегментов пространства
   // имен, и добавление их в общий набор сегментов
   namespaceSegments.forEach(segment => {
@@ -233,42 +239,42 @@ export function createDebugger(
   // создание новой функции логирования
   // с дополнительным пространством имен
   debugFn.withNs = function (namespace, ...args) {
-    const stCopy = JSON.parse(JSON.stringify(state));
+    const stateCopy = JSON.parse(JSON.stringify(state));
     [namespace, ...args].forEach(ns => {
       if (!ns || typeof ns !== 'string')
         throw new Errorf(
           'Debugger namespace must be a non-empty String, but %v given.',
           ns,
         );
-      stCopy.nsSegments.push(ns);
+      stateCopy.nsSegments.push(ns);
     });
-    return createDebugger(stCopy);
+    return createDebugger(stateCopy);
   };
   // создание новой функции логирования
   // со статическим хэшем
   debugFn.withHash = function (hashLength = 4) {
-    const stCopy = JSON.parse(JSON.stringify(state));
+    const stateCopy = JSON.parse(JSON.stringify(state));
     if (!hashLength || typeof hashLength !== 'number' || hashLength < 1) {
       throw new Errorf(
         'Debugger hash must be a positive Number, but %v given.',
         hashLength,
       );
     }
-    stCopy.hash = generateRandomHex(hashLength);
-    return createDebugger(stCopy);
+    stateCopy.hash = generateRandomHex(hashLength);
+    return createDebugger(stateCopy);
   };
   // создание новой функции логирования
   // со смещением сообщений отладки
   debugFn.withOffset = function (offsetSize) {
-    const stCopy = JSON.parse(JSON.stringify(state));
+    const stateCopy = JSON.parse(JSON.stringify(state));
     if (!offsetSize || typeof offsetSize !== 'number' || offsetSize < 1) {
       throw new Errorf(
         'Debugger offset must be a positive Number, but %v given.',
         offsetSize,
       );
     }
-    stCopy.offsetSize = offsetSize;
-    return createDebugger(stCopy);
+    stateCopy.offsetSize = offsetSize;
+    return createDebugger(stateCopy);
   };
   return debugFn;
 }

+ 94 - 60
src/create-debugger.spec.js

@@ -109,8 +109,6 @@ describe('createDebugger', function () {
       const debug = createDebugger('obj');
       const data = {a: 1, b: {c: 'deep'}};
       debug(data);
-      // ожидаем, что inspect будет вызван и его результат
-      // будет выведен построчно
       const expectedInspect = inspect(data, {
         colors: true,
         depth: null,
@@ -119,13 +117,9 @@ describe('createDebugger', function () {
       const expectedLines = expectedInspect.split('\n');
       expect(consoleLogSpy.callCount).to.equal(expectedLines.length);
       expectedLines.forEach((line, index) => {
-        // проверяем каждую строку с префиксом
-        // замечание: точное сравнение с inspect может быть хрупким из-за версий node/util
-        // здесь мы проверяем, что префикс есть и остальная часть строки соответствует inspect
         expect(stripAnsi(consoleLogSpy.getCall(index).args[0])).to.contain(
           'obj ',
         );
-        // ожидаем, что строка вывода содержит соответствующую строку из inspect (без цвета)
         expect(stripAnsi(consoleLogSpy.getCall(index).args[0])).to.have.string(
           stripAnsi(line),
         );
@@ -144,14 +138,11 @@ describe('createDebugger', function () {
         compact: false,
       });
       const expectedLines = expectedInspect.split('\n');
-      // 1 для описания + строки объекта
       const totalExpectedCalls = 1 + expectedLines.length;
       expect(consoleLogSpy.callCount).to.equal(totalExpectedCalls);
-      // первая строка - описание
       expect(stripAnsi(consoleLogSpy.getCall(0).args[0])).to.equal(
         `objdesc ${description}`,
       );
-      // последующие строки - объект
       expectedLines.forEach((line, index) => {
         const callIndex = index + 1;
         expect(stripAnsi(consoleLogSpy.getCall(callIndex).args[0])).to.contain(
@@ -165,6 +156,7 @@ describe('createDebugger', function () {
   });
 
   describe('namespaces', function () {
+    // --- 1. Базовое создание (createDebugger) ---
     it('should use namespace provided in createDebugger', function () {
       process.env.DEBUG = 'app';
       const debug = createDebugger('app');
@@ -194,11 +186,10 @@ describe('createDebugger', function () {
       );
     });
 
+    // --- 2. Влияние DEBUGGER_NAMESPACE при создании ---
     it('should use namespace from DEBUGGER_NAMESPACE env variable', function () {
       process.env.DEBUGGER_NAMESPACE = 'base';
-      // должен быть включен для вывода
       process.env.DEBUG = 'base';
-      // без явного namespace
       const debug = createDebugger();
       debug('message');
       expect(stripAnsi(consoleLogSpy.firstCall.args[0])).to.equal(
@@ -227,6 +218,7 @@ describe('createDebugger', function () {
       );
     });
 
+    // --- 3. Модификация (withNs) ---
     it('should extend namespace with withNs()', function () {
       process.env.DEBUG = 'app:service';
       const debugApp = createDebugger('app');
@@ -237,6 +229,25 @@ describe('createDebugger', function () {
       );
     });
 
+    it('should extend namespace with multiple args in withNs()', function () {
+      process.env.DEBUG = 'app:service:module';
+      const debugApp = createDebugger('app');
+      const debugService = debugApp.withNs('service', 'module');
+      debugService('message');
+      expect(stripAnsi(consoleLogSpy.firstCall.args[0])).to.equal(
+        'app:service:module message',
+      );
+    });
+
+    it('should allow chaining withNs()', function () {
+      process.env.DEBUG = 'app:service:module';
+      const debug = createDebugger('app').withNs('service').withNs('module');
+      debug('message');
+      expect(stripAnsi(consoleLogSpy.firstCall.args[0])).to.equal(
+        'app:service:module message',
+      );
+    });
+
     it('should extend namespace with withNs() after creating with multiple segments', function () {
       process.env.DEBUG = 'app:svc:mod';
       const debugBase = createDebugger('app', 'svc');
@@ -248,25 +259,44 @@ describe('createDebugger', function () {
       );
     });
 
-    it('should extend namespace with multiple args in withNs()', function () {
-      process.env.DEBUG = 'app:service:module';
-      const debugApp = createDebugger('app');
-      const debugService = debugApp.withNs('service', 'module');
+    // --- 4. Взаимодействие DEBUGGER_NAMESPACE с модификаторами (Тесты на фикс дублирования) ---
+    it('should NOT duplicate DEBUGGER_NAMESPACE when using withNs', function () {
+      process.env.DEBUGGER_NAMESPACE = 'envNs';
+      process.env.DEBUG = 'envNs:service';
+      const debugBase = createDebugger();
+      const debugService = debugBase.withNs('service');
       debugService('message');
+      expect(consoleLogSpy.calledOnce).to.be.true;
       expect(stripAnsi(consoleLogSpy.firstCall.args[0])).to.equal(
-        'app:service:module message',
+        'envNs:service message',
       );
     });
 
-    it('should allow chaining withNs()', function () {
-      process.env.DEBUG = 'app:service:module';
-      const debug = createDebugger('app').withNs('service').withNs('module');
-      debug('message');
+    it('should correctly combine DEBUGGER_NAMESPACE, initial segments, and withNs without duplicates', function () {
+      process.env.DEBUGGER_NAMESPACE = 'envNs';
+      process.env.DEBUG = 'envNs:init1:init2:added';
+      const debugBase = createDebugger('init1', 'init2');
+      const debugAdded = debugBase.withNs('added');
+      debugAdded('combined message');
+      expect(consoleLogSpy.calledOnce).to.be.true;
       expect(stripAnsi(consoleLogSpy.firstCall.args[0])).to.equal(
-        'app:service:module message',
+        'envNs:init1:init2:added combined message',
       );
     });
 
+    it('should work correctly with withNs when DEBUGGER_NAMESPACE is NOT set', function () {
+      expect(process.env.DEBUGGER_NAMESPACE).to.be.undefined;
+      process.env.DEBUG = 'app:service';
+      const debugBase = createDebugger('app');
+      const debugService = debugBase.withNs('service');
+      debugService('message');
+      expect(consoleLogSpy.calledOnce).to.be.true;
+      expect(stripAnsi(consoleLogSpy.firstCall.args[0])).to.equal(
+        'app:service message',
+      );
+    });
+
+    // --- 5. Обработка ошибок ---
     it('should throw error if createDebugger is called with invalid subsequent segment type', function () {
       expect(() => createDebugger('app', 'valid', 123)).to.throw(
         /Namespace segment must be a non-empty String/,
@@ -279,18 +309,24 @@ describe('createDebugger', function () {
       );
       expect(() => createDebugger('app', '')).to.throw(
         /Namespace segment must be a non-empty String/,
-      ); // Также проверяем пустой второй сегмент
+      );
     });
 
     it('should throw error if withNs is called with non-string', function () {
       const debug = createDebugger('app');
-      expect(() => debug.withNs(123)).to.throw(/must be a non-empty String/);
-      expect(() => debug.withNs(null)).to.throw(/must be a non-empty String/);
-      expect(() => debug.withNs('')).to.throw(/must be a non-empty String/);
+      expect(() => debug.withNs(123)).to.throw(
+        /Debugger namespace must be a non-empty String/,
+      );
+      expect(() => debug.withNs(null)).to.throw(
+        /Debugger namespace must be a non-empty String/,
+      );
+      expect(() => debug.withNs('')).to.throw(
+        /Debugger namespace must be a non-empty String/,
+      );
     });
   });
 
-  describe('DEBUG / localStorage', function () {
+  describe('environment', function () {
     it('should enable debugger based on exact match in DEBUG', function () {
       process.env.DEBUG = 'app:service';
       const debug = createDebugger('app:service');
@@ -341,7 +377,6 @@ describe('createDebugger', function () {
       debugAppSvc('1');
       debugAppDb('2');
       debugSvcAuth('3');
-      // не должен выводиться
       debugSvcOther('4');
       debugOther('5');
       expect(consoleLogSpy.calledThrice).to.be.true;
@@ -357,14 +392,12 @@ describe('createDebugger', function () {
     });
 
     it('should handle multiple patterns in DEBUG (space)', function () {
-      // используем пробел
       process.env.DEBUG = 'app:* svc:auth';
       const debugAppSvc = createDebugger('app:service');
       const debugSvcAuth = createDebugger('svc:auth');
       const debugOther = createDebugger('other');
       debugAppSvc('1');
       debugSvcAuth('3');
-      // не должен выводиться
       debugOther('5');
       expect(consoleLogSpy.calledTwice).to.be.true;
       expect(stripAnsi(consoleLogSpy.getCall(0).args[0])).to.contain(
@@ -376,7 +409,6 @@ describe('createDebugger', function () {
     });
 
     it('should use localStorage pattern if DEBUG env is not set', function () {
-      // process.env.debug не установлен (по умолчанию в beforeEach)
       global.localStorage.setItem('debug', 'local:*');
       const debugLocal = createDebugger('local:test');
       const debugOther = createDebugger('other:test');
@@ -394,7 +426,6 @@ describe('createDebugger', function () {
       const debugEnv = createDebugger('env:test');
       const debugLocal = createDebugger('local:test');
       debugEnv('message env');
-      // не должен выводиться
       debugLocal('message local');
       expect(consoleLogSpy.calledOnce).to.be.true;
       expect(stripAnsi(consoleLogSpy.firstCall.args[0])).to.equal(
@@ -406,11 +437,9 @@ describe('createDebugger', function () {
   describe('hashing', function () {
     it('should add a hash prefix with withHash()', function () {
       process.env.DEBUG = 'hash';
-      // default length 4
       const debug = createDebugger('hash').withHash();
       debug('message');
       expect(consoleLogSpy.calledOnce).to.be.true;
-      // проверяем формат: namespace:hash message
       expect(stripAnsi(consoleLogSpy.firstCall.args[0])).to.match(
         /^hash:[a-f0-9]{4} message$/,
       );
@@ -444,7 +473,6 @@ describe('createDebugger', function () {
       const hash2 = stripAnsi(consoleLogSpy.getCall(1).args[0]).match(
         /hash:([a-f0-9]{4})/,
       )[1];
-      // вероятность коллизии крайне мала для 4 символов
       expect(hash1).to.not.equal(hash2);
     });
 
@@ -464,19 +492,29 @@ describe('createDebugger', function () {
       expect(() => debug.withHash(null)).to.throw(/must be a positive Number/);
       expect(() => debug.withHash('abc')).to.throw(/must be a positive Number/);
     });
+
+    it('should NOT duplicate DEBUGGER_NAMESPACE when using withHash', function () {
+      process.env.DEBUGGER_NAMESPACE = 'envNs';
+      process.env.DEBUG = 'envNs';
+      const debugBase = createDebugger();
+      const debugHashed = debugBase.withHash();
+      debugHashed('message');
+      expect(consoleLogSpy.calledOnce).to.be.true;
+      expect(stripAnsi(consoleLogSpy.firstCall.args[0])).to.match(
+        /^envNs:[a-f0-9]{4} message$/,
+      );
+    });
   });
 
   describe('offset', function () {
-    // предупреждение: ожидания в этом тесте (`offset    message1`) могут не соответствовать
-    // предполагаемой логике (2 пробела на уровень), проверьте реализацию `getPrefix` и `offsetStep`.
     it('should add offset spaces with withOffset()', function () {
       process.env.DEBUG = 'offset';
+      // предполагая, что offsetStep = '   '
       const debug1 = createDebugger('offset').withOffset(1);
       const debug2 = createDebugger('offset').withOffset(2);
       debug1('message1');
       debug2('message2');
       expect(consoleLogSpy.calledTwice).to.be.true;
-      // проверяем отступы (этот комментарий может быть неточен, см. предупреждение выше)
       expect(stripAnsi(consoleLogSpy.getCall(0).args[0])).to.equal(
         'offset    message1',
       );
@@ -490,20 +528,17 @@ describe('createDebugger', function () {
       const debug = createDebugger('offsetobj').withOffset(1);
       const data = {a: 1, b: 2};
       debug(data);
-
       const expectedInspect = inspect(data, {
         colors: true,
         depth: null,
         compact: false,
       });
       const expectedLines = expectedInspect.split('\n');
-
       expect(consoleLogSpy.callCount).to.equal(expectedLines.length);
       expectedLines.forEach((line, index) => {
-        // ожидаем префикс + отступ + текст строки
-        // предупреждение: \s{2} здесь может не соответствовать ожиданиям в других тестах смещения.
+        // предполагая, что offsetStep = '   '
         expect(stripAnsi(consoleLogSpy.getCall(index).args[0])).to.match(
-          /^offsetobj\s{2}/,
+          /^offsetobj\s{4}/,
         );
         expect(stripAnsi(consoleLogSpy.getCall(index).args[0])).to.contain(
           stripAnsi(line),
@@ -522,6 +557,19 @@ describe('createDebugger', function () {
         /must be a positive Number/,
       );
     });
+
+    it('should NOT duplicate DEBUGGER_NAMESPACE when using withOffset', function () {
+      process.env.DEBUGGER_NAMESPACE = 'envNs';
+      process.env.DEBUG = 'envNs';
+      const debugBase = createDebugger();
+      const debugOffset = debugBase.withOffset(1);
+      debugOffset('message');
+      expect(consoleLogSpy.calledOnce).to.be.true;
+      // предполагая, что offsetStep = '   '
+      expect(stripAnsi(consoleLogSpy.firstCall.args[0])).to.equal(
+        'envNs    message',
+      );
+    });
   });
 
   describe('combine', function () {
@@ -532,10 +580,8 @@ describe('createDebugger', function () {
         .withHash(5)
         .withOffset(1);
       debug('combined message');
-
       expect(consoleLogSpy.calledOnce).to.be.true;
-      // ожидаемый формат: namespace:hash<offset>message
-      // предупреждение: \s{4} здесь может не соответствовать ожиданиям в других тестах смещения.
+      // предполагая, что offsetStep = '   '
       expect(stripAnsi(consoleLogSpy.firstCall.args[0])).to.match(
         /^app:svc:[a-f0-9]{5}\s{4}combined message$/,
       );
@@ -547,11 +593,9 @@ describe('createDebugger', function () {
         .withNs('svc')
         .withHash(3)
         .withOffset(1);
-
       const data = {id: 123};
       const description = 'Data:';
       debug(data, description);
-
       const expectedInspect = inspect(data, {
         colors: true,
         depth: null,
@@ -559,23 +603,16 @@ describe('createDebugger', function () {
       });
       const expectedLines = expectedInspect.split('\n');
       const totalExpectedCalls = 1 + expectedLines.length;
-
       expect(consoleLogSpy.callCount).to.equal(totalExpectedCalls);
-
-      // проверяем строку описания
-      // предупреждение: \s{4} здесь может не соответствовать ожиданиям в других тестах смещения.
+      // предполагая, что offsetStep = '   '
       expect(stripAnsi(consoleLogSpy.getCall(0).args[0])).to.match(
         /^app:svc:[a-f0-9]{3}\s{4}Data:$/,
       );
-
-      // проверяем строки объекта
       expectedLines.forEach((line, index) => {
         const callIndex = index + 1;
         const logLine = stripAnsi(consoleLogSpy.getCall(callIndex).args[0]);
-        // префикс, хэш, отступ
-        // предупреждение: \s{2} здесь может не соответствовать ожиданиям в других тестах смещения.
-        expect(logLine).to.match(/^app:svc:[a-f0-9]{3}\s{2}/);
-        // содержимое строки inspect
+        // предполагая, что offsetStep = '   '
+        expect(logLine).to.match(/^app:svc:[a-f0-9]{3}\s{4}/);
         expect(logLine).to.contain(stripAnsi(line));
       });
     });
@@ -589,12 +626,9 @@ describe('createDebugger', function () {
       expect(() => createDebugger(true)).to.throw(
         /must be a String or an Object/,
       );
-      // массив - не простой объект
       expect(() => createDebugger([])).to.throw(
         /must be a String or an Object/,
       );
-      // null должен вызывать ошибку (проверяется отдельно или убедитесь, что isNonArrayObject(null) === false)
-      // expect(() => createDebugger(null)).to.throw(/must be a String or an Object/);
     });
   });
 });