Better source tracking through syndicate/ts
This commit is contained in:
parent
45e8ebef12
commit
3908f2ff26
|
@ -2,7 +2,7 @@ import {
|
||||||
isToken, isTokenType, replace, commaJoin, startPos, fixPos, joinItems,
|
isToken, isTokenType, replace, commaJoin, startPos, fixPos, joinItems,
|
||||||
anonymousTemplate, laxRead,
|
anonymousTemplate, laxRead,
|
||||||
|
|
||||||
Items, Pattern, Templates, Substitution, TokenType,
|
Items, Pattern, Templates, Substitution, TokenType, Pos,
|
||||||
SourceMap, CodeWriter, TemplateFunction, Token, itemText,
|
SourceMap, CodeWriter, TemplateFunction, Token, itemText,
|
||||||
} from '../syntax/index.js';
|
} from '../syntax/index.js';
|
||||||
import {
|
import {
|
||||||
|
@ -41,9 +41,16 @@ export interface CompileOptions {
|
||||||
typescript?: boolean,
|
typescript?: boolean,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Essentially the same as a SourceMap, but indexed differently
|
||||||
|
// (originally for use with the TypeScript compiler).
|
||||||
|
export interface SourcePositionIndex {
|
||||||
|
sourcePositionAt(pos: number): Pos;
|
||||||
|
}
|
||||||
|
|
||||||
export interface CompilerOutput {
|
export interface CompilerOutput {
|
||||||
text: string,
|
text: string,
|
||||||
map: SourceMap,
|
map: SourceMap,
|
||||||
|
positionIndex: SourcePositionIndex,
|
||||||
}
|
}
|
||||||
|
|
||||||
function receiverFor(s: FacetAction): Substitution {
|
function receiverFor(s: FacetAction): Substitution {
|
||||||
|
@ -354,8 +361,40 @@ export function compile(options: CompileOptions): CompilerOutput {
|
||||||
const cw = new CodeWriter(inputFilename);
|
const cw = new CodeWriter(inputFilename);
|
||||||
cw.emit(tree);
|
cw.emit(tree);
|
||||||
|
|
||||||
|
const positionMap = cw.positionMap;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
text: cw.text,
|
text: cw.text,
|
||||||
map: cw.map,
|
map: cw.map,
|
||||||
|
positionIndex: {
|
||||||
|
sourcePositionAt(pos: number): Pos {
|
||||||
|
if (positionMap.length === 0) return start;
|
||||||
|
|
||||||
|
let lo = 0;
|
||||||
|
let hi = positionMap.length;
|
||||||
|
|
||||||
|
// console.log(`\nsearching for ${pos}`);
|
||||||
|
while (true) {
|
||||||
|
if (lo === hi) {
|
||||||
|
const e = positionMap[lo - 1] ?? [0, start];
|
||||||
|
if (e[0] > pos) throw new Error();
|
||||||
|
if (positionMap[lo]?.[0] <= pos) throw new Error();
|
||||||
|
// console.log(`found ${JSON.stringify(e)}`);
|
||||||
|
return e[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
const mid = (lo + hi) >> 1;
|
||||||
|
const e = positionMap[mid];
|
||||||
|
|
||||||
|
// console.log(`${pos} lo ${lo} hi ${hi} mid ${mid} probe ${JSON.stringify([e[0], e[1].pos])}`);
|
||||||
|
|
||||||
|
if (e[0] <= pos) {
|
||||||
|
lo = mid + 1;
|
||||||
|
} else {
|
||||||
|
hi = mid;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -51,6 +51,7 @@ export class CodeWriter {
|
||||||
readonly sources: Array<string> = [];
|
readonly sources: Array<string> = [];
|
||||||
readonly chunks: Array<string> = [];
|
readonly chunks: Array<string> = [];
|
||||||
readonly mappings: Array<Array<NonEmptyMapping>> = [];
|
readonly mappings: Array<Array<NonEmptyMapping>> = [];
|
||||||
|
readonly positionMap: Array<[number, Pos]> = [];
|
||||||
previous: Partial<SourceNameMapping> = {};
|
previous: Partial<SourceNameMapping> = {};
|
||||||
previousPos: Pos | null = null;
|
previousPos: Pos | null = null;
|
||||||
|
|
||||||
|
@ -144,14 +145,24 @@ export class CodeWriter {
|
||||||
this.mappings[this.mappings.length - 1].push(n);
|
this.mappings[this.mappings.length - 1].push(n);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
augmentPositionMap(p: Pos) {
|
||||||
|
if (this.positionMap.length > 0) {
|
||||||
|
const prev = this.positionMap[this.positionMap.length - 1][1];
|
||||||
|
if ((prev.name === p.name) && (prev.pos === p.pos)) return;
|
||||||
|
}
|
||||||
|
this.positionMap.push([this.pos.pos, { ... p }]);
|
||||||
|
}
|
||||||
|
|
||||||
chunk(p: Pos, s: string, type: TokenType) {
|
chunk(p: Pos, s: string, type: TokenType) {
|
||||||
p = { ... p };
|
p = { ... p };
|
||||||
this.chunks.push(s);
|
this.chunks.push(s);
|
||||||
|
this.augmentPositionMap(p);
|
||||||
if (this.mappings.length === 0) this.finishLine();
|
if (this.mappings.length === 0) this.finishLine();
|
||||||
this.addMapping(p, type);
|
this.addMapping(p, type);
|
||||||
for (const ch of s) {
|
for (const ch of s) {
|
||||||
advancePos(p, ch);
|
advancePos(p, ch);
|
||||||
if (advancePos(this.pos, ch)) {
|
if (advancePos(this.pos, ch)) {
|
||||||
|
this.augmentPositionMap(p);
|
||||||
this.finishLine();
|
this.finishLine();
|
||||||
this.addMapping(p, type);
|
this.addMapping(p, type);
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
#!/usr/bin/env node
|
||||||
|
try {
|
||||||
|
require('../lib/maptool.js').main(process.argv.slice(2));
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
|
@ -20,6 +20,7 @@
|
||||||
"yargs": "^16.2.0"
|
"yargs": "^16.2.0"
|
||||||
},
|
},
|
||||||
"bin": {
|
"bin": {
|
||||||
|
"syndicate-maptool": "./bin/syndicate-maptool.js",
|
||||||
"syndicate-tsc": "./bin/syndicate-tsc.js",
|
"syndicate-tsc": "./bin/syndicate-tsc.js",
|
||||||
"syndicatec": "./bin/syndicatec.js"
|
"syndicatec": "./bin/syndicatec.js"
|
||||||
},
|
},
|
||||||
|
|
|
@ -0,0 +1,15 @@
|
||||||
|
import fs from 'fs';
|
||||||
|
import { SourceMap } from '@syndicate-lang/compiler/lib/syntax/index.js';
|
||||||
|
import { Syntax } from '@syndicate-lang/compiler';
|
||||||
|
const { vlqDecode } = Syntax;
|
||||||
|
|
||||||
|
export function main(argv: string[]) {
|
||||||
|
const mapFilename = argv[0];
|
||||||
|
console.log(mapFilename);
|
||||||
|
const map = JSON.parse(fs.readFileSync(mapFilename, 'utf-8')) as SourceMap;
|
||||||
|
console.log(map);
|
||||||
|
|
||||||
|
const entries = map.mappings.split(/;/).map(e => e.split(/,/).map(vlqDecode));
|
||||||
|
entries.forEach((line, lineNumber) =>
|
||||||
|
console.log(lineNumber + 1, line));
|
||||||
|
}
|
|
@ -1,9 +1,8 @@
|
||||||
import yargs from 'yargs/yargs';
|
|
||||||
import ts from 'typescript';
|
import ts from 'typescript';
|
||||||
import crypto from 'crypto';
|
import crypto from 'crypto';
|
||||||
|
|
||||||
import { compile } from '@syndicate-lang/compiler';
|
import { compile } from '@syndicate-lang/compiler';
|
||||||
import { dataURL, sourceMappingComment } from './util.js';
|
import { SourcePositionIndex } from '@syndicate-lang/compiler/lib/compiler/codegen';
|
||||||
|
|
||||||
function reportDiagnostic(diagnostic: ts.Diagnostic) {
|
function reportDiagnostic(diagnostic: ts.Diagnostic) {
|
||||||
if (diagnostic.file) {
|
if (diagnostic.file) {
|
||||||
|
@ -21,6 +20,13 @@ function reportErrorSummary(n: number) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface SyndicateInfo {
|
||||||
|
originalSource: string;
|
||||||
|
positionIndex: SourcePositionIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
const syndicateInfo: Map<string, SyndicateInfo> = new Map();
|
||||||
|
|
||||||
function createProgram(rootNames: readonly string[] | undefined,
|
function createProgram(rootNames: readonly string[] | undefined,
|
||||||
options: ts.CompilerOptions | undefined,
|
options: ts.CompilerOptions | undefined,
|
||||||
host?: ts.CompilerHost,
|
host?: ts.CompilerHost,
|
||||||
|
@ -50,18 +56,15 @@ function createProgram(rootNames: readonly string[] | undefined,
|
||||||
onError?.(`Could not read input file ${fileName}`);
|
onError?.(`Could not read input file ${fileName}`);
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
const { text: baseExpandedText, map: sourceMap } = compile({
|
const { text: expandedText, positionIndex } = compile({
|
||||||
source: inputText,
|
source: inputText,
|
||||||
name: fileName,
|
name: fileName,
|
||||||
typescript: true,
|
typescript: true,
|
||||||
});
|
});
|
||||||
sourceMap.sourcesContent = [inputText];
|
syndicateInfo.set(fileName, {
|
||||||
const expandedText = baseExpandedText + sourceMappingComment(dataURL(JSON.stringify(sourceMap)));
|
originalSource: inputText,
|
||||||
|
positionIndex,
|
||||||
// console.log('\n\n', fileName);
|
});
|
||||||
// expandedText.split(/\n/).forEach((line, i) => {
|
|
||||||
// console.log(i + 1, line);
|
|
||||||
// });
|
|
||||||
const sf = ts.createSourceFile(fileName, expandedText, languageVersion, true);
|
const sf = ts.createSourceFile(fileName, expandedText, languageVersion, true);
|
||||||
(sf as any).version = crypto.createHash('sha256').update(expandedText).digest('hex');
|
(sf as any).version = crypto.createHash('sha256').update(expandedText).digest('hex');
|
||||||
return sf;
|
return sf;
|
||||||
|
@ -82,6 +85,44 @@ function createProgram(rootNames: readonly string[] | undefined,
|
||||||
projectReferences);
|
projectReferences);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function fixSourceMap(ctx: ts.TransformationContext): ts.Transformer<ts.SourceFile> {
|
||||||
|
return sf => {
|
||||||
|
const fileName = sf.fileName;
|
||||||
|
const info = syndicateInfo.get(fileName);
|
||||||
|
if (info === void 0) throw new Error("No Syndicate info available for " + fileName);
|
||||||
|
const positionIndex = info.positionIndex;
|
||||||
|
|
||||||
|
// console.log('fixSourceMap', fileName, sf.text.length, info.originalSource.length);
|
||||||
|
|
||||||
|
const syndicateSource = ts.createSourceMapSource(fileName, info.originalSource);
|
||||||
|
const expandedSource = ts.createSourceMapSource(fileName + '.expanded', sf.text);
|
||||||
|
|
||||||
|
function adjustSourceMap(n: ts.Node) {
|
||||||
|
const ps = positionIndex.sourcePositionAt(n.pos);
|
||||||
|
const pe = positionIndex.sourcePositionAt(n.end);
|
||||||
|
if (ps.name === fileName && pe.name === fileName) {
|
||||||
|
ts.setSourceMapRange(n, { pos: ps.pos, end: pe.pos, source: syndicateSource });
|
||||||
|
// console.group(ts.SyntaxKind[n.kind], `${n.pos}-${n.end} ==> ${ps.pos}-${pe.pos}`);
|
||||||
|
} else if (ps.name === null && pe.name === null) {
|
||||||
|
ts.setSourceMapRange(n, { pos: ps.pos, end: pe.pos, source: expandedSource });
|
||||||
|
// console.group(ts.SyntaxKind[n.kind], n.pos, 'synthetic');
|
||||||
|
} else if (ps.name === null) {
|
||||||
|
ts.setSourceMapRange(n, { pos: pe.pos, end: pe.pos, source: expandedSource });
|
||||||
|
// console.group(ts.SyntaxKind[n.kind], n.pos, 'mixed end');
|
||||||
|
} else {
|
||||||
|
ts.setSourceMapRange(n, { pos: ps.pos, end: ps.pos, source: expandedSource });
|
||||||
|
// console.group(ts.SyntaxKind[n.kind], n.pos, 'mixed start');
|
||||||
|
}
|
||||||
|
ts.forEachChild(n, adjustSourceMap);
|
||||||
|
// console.groupEnd();
|
||||||
|
}
|
||||||
|
|
||||||
|
adjustSourceMap(sf);
|
||||||
|
|
||||||
|
return sf;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export function main(argv: string[]) {
|
export function main(argv: string[]) {
|
||||||
const sbh = ts.createSolutionBuilderHost(ts.sys,
|
const sbh = ts.createSolutionBuilderHost(ts.sys,
|
||||||
createProgram,
|
createProgram,
|
||||||
|
@ -91,5 +132,12 @@ export function main(argv: string[]) {
|
||||||
const sb = ts.createSolutionBuilder(sbh, ['.'], {
|
const sb = ts.createSolutionBuilder(sbh, ['.'], {
|
||||||
verbose: true,
|
verbose: true,
|
||||||
});
|
});
|
||||||
ts.sys.exit(sb.build());
|
while (true) {
|
||||||
|
const project = sb.getNextInvalidatedProject();
|
||||||
|
if (project === void 0) break;
|
||||||
|
project.done(void 0, void 0, {
|
||||||
|
before: [fixSourceMap]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
ts.sys.exit(0);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,2 +0,0 @@
|
||||||
#!/bin/sh
|
|
||||||
exec inotifytest redo -j$(nproc)
|
|
Loading…
Reference in New Issue