{"record":{"id":"7d2c8920fbc145ef","repo":"apple/pkl","slug":"danglingdoccomment-7d2c89","errorCode":"danglingDocComment","errorMessage":"Dangling documentation comment.\n\nDocumentation comments must be attached to modules, classes, typealiases, methods, or properties.","messagePattern":"Dangling documentation comment\\.\n\nDocumentation comments must be attached to modules, classes, typealiases, methods, or properties\\.","errorType":"exception","errorClass":"ParserError","httpStatus":null,"severity":"error","filePath":"pkl-parser/src/main/java/org/pkl/parser/ParserImpl.java","lineNumber":354,"sourceCode":"        return node.span();\n      }\n      case CLASS -> {\n        var node = parseClass(header);\n        nodes.add(node);\n        return node.span();\n      }\n      case FUNCTION -> {\n        var node = parseClassMethod(header);\n        nodes.add(node);\n        return node.span();\n      }\n      case EOF -> throw parserError(\"unexpectedEndOfFile\");\n      default -> {\n        if (lookahead.isKeyword()) {\n          throw parserError(\"keywordNotAllowedHere\", lookahead.text());\n        }\n        if (lookahead == Token.DOC_COMMENT) {\n          throw parserError(\"danglingDocComment\");\n        }\n        throw parserError(\"invalidTopLevelToken\");\n      }\n    }\n  }\n\n  private TypeAlias parseTypeAlias(MemberHeader header) {\n    var typeAlias = next().span;\n    var startSpan = header.span(typeAlias);\n\n    var identifier = parseIdentifier();\n    TypeParameterList typePars = null;\n    if (lookahead == Token.LT) {\n      typePars = parseTypeParameterList();\n    }\n    expect(Token.ASSIGN, \"unexpectedToken\", \"=\");\n    var type = parseType();\n    var children =","sourceCodeStart":336,"sourceCodeEnd":372,"githubUrl":"https://github.com/apple/pkl/blob/f3efcbfc9b60d30053b0536d664948d7aa1b8673/pkl-parser/src/main/java/org/pkl/parser/ParserImpl.java#L336-L372","documentation":"A `///` documentation comment was found where it cannot attach to any declaration — at top level it must directly precede a module header, class, typealias, method, or property. parseModuleMember throws \"Dangling documentation comment\" when a DOC_COMMENT token appears where no attachable member follows.","triggerScenarios":"A `///` comment at the end of the file with no declaration after it; a `///` comment separated from its declaration by a blank line is fine, but one followed by another comment, an import, or a closing brace dangles; doc comment placed inside a block or between imports.","commonSituations":"Deleting a declaration but leaving its doc comment behind; refactorings that move code and strand trailing `///` comments; auto-generated docs inserting comments before imports.","solutions":["Remove the orphaned `///` comment, or convert it to a plain `//` comment.","Re-attach it directly above the class/typealias/method/property it documents (no intervening tokens).","If it documents the module itself, place it above the module `amends`/`extends` header at the top of the file.","Check the code after the comment wasn't accidentally deleted during refactoring."],"exampleFix":"// before\n/// This documented something, but the code below is gone.\n}\n// after — either delete the comment or attach it to a real declaration:\n/// Describes the bird's name.\nname: String","handlingStrategy":"validation","validationCode":"// Ensure every '///' comment is immediately followed by a declaration-looking line\nfunction danglingDocComments(lines) {\n  const decl = /^\\s*(class\\b|typealias\\b|function\\b|`?[A-Za-z_]\\w*`?\\s*(:|=|\\())|^(amends|extends)\\b/;\n  return lines\n    .map((l, i) => [l.trim(), i])\n    .filter(([l, i]) => l.startsWith('///') && (i + 1 >= lines.length || !decl.test(lines[i + 1])))\n    .map(([, i]) => i + 1);\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Delete a `///` comment together with the declaration it documents.","Convert explanatory-only comments to `//` instead of `///`.","When moving code, move its doc comment with it and re-verify attachment."],"tags":["parser","doc-comment","syntax"],"backgroundTag":"invalid-argument-value","analyzedSha":"f3efcbfc9b60d30053b0536d664948d7aa1b8673","analyzedAt":"2026-09-08T13:10:45.570Z","contentChangedAt":"2026-09-08T13:10:45.570Z","schemaVersion":2},"datasetVersion":"2026-09-17T15:17:12.973Z"}