Replies: 4 comments 8 replies
-
(It would be better if we have a summary table, listing a few examples for each type.) I don't have a strong opinion on whatever patterns we use as long as they are consistent. As for the file name, is there any reason to use |
Beta Was this translation helpful? Give feedback.
-
What about: // File: i-disposable.ts
export interface IDisposable {
/**
* Performs application-defined tasks associated with freeing, releasing, or resetting resources.
*/
dispose(): any;
} // File: text-writer.ts
import * as vscode from "vscode";
import { IDisposable } from "./i-disposable";
const utf8Encoder = new TextEncoder();
/**
* Represents the state of a writer.
*/
export enum WriterState {
Idle,
Dirty,
Disposed,
}
/**
* Represents a writer that can write a sequential series of characters.
*/
export interface ITextWriter extends IDisposable {
/**
* Clears all buffers for the current writer and causes any buffered data to be written to the underlying stream.
*/
flush(): void;
/**
* Writes a string to the text stream.
*/
write(text: string): void;
}
/**
* Default buffer size: 1024 bytes.
*/
const Default_Buffer_Size = 1024;
/**
* Implements a naive text writer that appends text to a file.
*/
export class TextFileWriter implements ITextWriter {
readonly #buffer: Uint8Array;
readonly #file: vscode.Uri;
/**
* The next available index in the buffer.
*/
#bufferRear = 0;
#state: WriterState = WriterState.Idle;
get file() {
return this.#file;
}
get state() {
return this.#state;
}
/**
* @param file The file will be created, if it does not already exist.
* @param bufferSize The buffer size in bytes.
*/
constructor(file: vscode.Uri, bufferSize = Default_Buffer_Size) {
this.#file = file;
this.#buffer = new Uint8Array(bufferSize);
}
async dispose(): Promise<void> {
if (this.#state === WriterState.Disposed) {
return;
}
// Avoid throwing error.
try {
await this.flush();
} catch {}
this.#bufferRear = 0;
this.#state = WriterState.Disposed;
}
#readFile(): Thenable<Uint8Array> {
return vscode.workspace.fs.readFile(this.#file).then(
(value) => value,
(error) => {
if (error instanceof vscode.FileSystemError && error.code === "FileNotFound") {
return new Uint8Array(0);
} else {
throw error;
}
}
);
}
async #writeFile(...data: readonly Uint8Array[]): Promise<void> {
const totalLength = data.reduce((result, item) => result + item.length, 0);
if (!totalLength) {
throw new Error("`data` is empty.");
}
const content = new Uint8Array(totalLength);
let ptr = 0;
for (const item of data) {
content.set(item, ptr);
ptr += item.length;
}
await vscode.workspace.fs.writeFile(this.#file, content);
}
async flush(): Promise<void> {
if (this.#bufferRear === 0) {
return;
}
await this.#writeFile(
await this.#readFile(), //
this.#buffer.subarray(0, this.#bufferRear)
);
this.#bufferRear = 0;
this.#state = WriterState.Idle;
}
async write(text: string): Promise<void> {
const bin = utf8Encoder.encode(text);
if (this.#bufferRear + bin.length >= this.#buffer.length) {
await this.#writeFile(
await this.#readFile(), //
this.#buffer.subarray(0, this.#bufferRear),
bin
);
this.#bufferRear = 0;
this.#state = WriterState.Idle;
} else {
this.#buffer.set(bin, this.#bufferRear);
this.#bufferRear += bin.length;
this.#state = WriterState.Dirty;
}
}
} // File: dump.ts
import * as vscode from "vscode";
import { TextFileWriter } from "./text-writer";
export type ISerializablePrimitive = string | number | boolean | null;
/**
* Represents an object that can be serialized to JSON.
*/
export interface ISerializableObject {
readonly [key: string]:
| ISerializablePrimitive
| ISerializableObject
| ReadonlyArray<ISerializablePrimitive | ISerializableObject>;
}
/**
* Serializes the data to JSON, and appends it to the file.
*
* @param path The Uri that points to the file.
*/
export async function dump(path: vscode.Uri, data: ISerializableObject): Promise<void> {
const writer = new TextFileWriter(path);
try {
await writer.write(JSON.stringify(data));
await writer.flush();
} finally {
await writer.dispose();
}
} |
Beta Was this translation helpful? Give feedback.
-
BTW, how do you like yzhang-gh/vscode-markdown#802 (comment)? Should we adopt |
Beta Was this translation helpful? Give feedback.
-
A funny issue at VS Code. I guess they're facing similar problems about distinguishing interfaces and classes. 😂 |
Beta Was this translation helpful? Give feedback.
-
We've tried a few different patterns in the past years. Here are my thoughts.
We need another thread to discuss abbreviations and compound words.
Overall goal
An identifier should contain only upper case ASCII letters (U+0041 ~ U+005A), lower case ASCII letters (U+0061 ~ U+007A), ASCII digits (U+0030 ~ U+0039), hyphens (U+002D), and underscores (U+005F).
The rule for a kind of identifiers should add details upon generic common styles.
File name
/^[a-z][a-z0-9]*(-[a-z0-9]+)*$/
Examples:
json5
markdown-engine
markdown-it-task-list
Description:
Unless conventions apply, the base name of a file and the name of a directory use
kebab-case
.This roughly follows the rules of npm package name, and adds a few restrictions to ensure interoperability.
It's recommended to only use lower case ASCII letters in each word:
/^[a-z]+(-[a-z]+)*$/
Entries with conventions, such as
README
, are not affected by this rule.Background:
The printable ASCII characters are still the safest set nowadays.
Case sensitivity is also a big concern. URI is case-sensitive. Some file systems are case-sensitive, while others are not. Mixed case can frequently lead to unexpected behavior in access, searching, comparison, and sorting.
Some projects choose
snake_case
. But renaming is painful, since most file explorers and text editors don't recognize underscores as word separators.Constant
/^[A-Z][a-z0-9]*(_[A-Z][a-z0-9]*)+$/
Examples:
Non_Archived_Resources
Sidebar_Config
Slugify_Methods
Test_Workspace_Path
Description:
A constant can be instantiated only once over the lifetime of the program, is intended to not be changed, and users must not modify it in any way.
The identifier of a constant use
snake_case
with the first letter of each word capitalized, and must be made of at least two words. We call itTitle_Case_Snake_Case
.Background:
Enormous projects use
UPPER_CASE_SNAKE_CASE
, and even call itCONSTANT_CASE
. However, I find it problematic, and suggest avoiding it. Generally speaking, mixed case is easiest to read and write, and all lower case is equally good, while all upper case is terrible. Constants are often used to hold important immutable values. When you propagate them across the codebase, significant negative visual effect of terrible styles will arise.As for the "at least two words" requirement, I find in practice that the names of constants usually need two or three words to be clear and precise.
Class, Enum, Enum member
/^([A-Z][a-z0-9]+)+$/
Examples:
DownwardsArrowWithCornerLeftwards
EventEmitter
FontIcon
Lazy
Description:
Use
PascalCase
.Interface, Type alias
/^I([A-Z][a-z0-9]+)+$/
Examples:
ICase
IDecorationAnalysisTask
IDecorationRecord
IDisposable
IDocumentToken
IInternalOption
IKnownKey
IMarkdownEngine
INlsBundle
IPrimitive
IWorkerRegistry
Description:
It's the same as class, but prefixed with
I
.Background:
Someone advised me to not mark interfaces with special prefix, probably because interfaces are natural and frequent in JavaScript's structural type system.
I tried, but had a worse experience. The style helps to reduce distraction on small projects. But it soon becomes friction as the scale grows.
Interfaces are basically contracts that cannot be instantiated or contain any implementation. However, it's quite easy to forget this without a special mark. Too many times, I found myself wondering why I could not
new
an object, and the codebase eventually told me that the type is an interface.To let it hit developers that they are not classes, you need to name interfaces nicely. From my experience, very few names can be intuitively recognized as "just a set of declarations of properties". Then, it goes back to how to mark interfaces.
Thus, I think marking interfaces specially is good for engineering. We can discuss it as this style seems not consistent with other parts.
Type parameter
Description:
It's the same as class, but can also be single upper case letter.
Variable, Parameter, Function, Method, Property, Module alias
/^[a-z][a-z0-9]*([A-Z][a-z0-9]+)*$/
Examples:
applyDecoration
fs
inlineTokens
isWelcomeMessagesExist
onDidOpenTextDocument
parseInline
projectLevelOmittedHeadings
resolveResource
utf8Encoder
workers
Description:
Use
camelCase
.Namespace, Decorator
Avoid.
Beta Was this translation helpful? Give feedback.
All reactions