{"record":{"id":"3619772c5b5f2eec","repo":"emberjs/ember.js","slug":"b-concat-requires-at-least-one-part","errorCode":null,"errorMessage":"b.concat requires at least one part","messagePattern":"b\\.concat requires at least one part","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/@glimmer/syntax/lib/v1/public-builders.ts","lineNumber":139,"sourceCode":"  return b.comment({\n    value: value,\n    loc: buildLoc(loc || null),\n  });\n}\n\nfunction buildMustacheComment(value: string, loc?: SourceLocation): ASTv1.MustacheCommentStatement {\n  return b.mustacheComment({\n    value: value,\n    loc: buildLoc(loc || null),\n  });\n}\n\nfunction buildConcat(\n  parts: (ASTv1.TextNode | ASTv1.MustacheStatement)[],\n  loc?: SourceLocation\n): ASTv1.ConcatStatement {\n  if (!isPresentArray(parts)) {\n    throw new Error(`b.concat requires at least one part`);\n  }\n\n  return b.concat({\n    parts,\n    loc: buildLoc(loc || null),\n  });\n}\n\n// Nodes\n\nexport type ElementParts =\n  | ['attrs', ...AttrSexp[]]\n  | ['modifiers', ...ModifierSexp[]]\n  | ['body', ...ASTv1.Statement[]]\n  | ['comments', ...ElementComment[]]\n  | ['as', ...string[]]\n  | ['loc', SourceLocation];\n","sourceCodeStart":121,"sourceCodeEnd":157,"githubUrl":"https://github.com/emberjs/ember.js/blob/26f97246a8bf2e28edf26ac3093da2e86c04ffc5/packages/@glimmer/syntax/lib/v1/public-builders.ts#L121-L157","documentation":"The AST builder helper `b.concat(parts)` must receive a non-empty array of TextNode/MustacheStatement parts. buildConcat uses isPresentArray to reject empty (or null-length) arrays because an AST ConcatStatement with zero parts is meaningless and would serialize to nothing.","triggerScenarios":"Calling `b.concat([])` (or passing an array that filters down to empty at runtime) when programmatically building Glimmer templates — e.g. AST plugins/codemods that accumulate parts conditionally and end up with none.","commonSituations":"Writing an Ember template AST transform/codemod that builds `{{...}}` interpolations dynamically; collecting parts from user input that turned out empty.","solutions":["Guard before calling: only call b.concat when parts.length > 0.","When parts is empty, return a TextNode (e.g. b.text('')) instead of a concat.","Fix the upstream collection logic so at least one part is always produced."],"exampleFix":"// before\nreturn b.concat(parts);\n\n// after\nif (parts.length === 0) {\n  return b.text('');\n}\nreturn b.concat(parts);","handlingStrategy":"validation","validationCode":"function safeConcat(b, parts, loc) {\n  if (!Array.isArray(parts) || parts.length === 0) return b.text('');\n  return b.concat(parts, loc);\n}","typeGuard":"function isNonEmptyParts(parts) {\n  return Array.isArray(parts) && parts.length > 0 && parts.every((p) => p && (p.type === 'TextNode' || p.type === 'MustacheStatement'));\n}","tryCatchPattern":"try {\n  return b.concat(parts);\n} catch (e) {\n  if (e.message.includes('b.concat requires at least one part')) {\n    return b.text('');\n  }\n  throw e;\n}","preventionTips":["Check parts.length before calling b.concat.","Return a text/empty node for the zero-parts case in AST transforms.","Unit-test codemods with inputs that produce empty part arrays.","Prefer b.text for single/static content instead of concat."],"tags":["glimmer","syntax","ast","codemod","template"],"backgroundTag":"empty-argument-not-allowed","analyzedSha":"26f97246a8bf2e28edf26ac3093da2e86c04ffc5","analyzedAt":"2026-09-01T05:01:28.182Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}