1'use strict';
2
3const {
4  ArrayIsArray,
5  Error,
6  ErrorPrototypeToString,
7  ErrorCaptureStackTrace,
8  String,
9} = primordials;
10
11const assert = require('internal/assert');
12const {
13  codes: {
14    ERR_INVALID_ARG_TYPE,
15  },
16  isErrorStackTraceLimitWritable,
17} = require('internal/errors');
18const { validateString } = require('internal/validators');
19
20// Lazily loaded
21let fs;
22let fd;
23let warningFile;
24let options;
25let traceWarningHelperShown = false;
26
27function resetForSerialization() {
28  if (fd !== undefined) {
29    process.removeListener('exit', closeFdOnExit);
30  }
31  fd = undefined;
32  warningFile = undefined;
33  traceWarningHelperShown = false;
34}
35
36function lazyOption() {
37  // This will load `warningFile` only once. If the flag is not set,
38  // `warningFile` will be set to an empty string.
39  if (warningFile === undefined) {
40    options = require('internal/options');
41    if (options.getOptionValue('--diagnostic-dir') !== '') {
42      warningFile = options.getOptionValue('--diagnostic-dir');
43    }
44    if (options.getOptionValue('--redirect-warnings') !== '') {
45      warningFile = options.getOptionValue('--redirect-warnings');
46    } else {
47      warningFile = '';
48    }
49  }
50  return warningFile;
51}
52
53// If we can't write to stderr, we'd like to make this a noop,
54// so use console.error.
55let error;
56function writeOut(message) {
57  if (!error) {
58    error = require('internal/console/global').error;
59  }
60  error(message);
61}
62
63function closeFdOnExit() {
64  try {
65    fs.closeSync(fd);
66  } catch {
67    // Continue regardless of error.
68  }
69}
70
71function writeToFile(message) {
72  if (fd === undefined) {
73    fs = require('fs');
74    try {
75      fd = fs.openSync(warningFile, 'a');
76    } catch {
77      return writeOut(message);
78    }
79    process.on('exit', closeFdOnExit);
80  }
81  fs.appendFile(fd, `${message}\n`, (err) => {
82    if (err) {
83      writeOut(message);
84    }
85  });
86}
87
88function doEmitWarning(warning) {
89  process.emit('warning', warning);
90}
91
92function onWarning(warning) {
93  if (!(warning instanceof Error)) return;
94  const isDeprecation = warning.name === 'DeprecationWarning';
95  if (isDeprecation && process.noDeprecation) return;
96  const trace = process.traceProcessWarnings ||
97                (isDeprecation && process.traceDeprecation);
98  let msg = `(${process.release.name}:${process.pid}) `;
99  if (warning.code)
100    msg += `[${warning.code}] `;
101  if (trace && warning.stack) {
102    msg += `${warning.stack}`;
103  } else {
104    msg +=
105      typeof warning.toString === 'function' ?
106        `${warning.toString()}` :
107        ErrorPrototypeToString(warning);
108  }
109  if (typeof warning.detail === 'string') {
110    msg += `\n${warning.detail}`;
111  }
112  if (!trace && !traceWarningHelperShown) {
113    const flag = isDeprecation ? '--trace-deprecation' : '--trace-warnings';
114    const argv0 = require('path').basename(process.argv0 || 'node', '.exe');
115    msg += `\n(Use \`${argv0} ${flag} ...\` to show where the warning ` +
116           'was created)';
117    traceWarningHelperShown = true;
118  }
119  const warningFile = lazyOption();
120  if (warningFile) {
121    return writeToFile(msg);
122  }
123  writeOut(msg);
124}
125
126// process.emitWarning(error)
127// process.emitWarning(str[, type[, code]][, ctor])
128// process.emitWarning(str[, options])
129function emitWarning(warning, type, code, ctor) {
130  let detail;
131  if (type !== null && typeof type === 'object' && !ArrayIsArray(type)) {
132    ctor = type.ctor;
133    code = type.code;
134    if (typeof type.detail === 'string')
135      detail = type.detail;
136    type = type.type || 'Warning';
137  } else if (typeof type === 'function') {
138    ctor = type;
139    code = undefined;
140    type = 'Warning';
141  }
142  if (type !== undefined)
143    validateString(type, 'type');
144  if (typeof code === 'function') {
145    ctor = code;
146    code = undefined;
147  } else if (code !== undefined) {
148    validateString(code, 'code');
149  }
150  if (typeof warning === 'string') {
151    warning = createWarningObject(warning, type, code, ctor, detail);
152  } else if (!(warning instanceof Error)) {
153    throw new ERR_INVALID_ARG_TYPE('warning', ['Error', 'string'], warning);
154  }
155  if (warning.name === 'DeprecationWarning') {
156    if (process.noDeprecation)
157      return;
158    if (process.throwDeprecation) {
159      // Delay throwing the error to guarantee that all former warnings were
160      // properly logged.
161      return process.nextTick(() => {
162        throw warning;
163      });
164    }
165  }
166  process.nextTick(doEmitWarning, warning);
167}
168
169function emitWarningSync(warning, type, code, ctor) {
170  process.emit('warning', createWarningObject(warning, type, code, ctor));
171}
172
173function createWarningObject(warning, type, code, ctor, detail) {
174  assert(typeof warning === 'string');
175  // Improve error creation performance by skipping the error frames.
176  // They are added in the `captureStackTrace()` function below.
177  const tmpStackLimit = Error.stackTraceLimit;
178  if (isErrorStackTraceLimitWritable()) Error.stackTraceLimit = 0;
179  // eslint-disable-next-line no-restricted-syntax
180  warning = new Error(warning);
181  if (isErrorStackTraceLimitWritable()) Error.stackTraceLimit = tmpStackLimit;
182  warning.name = String(type || 'Warning');
183  if (code !== undefined) warning.code = code;
184  if (detail !== undefined) warning.detail = detail;
185  ErrorCaptureStackTrace(warning, ctor || process.emitWarning);
186  return warning;
187}
188
189module.exports = {
190  emitWarning,
191  emitWarningSync,
192  onWarning,
193  resetForSerialization,
194};
195