Use 4 spaces per indentation level.
Use organize imports
to sort imports, and make sure the imports work properly (e.g. imports from /src/
rather than /lib/
for *.ts files may break builds).
type
names.
enum
values.
function
and method
names.
property
names and local variables
.
// bad
const termWdgId = 1;
// good
const terminalWidgetId = 1;
document-provider.ts
).
Why? In order to avoid duplicate records in file and type search.
// bad
export interface TitleButton {}
// good
export interface QuickInputTitleButton {}
on[Will|Did]VerbNoun?
pattern. The name signals if the event is going to happen (onWill) or already happened (onDid), what happened (verb), and the context (noun) unless obvious from the context.
// bad
export namespace TerminalSearchKeybindingContext {
export const disableSearch = 'hideSearch';
}
// good
export namespace TerminalSearchKeybindingContext {
export const disableSearch = 'terminalHideSearch';
}
// bad
const terminalFocusKey = this.contextKeyService.createKey<boolean>('focus', false);
// good
const terminalFocusKey = this.contextKeyService.createKey<boolean>('terminalFocus', false);
types
or functions
unless you need to share it across multiple components, see as well.
types
or values
to the global namespace.
I
prefix for interfaces. Use Impl
suffix for implementation of interfaces with the same name. See 624 for the discussion on this.
// bad
export const TaskDefinitionRegistry = Symbol('TaskDefinitionRegistry');
export interface TaskDefinitionRegistry {
register(definition: TaskDefinition): void;
}
export class TaskDefinitionRegistryImpl implements TaskDefinitionRegistry {
register(definition: TaskDefinition): void {
}
}
bind(TaskDefinitionRegistryImpl).toSelf().inSingletonScope();
bind(TaskDefinitionRegistry).toService(TaskDefinitionRegistryImpl);
// good
export class TaskDefinitionRegistry {
register(definition: TaskDefinition): void {
}
}
bind(TaskDefinitionRegistry).toSelf().inSingletonScope();
functions
, interfaces
, enums
, and classes
Use undefined
; do not use null
.
nls.localize(key, defaultValue, ...args)
function.What is user-facing text? Any strings that are hard-coded (not calculated) that could be in any way visible to the user, be it labels for commands and menus, messages/notifications/dialogs, quick-input placeholders or preferences.
args
of the localize
function. They are inserted at the location of the placeholders - in the form of {\d+}
- in the localized text. E.g. {0}
will be replaced with the first arg
, {1}
with the second, etc.// bad
nls.localize('hello', `Hello there ${name}.`);
// good
nls.localize('hello', 'Hello there {0}.', name);
nls.localizeByDefault
function automatically finds the translation key for VS Code's language packs just by using the default value as its argument and translates it into the currently used locale. If the nls.localizeByDefault
function is not able to find a key for the supplied default value, a warning will be shown in the browser console. If there is no appropriate translation in VSCode, just use the nls.localize
function with a new key using the syntax theia/<package>/<id>
.// bad
nls.localize('vscode/dialogService/close', 'Close');
// good
nls.localizeByDefault('Close');
// bad
command: Command = { label: nls.localize(key, defaultValue), originalLabel: defaultValue };
// good
command = Command.toLocalizedCommand({ id: key, label: defaultValue });
=>
over anonymous function expressions.(x) => x + x
is wrong, but the following are correct:x => x + x
(x,y) => x + y
<T>(x: T, y: T) => x === y
for (var i = 0, n = str.length; i < 10; i++) { }
if (x < 10) { }
function f(x: number, y: string): void { }
var x = 1; var y = 2;
over var x = 1, y = 2;
).else
goes on the line of the closing curly brace.postConstruct
rather than the constructor to initialize an object, for example to register event listeners.@injectable()
export class MyComponent {
@inject(ApplicationShell)
protected readonly shell: ApplicationShell;
@postConstruct()
protected init(): void {
this.shell.activeChanged.connect(() => this.doSomething());
}
}
inSingletonScope
for singleton instances, otherwise a new instance will be created on each injection request.// bad
bind(CommandContribution).to(LoggerFrontendContribution);
// good
bind(CommandContribution).to(LoggerFrontendContribution).inSingletonScope();
// bad
export function createWebSocket(url: string): WebSocket {
...
}
// good
@injectable()
export class WebSocketProvider {
protected createWebSocket(url: string): WebSocket {
...
}
}
@injectable()
export class MyWebSocketProvider extends WebSocketProvider {
protected createWebSocket(url: string): WebSocket {
// create a web socket with custom options
}
}
In this case clients:
export namespace MonacoEditor {
// convenient function to get a Monaco editor based on the editor manager API
export function getCurrent(manager: EditorManager): MonacoEditor | undefined {
return get(manager.currentEditor);
}
...
}
JSON types are not supposed to be implementable, but only instantiable. They cannot have functions to avoid serialization issues.
export interface CompositeTreeNode extends TreeNode {
children: ReadonlyArray<TreeNode>;
// bad - JSON types should not have functions
getFirstChild(): TreeNode | undefined;
}
// good - JSON types can have corresponding namespaces with functions
export namespace CompositeTreeNode {
export function getFirstChild(parent: CompositeTreeNode): TreeNode | undefined {
return parent.children[0];
}
...
}
// bad - JSON types should not be implemented
export class MyCompositeTreeNode implements CompositeTreeNode {
...
}
// good - JSON types can be extended
export interface MyCompositeTreeNode extends CompositeTreeNode {
...
}
@injectable()
export class DirtyDiffModel {
// This method can be overridden. Subclasses have access to `DirtyDiffModel.documentContentLines`.
protected handleDocumentChanged(document: TextEditorDocument): void {
this.currentContent = DirtyDiffModel.documentContentLines(document);
this.update();
}
}
export namespace DirtyDiffModel {
// the auxiliary function
export function documentContentLines(document: TextEditorDocument): ContentLines {
...
}
}
@multiInject
, use Theia's utility ContributionProvider
to inject multiple instances.Why?
ContributionProvider
is a documented way to introduce contribution points. SeeContribution-Points
: https://www.theia-ide.org/docs/services_and_contributions- If nothing is bound to an identifier, multi-inject resolves to
undefined
, not an empty array.ContributionProvider
provides an empty array.- Multi-inject does not guarantee the same instances are injected if an extender does not use
inSingletonScope
.ContributionProvider
caches instances to ensure uniqueness.ContributionProvider
supports filtering. SeeContributionFilterRegistry
.
lower-case-with-dashes
format.
theia
when used as global classes.
Why? It is not possible to play with such styles in the dev tools without recompiling the code. CSS classes can be edited in the dev tools.
ColorContribution
and use ColorRegistry.register
to register new colors.
--theia
and replacing all dots with dashes. For example widget.shadow
color can be referred to in CSS with var(--theia-widget-shadow)
.
dark: 'widget.shadow'
, or transformation, e.g. dark: Color.lighten('widget.shadow', 0.4)
.Why? Otherwise, there is no guarantee that new colors will fit well into new VSCode color themes.
object.property
pattern.// bad
'button.secondary.foreground'
'button.secondary.disabled.foreground'
// good
'secondaryButton.foreground'
'secondaryButton.disabledForeground'
Why? Because doing so creates a new instance of the event handler function on each render and breaks React element caching leading to re-rendering and bad performance.
// bad
class MyWidget extends ReactWidget {
render(): React.ReactNode {
return <div onClick={this.onClickDiv.bind(this)} />;
}
protected onClickDiv(): void {
// do stuff
}
}
// bad
class MyWidget extends ReactWidget {
render(): React.ReactNode {
return <div onClick={() => this.onClickDiv()} />;
}
protected onClickDiv(): void {
// do stuff
}
}
// very bad
class MyWidget extends ReactWidget {
render(): React.ReactNode {
return <div onClick={this.onClickDiv} />;
}
protected onClickDiv(): void {
// do stuff, no `this` access
}
}
// good
class MyWidget extends ReactWidget {
render(): React.ReactNode {
return <div onClick={this.onClickDiv} />
}
protected onClickDiv = () => {
// do stuff, can access `this`
}
}
RemoteFileSystemServer
accepts strings, not URIs.Why? Frontend and backend can have different operating systems leading to incompatibilities between paths. URIs are normalized in order to be OS-agnostic.
FileService.fsPath
to get a path on the frontend from a URI.
FileUri.fsPath
to get a path on the backend from a URI. Never use it on the frontend.
Why? A URI without scheme will fall back to
file
scheme for now; in the future it will lead to a runtime error.
Path
Theia API to manipulate paths on the frontend. Don't use Node.js APIs like path
module. Also see the code organization guideline.
fs
and fs-extra
modules.Why?
FileService
is to expose file system capabilities to the frontend only. It's aligned with expectations and requirements on the frontend. Using it on the backend is not possible.
LabelProvider.getLongName(uri)
to get a system-wide human-readable representation of a full path. Don't use uri.toString()
or uri.path.toString()
.
LabelProvider.getName(uri)
to get a system-wide human-readable representation of a simple file name.
LabelProvider.getIcon(uri)
to get a system-wide file icon.
string
to manipulate URIs and paths. Use URI
and Path
capabilities instead, like join
, resolve
and relative
.Why? Because object representation can handle corner cases properly, like trailing separators.
// bad
uriString + '/' + pathString
// good
new URI(uriString).join(pathString)
// bad
pathString.substring(absolutePathString.length + 1)
// good
new Path(absolutePathString).relative(pathString)
console
instead of ILogger
for the root (top-level) logging.// bad
@inject(ILogger)
protected readonly logger: ILogger;
this.logger.info(``);
// good
console.info(``)
Why? All calls to console are intercepted on the frontend and backend and then forwarded to an
ILogger
instance already. The log level can be configured from the CLI:theia start --log-level=debug
.
There are situations where we can't properly implement some functionality at the time we merge a PR. In those cases, it is sometimes good practice to leave an indication that something needs to be fixed later in the code. This can be done by putting a "tag" string in a comment. This allows us to find the places we need to fix again later. Currently, we use two "standard" tags in Theia:
@stubbed
This tag is used in VS Code API implementations. Sometimes we need an implementation of an API in order for VS Code extensions to start up correctly, but we can't provide a proper implementation of the underlying feature at this time. This might be because a certain feature has no corresponding UI in Theia or because we do not have the resources to provide a proper implementation.
Using the @stubbed
tag in a JSDoc comment will mark the element as "stubbed" on the API status page
@monaco-uplift
Use this tag when some functionality can be added or needs to be fixed when we move to a newer version of the monaco editor. If you know which minimum version of Monaco we need, you can add that as a reminder.Вы можете оставить комментарий после Вход в систему
Неприемлемый контент может быть отображен здесь и не будет показан на странице. Вы можете проверить и изменить его с помощью соответствующей функции редактирования.
Если вы подтверждаете, что содержание не содержит непристойной лексики/перенаправления на рекламу/насилия/вульгарной порнографии/нарушений/пиратства/ложного/незначительного или незаконного контента, связанного с национальными законами и предписаниями, вы можете нажать «Отправить» для подачи апелляции, и мы обработаем ее как можно скорее.
Опубликовать ( 0 )