Skip to content

Commit 997e641

Browse files
Merge pull request #28 from BenoitZugmeyer/char-position
Char position
2 parents 4e8593d + 90fef71 commit 997e641

13 files changed

+205
-65
lines changed

README.md

+5-3
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
# in-situ
22

3-
`in-situ` is a simple CLI application taking a JavaScript file URL and a line/column position inside
3+
`in-situ` is a simple CLI application taking a JavaScript file URL and a line/column location inside
44
it. It will download the JavaScript file, beautify it, and print the context around the given
5-
position.
5+
location.
66

77
## Installation
88

@@ -17,10 +17,12 @@ Note: instead of installing it, you can use `npx in-situ` to run it directly.
1717
## Usage
1818

1919
```
20-
Usage: in-situ [options] <URL:LINE:COLUMN>
20+
Usage: in-situ [options] <LOCATION>
2121
2222
Download, beautify and print lines from a minified JavaScript source
2323
24+
The <LOCATION> argument is an URL or a file path to a JavaScript bundle, followed by a location formatted as ':line:column' or ':osition'. Example: 'https://example.com/bundle.js:10:20' or './bundle.js:123'.
25+
2426
Options:
2527
-A, --after-context <num> print <num> lines of trailing context after the selected line
2628
-B, --before-context <num> print <num> lines of leading context before the selected line

src/applyBeautify.ts

+6-6
Original file line numberDiff line numberDiff line change
@@ -28,18 +28,18 @@ export default async function applyBeautify(
2828
}
2929

3030
const map = new TraceMap(uglifyResult.map as string);
31-
const position = generatedPositionFor(map, {
32-
line: source.position.line,
33-
column: source.position.column,
31+
const location = generatedPositionFor(map, {
32+
line: source.location.line,
33+
column: source.location.column,
3434
source: map.sources[0]!,
3535
});
36-
if (position.line === null || position.column === null) {
37-
throw new CLIError("Failed to map position using beautify");
36+
if (location.line === null || location.column === null) {
37+
throw new CLIError("Failed to map location using beautify");
3838
}
3939
return {
4040
type: "resolved",
4141
fileName: undefined,
4242
content: uglifyResult.code!,
43-
position,
43+
location,
4444
};
4545
}

src/applySourceMap.ts

+6-6
Original file line numberDiff line numberDiff line change
@@ -9,21 +9,21 @@ import log from "./log.ts";
99
import type { ApplyResult, InputSource } from "./types.ts";
1010

1111
export default async function applySourceMap({
12-
position,
12+
location,
1313
readResult,
1414
}: InputSource): Promise<ApplyResult> {
1515
const sourceMapContent = await readSourceMap(readResult);
1616
if (!sourceMapContent) return { type: "unresolved" };
1717

1818
const map = new TraceMap(sourceMapContent);
1919

20-
const mappedPosition = originalPositionFor(map, position);
21-
if (!mappedPosition.source) {
22-
log.error("Failed to resolve the position with source map");
20+
const mappedLocation = originalPositionFor(map, location);
21+
if (!mappedLocation.source) {
22+
log.error("Failed to resolve the location with source map");
2323
return { type: "unresolved" };
2424
}
2525

26-
const fileName = mappedPosition.source;
26+
const fileName = mappedLocation.source;
2727
const content = sourceContentFor(map, fileName);
2828
if (!content) {
2929
log.error("Source map doesn't include the source content");
@@ -36,7 +36,7 @@ export default async function applySourceMap({
3636
return {
3737
type: "resolved",
3838
content,
39-
position: mappedPosition,
39+
location: mappedLocation,
4040
fileName,
4141
};
4242
}

src/main.ts

+9-4
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import printContext from "./printContext.ts";
1111
import log from "./log.ts";
1212
import read from "./read.ts";
1313
import type { ApplyResult, Configuration } from "./types.ts";
14+
import resolveGridLocation from "./resolveGridLocation.ts";
1415

1516
main().catch((e) => {
1617
if (e instanceof CLIError) {
@@ -43,10 +44,12 @@ async function main() {
4344
export function commandHelp() {
4445
const pkg = getPackageInfos();
4546
let message = `\
46-
Usage: ${pkg.name} [options] <URL:LINE:COLUMN>
47+
Usage: ${pkg.name} [options] <LOCATION>
4748
4849
${pkg.description}
4950
51+
The <LOCATION> argument is an URL or a file path to a JavaScript bundle, followed by a location formatted as ':line:column' or ':osition'. Example: 'https://example.com/bundle.js:10:20' or './bundle.js:123'.
52+
5053
Options:`;
5154
for (const [name, option] of Object.entries(OPTIONS)) {
5255
let names = "";
@@ -69,7 +72,7 @@ function commanVersion() {
6972
async function commandContext({
7073
debug,
7174
sourceURL,
72-
position,
75+
location,
7376
beforeContext,
7477
afterContext,
7578
useSourceMap,
@@ -84,15 +87,17 @@ async function commandContext({
8487
throw new CLIError(`Failed to fetch source code: ${e}`);
8588
}
8689

90+
location = resolveGridLocation(location, readResult.content);
91+
8792
let applyResult: ApplyResult = {
8893
type: "unresolved",
8994
};
9095
if (useSourceMap) {
91-
applyResult = await applySourceMap({ readResult, position });
96+
applyResult = await applySourceMap({ readResult, location });
9297
}
9398
if (applyResult.type !== "resolved") {
9499
// TODO: use filename from sourcemaps? is that an actual use case?
95-
applyResult = await applyBeautify({ readResult, position });
100+
applyResult = await applyBeautify({ readResult, location });
96101
}
97102
if (applyResult.type !== "resolved") {
98103
throw new CLIError("Failed to apply source map or beautify");

src/parseArguments.test.ts

+41-12
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ describe("parseArguments", () => {
99
t.assert.throws(
1010
() => parseArguments([]),
1111
new CLIError(
12-
"Missing positional argument URL:LINE:COLUMN. Use --help for documentation.",
12+
"Missing positional argument LOCATION. Use --help for documentation.",
1313
),
1414
);
1515
});
@@ -44,24 +44,53 @@ describe("parseArguments", () => {
4444
configuration: {
4545
debug: false,
4646
sourceURL: "https://foo.com",
47-
position: { line: 1, column: 1 },
47+
location: { line: 1, column: 1 },
4848
useSourceMap: true,
4949
beforeContext: 5,
5050
afterContext: 5,
5151
},
5252
});
5353
});
5454

55-
test("URL and position", (t: TestContext) => {
56-
const parsedArguments = parseArguments([`https://foo.com:42:12`]);
57-
t.assert.strictEqual(parsedArguments.command, "context");
58-
t.assert.strictEqual(
59-
parsedArguments.configuration.sourceURL,
60-
"https://foo.com",
61-
);
62-
t.assert.deepStrictEqual(parsedArguments.configuration.position, {
63-
line: 42,
64-
column: 12,
55+
describe("location", () => {
56+
test("URL:LINE:COLUMN", (t: TestContext) => {
57+
const parsedArguments = parseArguments([`https://foo.com:42:12`]);
58+
t.assert.strictEqual(parsedArguments.command, "context");
59+
t.assert.strictEqual(
60+
parsedArguments.configuration.sourceURL,
61+
"https://foo.com",
62+
);
63+
t.assert.deepStrictEqual(parsedArguments.configuration.location, {
64+
line: 42,
65+
column: 12,
66+
});
67+
});
68+
test("URL:POSITION", (t: TestContext) => {
69+
const parsedArguments = parseArguments([`https://foo.com:42`]);
70+
t.assert.strictEqual(parsedArguments.command, "context");
71+
t.assert.strictEqual(
72+
parsedArguments.configuration.sourceURL,
73+
"https://foo.com",
74+
);
75+
t.assert.deepStrictEqual(parsedArguments.configuration.location, {
76+
position: 42,
77+
});
78+
});
79+
test("invalid location", (t: TestContext) => {
80+
t.assert.throws(
81+
() => parseArguments([`https://foo.com:42:`]),
82+
new CLIError(
83+
"Invalid positional argument https://foo.com:42:. Use --help for documentation.",
84+
),
85+
);
86+
});
87+
test("missing location", (t: TestContext) => {
88+
t.assert.throws(
89+
() => parseArguments([`https://foo.com`]),
90+
new CLIError(
91+
"Invalid positional argument https://foo.com. Use --help for documentation.",
92+
),
93+
);
6594
});
6695
});
6796

src/parseArguments.ts

+8-17
Original file line numberDiff line numberDiff line change
@@ -89,38 +89,29 @@ export default function parseArguments(args: string[]): ParseArgumentsResult {
8989
const arg = positionals[0];
9090
if (!arg) {
9191
throw new CLIError(
92-
"Missing positional argument URL:LINE:COLUMN. Use --help for documentation.",
92+
"Missing positional argument LOCATION. Use --help for documentation.",
9393
);
9494
}
9595

96-
const matches = /^(.*):(\d+):(\d+)$/.exec(arg);
96+
const matches = /^(.*?):(\d+)(?::(\d+))?$/.exec(arg);
9797
if (!matches) {
9898
throw new CLIError(
9999
`Invalid positional argument ${arg}. Use --help for documentation.`,
100100
);
101101
}
102102

103-
const [, sourceURL, line, column] = matches;
104-
105-
const beforeContext = parseInteger(
106-
values["before-context"] !== undefined
107-
? values["before-context"]
108-
: values.context!,
109-
);
110-
const afterContext = parseInteger(
111-
values["after-context"] !== undefined
112-
? values["after-context"]
113-
: values.context!,
114-
);
103+
const [, sourceURL, lineOrPosition, column] = matches;
115104

116105
return {
117106
command: "context",
118107
configuration: {
119108
debug: values.debug!,
120109
sourceURL,
121-
position: { line: Number(line), column: Number(column) },
122-
beforeContext,
123-
afterContext,
110+
location: column
111+
? { line: Number(lineOrPosition), column: Number(column) }
112+
: { position: Number(lineOrPosition) },
113+
beforeContext: parseInteger(values["before-context"] ?? values.context),
114+
afterContext: parseInteger(values["after-context"] ?? values.context),
124115
useSourceMap: !values["no-source-map"]!,
125116
},
126117
};

src/printContext.test.ts

+9-9
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ test("prints simple source context", (t: TestContext) => {
1010
type: "resolved",
1111
fileName: undefined,
1212
content: "a",
13-
position: { line: 1, column: 0 },
13+
location: { line: 1, column: 0 },
1414
}),
1515
"a\n^",
1616
);
@@ -22,7 +22,7 @@ test("context limit", (t: TestContext) => {
2222
type: "resolved",
2323
fileName: undefined,
2424
content: "a\nb\nc\nd\ne\nf\ng\nh\ni\nj\nk\nl\nm",
25-
position: { line: 7, column: 0 },
25+
location: { line: 7, column: 0 },
2626
}),
2727
"b\nc\nd\ne\nf\ng\n^\nh\ni\nj\nk\nl",
2828
);
@@ -32,7 +32,7 @@ test("context limit", (t: TestContext) => {
3232
type: "resolved",
3333
fileName: undefined,
3434
content: "a\nb\nc\nd\ne\nf\ng\nh\ni\nj\nk\nl\nm",
35-
position: { line: 7, column: 0 },
35+
location: { line: 7, column: 0 },
3636
},
3737
{ beforeContext: 0 },
3838
),
@@ -44,7 +44,7 @@ test("context limit", (t: TestContext) => {
4444
type: "resolved",
4545
fileName: undefined,
4646
content: "a\nb\nc\nd\ne\nf\ng\nh\ni\nj\nk\nl\nm",
47-
position: { line: 7, column: 0 },
47+
location: { line: 7, column: 0 },
4848
},
4949
{ afterContext: 0 },
5050
),
@@ -56,7 +56,7 @@ test("context limit", (t: TestContext) => {
5656
type: "resolved",
5757
fileName: undefined,
5858
content: "a\nb\nc\nd\ne\nf\ng\nh\ni\nj\nk\nl\nm",
59-
position: { line: 7, column: 0 },
59+
location: { line: 7, column: 0 },
6060
},
6161
{ beforeContext: 0, afterContext: 0 },
6262
),
@@ -70,7 +70,7 @@ test("lastColumn", (t: TestContext) => {
7070
type: "resolved",
7171
fileName: undefined,
7272
content: "abcdefghi",
73-
position: { line: 1, column: 1, lastColumn: 4 },
73+
location: { line: 1, column: 1, lastColumn: 4 },
7474
}),
7575
"abcdefghi\n ^^^",
7676
);
@@ -82,7 +82,7 @@ test("tab", (t: TestContext) => {
8282
type: "resolved",
8383
fileName: undefined,
8484
content: "\t\tabcdefghi",
85-
position: { line: 1, column: 2 },
85+
location: { line: 1, column: 2 },
8686
}),
8787
"\t\tabcdefghi\n ^",
8888
);
@@ -94,7 +94,7 @@ test("wide character before cursor", (t: TestContext) => {
9494
type: "resolved",
9595
fileName: undefined,
9696
content: "杨abcdefghi",
97-
position: { line: 1, column: 1 },
97+
location: { line: 1, column: 1 },
9898
}),
9999
"杨abcdefghi\n ^",
100100
);
@@ -106,7 +106,7 @@ test("wide character at cursor", (t: TestContext) => {
106106
type: "resolved",
107107
fileName: undefined,
108108
content: "abc杨defghi",
109-
position: { line: 1, column: 3 },
109+
location: { line: 1, column: 3 },
110110
}),
111111
"abc杨defghi\n ^^",
112112
);

src/printContext.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ function formatContext(
2626
{
2727
content,
2828
fileName,
29-
position: { line, column, lastColumn },
29+
location: { line, column, lastColumn },
3030
}: ResolvedApplyResult,
3131
{
3232
shouldHighlight = false,

0 commit comments

Comments
 (0)