parseElement method Null safety

Element? parseElement()

Implementation

Element? parseElement() {
  if (!next(TokenType.lt)) return null;
  var lt = _current;

  if (next(TokenType.slash)) {
    // We entered a closing tag, don't keep reading...
    _index -= 2;
    return null;
  }

  var tagName = parseIdentifier();

  if (tagName == null) {
    errors.add(
        JaelError(JaelErrorSeverity.error, 'Missing tag name.', lt.span));
    return null;
  }

  var attributes = <Attribute>[];
  var attribute = parseAttribute();

  while (attribute != null) {
    attributes.add(attribute);
    attribute = parseAttribute();
  }

  if (next(TokenType.slash)) {
    // Try for self-closing...
    var slash = _current;

    if (!next(TokenType.gt)) {
      errors.add(JaelError(JaelErrorSeverity.error,
          'Missing ">" in self-closing "${tagName.name}" tag.', slash.span));
      return null;
    }

    return SelfClosingElement(lt, tagName, attributes, slash, _current);
  }

  if (!next(TokenType.gt)) {
    errors.add(JaelError(
        JaelErrorSeverity.error,
        'Missing ">" in "${tagName.name}" tag.',
        attributes.isEmpty ? tagName.span : attributes.last.span));
    return null;
  }

  var gt = _current;

  // Implicit self-closing
  if (Element.selfClosing.contains(tagName.name)) {
    return SelfClosingElement(lt, tagName, attributes, null, gt);
  }

  var children = <ElementChild>[];
  var child = parseElementChild();

  while (child != null) {
    // if (child is! HtmlComment) children.add(child);
    children.add(child);
    child = parseElementChild();
  }

  // Parse closing tag
  if (!next(TokenType.lt)) {
    errors.add(JaelError(
        JaelErrorSeverity.error,
        'Missing closing tag for "${tagName.name}" tag.',
        children.isEmpty ? tagName.span : children.last.span));
    return null;
  }

  var lt2 = _current;

  if (!next(TokenType.slash)) {
    errors.add(JaelError(JaelErrorSeverity.error,
        'Missing "/" in "${tagName.name}" closing tag.', lt2.span));
    return null;
  }

  var slash = _current;
  var tagName2 = parseIdentifier();

  if (tagName2 == null) {
    errors.add(JaelError(
        JaelErrorSeverity.error,
        'Missing "${tagName.name}" in "${tagName.name}" closing tag.',
        slash.span));
    return null;
  }

  if (tagName2.name != tagName.name) {
    errors.add(JaelError(
        JaelErrorSeverity.error,
        'Mismatched closing tags. Expected "${tagName.span.text}"; got "${tagName2.name}" instead.',
        lt2.span));
    return null;
  }

  if (!next(TokenType.gt)) {
    errors.add(JaelError(JaelErrorSeverity.error,
        'Missing ">" in "${tagName.name}" closing tag.', tagName2.span));
    return null;
  }

  return RegularElement(
      lt, tagName, attributes, gt, children, lt2, slash, tagName2, _current);
}