Skip to content

Commit

Permalink
move more repetitve code to code changes
Browse files Browse the repository at this point in the history
  • Loading branch information
Andarist committed Feb 16, 2024
1 parent 4983411 commit ae7e679
Show file tree
Hide file tree
Showing 3 changed files with 143 additions and 82 deletions.
68 changes: 32 additions & 36 deletions new-packages/ts-project/__tests__/source-edits/add-state.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -571,52 +571,48 @@ test('should add a state into an existing single-line empty states property (wit
);
});

test.todo(
'should successfully add a state and use it as initial state of its parent',
async () => {
const tmpPath = await testdir({
'tsconfig.json': JSON.stringify({}),
'index.ts': ts`
import { createMachine } from "xstate";
test('should successfully add a state and use it as initial state of its parent', async () => {
const tmpPath = await testdir({
'tsconfig.json': JSON.stringify({}),
'index.ts': ts`
import { createMachine } from "xstate";
createMachine({});
`,
});
createMachine({});
`,
});

const project = await createTestProject(tmpPath);
const project = await createTestProject(tmpPath);

const textEdits = project.editDigraph(
const textEdits = project.editDigraph(
{
fileName: 'index.ts',
machineIndex: 0,
},
[
{
fileName: 'index.ts',
machineIndex: 0,
type: 'add_state',
path: [],
name: 'foo',
},
[
{
type: 'add_state',
path: [],
name: 'foo',
},
{
type: 'set_initial_state',
path: [],
initialState: 'foo',
},
],
);
expect(await project.applyTextEdits(textEdits)).toMatchInlineSnapshot(
`
{
type: 'set_initial_state',
path: [],
initialState: 'foo',
},
],
);
expect(await project.applyTextEdits(textEdits)).toMatchInlineSnapshot(
`
{
"index.ts": "import { createMachine } from "xstate";
createMachine({
initial: "bar",
initial: "foo",
states: {
foo: {},
bar: {},
},
foo: {}
}
});",
}
`,
);
},
);
);
});
111 changes: 91 additions & 20 deletions new-packages/ts-project/src/codeChanges.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,24 +5,38 @@ import type {
SourceFile,
} from 'typescript';
import { Range, TextEdit } from './types';
import { findLast, first, last } from './utils';
import {
findLast,
first,
getPreferredQuoteCharCode,
isValidIdentifier,
last,
safePropertyNameString,
} from './utils';

interface BaseCodeChange {
type: string;
sourceFile: SourceFile;
range: Range;
}

interface InsertBeforePropertyCodeChange extends BaseCodeChange {
type: 'insert_before_property';
interface InsertPropertyBeforePropertyCodeChange extends BaseCodeChange {
type: 'insert_property_before_property';
property: PropertyAssignment;
newText: string;
name: string;
initializerText: string;
}

interface InsertIntoObjectCodeChange extends BaseCodeChange {
type: 'insert_into_object';
interface InsertPropertyIntoObjectCodeChange extends BaseCodeChange {
type: 'insert_property_into_object';
object: ObjectLiteralExpression;
newText: string;
name: string;
initializerText: string;
}

interface ReplacePropertyNameChange extends BaseCodeChange {
type: 'replace_property_name';
name: string;
}

interface ReplaceCodeChange extends BaseCodeChange {
Expand All @@ -31,8 +45,9 @@ interface ReplaceCodeChange extends BaseCodeChange {
}

type CodeChange =
| InsertBeforePropertyCodeChange
| InsertIntoObjectCodeChange
| InsertPropertyBeforePropertyCodeChange
| InsertPropertyIntoObjectCodeChange
| ReplacePropertyNameChange
| ReplaceCodeChange;

function toZeroLengthRange(position: number) {
Expand Down Expand Up @@ -95,26 +110,74 @@ function getLastComment(
);
}

function toSafePropertyNameText(
ts: typeof import('typescript'),
{ sourceFile, name }: { sourceFile: SourceFile; name: string },
) {
return isValidIdentifier(name)
? name
: safePropertyNameString(name, getPreferredQuoteCharCode(ts, sourceFile));
}

function toInsertedPropertyText(
ts: typeof import('typescript'),
{
sourceFile,
name,
initializerText,
}: {
sourceFile: SourceFile;
name: string;
initializerText: string;
},
) {
return `${toSafePropertyNameText(ts, {
sourceFile,
name,
})}: ${initializerText}`;
}

export function createCodeChanges(ts: typeof import('typescript')) {
const changes: CodeChange[] = [];

return {
insertBeforeProperty: (property: PropertyAssignment, newText: string) => {
insertPropertyBeforeProperty: (
property: PropertyAssignment,
name: string,
initializerText: string,
) => {
changes.push({
type: 'insert_before_property',
type: 'insert_property_before_property',
sourceFile: property.getSourceFile(),
range: toZeroLengthRange(property.getFullStart()),
property,
newText,
name,
initializerText,
});
},
insertIntoObject: (object: ObjectLiteralExpression, newText: string) => {
insertPropertyIntoObject: (
object: ObjectLiteralExpression,
name: string,
initializerText: string,
) => {
changes.push({
type: 'insert_into_object',
type: 'insert_property_into_object',
sourceFile: object.getSourceFile(),
range: toZeroLengthRange(object.getStart() + 1), // position right after the opening curly brace
object,
newText,
name,
initializerText,
});
},
replacePropertyName: (property: PropertyAssignment, name: string) => {
changes.push({
type: 'replace_property_name',
sourceFile: property.getSourceFile(),
range: {
start: property.name.getStart(),
end: property.name.getEnd(),
},
name,
});
},
replace: (
Expand Down Expand Up @@ -145,7 +208,7 @@ export function createCodeChanges(ts: typeof import('typescript')) {
for (let i = 0; i < sortedChanges.length; i++) {
const change = sortedChanges[i];
switch (change.type) {
case 'insert_before_property': {
case 'insert_property_before_property': {
edits.push({
type: 'insert',
fileName: change.sourceFile.fileName,
Expand All @@ -157,13 +220,13 @@ export function createCodeChanges(ts: typeof import('typescript')) {
),
)?.pos || change.property.getStart(),
newText:
change.newText +
toInsertedPropertyText(ts, change) +
',\n' +
getIndentationOfNode(change.sourceFile, change.property),
});
break;
}
case 'insert_into_object': {
case 'insert_property_into_object': {
const lastElement = last(change.object.properties);

if (lastElement) {
Expand Down Expand Up @@ -204,7 +267,7 @@ export function createCodeChanges(ts: typeof import('typescript')) {
(!trailingCommaPosition && !lastTrailingComment ? ',' : '') +
'\n' +
getIndentationOfNode(change.sourceFile, lastElement) +
change.newText +
toInsertedPropertyText(ts, change) +
(!!trailingCommaPosition ? ',' : ''),
});

Expand Down Expand Up @@ -235,11 +298,19 @@ export function createCodeChanges(ts: typeof import('typescript')) {
'\n' +
currentIdentation +
singleIndentation +
change.newText +
toInsertedPropertyText(ts, change) +
(!isObjectMultiline ? `\n${currentIdentation}` : ''),
});
break;
}
case 'replace_property_name':
edits.push({
type: 'replace',
fileName: change.sourceFile.fileName,
range: change.range,
newText: toSafePropertyNameText(ts, change),
});
break;
case 'replace':
edits.push({
type: 'replace',
Expand Down
46 changes: 20 additions & 26 deletions new-packages/ts-project/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -275,23 +275,23 @@ function createProjectMachine({
'states',
);

const keyString = isValidIdentifier(key)
? key
: safePropertyNameString(
key,
getPreferredQuoteCharCode(host.ts, sourceFile),
);

if (statesProp) {
assert(
host.ts.isObjectLiteralExpression(statesProp.initializer),
);
codeChanges.insertIntoObject(
codeChanges.insertPropertyIntoObject(
statesProp.initializer,
`${keyString}: {}`,
key,
`{}`,
);
break;
}

codeChanges.insertPropertyIntoObject(
parentNode,
'states',
'{}',
);
}
}
break;
Expand All @@ -317,18 +317,7 @@ function createProjectMachine({
(p): p is PropertyAssignment =>
host.ts.isPropertyAssignment(p) && p.initializer === node,
)!;
codeChanges.replace(sourceFile, {
range: {
start: prop.name.getStart(),
end: prop.name.getEnd(),
},
newText: isValidIdentifier(patch.value)
? patch.value
: safePropertyNameString(
patch.value,
getPreferredQuoteCharCode(host.ts, sourceFile),
),
});
codeChanges.replacePropertyName(prop, patch.value);
break;
}
if (patch.path[2] === 'data' && patch.path[3] === 'initial') {
Expand Down Expand Up @@ -373,20 +362,25 @@ function createProjectMachine({
'states',
);

const initialPropertyText = `initial: ${safeStringLikeLiteralText(
const initializerText = safeStringLikeLiteralText(
patch.value,
getPreferredQuoteCharCode(host.ts, sourceFile),
)}`;
);

if (statesProp) {
codeChanges.insertBeforeProperty(
codeChanges.insertPropertyBeforeProperty(
statesProp,
initialPropertyText,
'initial',
initializerText,
);
break;
}

codeChanges.insertIntoObject(node, initialPropertyText);
codeChanges.insertPropertyIntoObject(
node,
'initial',
initializerText,
);
}
}
break;
Expand Down

0 comments on commit ae7e679

Please sign in to comment.