/
index.ts
148 lines (137 loc) · 5.34 KB
/
index.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
import { lex } from 'coffee-lex';
import { nodes as getCoffee1Nodes, tokens as getCoffee1Tokens } from 'decaffeinate-coffeescript';
import { nodes as getCoffee2Nodes, tokens as getCoffee2Tokens } from 'decaffeinate-coffeescript2';
import { parse as decaffeinateParse } from 'decaffeinate-parser';
import AddVariableDeclarationsStage from './stages/add-variable-declarations/index';
import ResugarStage from './stages/resugar/index';
import LiterateStage from './stages/literate/index';
import MainStage from './stages/main/index';
import NormalizeStage from './stages/normalize/index';
import SemicolonsStage from './stages/semicolons/index';
import { mergeSuggestions, prependSuggestionComment, Suggestion } from './suggestions';
import CodeContext from './utils/CodeContext';
import convertNewlines from './utils/convertNewlines';
import detectNewlineStr from './utils/detectNewlineStr';
import formatCoffeeLexTokens from './utils/formatCoffeeLexTokens';
import formatCoffeeScriptAst from './utils/formatCoffeeScriptAst';
import formatCoffeeScriptLexerTokens from './utils/formatCoffeeScriptLexerTokens';
import formatDecaffeinateParserAst from './utils/formatDecaffeinateParserAst';
import PatchError from './utils/PatchError';
import removeUnicodeBOMIfNecessary from './utils/removeUnicodeBOMIfNecessary';
import resolveToPatchError from './utils/resolveToPatchError';
export { default as run } from './cli';
import { resolveOptions, Options } from './options';
import notNull from './utils/notNull';
import assert from 'assert';
export { PatchError };
export interface ConversionResult {
code: string;
}
export interface StageResult {
code: string;
suggestions: Array<Suggestion>;
}
interface Stage {
name: string;
run: (content: string, options: Options) => StageResult;
}
/**
* Convert CoffeeScript source code into modern JavaScript preserving comments
* and formatting.
*/
export function convert(source: string, options: Options = {}): ConversionResult {
if (!options.bare && options.useJSModules) {
throw new Error('useJSModules requires bare output');
}
source = removeUnicodeBOMIfNecessary(source);
options = resolveOptions(options);
const originalNewlineStr = detectNewlineStr(source);
source = convertNewlines(source, '\n');
const literate =
options.literate ||
notNull(options.filename).endsWith('.litcoffee') ||
notNull(options.filename).endsWith('.coffee.md');
let stages = [
...(literate ? [LiterateStage] : []),
NormalizeStage,
MainStage,
AddVariableDeclarationsStage,
SemicolonsStage,
ResugarStage,
];
const runToStage = options.runToStage;
if (runToStage !== null && runToStage !== undefined) {
const stageIndex = stages.findIndex((stage) => stage.name === runToStage);
if (stageIndex !== -1) {
stages = stages.slice(0, stageIndex + 1);
} else {
return convertCustomStage(source, runToStage, Boolean(options.useCS2));
}
}
const result = runStages(source, options, stages);
if (!options.disableSuggestionComment) {
result.code = prependSuggestionComment(result.code, result.suggestions);
}
result.code = convertNewlines(result.code, originalNewlineStr);
return {
code: options.bare ? result.code : `(function() {\n${result.code}\n}).call(this);`,
};
}
export function modernizeJS(source: string, options: Options = {}): ConversionResult {
source = removeUnicodeBOMIfNecessary(source);
options = resolveOptions(options);
const originalNewlineStr = detectNewlineStr(source);
source = convertNewlines(source, '\n');
const stages = [ResugarStage];
const result = runStages(source, options, stages);
result.code = convertNewlines(result.code, originalNewlineStr);
return {
code: result.code,
};
}
function runStages(initialContent: string, options: Options, stages: Array<Stage>): StageResult {
let content = initialContent;
const suggestions: Array<Suggestion> = [];
stages.forEach((stage) => {
const { code, suggestions: stageSuggestions } = runStage(stage, content, options);
content = code;
suggestions.push(...stageSuggestions);
});
return { code: content, suggestions: mergeSuggestions(suggestions) };
}
function runStage(stage: Stage, content: string, options: Options): StageResult {
try {
return stage.run(content, options);
} catch (err: unknown) {
assert(err instanceof Error);
const patchError = resolveToPatchError(err, content, stage.name);
if (patchError !== null) {
throw patchError;
}
throw err;
}
}
function convertCustomStage(source: string, stageName: string, useCS2: boolean): ConversionResult {
const context = new CodeContext(source);
if (stageName === 'coffeescript-lexer') {
const tokens = useCS2 ? getCoffee2Tokens(source) : getCoffee1Tokens(source);
return {
code: formatCoffeeScriptLexerTokens(tokens, context),
};
} else if (stageName === 'coffeescript-parser') {
const nodes = useCS2 ? getCoffee2Nodes(source) : getCoffee1Nodes(source);
return {
code: formatCoffeeScriptAst(nodes, context),
};
} else if (stageName === 'coffee-lex') {
return {
code: formatCoffeeLexTokens(lex(source, { useCS2 }), context),
};
} else if (stageName === 'decaffeinate-parser') {
return {
code: formatDecaffeinateParserAst(decaffeinateParse(source, { useCS2 }), context),
};
} else {
throw new Error(`Unrecognized stage name: ${stageName}`);
}
}