<commit_before>import { AST, SAXParser, MarkupData } from 'parse5';
import { Readable } from 'stream';
import { Location } from 'vscode-languageserver/lib/main';

export class HTMLDocumentParser {

  public parse(text: string): Promise<Array<TagDefinition>> {
    return new Promise((resolve, reject) => {
      let stream = new Readable();
      stream.push(text);
      stream.push(null);

      let stack: Array<TagDefinition> = [];

      const parser = new SAXParser({ locationInfo: true });
      parser.on('startTag', (name, attrs, selfClosing, location) => {
        stack.push(new TagDefinition(
          true, 
          name, 
          location.startOffset, 
          location.endOffset,
          selfClosing,
          attrs.map(i => new AttributeDefinition(i.name, i.value, location.attrs[i.name]))));
      });
      parser.on("endTag", (name, location) => {
        stack.push(new TagDefinition(
          false,
          name, 
          location.startOffset, 
          location.endOffset));        
      });

      stream.on('end', x => {
        resolve(stack);
      });
      stream.pipe(parser);

    });  
  }

  public getElementAtPosition(text: string, start: number, end: number): Promise<TagDefinition> {
    return new Promise((resolve, reject) => {
      let stream = new Readable();
      stream.push(text);
      stream.push(null);

      let tagDefinition: TagDefinition;

      const parser = new SAXParser({ locationInfo: true });
      parser.on('startTag', (name, attrs, selfClosing, location) => {

         if (location.startOffset <= start && location.endOffset >= end) {
          tagDefinition = new TagDefinition(
            true, 
            name, 
            location.startOffset, 
            location.endOffset,
            selfClosing,
            attrs.map(i => new AttributeDefinition(i.name, i.value, location.attrs[i.name])));
         } 
      });

      stream.on('end', x => {
        resolve(tagDefinition);
      });
      stream.pipe(parser);
    }); 
  }

}

export class TagDefinition {
  constructor(
    public startTag: boolean, 
    public name: string, 
    public startOffset: number, 
    public endOffset: number,
    public selfClosing: boolean = null,
    public attributes: Array<AttributeDefinition> = []) {
  }
}
export class AttributeDefinition {

  public name: string;
  public binding: string;

  public endOffset: number;
  public startOffset: number;

  constructor(name: string, public value: string, location?: MarkupData.Location) {
    let parts = name.split('.');
    if (parts.length == 2) {
      this.name = parts[0];
      this.binding = parts[1];
    } else {
      this.name = name;
    }

    this.startOffset = location.startOffset;
    this.endOffset = location.endOffset;
  }
}<commit_msg>fix(smart-autocomplete): don't fail on undefined location for attribute, resolves #74 (#75)<commit_after>import { AST, SAXParser, MarkupData } from 'parse5';
import { Readable } from 'stream';
import { Location } from 'vscode-languageserver/lib/main';

export class HTMLDocumentParser {

  public parse(text: string): Promise<Array<TagDefinition>> {
    return new Promise((resolve, reject) => {
      let stream = new Readable();
      stream.push(text);
      stream.push(null);

      let stack: Array<TagDefinition> = [];

      const parser = new SAXParser({ locationInfo: true });
      parser.on('startTag', (name, attrs, selfClosing, location) => {
        stack.push(new TagDefinition(
          true, 
          name, 
          location.startOffset, 
          location.endOffset,
          selfClosing,
          attrs.map(i => new AttributeDefinition(i.name, i.value, location.attrs[i.name]))));
      });
      parser.on("endTag", (name, location) => {
        stack.push(new TagDefinition(
          false,
          name, 
          location.startOffset, 
          location.endOffset));    
      });

      stream.on('end', x => {
        resolve(stack);
      });
      stream.pipe(parser);

    });  
  }

  public getElementAtPosition(text: string, start: number, end: number): Promise<TagDefinition> {
    return new Promise((resolve, reject) => {
      let stream = new Readable();
      stream.push(text);
      stream.push(null);

      let tagDefinition: TagDefinition;

      const parser = new SAXParser({ locationInfo: true });
      parser.on('startTag', (name, attrs, selfClosing, location) => {

         if (location.startOffset <= start && location.endOffset >= end) {
          tagDefinition = new TagDefinition(
            true, 
            name, 
            location.startOffset, 
            location.endOffset,
            selfClosing,
            attrs.map(i => new AttributeDefinition(i.name, i.value, location.attrs[i.name])));
         } 
      });

      stream.on('end', x => {
        resolve(tagDefinition);
      });
      stream.pipe(parser);
    }); 
  }

}

export class TagDefinition {
  constructor(
    public startTag: boolean, 
    public name: string, 
    public startOffset: number, 
    public endOffset: number,
    public selfClosing: boolean = null,
    public attributes: Array<AttributeDefinition> = []) {
  }
}
export class AttributeDefinition {

  public name: string;
  public binding: string;

  public endOffset: number;
  public startOffset: number;

  constructor(name: string, public value: string, location?: MarkupData.Location) {
    if (name) {
      let parts = name.split('.');
      if (parts.length == 2) {
        this.name = parts[0];
        this.binding = parts[1];
      } else {
        this.name = name;
      }
    }

    if (location) {
      this.startOffset = location.startOffset;
      this.endOffset = location.endOffset;
    }
  }
}
{'fraction_non_alphanumeric': 0.12722824187347082, 'fraction_numerical': 0.0020971688220901784, 'mean_word_length': 2.422846889952153, 'pattern_counts': {'":': 0, '<': 15, '<?xml version=': 0, '>': 33, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4813361', 'n_tokens_mistral': 1660, 'n_tokens_neox': 1598, 'n_words': 386}