{"id":"f0d60f2a0f1912a4","repo":"evanw/esbuild","slug":"quote-property-must-be-an-array-of-strings","errorCode":null,"errorMessage":"${quote(property)} must be an array of strings","messagePattern":"(.+?) must be an array of strings","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"lib/shared/common.ts","lineNumber":1812,"sourceCode":"\n    messagesClone.push({\n      id: id || '',\n      pluginName: pluginName || fallbackPluginName,\n      text: text || '',\n      location: sanitizeLocation(location, where, terminalWidth),\n      notes: notesClone,\n      detail: stash ? stash.store(detail) : -1,\n    })\n    index++\n  }\n\n  return messagesClone\n}\n\nfunction sanitizeStringArray(values: any[], property: string): string[] {\n  const result: string[] = []\n  for (const value of values) {\n    if (typeof value !== 'string') throw new Error(`${quote(property)} must be an array of strings`)\n    result.push(value)\n  }\n  return result\n}\n\nfunction sanitizeStringMap(map: Record<string, any>, property: string): Record<string, string> {\n  const result: Record<string, string> = Object.create(null)\n  for (const key in map) {\n    const value = map[key]\n    if (typeof value !== 'string') throw new Error(`key ${quote(key)} in object ${quote(property)} must be a string`)\n    result[key] = value\n  }\n  return result\n}\n\nfunction convertOutputFiles({ path, contents, hash }: protocol.BuildOutputFile): types.OutputFile {\n  // The text is lazily-generated for performance reasons. If no one asks for\n  // it, then it never needs to be generated.","sourceCodeStart":1794,"sourceCodeEnd":1830,"githubUrl":"https://github.com/evanw/esbuild/blob/6ff1d8b0d8c134e867a397eef39702a223ebef9e/lib/shared/common.ts#L1794-L1830","documentation":"Plugin callbacks can declare watchFiles and watchDirs (from onResolve/onLoad results) so esbuild knows which extra paths to monitor in watch mode. sanitizeStringArray (lib/shared/common.ts:1809) iterates the array and throws at :1812 if any element is not a string — esbuild needs string paths to pass to the native fs watcher. The 'property' name in the message is the offending field name (watchFiles or watchDirs).","triggerScenarios":"Returning watchFiles: [somePath, null] or watchDirs: [123]. watchFiles containing a Path object (from node:path) instead of a string. Mixing in undefined from an array with holes.","commonSituations":"Plugin collects candidate files from a glob and forgets to filter out undefined/null. watchFiles from a recursive scan that includes a Buffer or fs.Dirent. Author pushes a Path object from node:path.win32.","solutions":["Coerce/filter the array before returning: watchFiles: files.filter(f => typeof f === 'string').","Use String(p) only if p is path-like; prefer resolving to absolute strings explicitly via path.resolve.","Add a TS annotation watchFiles?: string[] to surface type errors."],"exampleFix":"// before\nbuild.onLoad({ filter: /.*/ }, args => ({\n  contents: read(...),\n  watchFiles: [args.path, getDepCount(args.path)], // second item is a number\n}));\n// after\nbuild.onLoad({ filter: /.*/ }, args => ({\n  contents: read(...),\n  watchFiles: [args.path, ...getDeps(args.path)].filter(\n    (f): f is string => typeof f === 'string',\n  ),\n}));","handlingStrategy":"validation","validationCode":"function asStringArray(v, name) {\n  if (!Array.isArray(v)) return undefined;\n  const out = v.filter((x): x is string => typeof x === 'string');\n  if (out.length !== v.length) {\n    throw new TypeError(`${name} must be an array of strings`);\n  }\n  return out;\n}","typeGuard":"function isStringArray(v): v is string[] {\n  return Array.isArray(v) && v.every(x => typeof x === 'string');\n}","tryCatchPattern":null,"preventionTips":["Filter watchFiles/watchDirs to strings before returning them from a callback.","Resolve Path objects to absolute strings via path.resolve before pushing.","Type these fields as string[] in plugin result types."],"tags":["plugins","watch","validation","filesystem"],"analyzedSha":"6ff1d8b0d8c134e867a397eef39702a223ebef9e","analyzedAt":"2026-08-03T19:42:38.433Z","schemaVersion":2}