Skip to content

Commit

Permalink
feat: add optional support for source locations
Browse files Browse the repository at this point in the history
  • Loading branch information
devongovett committed May 4, 2021
1 parent 9676edd commit 6faf50d
Show file tree
Hide file tree
Showing 3 changed files with 105 additions and 2 deletions.
44 changes: 42 additions & 2 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import {Parser, ParserOptions} from 'htmlparser2';
import {Directive, Node, Options, Attributes} from '../types/index.d';
import {Directive, Node, NodeTag, Options, Attributes} from '../types/index.d';

const defaultOptions: ParserOptions = {
lowerCaseTags: false,
Expand Down Expand Up @@ -49,6 +49,34 @@ const parser = (html: string, options: Options = {}): Node[] => {
return result;
}

const lastLoc = {
line: 1,
column: 1
};

let lastIndex = 0;
function getLoc(index: number) {
if (index < lastIndex) {
throw new Error('Source indices must be monotonic');
}

while (lastIndex < index) {
if (html.charCodeAt(lastIndex) === /* \n */ 10) {
lastLoc.line++;
lastLoc.column = 1;
} else {
lastLoc.column++;
}

lastIndex++;
}

return {
line: lastLoc.line,
column: lastLoc.column
};
}

function onprocessinginstruction(name: string, data: string) {
const directives = defaultDirectives.concat(options.directives ?? []);
const last: Node = bufferArrayLast();
Expand Down Expand Up @@ -92,7 +120,15 @@ const parser = (html: string, options: Options = {}): Node[] => {
}

function onopentag(tag: string, attrs: Attributes) {
const buf: Node = {tag};
const start = getLoc(parser.startIndex);
const buf: NodeTag = {tag};

if (options.sourceLocations) {
buf.loc = {
start,
end: start
};
}

if (Object.keys(attrs).length > 0) {
buf.attrs = normalizeArributes(attrs);
Expand All @@ -104,6 +140,10 @@ const parser = (html: string, options: Options = {}): Node[] => {
function onclosetag() {
const buf: Node | undefined = bufArray.pop();

if (buf && typeof buf === 'object' && buf.loc && parser.endIndex !== null) {
buf.loc.end = getLoc(parser.endIndex);
}

if (buf) {
const last = bufferArrayLast();

Expand Down
52 changes: 52 additions & 0 deletions test/test-core.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -240,3 +240,55 @@ test('should be not converting html entity name', t => {
const expected = ['&zwnj;&nbsp;&copy;'];
t.deepEqual(tree, expected);
});

test('should parse with source locations', t => {
const html = '<h1>Test</h1>\n<p><b>Foo</b></p>';
const tree = parser(html, {sourceLocations: true});
const expected = [
{
tag: 'h1',
content: ['Test'],
loc: {
start: {
line: 1,
column: 1
},
end: {
line: 1,
column: 13
}
}
},
'\n',
{
tag: 'p',
content: [
{
tag: 'b',
content: ['Foo'],
loc: {
start: {
line: 2,
column: 4
},
end: {
line: 2,
column: 13
}
}
}
],
loc: {
start: {
line: 2,
column: 1
},
end: {
line: 2,
column: 17
}
}
}
];
t.deepEqual(tree, expected);
});
11 changes: 11 additions & 0 deletions types/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export type Directive = {

export type Options = {
directives?: Directive[];
sourceLocations?: boolean;
} & ParserOptions;

export type Node = NodeText | NodeTag;
Expand All @@ -20,6 +21,16 @@ export type NodeTag = {
tag?: string | boolean;
attrs?: Attributes;
content?: Node[];
loc?: SourceLocation;
};

export type Attributes = Record<string, string>;
export type SourceLocation = {
start: Position;
end: Position;
};

export type Position = {
line: number;
column: number;
};

0 comments on commit 6faf50d

Please sign in to comment.